From f5d679a57382c95124ef357b19d136c909edd2b4 Mon Sep 17 00:00:00 2001 From: Evgenii Baidakov Date: Tue, 5 Sep 2023 11:31:25 +0400 Subject: [PATCH] session: Add CopyTo method for structs closes #194 Signed-off-by: Evgenii Baidakov --- session/common.go | 31 ++++ session/common_test.go | 232 +++++++++++++++++++++++++++++ session/container.go | 15 ++ session/container_internal_test.go | 68 +++++++++ session/object.go | 21 +++ session/object_internal_test.go | 105 +++++++++++++ 6 files changed, 472 insertions(+) create mode 100644 session/common_test.go create mode 100644 session/container_internal_test.go create mode 100644 session/object_internal_test.go diff --git a/session/common.go b/session/common.go index 244d81bd6..1dc94cafe 100644 --- a/session/common.go +++ b/session/common.go @@ -30,6 +30,37 @@ type commonData struct { type contextReader func(session.TokenContext, bool) error +func (x commonData) copyTo(dst *commonData) { + dst.idSet = x.idSet + copy(dst.id[:], x.id[:]) + + dst.issuerSet = x.issuerSet + iss := x.issuer + dst.issuer = iss + + dst.lifetimeSet = x.lifetimeSet + dst.iat = x.iat + dst.nbf = x.nbf + dst.exp = x.exp + + dst.authKey = make([]byte, len(x.authKey)) + copy(dst.authKey, x.authKey) + + dst.sigSet = x.sigSet + + sigKey := x.sig.GetKey() + bts := make([]byte, len(sigKey)) + copy(bts, sigKey) + dst.sig.SetKey(bts) + + dst.sig.SetScheme(x.sig.GetScheme()) + + sign := x.sig.GetSign() + bts = make([]byte, len(sign)) + copy(bts, sign) + dst.sig.SetSign(sign) +} + // reads commonData and custom context from the session.Token message. // If checkFieldPresence is set, returns an error on absence of any protocol-required // field. Verifies format of any presented field according to NeoFS API V2 protocol. diff --git a/session/common_test.go b/session/common_test.go new file mode 100644 index 000000000..8507165bf --- /dev/null +++ b/session/common_test.go @@ -0,0 +1,232 @@ +package session + +import ( + "bytes" + "testing" + + "github.com/google/uuid" + "github.com/nspcc-dev/neofs-api-go/v2/refs" + "github.com/nspcc-dev/neofs-api-go/v2/session" + "github.com/nspcc-dev/neofs-sdk-go/crypto/test" + "github.com/stretchr/testify/require" +) + +func Test_commonData_copyTo(t *testing.T) { + var sig refs.Signature + + sig.SetKey([]byte("key")) + sig.SetSign([]byte("sign")) + sig.SetScheme(refs.ECDSA_SHA512) + + signer := test.RandomSignerRFC6979(t) + + data := commonData{ + idSet: true, + id: uuid.New(), + issuerSet: true, + issuer: signer.UserID(), + lifetimeSet: true, + iat: 1, + nbf: 2, + exp: 3, + authKey: []byte{1, 2, 3, 4}, + sigSet: true, + sig: sig, + } + + t.Run("copy", func(t *testing.T) { + var dst commonData + data.copyTo(&dst) + + emptyWriter := func() session.TokenContext { + return &session.ContainerSessionContext{} + } + require.True(t, bytes.Equal(data.marshal(emptyWriter), dst.marshal(emptyWriter))) + + require.Equal(t, data.issuerSet, dst.issuerSet) + require.Equal(t, data.issuer.String(), dst.issuer.String()) + }) + + t.Run("change id", func(t *testing.T) { + var dst commonData + data.copyTo(&dst) + + require.Equal(t, data.idSet, dst.idSet) + require.Equal(t, data.id.String(), dst.id.String()) + + dst.SetID(uuid.New()) + + require.Equal(t, data.idSet, dst.idSet) + require.NotEqual(t, data.id.String(), dst.id.String()) + }) + + t.Run("overwrite id", func(t *testing.T) { + // id is not set + local := commonData{} + require.False(t, local.idSet) + + // id is set + var dst commonData + dst.SetID(uuid.New()) + require.True(t, dst.idSet) + + // overwrite ID data + local.copyTo(&dst) + + emptyWriter := func() session.TokenContext { + return &session.ContainerSessionContext{} + } + require.True(t, bytes.Equal(local.marshal(emptyWriter), dst.marshal(emptyWriter))) + + require.False(t, local.idSet) + require.False(t, dst.idSet) + + // update id + dst.SetID(uuid.New()) + + // check that affects only dst + require.False(t, local.idSet) + require.True(t, dst.idSet) + }) + + t.Run("change issuer", func(t *testing.T) { + var dst commonData + data.copyTo(&dst) + + require.Equal(t, data.issuerSet, dst.issuerSet) + require.True(t, data.issuer.Equals(dst.issuer)) + + dst.SetIssuer(test.RandomSignerRFC6979(t).UserID()) + + require.Equal(t, data.issuerSet, dst.issuerSet) + require.False(t, data.issuer.Equals(dst.issuer)) + }) + + t.Run("overwrite issuer", func(t *testing.T) { + var local commonData + require.False(t, local.issuerSet) + + var dst commonData + dst.SetIssuer(test.RandomSignerRFC6979(t).UserID()) + require.True(t, dst.issuerSet) + + local.copyTo(&dst) + require.False(t, local.issuerSet) + require.False(t, dst.issuerSet) + + emptyWriter := func() session.TokenContext { + return &session.ContainerSessionContext{} + } + require.True(t, bytes.Equal(local.marshal(emptyWriter), dst.marshal(emptyWriter))) + + require.Equal(t, local.issuerSet, dst.issuerSet) + require.True(t, local.issuer.Equals(dst.issuer)) + + dst.SetIssuer(test.RandomSignerRFC6979(t).UserID()) + require.False(t, local.issuerSet) + require.True(t, dst.issuerSet) + + require.False(t, local.issuer.Equals(dst.issuer)) + }) + + t.Run("change lifetime", func(t *testing.T) { + var dst commonData + data.copyTo(&dst) + + require.Equal(t, data.lifetimeSet, dst.lifetimeSet) + require.Equal(t, data.iat, dst.iat) + require.Equal(t, data.nbf, dst.nbf) + require.Equal(t, data.exp, dst.exp) + + dst.SetExp(100) + dst.SetIat(200) + dst.SetNbf(300) + + require.Equal(t, data.lifetimeSet, dst.lifetimeSet) + require.NotEqual(t, data.iat, dst.iat) + require.NotEqual(t, data.nbf, dst.nbf) + require.NotEqual(t, data.exp, dst.exp) + }) + + t.Run("overwrite lifetime", func(t *testing.T) { + // lifetime is not set + local := commonData{} + require.False(t, local.lifetimeSet) + + // lifetime is set + var dst commonData + dst.SetExp(100) + dst.SetIat(200) + dst.SetNbf(300) + require.True(t, dst.lifetimeSet) + + local.copyTo(&dst) + require.False(t, local.lifetimeSet) + require.False(t, dst.lifetimeSet) + + emptyWriter := func() session.TokenContext { + return &session.ContainerSessionContext{} + } + require.True(t, bytes.Equal(local.marshal(emptyWriter), dst.marshal(emptyWriter))) + + // check both are equal + require.Equal(t, local.lifetimeSet, dst.lifetimeSet) + require.Equal(t, local.iat, dst.iat) + require.Equal(t, local.nbf, dst.nbf) + require.Equal(t, local.exp, dst.exp) + + // update lifetime + dst.SetExp(100) + dst.SetIat(200) + dst.SetNbf(300) + + // check that affects only dst + require.False(t, local.lifetimeSet) + require.True(t, dst.lifetimeSet) + require.NotEqual(t, local.iat, dst.iat) + require.NotEqual(t, local.nbf, dst.nbf) + require.NotEqual(t, local.exp, dst.exp) + }) + + t.Run("change sig", func(t *testing.T) { + var dst commonData + data.copyTo(&dst) + + require.Equal(t, data.sigSet, dst.sigSet) + require.Equal(t, data.sig.GetScheme(), dst.sig.GetScheme()) + require.True(t, bytes.Equal(data.sig.GetKey(), dst.sig.GetKey())) + require.True(t, bytes.Equal(data.sig.GetSign(), dst.sig.GetSign())) + + dst.sig.SetKey([]byte{1, 2, 3}) + dst.sig.SetScheme(100) + dst.sig.SetSign([]byte{10, 11, 12}) + + require.Equal(t, data.issuerSet, dst.issuerSet) + require.NotEqual(t, data.sig.GetScheme(), dst.sig.GetScheme()) + require.False(t, bytes.Equal(data.sig.GetKey(), dst.sig.GetKey())) + require.False(t, bytes.Equal(data.sig.GetSign(), dst.sig.GetSign())) + }) + + t.Run("overwrite sig", func(t *testing.T) { + local := commonData{} + require.False(t, local.sigSet) + + emptyWriter := func() session.TokenContext { + return &session.ContainerSessionContext{} + } + + var dst commonData + require.NoError(t, dst.sign(signer, emptyWriter)) + require.True(t, dst.sigSet) + + local.copyTo(&dst) + require.False(t, local.sigSet) + require.False(t, dst.sigSet) + + require.True(t, bytes.Equal(local.marshal(emptyWriter), dst.marshal(emptyWriter))) + + require.NoError(t, dst.sign(signer, emptyWriter)) + require.False(t, local.sigSet) + require.True(t, dst.sigSet) + }) +} diff --git a/session/container.go b/session/container.go index 37cdd58ed..6ef749a46 100644 --- a/session/container.go +++ b/session/container.go @@ -30,6 +30,21 @@ type Container struct { cnr cid.ID } +// CopyTo writes deep copy of the [Container] to dst. +func (x Container) CopyTo(dst *Container) { + if dst == nil { + return + } + + x.commonData.copyTo(&dst.commonData) + + dst.verb = x.verb + + dst.cnrSet = x.cnrSet + contID := x.cnr + dst.cnr = contID +} + // readContext is a contextReader needed for commonData methods. func (x *Container) readContext(c session.TokenContext, checkFieldPresence bool) error { cCnr, ok := c.(*session.ContainerSessionContext) diff --git a/session/container_internal_test.go b/session/container_internal_test.go new file mode 100644 index 000000000..438173257 --- /dev/null +++ b/session/container_internal_test.go @@ -0,0 +1,68 @@ +package session + +import ( + "bytes" + "testing" + + "github.com/nspcc-dev/neofs-api-go/v2/session" + cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test" + "github.com/stretchr/testify/require" +) + +func TestContainer_CopyTo(t *testing.T) { + var container Container + + containerID := cidtest.ID() + + container.ForVerb(VerbContainerDelete) + container.ApplyOnlyTo(containerID) + + t.Run("copy", func(t *testing.T) { + var dst Container + container.CopyTo(&dst) + + emptyWriter := func() session.TokenContext { + return &session.ContainerSessionContext{} + } + + require.True(t, bytes.Equal(container.marshal(emptyWriter), dst.marshal(emptyWriter))) + }) + + t.Run("change", func(t *testing.T) { + var dst Container + container.CopyTo(&dst) + + require.Equal(t, container.verb, dst.verb) + require.True(t, container.cnrSet) + require.True(t, dst.cnrSet) + + container.ForVerb(VerbContainerSetEACL) + + require.NotEqual(t, container.verb, dst.verb) + require.True(t, container.cnrSet) + require.True(t, dst.cnrSet) + }) + + t.Run("overwrite container id", func(t *testing.T) { + var local Container + require.False(t, local.cnrSet) + + var dst Container + dst.ApplyOnlyTo(containerID) + require.True(t, dst.cnrSet) + + local.CopyTo(&dst) + emptyWriter := func() session.TokenContext { + return &session.ContainerSessionContext{} + } + + require.True(t, bytes.Equal(local.marshal(emptyWriter), dst.marshal(emptyWriter))) + + require.False(t, local.cnrSet) + require.False(t, dst.cnrSet) + + dst.ApplyOnlyTo(containerID) + require.True(t, dst.cnrSet) + require.False(t, local.cnrSet) + }) +} diff --git a/session/object.go b/session/object.go index 08a101203..fdb6958bf 100644 --- a/session/object.go +++ b/session/object.go @@ -32,6 +32,27 @@ type Object struct { objs []oid.ID } +// CopyTo writes deep copy of the [Container] to dst. +func (x Object) CopyTo(dst *Object) { + if dst == nil { + return + } + + x.commonData.copyTo(&dst.commonData) + + dst.verb = x.verb + + dst.cnrSet = x.cnrSet + contID := x.cnr + dst.cnr = contID + + dst.objs = make([]oid.ID, len(x.objs)) + for i, id := range x.objs { + newID := id + dst.objs[i] = newID + } +} + func (x *Object) readContext(c session.TokenContext, checkFieldPresence bool) error { cObj, ok := c.(*session.ObjectSessionContext) if !ok || cObj == nil { diff --git a/session/object_internal_test.go b/session/object_internal_test.go new file mode 100644 index 000000000..92edd8e1e --- /dev/null +++ b/session/object_internal_test.go @@ -0,0 +1,105 @@ +package session + +import ( + "bytes" + "crypto/rand" + "crypto/sha256" + "testing" + + "github.com/nspcc-dev/neofs-api-go/v2/session" + cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test" + oid "github.com/nspcc-dev/neofs-sdk-go/object/id" + "github.com/stretchr/testify/require" +) + +func generateRandomOids(size int) []oid.ID { + checksum := [sha256.Size]byte{} + + result := make([]oid.ID, size) + for i := 0; i < size; i++ { + _, _ = rand.Read(checksum[:]) + + var id oid.ID + id.SetSHA256(checksum) + result[i] = id + } + + return result +} + +func TestObject_CopyTo(t *testing.T) { + var container Object + + containerID := cidtest.ID() + + container.ForVerb(VerbObjectDelete) + container.BindContainer(containerID) + container.LimitByObjects(generateRandomOids(2)...) + + t.Run("copy", func(t *testing.T) { + var dst Object + container.CopyTo(&dst) + + emptyWriter := func() session.TokenContext { + return &session.ContainerSessionContext{} + } + + require.True(t, bytes.Equal(container.marshal(emptyWriter), dst.marshal(emptyWriter))) + }) + + t.Run("change simple fields", func(t *testing.T) { + var dst Object + container.CopyTo(&dst) + + require.Equal(t, container.verb, dst.verb) + require.True(t, container.cnrSet) + require.True(t, dst.cnrSet) + + container.ForVerb(VerbObjectHead) + + require.NotEqual(t, container.verb, dst.verb) + require.True(t, container.cnrSet) + require.True(t, dst.cnrSet) + }) + + t.Run("change ids", func(t *testing.T) { + var dst Object + container.CopyTo(&dst) + + for i := range container.objs { + require.True(t, container.objs[i].Equals(dst.objs[i])) + + // change object id in the new object + for j := range dst.objs[i] { + dst.objs[i][j] = byte(j) + } + } + + for i := range container.objs { + require.False(t, container.objs[i].Equals(dst.objs[i])) + } + }) + + t.Run("overwrite container id", func(t *testing.T) { + var local Object + require.False(t, local.cnrSet) + + var dst Object + dst.BindContainer(containerID) + require.True(t, dst.cnrSet) + + local.CopyTo(&dst) + emptyWriter := func() session.TokenContext { + return &session.ContainerSessionContext{} + } + + require.True(t, bytes.Equal(local.marshal(emptyWriter), dst.marshal(emptyWriter))) + + require.False(t, local.cnrSet) + require.False(t, dst.cnrSet) + + dst.BindContainer(containerID) + require.True(t, dst.cnrSet) + require.False(t, local.cnrSet) + }) +}