From 7314038069ee37b03462f260b10a82833ac58dfa Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Mon, 24 May 2021 19:31:58 +0300 Subject: [PATCH] [#283] pkg/container: Define ID in a separate package In order to prevent potential cross imports, container ID should be defined in a separate package as a base type. A similar approach was used in the NeoFS API design. Create `pkg/container/id` package and replace container ID implementation to it. Related API in `container` package is deprecated from now. Signed-off-by: Leonard Lyubich --- pkg/client/container.go | 5 +- pkg/container/id.go | 97 +++--------------------------- pkg/container/id/id.go | 95 +++++++++++++++++++++++++++++ pkg/container/id/id_test.go | 73 ++++++++++++++++++++++ pkg/container/id/test/id.go | 26 ++++++++ pkg/container/id_test.go | 117 ------------------------------------ 6 files changed, 206 insertions(+), 207 deletions(-) create mode 100644 pkg/container/id/id.go create mode 100644 pkg/container/id/id_test.go create mode 100644 pkg/container/id/test/id.go delete mode 100644 pkg/container/id_test.go diff --git a/pkg/client/container.go b/pkg/client/container.go index e2257e34..98d9740d 100644 --- a/pkg/client/container.go +++ b/pkg/client/container.go @@ -2,6 +2,7 @@ package client import ( "context" + "errors" "fmt" "github.com/nspcc-dev/neofs-api-go/pkg" @@ -170,7 +171,7 @@ func (c *clientImpl) GetContainer(ctx context.Context, id *container.ID, opts .. // GetVerifiedContainerStructure is a wrapper over Client.GetContainer method // which checks if the structure of the resulting container matches its identifier. // -// Returns container.ErrIDMismatch if container does not match the identifier. +// Returns an error if container does not match the identifier. func GetVerifiedContainerStructure(ctx context.Context, c Client, id *container.ID, opts ...CallOption) (*container.Container, error) { cnr, err := c.GetContainer(ctx, id, opts...) if err != nil { @@ -178,7 +179,7 @@ func GetVerifiedContainerStructure(ctx context.Context, c Client, id *container. } if !container.CalculateID(cnr).Equal(id) { - return nil, container.ErrIDMismatch + return nil, errors.New("container structure does not match the identifier") } return cnr, nil diff --git a/pkg/container/id.go b/pkg/container/id.go index 86bb3e35..96a6483f 100644 --- a/pkg/container/id.go +++ b/pkg/container/id.go @@ -1,101 +1,22 @@ package container import ( - "bytes" - "crypto/sha256" - "errors" - "fmt" - - "github.com/mr-tron/base58" - "github.com/nspcc-dev/neofs-api-go/v2/refs" + cid "github.com/nspcc-dev/neofs-api-go/pkg/container/id" ) // ID represents v2-compatible container identifier. -type ID refs.ContainerID - -// ErrIDMismatch is returned when container structure does not match -// a specific identifier. -var ErrIDMismatch = errors.New("container structure does not match the identifier") - -var errInvalidIDString = errors.New("incorrect format of the string container ID") +// +// Deprecated: use cid.ID instead. +type ID = cid.ID // NewIDFromV2 wraps v2 ContainerID message to ID. -func NewIDFromV2(idV2 *refs.ContainerID) *ID { - return (*ID)(idV2) -} +// +// Deprecated: use cid.NewFromV2 instead. +var NewIDFromV2 = cid.NewFromV2 // NewID creates and initializes blank ID. // // Works similar to NewIDFromV2(new(ContainerID)). -func NewID() *ID { - return NewIDFromV2(new(refs.ContainerID)) -} - -// SetSHA256 sets container identifier value to SHA256 checksum. -func (id *ID) SetSHA256(v [sha256.Size]byte) { - (*refs.ContainerID)(id).SetValue(v[:]) -} - -// ToV2 returns the v2 container ID message. -func (id *ID) ToV2() *refs.ContainerID { - return (*refs.ContainerID)(id) -} - -// Equal returns true if identifiers are identical. -func (id *ID) Equal(id2 *ID) bool { - return bytes.Equal( - (*refs.ContainerID)(id).GetValue(), - (*refs.ContainerID)(id2).GetValue(), - ) -} - -// Parse converts base58 string representation into ID. -func (id *ID) Parse(s string) error { - data, err := base58.Decode(s) - if err != nil { - return fmt.Errorf("could not parse container.ID from string: %w", err) - } else if len(data) != sha256.Size { - return errInvalidIDString - } - - (*refs.ContainerID)(id).SetValue(data) - - return nil -} - -// String returns base58 string representation of ID. -func (id *ID) String() string { - return base58.Encode((*refs.ContainerID)(id).GetValue()) -} - -// Marshal marshals ID into a protobuf binary form. // -// Buffer is allocated when the argument is empty. -// Otherwise, the first buffer is used. -func (id *ID) Marshal(b ...[]byte) ([]byte, error) { - var buf []byte - if len(b) > 0 { - buf = b[0] - } - - return (*refs.ContainerID)(id). - StableMarshal(buf) -} - -// Unmarshal unmarshals protobuf binary representation of ID. -func (id *ID) Unmarshal(data []byte) error { - return (*refs.ContainerID)(id). - Unmarshal(data) -} - -// MarshalJSON encodes ID to protobuf JSON format. -func (id *ID) MarshalJSON() ([]byte, error) { - return (*refs.ContainerID)(id). - MarshalJSON() -} - -// UnmarshalJSON decodes ID from protobuf JSON format. -func (id *ID) UnmarshalJSON(data []byte) error { - return (*refs.ContainerID)(id). - UnmarshalJSON(data) -} +// Deprecated: use cid.New instead. +var NewID = cid.New diff --git a/pkg/container/id/id.go b/pkg/container/id/id.go new file mode 100644 index 00000000..46b8d371 --- /dev/null +++ b/pkg/container/id/id.go @@ -0,0 +1,95 @@ +package cid + +import ( + "bytes" + "crypto/sha256" + "errors" + + "github.com/mr-tron/base58" + "github.com/nspcc-dev/neofs-api-go/v2/refs" +) + +// ID represents v2-compatible container identifier. +type ID refs.ContainerID + +// NewFromV2 wraps v2 ContainerID message to ID. +func NewFromV2(idV2 *refs.ContainerID) *ID { + return (*ID)(idV2) +} + +// New creates and initializes blank ID. +func New() *ID { + return NewFromV2(new(refs.ContainerID)) +} + +// SetSHA256 sets container identifier value to SHA256 checksum. +func (id *ID) SetSHA256(v [sha256.Size]byte) { + (*refs.ContainerID)(id).SetValue(v[:]) +} + +// ToV2 returns the v2 container ID message. +func (id *ID) ToV2() *refs.ContainerID { + return (*refs.ContainerID)(id) +} + +// Equal returns true if identifiers are identical. +func (id *ID) Equal(id2 *ID) bool { + return bytes.Equal( + (*refs.ContainerID)(id).GetValue(), + (*refs.ContainerID)(id2).GetValue(), + ) +} + +// Parse parses string representation of ID. +// +// Returns error if s is not a base58 encoded +// ID data. +func (id *ID) Parse(s string) error { + data, err := base58.Decode(s) + if err != nil { + return err + } else if len(data) != sha256.Size { + return errors.New("incorrect format of the string container ID") + } + + (*refs.ContainerID)(id).SetValue(data) + + return nil +} + +// String returns base58 string representation of ID. +func (id *ID) String() string { + return base58.Encode((*refs.ContainerID)(id).GetValue()) +} + +// Marshal marshals ID into a protobuf binary form. +// +// Buffer is allocated when the argument is empty. +// Otherwise, the first buffer is used. +func (id *ID) Marshal(b ...[]byte) ([]byte, error) { + var buf []byte + if len(b) > 0 { + buf = b[0] + } + + return (*refs.ContainerID)(id). + StableMarshal(buf) +} + +// Unmarshal unmarshals protobuf binary representation of ID. +func (id *ID) Unmarshal(data []byte) error { + return (*refs.ContainerID)(id). + Unmarshal(data) +} + +// MarshalJSON encodes ID to protobuf JSON format. +func (id *ID) MarshalJSON() ([]byte, error) { + return (*refs.ContainerID)(id). + MarshalJSON() +} + +// UnmarshalJSON decodes ID from protobuf JSON format. +func (id *ID) UnmarshalJSON(data []byte) error { + return (*refs.ContainerID)(id). + UnmarshalJSON(data) +} diff --git a/pkg/container/id/id_test.go b/pkg/container/id/id_test.go new file mode 100644 index 00000000..39755190 --- /dev/null +++ b/pkg/container/id/id_test.go @@ -0,0 +1,73 @@ +package cid_test + +import ( + "crypto/sha256" + "math/rand" + "testing" + + cid "github.com/nspcc-dev/neofs-api-go/pkg/container/id" + cidtest "github.com/nspcc-dev/neofs-api-go/pkg/container/id/test" + "github.com/stretchr/testify/require" +) + +func randSHA256Checksum() (cs [sha256.Size]byte) { + rand.Read(cs[:]) + return +} + +func TestID_ToV2(t *testing.T) { + id := cid.New() + + checksum := randSHA256Checksum() + + id.SetSHA256(checksum) + + idV2 := id.ToV2() + + require.Equal(t, id, cid.NewFromV2(idV2)) +} + +func TestID_Equal(t *testing.T) { + cs := randSHA256Checksum() + + id1 := cidtest.GenerateWithChecksum(cs) + id2 := cidtest.GenerateWithChecksum(cs) + + require.True(t, id1.Equal(id2)) + + id3 := cidtest.Generate() + + require.False(t, id1.Equal(id3)) +} + +func TestID_String(t *testing.T) { + id := cidtest.Generate() + id2 := cid.New() + + require.NoError(t, id2.Parse(id.String())) + require.Equal(t, id, id2) +} + +func TestContainerIDEncoding(t *testing.T) { + id := cidtest.Generate() + + t.Run("binary", func(t *testing.T) { + data, err := id.Marshal() + require.NoError(t, err) + + id2 := cid.New() + require.NoError(t, id2.Unmarshal(data)) + + require.Equal(t, id, id2) + }) + + t.Run("json", func(t *testing.T) { + data, err := id.MarshalJSON() + require.NoError(t, err) + + a2 := cid.New() + require.NoError(t, a2.UnmarshalJSON(data)) + + require.Equal(t, id, a2) + }) +} diff --git a/pkg/container/id/test/id.go b/pkg/container/id/test/id.go new file mode 100644 index 00000000..58eca656 --- /dev/null +++ b/pkg/container/id/test/id.go @@ -0,0 +1,26 @@ +package cidtest + +import ( + "crypto/sha256" + "math/rand" + + cid "github.com/nspcc-dev/neofs-api-go/pkg/container/id" +) + +// Generate returns random cid.ID. +func Generate() *cid.ID { + checksum := [sha256.Size]byte{} + + rand.Read(checksum[:]) + + return GenerateWithChecksum(checksum) +} + +// GenerateWithChecksum returns cid.ID initialized +// with specified checksum. +func GenerateWithChecksum(cs [sha256.Size]byte) *cid.ID { + id := cid.New() + id.SetSHA256(cs) + + return id +} diff --git a/pkg/container/id_test.go b/pkg/container/id_test.go deleted file mode 100644 index 346a1873..00000000 --- a/pkg/container/id_test.go +++ /dev/null @@ -1,117 +0,0 @@ -package container - -import ( - "crypto/rand" - "crypto/sha256" - "strconv" - "testing" - - "github.com/mr-tron/base58" - "github.com/stretchr/testify/require" -) - -func TestIDV2_0(t *testing.T) { - cid := NewID() - - checksum := [sha256.Size]byte{} - - _, err := rand.Read(checksum[:]) - require.NoError(t, err) - - cid.SetSHA256(checksum) - - cidV2 := cid.ToV2() - - require.Equal(t, checksum[:], cidV2.GetValue()) -} - -func randSHA256Checksum(t *testing.T) (cs [sha256.Size]byte) { - _, err := rand.Read(cs[:]) - require.NoError(t, err) - - return -} - -func TestID_Equal(t *testing.T) { - cs := randSHA256Checksum(t) - - id1 := NewID() - id1.SetSHA256(cs) - - id2 := NewID() - id2.SetSHA256(cs) - - id3 := NewID() - id3.SetSHA256(randSHA256Checksum(t)) - - require.True(t, id1.Equal(id2)) - require.False(t, id1.Equal(id3)) -} - -func TestID_Parse(t *testing.T) { - t.Run("should parse successful", func(t *testing.T) { - for i := 0; i < 10; i++ { - t.Run(strconv.Itoa(i), func(t *testing.T) { - cs := randSHA256Checksum(t) - str := base58.Encode(cs[:]) - cid := NewID() - - require.NoError(t, cid.Parse(str)) - require.Equal(t, cs[:], cid.ToV2().GetValue()) - }) - } - }) - - t.Run("should failure on parse", func(t *testing.T) { - for i := 0; i < 10; i++ { - j := i - t.Run(strconv.Itoa(j), func(t *testing.T) { - cs := []byte{1, 2, 3, 4, 5, byte(j)} - str := base58.Encode(cs) - cid := NewID() - - require.Error(t, cid.Parse(str)) - }) - } - }) -} - -func TestID_String(t *testing.T) { - t.Run("should be equal", func(t *testing.T) { - for i := 0; i < 10; i++ { - t.Run(strconv.Itoa(i), func(t *testing.T) { - cs := randSHA256Checksum(t) - str := base58.Encode(cs[:]) - cid := NewID() - - require.NoError(t, cid.Parse(str)) - require.Equal(t, str, cid.String()) - }) - } - }) -} - -func TestContainerIDEncoding(t *testing.T) { - id := NewID() - id.SetSHA256(randSHA256Checksum(t)) - - t.Run("binary", func(t *testing.T) { - data, err := id.Marshal() - require.NoError(t, err) - - id2 := NewID() - require.NoError(t, id2.Unmarshal(data)) - - require.Equal(t, id, id2) - }) - - t.Run("json", func(t *testing.T) { - data, err := id.MarshalJSON() - require.NoError(t, err) - - a2 := NewID() - require.NoError(t, a2.UnmarshalJSON(data)) - - require.Equal(t, id, a2) - }) -}