From d45548c43b8f4eb69ad95445d340ce77e1518902 Mon Sep 17 00:00:00 2001
From: Evgeniy Kulikov <kim@nspcc.ru>
Date: Fri, 3 Jul 2020 09:12:02 +0300
Subject: [PATCH] Implementing proto.Clone

---
 container/types.go   |  8 ++++++++
 hash/hash.go         |  8 ++++++++
 internal/proto.go    |  3 +++
 object/types.go      | 26 ++++++++++++++++++++------
 object/types_test.go | 21 +++++++++++++++++----
 refs/address.go      | 12 ++++++++++++
 refs/cid.go          |  8 ++++++++
 refs/owner.go        |  8 ++++++++
 refs/types_test.go   | 29 +++++++++++++++++++++++++++++
 refs/uuid.go         |  8 ++++++++
 service/verify.go    |  8 ++++++++
 11 files changed, 129 insertions(+), 10 deletions(-)

diff --git a/container/types.go b/container/types.go
index 38bdcff..be06b53 100644
--- a/container/types.go
+++ b/container/types.go
@@ -3,6 +3,7 @@ package container
 import (
 	"bytes"
 
+	"github.com/gogo/protobuf/proto"
 	"github.com/google/uuid"
 	"github.com/nspcc-dev/neofs-api-go/internal"
 	"github.com/nspcc-dev/neofs-api-go/refs"
@@ -63,6 +64,13 @@ func (m *Container) ID() (CID, error) {
 	return refs.CIDForBytes(data), nil
 }
 
+// Merge used by proto.Clone
+func (m *Container) Merge(src proto.Message) {
+	if tmp, ok := src.(*Container); ok {
+		*m = *tmp
+	}
+}
+
 // Empty checks that container is empty.
 func (m *Container) Empty() bool {
 	return m.Capacity == 0 || bytes.Equal(m.Salt.Bytes(), emptySalt) || bytes.Equal(m.OwnerID.Bytes(), emptyOwner)
diff --git a/hash/hash.go b/hash/hash.go
index 9d75278..4e2fb05 100644
--- a/hash/hash.go
+++ b/hash/hash.go
@@ -3,6 +3,7 @@ package hash
 import (
 	"bytes"
 
+	"github.com/gogo/protobuf/proto"
 	"github.com/mr-tron/base58"
 	"github.com/nspcc-dev/neofs-api-go/internal"
 	"github.com/nspcc-dev/tzhash/tz"
@@ -78,6 +79,13 @@ func (h Hash) Validate(hashes []Hash) bool {
 	return err == nil && ok
 }
 
+// Merge used by proto.Clone
+func (h *Hash) Merge(src proto.Message) {
+	if tmp, ok := src.(*Hash); ok {
+		*h = *tmp
+	}
+}
+
 // Sum returns Tillich-ZĂ©mor checksum of data.
 func Sum(data []byte) Hash { return tz.Sum(data) }
 
diff --git a/internal/proto.go b/internal/proto.go
index 951168b..9a924b5 100644
--- a/internal/proto.go
+++ b/internal/proto.go
@@ -13,4 +13,7 @@ type Custom interface {
 	MarshalTo(data []byte) (int, error)
 	Unmarshal(data []byte) error
 	proto.Message
+
+	// Should contains for proto.Clone
+	proto.Merger
 }
diff --git a/object/types.go b/object/types.go
index 058cd70..392e624 100644
--- a/object/types.go
+++ b/object/types.go
@@ -163,6 +163,13 @@ func (m *Object) SetHeader(h *Header) {
 	m.AddHeader(h)
 }
 
+// Merge used by proto.Clone
+func (m *Object) Merge(src proto.Message) {
+	if tmp, ok := src.(*Object); ok {
+		tmp.CopyTo(m)
+	}
+}
+
 func (m Header) typeOf(t isHeader_Value) (ok bool) {
 	switch t.(type) {
 	case *Header_Link:
@@ -233,8 +240,15 @@ func (m *Object) Copy() (obj *Object) {
 // This function creates copies on every available data slice.
 func (m *Object) CopyTo(o *Object) {
 	o.SystemHeader = m.SystemHeader
-	o.Headers = make([]Header, len(m.Headers))
-	o.Payload = make([]byte, len(m.Payload))
+
+	if m.Headers != nil {
+		o.Headers = make([]Header, len(m.Headers))
+	}
+
+	if m.Payload != nil {
+		o.Payload = make([]byte, len(m.Payload))
+		copy(o.Payload, m.Payload)
+	}
 
 	for i := range m.Headers {
 		switch v := m.Headers[i].Value.(type) {
@@ -246,23 +260,23 @@ func (m *Object) CopyTo(o *Object) {
 				},
 			}
 		case *Header_HomoHash:
+			hash := proto.Clone(&v.HomoHash).(*Hash)
 			o.Headers[i] = Header{
 				Value: &Header_HomoHash{
-					HomoHash: v.HomoHash,
+					HomoHash: *hash,
 				},
 			}
 		case *Header_Token:
+			token := *v.Token
 			o.Headers[i] = Header{
 				Value: &Header_Token{
-					Token: v.Token,
+					Token: &token,
 				},
 			}
 		default:
 			o.Headers[i] = *proto.Clone(&m.Headers[i]).(*Header)
 		}
 	}
-
-	copy(o.Payload, m.Payload)
 }
 
 // Address returns object's address.
diff --git a/object/types_test.go b/object/types_test.go
index 0a7085f..18c13f1 100644
--- a/object/types_test.go
+++ b/object/types_test.go
@@ -4,6 +4,7 @@ import (
 	"bytes"
 	"testing"
 
+	"github.com/gogo/protobuf/proto"
 	"github.com/nspcc-dev/neofs-api-go/refs"
 	"github.com/nspcc-dev/neofs-api-go/service"
 	"github.com/nspcc-dev/neofs-api-go/storagegroup"
@@ -192,11 +193,23 @@ func TestObject_Copy(t *testing.T) {
 			},
 		})
 
-		cp := obj.Copy()
+		{ // Copying
+			cp := obj.Copy()
 
-		_, h := cp.LastHeader(HeaderType(TokenHdr))
-		require.NotNil(t, h)
-		require.Equal(t, token, h.GetValue().(*Header_Token).Token)
+			_, h := cp.LastHeader(HeaderType(TokenHdr))
+			require.NotNil(t, h)
+			require.Equal(t, token, h.GetValue().(*Header_Token).Token)
+		}
+
+		{ // Cloning
+			cl := proto.Clone(obj).(*Object)
+			require.Equal(t, obj, cl)
+
+			_, h := cl.LastHeader(HeaderType(TokenHdr))
+			h.GetToken().SetID(service.TokenID{3, 2, 1})
+
+			require.NotEqual(t, token, h.GetToken())
+		}
 	})
 }
 
diff --git a/refs/address.go b/refs/address.go
index f07e317..ad5f420 100644
--- a/refs/address.go
+++ b/refs/address.go
@@ -4,6 +4,7 @@ import (
 	"crypto/sha256"
 	"strings"
 
+	"github.com/gogo/protobuf/proto"
 	"github.com/nspcc-dev/neofs-api-go/internal"
 )
 
@@ -66,3 +67,14 @@ func (m Address) Hash() ([]byte, error) {
 	h := sha256.Sum256(append(m.ObjectID.Bytes(), m.CID.Bytes()...))
 	return h[:], nil
 }
+
+// Merge used by proto.Clone
+func (m *Address) Merge(src proto.Message) {
+	if addr, ok := src.(*Address); ok {
+		cid := proto.Clone(&addr.CID).(*CID)
+		oid := proto.Clone(&addr.ObjectID).(*ObjectID)
+
+		m.CID = *cid
+		m.ObjectID = *oid
+	}
+}
diff --git a/refs/cid.go b/refs/cid.go
index 4f0cf39..83450a9 100644
--- a/refs/cid.go
+++ b/refs/cid.go
@@ -4,6 +4,7 @@ import (
 	"bytes"
 	"crypto/sha256"
 
+	"github.com/gogo/protobuf/proto"
 	"github.com/mr-tron/base58"
 	"github.com/pkg/errors"
 )
@@ -94,3 +95,10 @@ func (c CID) Verify(data []byte) error {
 	}
 	return nil
 }
+
+// Merge used by proto.Clone
+func (c *CID) Merge(src proto.Message) {
+	if cid, ok := src.(*CID); ok {
+		*c = *cid
+	}
+}
diff --git a/refs/owner.go b/refs/owner.go
index 1aed00c..6534c35 100644
--- a/refs/owner.go
+++ b/refs/owner.go
@@ -4,6 +4,7 @@ import (
 	"bytes"
 	"crypto/ecdsa"
 
+	"github.com/gogo/protobuf/proto"
 	"github.com/mr-tron/base58"
 	"github.com/nspcc-dev/neofs-api-go/chain"
 	"github.com/pkg/errors"
@@ -63,3 +64,10 @@ func (o *OwnerID) Unmarshal(data []byte) error {
 	copy((*o)[:], data)
 	return nil
 }
+
+// Merge used by proto.Clone
+func (o *OwnerID) Merge(src proto.Message) {
+	if uid, ok := src.(*OwnerID); ok {
+		*o = *uid
+	}
+}
diff --git a/refs/types_test.go b/refs/types_test.go
index 2fd3ced..5bba5b8 100644
--- a/refs/types_test.go
+++ b/refs/types_test.go
@@ -23,6 +23,19 @@ func TestSGID(t *testing.T) {
 		require.NoError(t, sgid2.Unmarshal(data))
 		require.Equal(t, sgid1, sgid2)
 	})
+
+	t.Run("check that proto.Clone works like expected", func(t *testing.T) {
+		var (
+			sgid1 UUID
+			sgid2 *UUID
+		)
+
+		sgid1, err := NewSGID()
+		require.NoError(t, err)
+
+		sgid2 = proto.Clone(&sgid1).(*SGID)
+		require.Equal(t, sgid1, *sgid2)
+	})
 }
 
 func TestUUID(t *testing.T) {
@@ -80,6 +93,18 @@ func TestOwnerID(t *testing.T) {
 		require.NoError(t, u2.Unmarshal(data))
 		require.Equal(t, u1, u2)
 	})
+
+	t.Run("check that proto.Clone works like expected", func(t *testing.T) {
+		var u2 *OwnerID
+
+		key := test.DecodeKey(0)
+
+		u1, err := NewOwnerID(&key.PublicKey)
+		require.NoError(t, err)
+
+		u2 = proto.Clone(&u1).(*OwnerID)
+		require.Equal(t, u1, *u2)
+	})
 }
 
 func TestAddress(t *testing.T) {
@@ -109,4 +134,8 @@ func TestAddress(t *testing.T) {
 	actual, err := ParseAddress(expect)
 	require.NoError(t, err)
 	require.Equal(t, expect, actual.String())
+
+	addr := proto.Clone(actual).(*Address)
+	require.Equal(t, actual, addr)
+	require.Equal(t, expect, addr.String())
 }
diff --git a/refs/uuid.go b/refs/uuid.go
index 2ffc525..5a49b92 100644
--- a/refs/uuid.go
+++ b/refs/uuid.go
@@ -4,6 +4,7 @@ import (
 	"bytes"
 	"encoding/hex"
 
+	"github.com/gogo/protobuf/proto"
 	"github.com/google/uuid"
 	"github.com/pkg/errors"
 )
@@ -74,3 +75,10 @@ func (u *UUID) Parse(id string) error {
 	copy((*u)[:], tmp[:])
 	return nil
 }
+
+// Merge used by proto.Clone
+func (u *UUID) Merge(src proto.Message) {
+	if tmp, ok := src.(*UUID); ok {
+		*u = *tmp
+	}
+}
diff --git a/service/verify.go b/service/verify.go
index e1caa06..4a6ec3f 100644
--- a/service/verify.go
+++ b/service/verify.go
@@ -3,6 +3,7 @@ package service
 import (
 	"crypto/ecdsa"
 
+	"github.com/gogo/protobuf/proto"
 	"github.com/nspcc-dev/neofs-api-go/internal"
 	crypto "github.com/nspcc-dev/neofs-crypto"
 )
@@ -104,6 +105,13 @@ func (t testCustomField) MarshalTo(data []byte) (int, error) { return 0, nil }
 // Marshal skip, it's for test usage only.
 func (t testCustomField) Marshal() ([]byte, error) { return nil, nil }
 
+// Merge used by proto.Clone
+func (t *testCustomField) Merge(src proto.Message) {
+	if tmp, ok := src.(*testCustomField); ok {
+		*t = *tmp
+	}
+}
+
 // GetBearerToken wraps Bearer field and return BearerToken interface.
 //
 // If Bearer field value is nil, nil returns.