[#170] oid, cid: Refactor and document package functionality

Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
This commit is contained in:
Pavel Karpy 2022-04-11 19:25:14 +03:00 committed by LeL
parent 24d6c2221f
commit f7172adf18
49 changed files with 831 additions and 439 deletions

View file

@ -2,6 +2,7 @@ package container
import (
"github.com/nspcc-dev/neofs-api-go/v2/container"
"github.com/nspcc-dev/neofs-api-go/v2/refs"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
)
@ -38,15 +39,25 @@ func (a *UsedSpaceAnnouncement) SetEpoch(epoch uint64) {
}
// ContainerID of the announcement.
func (a *UsedSpaceAnnouncement) ContainerID() *cid.ID {
return cid.NewFromV2(
(*container.UsedSpaceAnnouncement)(a).GetContainerID(),
)
func (a *UsedSpaceAnnouncement) ContainerID() (cID cid.ID) {
v2 := (*container.UsedSpaceAnnouncement)(a)
cidV2 := v2.GetContainerID()
if cidV2 == nil {
return
}
_ = cID.ReadFromV2(*cidV2)
return
}
// SetContainerID sets announcement container value.
func (a *UsedSpaceAnnouncement) SetContainerID(cid *cid.ID) {
(*container.UsedSpaceAnnouncement)(a).SetContainerID(cid.ToV2())
func (a *UsedSpaceAnnouncement) SetContainerID(cnr cid.ID) {
var cidV2 refs.ContainerID
cnr.WriteToV2(&cidV2)
(*container.UsedSpaceAnnouncement)(a).SetContainerID(&cidV2)
}
// UsedSpace in container.

View file

@ -46,9 +46,12 @@ func TestAnnouncement(t *testing.T) {
newA := container.NewAnnouncementFromV2(v2)
var cID cid.ID
_ = cID.ReadFromV2(*newCID)
require.Equal(t, newEpoch, newA.Epoch())
require.Equal(t, newUsedSpace, newA.UsedSpace())
require.Equal(t, cid.NewFromV2(newCID), newA.ContainerID())
require.Equal(t, cID, newA.ContainerID())
})
}
@ -79,7 +82,7 @@ func TestUsedSpaceAnnouncement_ToV2(t *testing.T) {
// check initial values
require.Zero(t, announcement.Epoch())
require.Zero(t, announcement.UsedSpace())
require.Nil(t, announcement.ContainerID())
require.True(t, announcement.ContainerID().Empty())
// convert to v2 message
announcementV2 := announcement.ToV2()

View file

@ -87,13 +87,13 @@ func NewContainerFromV2(c *container.Container) *Container {
// CalculateID calculates container identifier
// based on its structure.
func CalculateID(c *Container) *cid.ID {
func CalculateID(c *Container) cid.ID {
data, err := c.ToV2().StableMarshal(nil)
if err != nil {
panic(err)
}
id := cid.New()
var id cid.ID
id.SetSHA256(sha256.Sum256(data))
return id

8
container/id/doc.go Normal file
View file

@ -0,0 +1,8 @@
/*
Package cid provides primitives to work with container identification in NeoFS.
Using package types in an application is recommended to potentially work with
different protocol versions with which these types are compatible.
*/
package cid

View file

@ -1,90 +1,120 @@
package cid
import (
"bytes"
"crypto/sha256"
"errors"
"fmt"
"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.
// ID represents NeoFS container identifier.
//
// Nil refs.ContainerID converts to nil.
func NewFromV2(idV2 *refs.ContainerID) *ID {
return (*ID)(idV2)
}
// New creates and initializes blank ID.
// ID is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/refs.ContainerID
// message. See ReadFromV2 / WriteToV2 methods.
//
// Defaults:
// - value: nil.
func New() *ID {
return NewFromV2(new(refs.ContainerID))
}
// SetSHA256 sets container identifier value to SHA256 checksum of container body.
func (id *ID) SetSHA256(v [sha256.Size]byte) {
(*refs.ContainerID)(id).SetValue(v[:])
}
// ToV2 returns the v2 container ID message.
// Instances can be created using built-in var declaration.
//
// Nil ID converts to nil.
func (id *ID) ToV2() *refs.ContainerID {
return (*refs.ContainerID)(id)
}
// Note that direct typecast is not safe and may result in loss of compatibility:
// _ = ID([32]byte) // not recommended
type ID [sha256.Size]byte
// 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.
// ReadFromV2 reads ID from the refs.ContainerID message.
// Returns an error if the message is malformed according
// to the NeoFS API V2 protocol.
//
// 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")
// See also WriteToV2.
func (id *ID) ReadFromV2(m refs.ContainerID) error {
return id.Decode(m.GetValue())
}
// WriteToV2 writes ID to the refs.ContainerID message.
// The message must not be nil.
//
// See also ReadFromV2.
func (id ID) WriteToV2(m *refs.ContainerID) {
m.SetValue(id[:])
}
// Encode encodes ID into 32 bytes of dst. Panics if
// dst length is less than 32.
//
// Zero ID is all zeros.
//
// See also Decode.
func (id ID) Encode(dst []byte) {
if l := len(dst); l < sha256.Size {
panic(fmt.Sprintf("destination length is less than %d bytes: %d", sha256.Size, l))
}
(*refs.ContainerID)(id).SetValue(data)
copy(dst, id[:])
}
// Decode decodes src bytes into ID.
//
// Decode expects that src has 32 bytes length. If the input is malformed,
// Decode returns an error describing format violation. In this case ID
// remains unchanged.
//
// Decode doesn't mutate src.
//
// See also Encode.
func (id *ID) Decode(src []byte) error {
if len(src) != sha256.Size {
return fmt.Errorf("invalid length %d", len(src))
}
copy(id[:], src)
return nil
}
// String returns base58 string representation of ID.
func (id *ID) String() string {
return base58.Encode((*refs.ContainerID)(id).GetValue())
// SetSHA256 sets container identifier value to SHA256 checksum of container structure.
func (id *ID) SetSHA256(v [sha256.Size]byte) {
copy(id[:], v[:])
}
// Marshal marshals ID into a protobuf binary form.
func (id *ID) Marshal() ([]byte, error) {
return (*refs.ContainerID)(id).StableMarshal(nil)
// Equals defines a comparison relation between two ID instances.
//
// Note that comparison using '==' operator is not recommended since it MAY result
// in loss of compatibility.
func (id ID) Equals(id2 ID) bool {
return id == id2
}
// Unmarshal unmarshals protobuf binary representation of ID.
func (id *ID) Unmarshal(data []byte) error {
return (*refs.ContainerID)(id).Unmarshal(data)
// EncodeToString encodes ID into NeoFS API protocol string.
//
// Zero ID is base58 encoding of 32 zeros.
//
// See also DecodeString.
func (id ID) EncodeToString() string {
return base58.Encode(id[:])
}
// MarshalJSON encodes ID to protobuf JSON format.
func (id *ID) MarshalJSON() ([]byte, error) {
return (*refs.ContainerID)(id).MarshalJSON()
// DecodeString decodes string into ID according to NeoFS API protocol. Returns
// an error if s is malformed.
//
// See also DecodeString.
func (id *ID) DecodeString(s string) error {
data, err := base58.Decode(s)
if err != nil {
return fmt.Errorf("decode base58: %w", err)
}
return id.Decode(data)
}
// UnmarshalJSON decodes ID from protobuf JSON format.
func (id *ID) UnmarshalJSON(data []byte) error {
return (*refs.ContainerID)(id).UnmarshalJSON(data)
// String implements fmt.Stringer.
//
// String is designed to be human-readable, and its format MAY differ between
// SDK versions. String MAY return same result as EncodeToString. String MUST NOT
// be used to encode ID into NeoFS protocol string.
func (id ID) String() string {
return id.EncodeToString()
}
// Empty returns true if it is called on
// zero container ID.
func (id ID) Empty() bool {
return id == ID{}
}

View file

@ -5,6 +5,7 @@ import (
"math/rand"
"testing"
"github.com/mr-tron/base58"
"github.com/nspcc-dev/neofs-api-go/v2/refs"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test"
@ -16,30 +17,32 @@ func randSHA256Checksum() (cs [sha256.Size]byte) {
return
}
const emptyID = "11111111111111111111111111111111"
func TestID_ToV2(t *testing.T) {
t.Run("non-nil", func(t *testing.T) {
t.Run("non-zero", func(t *testing.T) {
checksum := randSHA256Checksum()
id := cidtest.IDWithChecksum(checksum)
idV2 := id.ToV2()
var idV2 refs.ContainerID
id.WriteToV2(&idV2)
require.Equal(t, id, cid.NewFromV2(idV2))
var newID cid.ID
require.NoError(t, newID.ReadFromV2(idV2))
require.Equal(t, id, newID)
require.Equal(t, checksum[:], idV2.GetValue())
})
t.Run("nil", func(t *testing.T) {
var x *cid.ID
t.Run("zero", func(t *testing.T) {
var (
x cid.ID
v2 refs.ContainerID
)
require.Nil(t, x.ToV2())
})
t.Run("default values", func(t *testing.T) {
id := cid.New()
// convert to v2 message
cidV2 := id.ToV2()
require.Nil(t, cidV2.GetValue())
x.WriteToV2(&v2)
require.Equal(t, emptyID, base58.Encode(v2.GetValue()))
})
}
@ -49,57 +52,57 @@ func TestID_Equal(t *testing.T) {
id1 := cidtest.IDWithChecksum(cs)
id2 := cidtest.IDWithChecksum(cs)
require.True(t, id1.Equal(id2))
require.True(t, id1.Equals(id2))
id3 := cidtest.ID()
require.False(t, id1.Equal(id3))
require.False(t, id1.Equals(id3))
}
func TestID_String(t *testing.T) {
t.Run("Parse/String", func(t *testing.T) {
t.Run("DecodeString/EncodeToString", func(t *testing.T) {
id := cidtest.ID()
id2 := cid.New()
var id2 cid.ID
require.NoError(t, id2.Parse(id.String()))
require.NoError(t, id2.DecodeString(id.String()))
require.Equal(t, id, id2)
})
t.Run("nil", func(t *testing.T) {
id := cid.New()
t.Run("zero", func(t *testing.T) {
var id cid.ID
require.Empty(t, id.String())
})
}
func TestContainerIDEncoding(t *testing.T) {
id := cidtest.ID()
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)
require.Equal(t, emptyID, id.EncodeToString())
})
}
func TestNewFromV2(t *testing.T) {
t.Run("from nil", func(t *testing.T) {
var x *refs.ContainerID
t.Run("from zero", func(t *testing.T) {
var (
x cid.ID
v2 refs.ContainerID
)
require.Nil(t, cid.NewFromV2(x))
require.Error(t, x.ReadFromV2(v2))
})
}
func TestID_Encode(t *testing.T) {
var id cid.ID
t.Run("panic", func(t *testing.T) {
dst := make([]byte, sha256.Size-1)
require.Panics(t, func() {
id.Encode(dst)
})
})
t.Run("correct", func(t *testing.T) {
dst := make([]byte, sha256.Size)
require.NotPanics(t, func() {
id.Encode(dst)
})
require.Equal(t, emptyID, id.EncodeToString())
})
}

13
container/id/test/doc.go Normal file
View file

@ -0,0 +1,13 @@
/*
Package cidtest provides functions for convenient testing of cid package API.
Note that importing the package into source files is highly discouraged.
Random instance generation functions can be useful when testing expects any value, e.g.:
import cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test"
cid := cidtest.ID()
// test the value
*/
package cidtest

View file

@ -8,7 +8,7 @@ import (
)
// ID returns random cid.ID.
func ID() *cid.ID {
func ID() cid.ID {
checksum := [sha256.Size]byte{}
rand.Read(checksum[:])
@ -18,8 +18,8 @@ func ID() *cid.ID {
// IDWithChecksum returns cid.ID initialized
// with specified checksum.
func IDWithChecksum(cs [sha256.Size]byte) *cid.ID {
id := cid.New()
func IDWithChecksum(cs [sha256.Size]byte) cid.ID {
var id cid.ID
id.SetSHA256(cs)
return id