[#170] checksum: Drop Empty method

Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
This commit is contained in:
Pavel Karpy 2021-03-22 13:56:18 +03:00 committed by LeL
parent 9c502a9cae
commit fd13e61266
11 changed files with 188 additions and 253 deletions

View file

@ -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
}

View file

@ -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())
})
}

18
checksum/doc.go Normal file
View file

@ -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

13
checksum/test/doc.go Normal file
View file

@ -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

View file

@ -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
}

1
go.mod
View file

@ -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
)

5
go.sum
View file

@ -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=

View file

@ -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
}

View file

@ -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)
})
}

View file

@ -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) {

View file

@ -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