From fd13e61266c00c61ca9b28e709d930e9d89f585f Mon Sep 17 00:00:00 2001 From: Pavel Karpy Date: Mon, 22 Mar 2021 13:56:18 +0300 Subject: [PATCH] [#170] checksum: Drop `Empty` method Signed-off-by: Pavel Karpy --- checksum/checksum.go | 197 +++++++++++++++-------------------- checksum/checksum_test.go | 143 +++++-------------------- checksum/doc.go | 18 ++++ checksum/test/doc.go | 13 +++ checksum/test/generate.go | 4 +- go.mod | 1 + go.sum | 5 +- object/fmt.go | 9 +- object/object.go | 24 +++-- object/raw_test.go | 12 +-- storagegroup/storagegroup.go | 15 ++- 11 files changed, 188 insertions(+), 253 deletions(-) create mode 100644 checksum/doc.go create mode 100644 checksum/test/doc.go diff --git a/checksum/checksum.go b/checksum/checksum.go index 19ad500..79324d4 100644 --- a/checksum/checksum.go +++ b/checksum/checksum.go @@ -1,15 +1,23 @@ package checksum import ( - "bytes" "crypto/sha256" "encoding/hex" "fmt" "github.com/nspcc-dev/neofs-api-go/v2/refs" + "github.com/nspcc-dev/tzhash/tz" ) -// Checksum represents v2-compatible checksum. +// Checksum represents checksum of some digital data. +// +// Checksum is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/refs.Checksum +// message. See ReadFromV2 / WriteToV2 methods. +// +// Instances can be created using built-in var declaration. +// +// Note that direct typecast is not safe and may result in loss of compatibility: +// _ = Checksum(refs.Checksum{}) // not recommended type Checksum refs.Checksum // Type represents the enumeration @@ -23,29 +31,33 @@ const ( // SHA256 is a SHA256 checksum type. SHA256 - // TZ is a Tillich-Zemor checksum type. + // TZ is a Tillich-Zémor checksum type. TZ ) -// NewFromV2 wraps v2 Checksum message to Checksum. +// ReadFromV2 reads Checksum from the refs.Checksum message. // -// Nil refs.Checksum converts to nil. -func NewFromV2(cV2 *refs.Checksum) *Checksum { - return (*Checksum)(cV2) +// See also WriteToV2. +func (c *Checksum) ReadFromV2(m refs.Checksum) { + *c = Checksum(m) } -// New creates and initializes blank Checksum. +// WriteToV2 writes Checksum to the refs.Checksum message. +// The message must not be nil. // -// Defaults: -// - sum: nil; -// - type: Unknown. -func New() *Checksum { - return NewFromV2(new(refs.Checksum)) +// See also ReadFromV2. +func (c Checksum) WriteToV2(m *refs.Checksum) { + *m = (refs.Checksum)(c) } // Type returns checksum type. -func (c *Checksum) Type() Type { - switch (*refs.Checksum)(c).GetType() { +// +// Zero Checksum has Unknown checksum type. +// +// See also SetTillichZemor and SetSHA256. +func (c Checksum) Type() Type { + v2 := (refs.Checksum)(c) + switch v2.GetType() { case refs.SHA256: return SHA256 case refs.TillichZemor: @@ -55,93 +67,77 @@ func (c *Checksum) Type() Type { } } -// Sum returns checksum bytes. -func (c *Checksum) Sum() []byte { - return (*refs.Checksum)(c).GetSum() +// Value returns checksum bytes. Return value +// MUST NOT be mutated. +// +// Zero Checksum has nil sum. +// +// See also SetTillichZemor and SetSHA256. +func (c Checksum) Value() []byte { + v2 := (refs.Checksum)(c) + return v2.GetSum() } // SetSHA256 sets checksum to SHA256 hash. +// +// See also Calculate. func (c *Checksum) SetSHA256(v [sha256.Size]byte) { - checksum := (*refs.Checksum)(c) + v2 := (*refs.Checksum)(c) - checksum.SetType(refs.SHA256) - checksum.SetSum(v[:]) + v2.SetType(refs.SHA256) + v2.SetSum(v[:]) } -// SetTillichZemor sets checksum to Tillich-Zemor hash. -func (c *Checksum) SetTillichZemor(v [64]byte) { - checksum := (*refs.Checksum)(c) - - checksum.SetType(refs.TillichZemor) - checksum.SetSum(v[:]) -} - -// ToV2 converts Checksum to v2 Checksum message. +// Calculate calculates checksum and sets it +// to the passed checksum. Checksum must not be nil. // -// Nil Checksum converts to nil. -func (c *Checksum) ToV2() *refs.Checksum { - return (*refs.Checksum)(c) -} - -func Equal(cs1, cs2 *Checksum) bool { - return cs1.Type() == cs2.Type() && bytes.Equal(cs1.Sum(), cs2.Sum()) -} - -// Marshal marshals Checksum into a protobuf binary form. -func (c *Checksum) Marshal() ([]byte, error) { - return (*refs.Checksum)(c).StableMarshal(nil) -} - -// Unmarshal unmarshals protobuf binary representation of Checksum. -func (c *Checksum) Unmarshal(data []byte) error { - return (*refs.Checksum)(c).Unmarshal(data) -} - -// MarshalJSON encodes Checksum to protobuf JSON format. -func (c *Checksum) MarshalJSON() ([]byte, error) { - return (*refs.Checksum)(c).MarshalJSON() -} - -// UnmarshalJSON decodes Checksum from protobuf JSON format. -func (c *Checksum) UnmarshalJSON(data []byte) error { - return (*refs.Checksum)(c).UnmarshalJSON(data) -} - -func (c *Checksum) String() string { - return hex.EncodeToString((*refs.Checksum)(c).GetSum()) -} - -// Parse parses Checksum from its string representation. -func (c *Checksum) Parse(s string) error { - data, err := hex.DecodeString(s) - if err != nil { - return err - } - - var typ refs.ChecksumType - - switch ln := len(data); ln { +// Does nothing if the passed type is not one of the: +// * SHA256; +// * TZ. +// +// Does not mutate the passed value. +// +// See also SetSHA256, SetTillichZemor. +func Calculate(c *Checksum, t Type, v []byte) { + switch t { + case SHA256: + c.SetSHA256(sha256.Sum256(v)) + case TZ: + c.SetTillichZemor(tz.Sum(v)) default: - return fmt.Errorf("unsupported checksum length %d", ln) - case sha256.Size: - typ = refs.SHA256 - case 64: - typ = refs.TillichZemor } - - cV2 := (*refs.Checksum)(c) - cV2.SetType(typ) - cV2.SetSum(data) - - return nil } -// String returns string representation of Type. +// SetTillichZemor sets checksum to Tillich-Zémor hash. // -// String mapping: -// * TZ: TZ; -// * SHA256: SHA256; -// * Unknown, default: CHECKSUM_TYPE_UNSPECIFIED. +// See also Calculate. +func (c *Checksum) SetTillichZemor(v [tz.Size]byte) { + v2 := (*refs.Checksum)(c) + + v2.SetType(refs.TillichZemor) + v2.SetSum(v[:]) +} + +// String implements fmt.Stringer. +// +// String is designed to be human-readable, and its format MAY differ between +// SDK versions. +func (c Checksum) String() string { + v2 := (refs.Checksum)(c) + return fmt.Sprintf("%s:%s", c.Type(), hex.EncodeToString(v2.GetSum())) +} + +// Empty returns true if it is called on +// zero checksum. +func (c Checksum) Empty() bool { + v2 := (refs.Checksum)(c) + return v2.GetSum() == nil +} + +// String implements fmt.Stringer. +// +// String is designed to be human-readable, and its format MAY differ between +// SDK versions. func (m Type) String() string { var m2 refs.ChecksumType @@ -156,26 +152,3 @@ func (m Type) String() string { return m2.String() } - -// FromString parses Type from a string representation. -// It is a reverse action to String(). -// -// Returns true if s was parsed successfully. -func (m *Type) FromString(s string) bool { - var g refs.ChecksumType - - ok := g.FromString(s) - - if ok { - switch g { - default: - *m = Unknown - case refs.TillichZemor: - *m = TZ - case refs.SHA256: - *m = SHA256 - } - } - - return ok -} diff --git a/checksum/checksum_test.go b/checksum/checksum_test.go index ce5515b..f9d22c9 100644 --- a/checksum/checksum_test.go +++ b/checksum/checksum_test.go @@ -6,19 +6,12 @@ import ( "testing" "github.com/nspcc-dev/neofs-api-go/v2/refs" + "github.com/nspcc-dev/tzhash/tz" "github.com/stretchr/testify/require" ) -func randSHA256(t *testing.T) [sha256.Size]byte { - cSHA256 := [sha256.Size]byte{} - _, err := rand.Read(cSHA256[:]) - require.NoError(t, err) - - return cSHA256 -} - func TestChecksum(t *testing.T) { - c := New() + var c Checksum cSHA256 := [sha256.Size]byte{} _, _ = rand.Read(cSHA256[:]) @@ -26,150 +19,62 @@ func TestChecksum(t *testing.T) { c.SetSHA256(cSHA256) require.Equal(t, SHA256, c.Type()) - require.Equal(t, cSHA256[:], c.Sum()) + require.Equal(t, cSHA256[:], c.Value()) - cV2 := c.ToV2() + var cV2 refs.Checksum + c.WriteToV2(&cV2) require.Equal(t, refs.SHA256, cV2.GetType()) require.Equal(t, cSHA256[:], cV2.GetSum()) - cTZ := [64]byte{} + cTZ := [tz.Size]byte{} _, _ = rand.Read(cSHA256[:]) c.SetTillichZemor(cTZ) require.Equal(t, TZ, c.Type()) - require.Equal(t, cTZ[:], c.Sum()) + require.Equal(t, cTZ[:], c.Value()) - cV2 = c.ToV2() + c.WriteToV2(&cV2) require.Equal(t, refs.TillichZemor, cV2.GetType()) require.Equal(t, cTZ[:], cV2.GetSum()) } -func TestEqualChecksums(t *testing.T) { - require.True(t, Equal(nil, nil)) - - csSHA := [sha256.Size]byte{} - _, _ = rand.Read(csSHA[:]) - - cs1 := New() - cs1.SetSHA256(csSHA) - - cs2 := New() - cs2.SetSHA256(csSHA) - - require.True(t, Equal(cs1, cs2)) - - csSHA[0]++ - cs2.SetSHA256(csSHA) - - require.False(t, Equal(cs1, cs2)) -} - -func TestChecksumEncoding(t *testing.T) { - cs := New() - cs.SetSHA256(randSHA256(t)) - - t.Run("binary", func(t *testing.T) { - data, err := cs.Marshal() - require.NoError(t, err) - - c2 := New() - require.NoError(t, c2.Unmarshal(data)) - - require.Equal(t, cs, c2) - }) - - t.Run("json", func(t *testing.T) { - data, err := cs.MarshalJSON() - require.NoError(t, err) - - cs2 := New() - require.NoError(t, cs2.UnmarshalJSON(data)) - - require.Equal(t, cs, cs2) - }) - - t.Run("string", func(t *testing.T) { - cs2 := New() - - require.NoError(t, cs2.Parse(cs.String())) - - require.Equal(t, cs, cs2) - }) -} - -func TestNewChecksumFromV2(t *testing.T) { - t.Run("from nil", func(t *testing.T) { - var x *refs.Checksum - - require.Nil(t, NewFromV2(x)) - }) -} - -func TestChecksum_ToV2(t *testing.T) { - t.Run("nil", func(t *testing.T) { - var x *Checksum - - require.Nil(t, x.ToV2()) - }) -} - func TestNewChecksum(t *testing.T) { t.Run("default values", func(t *testing.T) { - chs := New() + var chs Checksum // check initial values require.Equal(t, Unknown, chs.Type()) - require.Nil(t, chs.Sum()) + require.Nil(t, chs.Value()) // convert to v2 message - chsV2 := chs.ToV2() + var chsV2 refs.Checksum + chs.WriteToV2(&chsV2) require.Equal(t, refs.UnknownChecksum, chsV2.GetType()) require.Nil(t, chsV2.GetSum()) }) } -type enumIface interface { - FromString(string) bool - String() string -} +func TestCalculation(t *testing.T) { + var c Checksum + payload := []byte{0, 1, 2, 3, 4, 5} -type enumStringItem struct { - val enumIface - str string -} + t.Run("SHA256", func(t *testing.T) { + orig := sha256.Sum256(payload) -func testEnumStrings(t *testing.T, e enumIface, items []enumStringItem) { - for _, item := range items { - require.Equal(t, item.str, item.val.String()) + Calculate(&c, SHA256, payload) - s := item.val.String() + require.Equal(t, orig[:], c.Value()) + }) - require.True(t, e.FromString(s), s) + t.Run("TZ", func(t *testing.T) { + orig := tz.Sum(payload) - require.EqualValues(t, item.val, e, item.val) - } + Calculate(&c, TZ, payload) - // incorrect strings - for _, str := range []string{ - "some string", - "undefined", - } { - require.False(t, e.FromString(str)) - } -} - -func TestChecksumType_String(t *testing.T) { - toPtr := func(v Type) *Type { - return &v - } - - testEnumStrings(t, new(Type), []enumStringItem{ - {val: toPtr(TZ), str: "TZ"}, - {val: toPtr(SHA256), str: "SHA256"}, - {val: toPtr(Unknown), str: "CHECKSUM_TYPE_UNSPECIFIED"}, + require.Equal(t, orig[:], c.Value()) }) } diff --git a/checksum/doc.go b/checksum/doc.go new file mode 100644 index 0000000..1acac9f --- /dev/null +++ b/checksum/doc.go @@ -0,0 +1,18 @@ +/* +Package checksum provides primitives to work with checksums. + +Checksum is a basic type of data checksums. +For example, calculating checksums: + // retrieving any payload for hashing + + var sha256Sum Checksum + Calculate(&sha256Sum, SHA256, payload) // sha256Sum contains SHA256 hash of the payload + + var tzSum Checksum + Calculate(&tzSum, TZ, payload) // tzSum contains TZ hash of the payload + +Using package types in an application is recommended to potentially work with +different protocol versions with which these types are compatible. + +*/ +package checksum diff --git a/checksum/test/doc.go b/checksum/test/doc.go new file mode 100644 index 0000000..bb75038 --- /dev/null +++ b/checksum/test/doc.go @@ -0,0 +1,13 @@ +/* +Package checksumtest provides functions for convenient testing of checksum 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 checksumtest "github.com/nspcc-dev/neofs-sdk-go/checksum/test" + + cs := checksumtest.Checksum() + // test the value + +*/ +package checksumtest diff --git a/checksum/test/generate.go b/checksum/test/generate.go index 1f8fa97..c40bd7a 100644 --- a/checksum/test/generate.go +++ b/checksum/test/generate.go @@ -13,9 +13,9 @@ func Checksum() *checksum.Checksum { rand.Read(cs[:]) - x := checksum.New() + var x checksum.Checksum x.SetSHA256(cs) - return x + return &x } diff --git a/go.mod b/go.mod index 0ea096e..21917ac 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/nspcc-dev/hrw v1.0.9 github.com/nspcc-dev/neo-go v0.98.0 github.com/nspcc-dev/neofs-api-go/v2 v2.12.1 + github.com/nspcc-dev/tzhash v1.5.2 github.com/stretchr/testify v1.7.0 go.uber.org/zap v1.18.1 ) diff --git a/go.sum b/go.sum index b2836df..85118da 100644 --- a/go.sum +++ b/go.sum @@ -187,6 +187,8 @@ github.com/nspcc-dev/neofs-sdk-go v0.0.0-20211201182451-a5b61c4f6477/go.mod h1:d github.com/nspcc-dev/rfc6979 v0.1.0/go.mod h1:exhIh1PdpDC5vQmyEsGvc4YDM/lyQp/452QxGq/UEso= github.com/nspcc-dev/rfc6979 v0.2.0 h1:3e1WNxrN60/6N0DW7+UYisLeZJyfqZTNOjeV/toYvOE= github.com/nspcc-dev/rfc6979 v0.2.0/go.mod h1:exhIh1PdpDC5vQmyEsGvc4YDM/lyQp/452QxGq/UEso= +github.com/nspcc-dev/tzhash v1.5.2 h1:GuIQPOY2xpl5ZE1pbUbz+QdKXVOTyzbbxSVv0nBfa98= +github.com/nspcc-dev/tzhash v1.5.2/go.mod h1:gwAx6mcsbkfY+JVp+PovoP2Gvw6y57W8dj7zDHKOhzI= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -350,8 +352,9 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210429154555-c04ba851c2a4/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/object/fmt.go b/object/fmt.go index 1ae7b4a..5822484 100644 --- a/object/fmt.go +++ b/object/fmt.go @@ -1,6 +1,7 @@ package object import ( + "bytes" "crypto/ecdsa" "crypto/sha256" "errors" @@ -20,10 +21,10 @@ var errIncorrectID = errors.New("incorrect object identifier") // CalculatePayloadChecksum calculates and returns checksum of // object payload bytes. func CalculatePayloadChecksum(payload []byte) *checksum.Checksum { - res := checksum.New() - res.SetSHA256(sha256.Sum256(payload)) + var res checksum.Checksum + checksum.Calculate(&res, checksum.SHA256, payload) - return res + return &res } // CalculateAndSetPayloadChecksum calculates checksum of current @@ -38,7 +39,7 @@ func CalculateAndSetPayloadChecksum(obj *Object) { // corresponds to its payload. func VerifyPayloadChecksum(obj *Object) error { actual := CalculatePayloadChecksum(obj.Payload()) - if !checksum.Equal(obj.PayloadChecksum(), actual) { + if !bytes.Equal(obj.PayloadChecksum().Value(), actual.Value()) { return errCheckSumMismatch } diff --git a/object/object.go b/object/object.go index 37140ee..b47c87c 100644 --- a/object/object.go +++ b/object/object.go @@ -201,33 +201,45 @@ func (o *Object) SetCreationEpoch(v uint64) { // PayloadChecksum returns checksum of the object payload. func (o *Object) PayloadChecksum() *checksum.Checksum { - return checksum.NewFromV2( - (*object.Object)(o). + var v checksum.Checksum + v.ReadFromV2( + *(*object.Object)(o). GetHeader(). GetPayloadHash(), ) + + return &v } // SetPayloadChecksum sets checksum of the object payload. func (o *Object) SetPayloadChecksum(v *checksum.Checksum) { + var v2 refs.Checksum + v.WriteToV2(&v2) + o.setHeaderField(func(h *object.Header) { - h.SetPayloadHash(v.ToV2()) + h.SetPayloadHash(&v2) }) } // PayloadHomomorphicHash returns homomorphic hash of the object payload. func (o *Object) PayloadHomomorphicHash() *checksum.Checksum { - return checksum.NewFromV2( - (*object.Object)(o). + var v checksum.Checksum + v.ReadFromV2( + *(*object.Object)(o). GetHeader(). GetHomomorphicHash(), ) + + return &v } // SetPayloadHomomorphicHash sets homomorphic hash of the object payload. func (o *Object) SetPayloadHomomorphicHash(v *checksum.Checksum) { + var v2 refs.Checksum + v.WriteToV2(&v2) + o.setHeaderField(func(h *object.Header) { - h.SetHomomorphicHash(v.ToV2()) + h.SetHomomorphicHash(&v2) }) } diff --git a/object/raw_test.go b/object/raw_test.go index 930afc7..8a23aa3 100644 --- a/object/raw_test.go +++ b/object/raw_test.go @@ -122,23 +122,23 @@ func TestObject_SetCreationEpoch(t *testing.T) { func TestObject_SetPayloadChecksum(t *testing.T) { obj := New() - cs := checksum.New() + var cs checksum.Checksum cs.SetSHA256(randSHA256Checksum(t)) - obj.SetPayloadChecksum(cs) + obj.SetPayloadChecksum(&cs) - require.Equal(t, cs, obj.PayloadChecksum()) + require.Equal(t, &cs, obj.PayloadChecksum()) } func TestObject_SetPayloadHomomorphicHash(t *testing.T) { obj := New() - cs := checksum.New() + var cs checksum.Checksum cs.SetTillichZemor(randTZChecksum(t)) - obj.SetPayloadHomomorphicHash(cs) + obj.SetPayloadHomomorphicHash(&cs) - require.Equal(t, cs, obj.PayloadHomomorphicHash()) + require.Equal(t, &cs, obj.PayloadHomomorphicHash()) } func TestObject_SetAttributes(t *testing.T) { diff --git a/storagegroup/storagegroup.go b/storagegroup/storagegroup.go index e3d0952..ac626b1 100644 --- a/storagegroup/storagegroup.go +++ b/storagegroup/storagegroup.go @@ -43,14 +43,23 @@ func (sg *StorageGroup) SetValidationDataSize(epoch uint64) { // ValidationDataHash returns homomorphic hash from the // concatenation of the payloads of the storage group members. func (sg *StorageGroup) ValidationDataHash() *checksum.Checksum { - return checksum.NewFromV2( - (*storagegroup.StorageGroup)(sg).GetValidationHash()) + if v2 := (*storagegroup.StorageGroup)(sg).GetValidationHash(); v2 != nil { + var v checksum.Checksum + v.ReadFromV2(*v2) + + return &v + } + + return nil } // SetValidationDataHash sets homomorphic hash from the // concatenation of the payloads of the storage group members. func (sg *StorageGroup) SetValidationDataHash(hash *checksum.Checksum) { - (*storagegroup.StorageGroup)(sg).SetValidationHash(hash.ToV2()) + var v2 refs.Checksum + hash.WriteToV2(&v2) + + (*storagegroup.StorageGroup)(sg).SetValidationHash(&v2) } // ExpirationEpoch returns last NeoFS epoch number