From 9a95e91978241a97a60aadc3cfdfa0958e03237c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A1lint=20Ujv=C3=A1ri?= <58116288+bosi95@users.noreply.github.com> Date: Wed, 3 Apr 2024 14:05:25 +0200 Subject: [PATCH] Persist grantee list on swarm (#30) * Persist grantee list on swarm * accesslogic refactor * Refactor grantee list tests Co-authored-by: Roland Seres --- pkg/dynamicaccess/accesslogic.go | 5 +- pkg/dynamicaccess/controller.go | 6 +- pkg/dynamicaccess/controller_test.go | 4 +- pkg/dynamicaccess/grantee.go | 130 ++++++++++-- pkg/dynamicaccess/grantee_manager.go | 25 +-- pkg/dynamicaccess/grantee_manager_test.go | 6 +- pkg/dynamicaccess/grantee_test.go | 236 +++++++++++++++------- pkg/dynamicaccess/mock/container.go | 20 -- pkg/dynamicaccess/mock/grantee.go | 51 +++++ 9 files changed, 352 insertions(+), 131 deletions(-) delete mode 100644 pkg/dynamicaccess/mock/container.go create mode 100644 pkg/dynamicaccess/mock/grantee.go diff --git a/pkg/dynamicaccess/accesslogic.go b/pkg/dynamicaccess/accesslogic.go index 6c632a58351..593e02d630c 100644 --- a/pkg/dynamicaccess/accesslogic.go +++ b/pkg/dynamicaccess/accesslogic.go @@ -76,10 +76,11 @@ func (al ActLogic) AddGrantee(storage kvs.KeyValueStore, publisherPubKey, grante return err } lookupKey := keys[0] - accessKeyEncryptionKey := keys[1] + // accessKeyDecryptionKey is used for encryption of the access key + accessKeyDecryptionKey := keys[1] // Encrypt the access key for the new Grantee - cipher := encryption.New(encryption.Key(accessKeyEncryptionKey), 0, uint32(0), hashFunc) + cipher := encryption.New(encryption.Key(accessKeyDecryptionKey), 0, uint32(0), hashFunc) granteeEncryptedAccessKey, err := cipher.Encrypt(accessKey) if err != nil { return err diff --git a/pkg/dynamicaccess/controller.go b/pkg/dynamicaccess/controller.go index d118e22489f..df39fa23c32 100644 --- a/pkg/dynamicaccess/controller.go +++ b/pkg/dynamicaccess/controller.go @@ -9,7 +9,7 @@ import ( type Controller interface { DownloadHandler(timestamp int64, enryptedRef swarm.Address, publisher *ecdsa.PublicKey, tag string) (swarm.Address, error) - UploadHandler(ref swarm.Address, publisher *ecdsa.PublicKey, topic string) (swarm.Address, error) + UploadHandler(ref swarm.Address, publisher *ecdsa.PublicKey) (swarm.Address, error) } type defaultController struct { @@ -27,7 +27,7 @@ func (c *defaultController) DownloadHandler(timestamp int64, enryptedRef swarm.A return addr, err } -func (c *defaultController) UploadHandler(ref swarm.Address, publisher *ecdsa.PublicKey, topic string) (swarm.Address, error) { +func (c *defaultController) UploadHandler(ref swarm.Address, publisher *ecdsa.PublicKey) (swarm.Address, error) { kvs, err := c.history.Lookup(0) if err != nil { return swarm.EmptyAddress, err @@ -36,7 +36,7 @@ func (c *defaultController) UploadHandler(ref swarm.Address, publisher *ecdsa.Pu // new feed // TODO: putter session to create kvs kvs = kvsmock.New() - _, err = c.granteeManager.Publish(kvs, publisher, topic) + _, err = c.granteeManager.Publish(kvs, publisher) if err != nil { return swarm.EmptyAddress, err } diff --git a/pkg/dynamicaccess/controller_test.go b/pkg/dynamicaccess/controller_test.go index 63f80059e08..24107123ab5 100644 --- a/pkg/dynamicaccess/controller_test.go +++ b/pkg/dynamicaccess/controller_test.go @@ -72,9 +72,9 @@ func TestEncrypt(t *testing.T) { eref, ref := prepareEncryptedChunkReference(ak) key1, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - gm.Add("topic", []*ecdsa.PublicKey{&key1.PublicKey}) + gm.Add([]*ecdsa.PublicKey{&key1.PublicKey}) - addr, _ := c.UploadHandler(ref, &pk.PublicKey, "topic") + addr, _ := c.UploadHandler(ref, &pk.PublicKey) if !addr.Equal(eref) { t.Fatalf("Decrypted chunk address: %s is not the expected: %s", addr, eref) } diff --git a/pkg/dynamicaccess/grantee.go b/pkg/dynamicaccess/grantee.go index d85ebf217ea..afbe5349818 100644 --- a/pkg/dynamicaccess/grantee.go +++ b/pkg/dynamicaccess/grantee.go @@ -1,44 +1,134 @@ package dynamicaccess import ( + "context" "crypto/ecdsa" + "crypto/elliptic" + "fmt" + + "github.com/ethersphere/bee/pkg/file" + "github.com/ethersphere/bee/pkg/storer" + "github.com/ethersphere/bee/pkg/swarm" +) + +const ( + publicKeyLen = 65 ) type GranteeList interface { - Add(topic string, addList []*ecdsa.PublicKey) error - Remove(topic string, removeList []*ecdsa.PublicKey) error - Get(topic string) []*ecdsa.PublicKey + Add(addList []*ecdsa.PublicKey) error + Remove(removeList []*ecdsa.PublicKey) error + Get() []*ecdsa.PublicKey + Save() (swarm.Address, error) } type GranteeListStruct struct { - grantees map[string][]*ecdsa.PublicKey + grantees []byte + loadSave file.LoadSaver + putter storer.PutterSession +} + +var _ GranteeList = (*GranteeListStruct)(nil) + +func (g *GranteeListStruct) Get() []*ecdsa.PublicKey { + return g.deserialize(g.grantees) +} + +func (g *GranteeListStruct) serialize(publicKeys []*ecdsa.PublicKey) []byte { + b := make([]byte, 0, len(publicKeys)*publicKeyLen) + for _, key := range publicKeys { + b = append(b, g.serializePublicKey(key)...) + } + return b +} + +func (g *GranteeListStruct) serializePublicKey(pub *ecdsa.PublicKey) []byte { + return elliptic.Marshal(pub.Curve, pub.X, pub.Y) } -func (g *GranteeListStruct) Get(topic string) []*ecdsa.PublicKey { - grantees := g.grantees[topic] - keys := make([]*ecdsa.PublicKey, len(grantees)) - copy(keys, grantees) - return keys +func (g *GranteeListStruct) deserialize(data []byte) []*ecdsa.PublicKey { + if len(data) == 0 { + return nil + } + + p := make([]*ecdsa.PublicKey, 0, len(data)/publicKeyLen) + for i := 0; i < len(data); i += publicKeyLen { + pubKey := g.deserializeBytes(data[i : i+publicKeyLen]) + if pubKey == nil { + return nil + } + p = append(p, pubKey) + } + return p } -func (g *GranteeListStruct) Add(topic string, addList []*ecdsa.PublicKey) error { - g.grantees[topic] = append(g.grantees[topic], addList...) +func (g *GranteeListStruct) deserializeBytes(data []byte) *ecdsa.PublicKey { + curve := elliptic.P256() + x, y := elliptic.Unmarshal(curve, data) + return &ecdsa.PublicKey{Curve: curve, X: x, Y: y} +} + +func (g *GranteeListStruct) Add(addList []*ecdsa.PublicKey) error { + if len(addList) == 0 { + return fmt.Errorf("no public key provided") + } + + data := g.serialize(addList) + g.grantees = append(g.grantees, data...) return nil } -func (g *GranteeListStruct) Remove(topic string, removeList []*ecdsa.PublicKey) error { - for _, remove := range removeList { - for i, grantee := range g.grantees[topic] { - if *grantee == *remove { - g.grantees[topic][i] = g.grantees[topic][len(g.grantees[topic])-1] - g.grantees[topic] = g.grantees[topic][:len(g.grantees[topic])-1] +func (g *GranteeListStruct) Save() (swarm.Address, error) { + refBytes, err := g.loadSave.Save(context.Background(), g.grantees) + if err != nil { + return swarm.ZeroAddress, fmt.Errorf("grantee save error: %w", err) + } + address := swarm.NewAddress(refBytes) + err = g.putter.Done(address) + if err != nil { + return swarm.ZeroAddress, err + } + return address, nil +} + +func (g *GranteeListStruct) Remove(keysToRemove []*ecdsa.PublicKey) error { + if len(keysToRemove) == 0 { + return fmt.Errorf("nothing to remove") + } + grantees := g.deserialize(g.grantees) + if grantees == nil { + return fmt.Errorf("no grantee found") + } + + for _, remove := range keysToRemove { + for i, grantee := range grantees { + if grantee.Equal(remove) { + grantees[i] = grantees[len(grantees)-1] + grantees = grantees[:len(grantees)-1] } } } - + g.grantees = g.serialize(grantees) return nil } -func NewGrantee() *GranteeListStruct { - return &GranteeListStruct{grantees: make(map[string][]*ecdsa.PublicKey)} +func NewGranteeList(ls file.LoadSaver, putter storer.PutterSession, reference swarm.Address) GranteeList { + var ( + data []byte + err error + ) + if swarm.ZeroAddress.Equal(reference) || swarm.EmptyAddress.Equal(reference) { + data = []byte{} + } else { + data, err = ls.Load(context.Background(), reference.Bytes()) + } + if err != nil { + return nil + } + + return &GranteeListStruct{ + grantees: data, + loadSave: ls, + putter: putter, + } } diff --git a/pkg/dynamicaccess/grantee_manager.go b/pkg/dynamicaccess/grantee_manager.go index 004a933b006..eaa9cc9ffd9 100644 --- a/pkg/dynamicaccess/grantee_manager.go +++ b/pkg/dynamicaccess/grantee_manager.go @@ -3,16 +3,17 @@ package dynamicaccess import ( "crypto/ecdsa" + "github.com/ethersphere/bee/pkg/dynamicaccess/mock" "github.com/ethersphere/bee/pkg/kvs" "github.com/ethersphere/bee/pkg/swarm" ) type GranteeManager interface { - Get(topic string) []*ecdsa.PublicKey - Add(topic string, addList []*ecdsa.PublicKey) error - Publish(kvs kvs.KeyValueStore, publisher *ecdsa.PublicKey, topic string) (swarm.Address, error) + Get() []*ecdsa.PublicKey + Add(addList []*ecdsa.PublicKey) error + Publish(kvs kvs.KeyValueStore, publisher *ecdsa.PublicKey) (swarm.Address, error) - // HandleGrantees(topic string, addList, removeList []*ecdsa.PublicKey) *Act + // HandleGrantees(addList, removeList []*ecdsa.PublicKey) *Act // Load(grantee Grantee) // Save() @@ -22,24 +23,24 @@ var _ GranteeManager = (*granteeManager)(nil) type granteeManager struct { accessLogic ActLogic - granteeList GranteeList + granteeList *mock.GranteeListStructMock } func NewGranteeManager(al ActLogic) *granteeManager { - return &granteeManager{accessLogic: al, granteeList: NewGrantee()} + return &granteeManager{accessLogic: al, granteeList: mock.NewGranteeList()} } -func (gm *granteeManager) Get(topic string) []*ecdsa.PublicKey { - return gm.granteeList.Get(topic) +func (gm *granteeManager) Get() []*ecdsa.PublicKey { + return gm.granteeList.Get() } -func (gm *granteeManager) Add(topic string, addList []*ecdsa.PublicKey) error { - return gm.granteeList.Add(topic, addList) +func (gm *granteeManager) Add(addList []*ecdsa.PublicKey) error { + return gm.granteeList.Add(addList) } -func (gm *granteeManager) Publish(kvs kvs.KeyValueStore, publisher *ecdsa.PublicKey, topic string) (swarm.Address, error) { +func (gm *granteeManager) Publish(kvs kvs.KeyValueStore, publisher *ecdsa.PublicKey) (swarm.Address, error) { err := gm.accessLogic.AddPublisher(kvs, publisher) - for _, grantee := range gm.granteeList.Get(topic) { + for _, grantee := range gm.granteeList.Get() { err = gm.accessLogic.AddGrantee(kvs, publisher, grantee, nil) } return swarm.EmptyAddress, err diff --git a/pkg/dynamicaccess/grantee_manager_test.go b/pkg/dynamicaccess/grantee_manager_test.go index 7ac43425e10..d458e007088 100644 --- a/pkg/dynamicaccess/grantee_manager_test.go +++ b/pkg/dynamicaccess/grantee_manager_test.go @@ -24,15 +24,15 @@ func TestAdd(t *testing.T) { id1, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) id2, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - err := m.Add("topic", []*ecdsa.PublicKey{&id1.PublicKey}) + err := m.Add([]*ecdsa.PublicKey{&id1.PublicKey}) if err != nil { t.Errorf("Add() returned an error") } - err = m.Add("topic", []*ecdsa.PublicKey{&id2.PublicKey}) + err = m.Add([]*ecdsa.PublicKey{&id2.PublicKey}) if err != nil { t.Errorf("Add() returned an error") } s := kvsmock.New() - m.Publish(s, &pub.PublicKey, "topic") + m.Publish(s, &pub.PublicKey) fmt.Println("") } diff --git a/pkg/dynamicaccess/grantee_test.go b/pkg/dynamicaccess/grantee_test.go index 7962690ccb4..5b7a9bc5402 100644 --- a/pkg/dynamicaccess/grantee_test.go +++ b/pkg/dynamicaccess/grantee_test.go @@ -1,104 +1,202 @@ package dynamicaccess_test import ( + "context" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" - "reflect" "testing" "github.com/ethersphere/bee/pkg/dynamicaccess" + "github.com/ethersphere/bee/pkg/file" + "github.com/ethersphere/bee/pkg/file/loadsave" + "github.com/ethersphere/bee/pkg/file/pipeline" + "github.com/ethersphere/bee/pkg/file/pipeline/builder" + "github.com/ethersphere/bee/pkg/file/redundancy" + "github.com/ethersphere/bee/pkg/storage" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" + "github.com/ethersphere/bee/pkg/swarm" + "github.com/stretchr/testify/assert" ) -var _ dynamicaccess.GranteeList = (*dynamicaccess.GranteeListStruct)(nil) +var mockStorer = mockstorer.New() -func TestGranteeAddGrantees(t *testing.T) { - grantee := dynamicaccess.NewGrantee() - - key1, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - if err != nil { - t.Errorf("Expected no error, got %v", err) - } - - key2, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - if err != nil { - t.Errorf("Expected no error, got %v", err) - } - - addList := []*ecdsa.PublicKey{&key1.PublicKey, &key2.PublicKey} - exampleTopic := "topic" - err = grantee.Add(exampleTopic, addList) - - if err != nil { - t.Errorf("Expected no error, got %v", err) - } - - grantees := grantee.Get(exampleTopic) - if !reflect.DeepEqual(grantees, addList) { - t.Errorf("Expected grantees %v, got %v", addList, grantees) +func requestPipelineFactory(ctx context.Context, s storage.Putter, encrypt bool, rLevel redundancy.Level) func() pipeline.Interface { + return func() pipeline.Interface { + return builder.NewPipelineBuilder(ctx, s, encrypt, rLevel) } } -func TestRemoveGrantees(t *testing.T) { - grantee := dynamicaccess.NewGrantee() +func createLs() file.LoadSaver { + return loadsave.New(mockStorer.ChunkStore(), mockStorer.Cache(), requestPipelineFactory(context.Background(), mockStorer.Cache(), false, redundancy.NONE)) +} +func generateKeyListFixture() ([]*ecdsa.PublicKey, error) { key1, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - if err != nil { - t.Errorf("Expected no error, got %v", err) - } - key2, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + key3, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { - t.Errorf("Expected no error, got %v", err) - } - - addList := []*ecdsa.PublicKey{&key1.PublicKey, &key2.PublicKey} - exampleTopic := "topic" - err = grantee.Add(exampleTopic, addList) - if err != nil { - t.Errorf("Expected no error, got %v", err) + return nil, err } + return []*ecdsa.PublicKey{&key1.PublicKey, &key2.PublicKey, &key3.PublicKey}, nil +} - removeList := []*ecdsa.PublicKey{&key1.PublicKey} - err = grantee.Remove(exampleTopic, removeList) +func TestGranteeAddGet(t *testing.T) { + putter := mockStorer.DirectUpload() + gl := dynamicaccess.NewGranteeList(createLs(), putter, swarm.ZeroAddress) + keys, err := generateKeyListFixture() if err != nil { - t.Errorf("Expected no error, got %v", err) + t.Errorf("key generation error: %v", err) } - grantees := grantee.Get(exampleTopic) - expectedGrantees := []*ecdsa.PublicKey{&key2.PublicKey} + t.Run("Get empty grantee list should return error", func(t *testing.T) { + val := gl.Get() + assert.Nil(t, val) + }) + + t.Run("Get should return value equal to put value", func(t *testing.T) { + var ( + addList1 []*ecdsa.PublicKey = []*ecdsa.PublicKey{keys[0]} + addList2 []*ecdsa.PublicKey = []*ecdsa.PublicKey{keys[1], keys[0]} + addList3 []*ecdsa.PublicKey = keys + ) + testCases := []struct { + name string + list []*ecdsa.PublicKey + }{ + { + name: "Test list = 1", + list: addList1, + }, + { + name: "Test list = 2", + list: addList2, + }, + { + name: "Test list = 3", + list: addList3, + }, + { + name: "Test empty add list", + list: nil, + }, + } - for i, grantee := range grantees { - if grantee != expectedGrantees[i] { - t.Errorf("Expected grantee %v, got %v", expectedGrantees[i], grantee) + expList := []*ecdsa.PublicKey{} + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := gl.Add(tc.list) + if tc.list == nil { + assert.Error(t, err) + } else { + assert.NoError(t, err) + expList = append(expList, tc.list...) + retVal := gl.Get() + assert.Equal(t, expList, retVal) + } + }) } - } + }) } -func TestGetGrantees(t *testing.T) { - grantee := dynamicaccess.NewGrantee() - - key1, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) +func TestGranteeRemove(t *testing.T) { + putter := mockStorer.DirectUpload() + gl := dynamicaccess.NewGranteeList(createLs(), putter, swarm.ZeroAddress) + keys, err := generateKeyListFixture() if err != nil { - t.Errorf("Expected no error, got %v", err) + t.Errorf("key generation error: %v", err) } - key2, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - if err != nil { - t.Errorf("Expected no error, got %v", err) - } + t.Run("Add should NOT return error", func(t *testing.T) { + err := gl.Add(keys) + assert.NoError(t, err) + retVal := gl.Get() + assert.Equal(t, keys, retVal) + }) + removeList1 := []*ecdsa.PublicKey{keys[0]} + removeList2 := []*ecdsa.PublicKey{keys[2], keys[1]} + t.Run("Remove the first item should return NO error", func(t *testing.T) { + err := gl.Remove(removeList1) + assert.NoError(t, err) + retVal := gl.Get() + assert.Equal(t, removeList2, retVal) + }) + t.Run("Remove non-existent item should return NO error", func(t *testing.T) { + err := gl.Remove(removeList1) + assert.NoError(t, err) + retVal := gl.Get() + assert.Equal(t, removeList2, retVal) + }) + t.Run("Remove second and third item should return NO error", func(t *testing.T) { + err := gl.Remove(removeList2) + assert.NoError(t, err) + retVal := gl.Get() + assert.Nil(t, retVal) + }) + t.Run("Remove from empty grantee list should return error", func(t *testing.T) { + err := gl.Remove(removeList1) + assert.Error(t, err) + retVal := gl.Get() + assert.Nil(t, retVal) + }) + t.Run("Remove empty remove list should return error", func(t *testing.T) { + err := gl.Remove(nil) + assert.Error(t, err) + retVal := gl.Get() + assert.Nil(t, retVal) + }) +} - addList := []*ecdsa.PublicKey{&key1.PublicKey, &key2.PublicKey} - exampleTopic := "topic" - err = grantee.Add(exampleTopic, addList) +func TestGranteeSave(t *testing.T) { + keys, err := generateKeyListFixture() if err != nil { - t.Errorf("Expected no error, got %v", err) - } - - grantees := grantee.Get(exampleTopic) - for i, grantee := range grantees { - if grantee != addList[i] { - t.Errorf("Expected grantee %v, got %v", addList[i], grantee) - } + t.Errorf("key generation error: %v", err) } + t.Run("Save empty grantee list return NO error", func(t *testing.T) { + gl := dynamicaccess.NewGranteeList(createLs(), mockStorer.DirectUpload(), swarm.ZeroAddress) + _, err := gl.Save() + assert.NoError(t, err) + }) + t.Run("Save not empty grantee list return valid swarm address", func(t *testing.T) { + gl := dynamicaccess.NewGranteeList(createLs(), mockStorer.DirectUpload(), swarm.ZeroAddress) + err = gl.Add(keys) + ref, err := gl.Save() + assert.NoError(t, err) + assert.True(t, ref.IsValidNonEmpty()) + }) + t.Run("Save grantee list with one item, no error, pre-save value exist", func(t *testing.T) { + ls := createLs() + putter := mockStorer.DirectUpload() + gl1 := dynamicaccess.NewGranteeList(ls, putter, swarm.ZeroAddress) + + err := gl1.Add(keys) + assert.NoError(t, err) + + ref, err := gl1.Save() + assert.NoError(t, err) + + gl2 := dynamicaccess.NewGranteeList(ls, putter, ref) + val := gl2.Get() + assert.NoError(t, err) + assert.Equal(t, keys, val) + }) + t.Run("Save grantee list and add one item, no error, after-save value exist", func(t *testing.T) { + ls := createLs() + putter := mockStorer.DirectUpload() + + gl1 := dynamicaccess.NewGranteeList(ls, putter, swarm.ZeroAddress) + + err := gl1.Add(keys) + assert.NoError(t, err) + ref, err := gl1.Save() + assert.NoError(t, err) + + // New KVS + gl2 := dynamicaccess.NewGranteeList(ls, putter, ref) + err = gl2.Add(keys) + assert.NoError(t, err) + + val := gl2.Get() + assert.Equal(t, append(keys, keys...), val) + }) } diff --git a/pkg/dynamicaccess/mock/container.go b/pkg/dynamicaccess/mock/container.go deleted file mode 100644 index 3cad9badd39..00000000000 --- a/pkg/dynamicaccess/mock/container.go +++ /dev/null @@ -1,20 +0,0 @@ -package mock - -type ContainerMock struct { - AddFunc func(string, string, string) error - GetFunc func(string, string, string) (string, error) -} - -func (ma *ContainerMock) Add(ref string, publisher string, tag string) error { - if ma.AddFunc == nil { - return nil - } - return ma.AddFunc(ref, publisher, tag) -} - -func (ma *ContainerMock) Get(ref string, publisher string, tag string) (string, error) { - if ma.GetFunc == nil { - return "", nil - } - return ma.GetFunc(ref, publisher, tag) -} diff --git a/pkg/dynamicaccess/mock/grantee.go b/pkg/dynamicaccess/mock/grantee.go new file mode 100644 index 00000000000..f4bd90740ab --- /dev/null +++ b/pkg/dynamicaccess/mock/grantee.go @@ -0,0 +1,51 @@ +package mock + +import ( + "crypto/ecdsa" + + "github.com/ethersphere/bee/pkg/swarm" +) + +type GranteeListMock interface { + Add(publicKeys []*ecdsa.PublicKey) error + Remove(removeList []*ecdsa.PublicKey) error + Get() []*ecdsa.PublicKey + Save() (swarm.Address, error) +} + +type GranteeListStructMock struct { + grantees []*ecdsa.PublicKey +} + +func (g *GranteeListStructMock) Get() []*ecdsa.PublicKey { + grantees := g.grantees + keys := make([]*ecdsa.PublicKey, len(grantees)) + copy(keys, grantees) + return keys +} + +func (g *GranteeListStructMock) Add(addList []*ecdsa.PublicKey) error { + g.grantees = append(g.grantees, addList...) + return nil +} + +func (g *GranteeListStructMock) Remove(removeList []*ecdsa.PublicKey) error { + for _, remove := range removeList { + for i, grantee := range g.grantees { + if *grantee == *remove { + g.grantees[i] = g.grantees[len(g.grantees)-1] + g.grantees = g.grantees[:len(g.grantees)-1] + } + } + } + + return nil +} + +func (g *GranteeListStructMock) Save() (swarm.Address, error) { + return swarm.EmptyAddress, nil +} + +func NewGranteeList() *GranteeListStructMock { + return &GranteeListStructMock{grantees: []*ecdsa.PublicKey{}} +}