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

Add CopyTo method for deep copy #512

Merged
merged 5 commits into from
Sep 7, 2023
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
56 changes: 56 additions & 0 deletions container/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,62 @@ const (
attributeTimestamp = "Timestamp"
)

// CopyTo writes deep copy of the [Container] to dst.
func (x Container) CopyTo(dst *Container) {
dst.SetBasicACL(x.BasicACL())

if owner := x.v2.GetOwnerID(); owner != nil {
var newOwner refs.OwnerID
if val := owner.GetValue(); val != nil {
bts := make([]byte, len(val))
copy(bts, val)

newOwner.SetValue(bts)
} else {
newOwner.SetValue(nil)
}

dst.v2.SetOwnerID(&newOwner)
} else {
dst.v2.SetOwnerID(nil)
}

if x.v2.GetVersion() != nil {
ver := x.v2.GetVersion()
newVer := *ver
dst.v2.SetVersion(&newVer)
} else {
dst.v2.SetVersion(nil)
}

// do we need to set the different nonce?
if nonce := x.v2.GetNonce(); nonce != nil {
newNonce := make([]byte, len(nonce))
copy(newNonce, nonce)
dst.v2.SetNonce(nonce)
} else {
dst.v2.SetNonce(nil)
}

if len(x.v2.GetAttributes()) > 0 {
dst.v2.SetAttributes([]container.Attribute{})

attributeIterator := func(key, val string) {
dst.SetAttribute(key, val)
}

x.IterateAttributes(attributeIterator)
}

if x.v2.GetPlacementPolicy() != nil {
var ppCopy netmap.PlacementPolicy
x.PlacementPolicy().CopyTo(&ppCopy)
dst.SetPlacementPolicy(ppCopy)
} else {
x.v2.SetPlacementPolicy(nil)
}
}

// reads Container from the container.Container message. If checkFieldPresence is set,
// returns an error on absence of any protocol-required field.
func (x *Container) readFromV2(m container.Container, checkFieldPresence bool) error {
Expand Down
142 changes: 142 additions & 0 deletions container/container_internal_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package container

import (
"bytes"
"math/rand"
"testing"

"github.com/nspcc-dev/neofs-sdk-go/container/acl"
"github.com/nspcc-dev/neofs-sdk-go/netmap"
usertest "github.com/nspcc-dev/neofs-sdk-go/user/test"
"github.com/stretchr/testify/require"
)

func TestContainer_CopyTo(t *testing.T) {
owner := usertest.ID(t)

var container Container
container.Init()

attrOne := "a0"
attrValue := "a1"

container.SetOwner(owner)
container.SetBasicACL(acl.PublicRWExtended)
container.SetAttribute(attrOne, attrValue)

var pp netmap.PlacementPolicy
pp.SetContainerBackupFactor(123)

var rd netmap.ReplicaDescriptor
rd.SetSelectorName("selector")
rd.SetNumberOfObjects(100)
pp.AddReplicas(rd)

var f netmap.Filter
f.SetName("filter")
pp.AddFilters(f)

var s netmap.Selector
s.SetName("selector")
pp.AddSelectors(s)

container.SetPlacementPolicy(pp)

t.Run("copy", func(t *testing.T) {
var dst Container
container.CopyTo(&dst)

require.Equal(t, container, dst)
require.True(t, bytes.Equal(container.Marshal(), dst.Marshal()))
})

t.Run("change acl", func(t *testing.T) {
var dst Container
container.CopyTo(&dst)

require.Equal(t, container.BasicACL(), dst.BasicACL())
dst.SetBasicACL(acl.Private)
require.NotEqual(t, container.BasicACL(), dst.BasicACL())
})

t.Run("change owner", func(t *testing.T) {
var dst Container
container.CopyTo(&dst)

require.True(t, container.Owner().Equals(dst.Owner()))

newOwner := usertest.ID(t)
dst.v2.GetOwnerID().SetValue(newOwner.WalletBytes())

require.False(t, container.Owner().Equals(dst.Owner()))
})

t.Run("replace owner", func(t *testing.T) {
var dst Container
container.CopyTo(&dst)

require.True(t, container.Owner().Equals(dst.Owner()))

newOwner := usertest.ID(t)
dst.SetOwner(newOwner)

require.False(t, container.Owner().Equals(dst.Owner()))
})

t.Run("change nonce", func(t *testing.T) {
var dst Container
container.CopyTo(&dst)

require.True(t, bytes.Equal(container.v2.GetNonce(), dst.v2.GetNonce()))
dst.v2.SetNonce([]byte{1, 2, 3})
require.False(t, bytes.Equal(container.v2.GetNonce(), dst.v2.GetNonce()))
})

t.Run("overwrite nonce", func(t *testing.T) {
var local Container
require.Empty(t, local.v2.GetNonce())

var dst Container
dst.v2.SetNonce([]byte{1, 2, 3})
require.NotEmpty(t, dst.v2.GetNonce())

local.CopyTo(&dst)
require.True(t, bytes.Equal(local.Marshal(), dst.Marshal()))

require.Empty(t, local.v2.GetNonce())
require.Empty(t, dst.v2.GetNonce())

require.True(t, bytes.Equal(local.v2.GetNonce(), dst.v2.GetNonce()))
dst.v2.SetNonce([]byte{1, 2, 3})
require.False(t, bytes.Equal(local.v2.GetNonce(), dst.v2.GetNonce()))
})

t.Run("change version", func(t *testing.T) {
var dst Container
container.CopyTo(&dst)

oldVer := container.v2.GetVersion()
require.NotNil(t, oldVer)

newVer := dst.v2.GetVersion()
require.NotNil(t, newVer)

require.Equal(t, oldVer.GetMajor(), newVer.GetMajor())
require.Equal(t, oldVer.GetMinor(), newVer.GetMinor())

newVer.SetMajor(rand.Uint32())
newVer.SetMinor(rand.Uint32())

require.NotEqual(t, oldVer.GetMajor(), newVer.GetMajor())
require.NotEqual(t, oldVer.GetMinor(), newVer.GetMinor())
})

t.Run("change attributes", func(t *testing.T) {
var dst Container
container.CopyTo(&dst)

require.Equal(t, container.Attribute(attrOne), dst.Attribute(attrOne))
dst.SetAttribute(attrOne, "value")
require.NotEqual(t, container.Attribute(attrOne), dst.Attribute(attrOne))
})
}
8 changes: 8 additions & 0 deletions eacl/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ func (u u64Stringer) EncodeToString() string {
return strconv.FormatUint(uint64(u), 10)
}

// CopyTo writes deep copy of the [Filter] to dst.
func (f Filter) CopyTo(dst *Filter) {
dst.from = f.from
dst.matcher = f.matcher
dst.key = f.key
dst.value = f.value
}

// Value returns filtered string value.
func (f Filter) Value() string {
return f.value.EncodeToString()
Expand Down
46 changes: 46 additions & 0 deletions eacl/filter_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package eacl

import (
"bytes"
"strconv"
"testing"

Expand Down Expand Up @@ -100,3 +101,48 @@ func TestFilter_ToV2(t *testing.T) {
}
})
}

func TestFilter_CopyTo(t *testing.T) {
var filter Filter
filter.value = staticStringer("value")
filter.from = 1
filter.matcher = 1
filter.key = filterKey{
typ: 1,
str: "1",
}

var dst Filter
t.Run("copy", func(t *testing.T) {
filter.CopyTo(&dst)

bts, err := filter.Marshal()
require.NoError(t, err)

bts2, err := dst.Marshal()
require.NoError(t, err)

require.Equal(t, filter, dst)
require.True(t, bytes.Equal(bts, bts2))
})

t.Run("change", func(t *testing.T) {
require.Equal(t, filter.value, dst.value)
require.Equal(t, filter.from, dst.from)
require.Equal(t, filter.matcher, dst.matcher)
require.Equal(t, filter.key.typ, dst.key.typ)
require.Equal(t, filter.key.str, dst.key.str)

dst.value = staticStringer("value2")
dst.from = 2
dst.matcher = 2
dst.key.typ = 2
dst.key.str = "2"

require.NotEqual(t, filter.value, dst.value)
require.NotEqual(t, filter.from, dst.from)
require.NotEqual(t, filter.matcher, dst.matcher)
require.NotEqual(t, filter.key.typ, dst.key.typ)
require.NotEqual(t, filter.key.str, dst.key.str)
})
}
17 changes: 17 additions & 0 deletions eacl/record.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,23 @@ type Record struct {
targets []Target
}

// CopyTo writes deep copy of the [Record] to dst.
func (r Record) CopyTo(dst *Record) {
dst.action = r.action
dst.operation = r.operation

dst.filters = make([]Filter, len(r.filters))
copy(dst.filters, r.filters)

dst.targets = make([]Target, len(r.targets))
for i, t := range r.targets {
var newTarget Target
t.CopyTo(&newTarget)

dst.targets[i] = newTarget
}
}

// Targets returns list of target subjects to apply ACL rule to.
//
// The value returned shares memory with the structure itself, so changing it can lead to data corruption.
Expand Down
69 changes: 69 additions & 0 deletions eacl/record_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package eacl

import (
"bytes"
"crypto/ecdsa"
"fmt"
"testing"
Expand Down Expand Up @@ -251,3 +252,71 @@ func randomPublicKey(t *testing.T) *ecdsa.PublicKey {
require.NoError(t, err)
return &p.PrivateKey.PublicKey
}

func TestRecord_CopyTo(t *testing.T) {
var record Record
record.action = ActionAllow
record.operation = OperationPut
record.AddObjectAttributeFilter(MatchStringEqual, "key", "value")

var target Target
target.SetRole(1)
target.SetBinaryKeys([][]byte{
{1, 2, 3},
})

record.SetTargets(target)
record.AddObjectAttributeFilter(MatchStringEqual, "key", "value")

t.Run("copy", func(t *testing.T) {
var dst Record
record.CopyTo(&dst)

bts, err := record.Marshal()
require.NoError(t, err)

bts2, err := dst.Marshal()
require.NoError(t, err)

require.Equal(t, record, dst)
require.True(t, bytes.Equal(bts, bts2))
})

t.Run("change filters", func(t *testing.T) {
var dst Record
record.CopyTo(&dst)

require.Equal(t, record.filters[0].key.str, dst.filters[0].key.str)
require.Equal(t, record.filters[0].key.typ, dst.filters[0].key.typ)
require.Equal(t, record.filters[0].matcher, dst.filters[0].matcher)
require.Equal(t, record.filters[0].value, dst.filters[0].value)
require.Equal(t, record.filters[0].from, dst.filters[0].from)

dst.filters[0].key.str = "key2"
dst.filters[0].key.typ = 12345
dst.filters[0].matcher = MatchStringNotEqual
dst.filters[0].value = staticStringer("staticStringer")
dst.filters[0].from = 12345

require.NotEqual(t, record.filters[0].key.str, dst.filters[0].key.str)
require.NotEqual(t, record.filters[0].key.typ, dst.filters[0].key.typ)
require.NotEqual(t, record.filters[0].matcher, dst.filters[0].matcher)
require.NotEqual(t, record.filters[0].value, dst.filters[0].value)
require.NotEqual(t, record.filters[0].from, dst.filters[0].from)
})

t.Run("change target", func(t *testing.T) {
var dst Record
record.CopyTo(&dst)

require.Equal(t, record.targets[0].role, dst.targets[0].role)
dst.targets[0].role = 12345
require.NotEqual(t, record.targets[0].role, dst.targets[0].role)

for i, key := range dst.targets[0].keys {
require.True(t, bytes.Equal(key, record.targets[0].keys[i]))
key[0] = 10
require.False(t, bytes.Equal(key, record.targets[0].keys[i]))
}
})
}
Loading