7d10b432d1
Return `error` from all `ReadFromV2` methods in order to support backward compatibility if message will be extended with some formatted field. Signed-off-by: Leonard Lyubich <leonard@nspcc.ru>
328 lines
8.4 KiB
Go
328 lines
8.4 KiB
Go
package storagegroup
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strconv"
|
|
|
|
objectV2 "github.com/nspcc-dev/neofs-api-go/v2/object"
|
|
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
|
"github.com/nspcc-dev/neofs-api-go/v2/storagegroup"
|
|
"github.com/nspcc-dev/neofs-sdk-go/checksum"
|
|
objectSDK "github.com/nspcc-dev/neofs-sdk-go/object"
|
|
oid "github.com/nspcc-dev/neofs-sdk-go/object/id"
|
|
)
|
|
|
|
// StorageGroup represents storage group of the NeoFS objects.
|
|
//
|
|
// StorageGroup is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/storagegroup.StorageGroup
|
|
// message. See ReadFromMessageV2 / WriteToMessageV2 methods.
|
|
//
|
|
// Instances can be created using built-in var declaration.
|
|
//
|
|
// Note that direct typecast is not safe and may result in loss of compatibility:
|
|
// _ = StorageGroup(storagegroup.StorageGroup) // not recommended
|
|
type StorageGroup storagegroup.StorageGroup
|
|
|
|
// reads StorageGroup from the storagegroup.StorageGroup message. If checkFieldPresence is set,
|
|
// returns an error on absence of any protocol-required field.
|
|
func (sg *StorageGroup) readFromV2(m storagegroup.StorageGroup, checkFieldPresence bool) error {
|
|
var err error
|
|
|
|
h := m.GetValidationHash()
|
|
if h != nil {
|
|
err = new(checksum.Checksum).ReadFromV2(*h)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid hash: %w", err)
|
|
}
|
|
} else if checkFieldPresence {
|
|
return errors.New("missing hash")
|
|
}
|
|
|
|
members := m.GetMembers()
|
|
if len(members) > 0 {
|
|
var member oid.ID
|
|
mMembers := make(map[oid.ID]struct{}, len(members))
|
|
var exits bool
|
|
|
|
for i := range members {
|
|
err = member.ReadFromV2(members[i])
|
|
if err != nil {
|
|
return fmt.Errorf("invalid member: %w", err)
|
|
}
|
|
|
|
_, exits = mMembers[member]
|
|
if exits {
|
|
return fmt.Errorf("duplicated member %s", member)
|
|
}
|
|
|
|
mMembers[member] = struct{}{}
|
|
}
|
|
} else if checkFieldPresence {
|
|
return errors.New("missing members")
|
|
}
|
|
|
|
*sg = StorageGroup(m)
|
|
|
|
return nil
|
|
}
|
|
|
|
// ReadFromV2 reads StorageGroup from the storagegroup.StorageGroup message.
|
|
// Checks if the message conforms to NeoFS API V2 protocol.
|
|
//
|
|
// See also WriteToV2.
|
|
func (sg *StorageGroup) ReadFromV2(m storagegroup.StorageGroup) error {
|
|
return sg.readFromV2(m, true)
|
|
}
|
|
|
|
// WriteToV2 writes StorageGroup to the storagegroup.StorageGroup message.
|
|
// The message must not be nil.
|
|
//
|
|
// See also ReadFromV2.
|
|
func (sg StorageGroup) WriteToV2(m *storagegroup.StorageGroup) {
|
|
*m = (storagegroup.StorageGroup)(sg)
|
|
}
|
|
|
|
// ValidationDataSize returns total size of the payloads
|
|
// of objects in the storage group.
|
|
//
|
|
// Zero StorageGroup has 0 data size.
|
|
//
|
|
// See also SetValidationDataSize.
|
|
func (sg StorageGroup) ValidationDataSize() uint64 {
|
|
v2 := (storagegroup.StorageGroup)(sg)
|
|
return v2.GetValidationDataSize()
|
|
}
|
|
|
|
// SetValidationDataSize sets total size of the payloads
|
|
// of objects in the storage group.
|
|
//
|
|
// See also ValidationDataSize.
|
|
func (sg *StorageGroup) SetValidationDataSize(epoch uint64) {
|
|
(*storagegroup.StorageGroup)(sg).SetValidationDataSize(epoch)
|
|
}
|
|
|
|
// ValidationDataHash returns homomorphic hash from the
|
|
// concatenation of the payloads of the storage group members
|
|
// and bool that indicates checksum presence in the storage
|
|
// group.
|
|
//
|
|
// Zero StorageGroup does not have validation data checksum.
|
|
//
|
|
// See also SetValidationDataHash.
|
|
func (sg StorageGroup) ValidationDataHash() (v checksum.Checksum, isSet bool) {
|
|
v2 := (storagegroup.StorageGroup)(sg)
|
|
if checksumV2 := v2.GetValidationHash(); checksumV2 != nil {
|
|
v.ReadFromV2(*checksumV2) // FIXME(@cthulhu-rider): #226 handle error
|
|
isSet = true
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// SetValidationDataHash sets homomorphic hash from the
|
|
// concatenation of the payloads of the storage group members.
|
|
//
|
|
// See also ValidationDataHash.
|
|
func (sg *StorageGroup) SetValidationDataHash(hash checksum.Checksum) {
|
|
var v2 refs.Checksum
|
|
hash.WriteToV2(&v2)
|
|
|
|
(*storagegroup.StorageGroup)(sg).SetValidationHash(&v2)
|
|
}
|
|
|
|
// ExpirationEpoch returns last NeoFS epoch number
|
|
// of the storage group lifetime.
|
|
//
|
|
// Zero StorageGroup has 0 expiration epoch.
|
|
//
|
|
// See also SetExpirationEpoch.
|
|
func (sg StorageGroup) ExpirationEpoch() uint64 {
|
|
v2 := (storagegroup.StorageGroup)(sg)
|
|
return v2.GetExpirationEpoch()
|
|
}
|
|
|
|
// SetExpirationEpoch sets last NeoFS epoch number
|
|
// of the storage group lifetime.
|
|
//
|
|
// See also ExpirationEpoch.
|
|
func (sg *StorageGroup) SetExpirationEpoch(epoch uint64) {
|
|
(*storagegroup.StorageGroup)(sg).SetExpirationEpoch(epoch)
|
|
}
|
|
|
|
// Members returns strictly ordered list of
|
|
// storage group member objects.
|
|
//
|
|
// Zero StorageGroup has nil members value.
|
|
//
|
|
// See also SetMembers.
|
|
func (sg StorageGroup) Members() []oid.ID {
|
|
v2 := (storagegroup.StorageGroup)(sg)
|
|
mV2 := v2.GetMembers()
|
|
|
|
if mV2 == nil {
|
|
return nil
|
|
}
|
|
|
|
m := make([]oid.ID, len(mV2))
|
|
|
|
for i := range mV2 {
|
|
_ = m[i].ReadFromV2(mV2[i])
|
|
}
|
|
|
|
return m
|
|
}
|
|
|
|
// SetMembers sets strictly ordered list of
|
|
// storage group member objects.
|
|
//
|
|
// See also Members.
|
|
func (sg *StorageGroup) SetMembers(members []oid.ID) {
|
|
mV2 := (*storagegroup.StorageGroup)(sg).GetMembers()
|
|
|
|
if members == nil {
|
|
mV2 = nil
|
|
} else {
|
|
ln := len(members)
|
|
|
|
if cap(mV2) >= ln {
|
|
mV2 = mV2[:0]
|
|
} else {
|
|
mV2 = make([]refs.ObjectID, 0, ln)
|
|
}
|
|
|
|
var oidV2 refs.ObjectID
|
|
|
|
for i := 0; i < ln; i++ {
|
|
members[i].WriteToV2(&oidV2)
|
|
mV2 = append(mV2, oidV2)
|
|
}
|
|
}
|
|
|
|
(*storagegroup.StorageGroup)(sg).SetMembers(mV2)
|
|
}
|
|
|
|
// Marshal marshals StorageGroup into a protobuf binary form.
|
|
//
|
|
// See also Unmarshal.
|
|
func (sg StorageGroup) Marshal() ([]byte, error) {
|
|
return (*storagegroup.StorageGroup)(&sg).StableMarshal(nil), nil
|
|
}
|
|
|
|
// Unmarshal unmarshals protobuf binary representation of StorageGroup.
|
|
//
|
|
// See also Marshal.
|
|
func (sg *StorageGroup) Unmarshal(data []byte) error {
|
|
v2 := (*storagegroup.StorageGroup)(sg)
|
|
err := v2.Unmarshal(data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return sg.readFromV2(*v2, false)
|
|
}
|
|
|
|
// MarshalJSON encodes StorageGroup to protobuf JSON format.
|
|
//
|
|
// See also UnmarshalJSON.
|
|
func (sg StorageGroup) MarshalJSON() ([]byte, error) {
|
|
v2 := (storagegroup.StorageGroup)(sg)
|
|
return v2.MarshalJSON()
|
|
}
|
|
|
|
// UnmarshalJSON decodes StorageGroup from protobuf JSON format.
|
|
//
|
|
// See also MarshalJSON.
|
|
func (sg *StorageGroup) UnmarshalJSON(data []byte) error {
|
|
v2 := (*storagegroup.StorageGroup)(sg)
|
|
err := v2.UnmarshalJSON(data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return sg.readFromV2(*v2, false)
|
|
}
|
|
|
|
// ReadFromObject assemble StorageGroup from a regular
|
|
// Object structure. Object must contain unambiguous information
|
|
// about its expiration epoch, otherwise behaviour is undefined.
|
|
//
|
|
// Returns any error appeared during storage group parsing; returns
|
|
// error if object is not of TypeStorageGroup type.
|
|
func ReadFromObject(sg *StorageGroup, o objectSDK.Object) error {
|
|
if typ := o.Type(); typ != objectSDK.TypeStorageGroup {
|
|
return fmt.Errorf("object is not of StorageGroup type: %s", typ)
|
|
}
|
|
|
|
err := sg.Unmarshal(o.Payload())
|
|
if err != nil {
|
|
return fmt.Errorf("could not unmarshal object: %w", err)
|
|
}
|
|
|
|
var expObj uint64
|
|
|
|
for _, attr := range o.Attributes() {
|
|
if attr.Key() == objectV2.SysAttributeExpEpoch {
|
|
expObj, err = strconv.ParseUint(attr.Value(), 10, 64)
|
|
if err != nil {
|
|
return fmt.Errorf("could not get expiration from object: %w", err)
|
|
}
|
|
|
|
break
|
|
}
|
|
}
|
|
|
|
// Supporting deprecated functionality.
|
|
// See https://github.com/nspcc-dev/neofs-api/pull/205.
|
|
if expSG := sg.ExpirationEpoch(); expObj != expSG {
|
|
return fmt.Errorf(
|
|
"expiration does not match: from object: %d, from payload: %d",
|
|
expObj, expSG)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// WriteToObject writes StorageGroup to a regular
|
|
// Object structure. Object must not contain ambiguous
|
|
// information about its expiration epoch or must not
|
|
// have it at all.
|
|
//
|
|
// Written information:
|
|
// * expiration epoch;
|
|
// * object type (TypeStorageGroup);
|
|
// * raw payload.
|
|
func WriteToObject(sg StorageGroup, o *objectSDK.Object) {
|
|
sgRaw, err := sg.Marshal()
|
|
if err != nil {
|
|
// Marshal() does not return errors
|
|
// in the next API release
|
|
panic(fmt.Errorf("could not marshal storage group: %w", err))
|
|
}
|
|
|
|
o.SetPayload(sgRaw)
|
|
o.SetType(objectSDK.TypeStorageGroup)
|
|
|
|
attrs := o.Attributes()
|
|
var expAttrFound bool
|
|
|
|
for i := range attrs {
|
|
if attrs[i].Key() == objectV2.SysAttributeExpEpoch {
|
|
expAttrFound = true
|
|
attrs[i].SetValue(strconv.FormatUint(sg.ExpirationEpoch(), 10))
|
|
|
|
break
|
|
}
|
|
}
|
|
|
|
if !expAttrFound {
|
|
var attr objectSDK.Attribute
|
|
|
|
attr.SetKey(objectV2.SysAttributeExpEpoch)
|
|
attr.SetValue(strconv.FormatUint(sg.ExpirationEpoch(), 10))
|
|
|
|
attrs = append(attrs, attr)
|
|
}
|
|
|
|
o.SetAttributes(attrs...)
|
|
}
|