forked from TrueCloudLab/frostfs-sdk-go
[#83] pkg/client: Support status returns
Make all `Client` methods to return structured values and error. Parse v2 status messages in all RPC and provide status getter from all result structures. Returns status failures as status result instead of error. Interface changes: * all methods return `<method>Res` structure; * rename some methods to be more clear; * unify TZ and SHA256 objecy payload hashing in single method. Behavior changes: * client doesn't verify object header structure received via Object.Head. If the caller was tied to verification, now it must do it explicitly. Signed-off-by: Leonard Lyubich <leonard@nspcc.ru>
This commit is contained in:
parent
9dcff95a29
commit
bf78cddf69
8 changed files with 835 additions and 372 deletions
|
@ -15,10 +15,24 @@ import (
|
||||||
// Accounting contains methods related to balance querying.
|
// Accounting contains methods related to balance querying.
|
||||||
type Accounting interface {
|
type Accounting interface {
|
||||||
// GetBalance returns balance of provided account.
|
// GetBalance returns balance of provided account.
|
||||||
GetBalance(context.Context, *owner.ID, ...CallOption) (*accounting.Decimal, error)
|
GetBalance(context.Context, *owner.ID, ...CallOption) (*BalanceOfRes, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *clientImpl) GetBalance(ctx context.Context, owner *owner.ID, opts ...CallOption) (*accounting.Decimal, error) {
|
type BalanceOfRes struct {
|
||||||
|
statusRes
|
||||||
|
|
||||||
|
amount *accounting.Decimal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *BalanceOfRes) setAmount(v *accounting.Decimal) {
|
||||||
|
x.amount = v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x BalanceOfRes) Amount() *accounting.Decimal {
|
||||||
|
return x.amount
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *clientImpl) GetBalance(ctx context.Context, owner *owner.ID, opts ...CallOption) (*BalanceOfRes, error) {
|
||||||
// apply all available options
|
// apply all available options
|
||||||
callOptions := c.defaultCallOptions()
|
callOptions := c.defaultCallOptions()
|
||||||
|
|
||||||
|
@ -43,15 +57,27 @@ func (c *clientImpl) GetBalance(ctx context.Context, owner *owner.ID, opts ...Ca
|
||||||
return nil, fmt.Errorf("transport error: %w", err)
|
return nil, fmt.Errorf("transport error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle response meta info
|
var (
|
||||||
if err := c.handleResponseInfoV2(callOptions, resp); err != nil {
|
res = new(BalanceOfRes)
|
||||||
return nil, err
|
procPrm processResponseV2Prm
|
||||||
|
procRes processResponseV2Res
|
||||||
|
)
|
||||||
|
|
||||||
|
procPrm.callOpts = callOptions
|
||||||
|
procPrm.resp = resp
|
||||||
|
|
||||||
|
procRes.statusRes = res
|
||||||
|
|
||||||
|
// process response in general
|
||||||
|
if c.processResponseV2(&procRes, procPrm) {
|
||||||
|
if procRes.cliErr != nil {
|
||||||
|
return nil, procRes.cliErr
|
||||||
}
|
}
|
||||||
|
|
||||||
err = v2signature.VerifyServiceMessage(resp)
|
return res, nil
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("can't verify response message: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return accounting.NewDecimalFromV2(resp.GetBody().GetBalance()), nil
|
res.setAmount(accounting.NewDecimalFromV2(resp.GetBody().GetBalance()))
|
||||||
|
|
||||||
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
80
client/common.go
Normal file
80
client/common.go
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neofs-api-go/v2/signature"
|
||||||
|
apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
// common interface of resulting structures with API status.
|
||||||
|
type resCommon interface {
|
||||||
|
setStatus(apistatus.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
// structure is embedded to all resulting types in order to inherit status-related methods.
|
||||||
|
type statusRes struct {
|
||||||
|
st apistatus.Status
|
||||||
|
}
|
||||||
|
|
||||||
|
// setStatus implements resCommon interface method.
|
||||||
|
func (x *statusRes) setStatus(st apistatus.Status) {
|
||||||
|
x.st = st
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status returns server's status return.
|
||||||
|
//
|
||||||
|
// Use apistatus package functionality to handle the status.
|
||||||
|
func (x statusRes) Status() apistatus.Status {
|
||||||
|
return x.st
|
||||||
|
}
|
||||||
|
|
||||||
|
// checks response signature and write client error if it is not correct (in this case returns true).
|
||||||
|
func isInvalidSignatureV2(res *processResponseV2Res, resp responseV2) bool {
|
||||||
|
err := signature.VerifyServiceMessage(resp)
|
||||||
|
|
||||||
|
isErr := err != nil
|
||||||
|
if isErr {
|
||||||
|
res.cliErr = fmt.Errorf("invalid response signature: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return isErr
|
||||||
|
}
|
||||||
|
|
||||||
|
type processResponseV2Prm struct {
|
||||||
|
callOpts *callOptions
|
||||||
|
|
||||||
|
resp responseV2
|
||||||
|
}
|
||||||
|
|
||||||
|
type processResponseV2Res struct {
|
||||||
|
statusRes resCommon
|
||||||
|
|
||||||
|
cliErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
// performs common actions of response processing and writes any problem as a result status or client error
|
||||||
|
// (in both cases returns true).
|
||||||
|
//
|
||||||
|
// Actions:
|
||||||
|
// * verify signature (internal);
|
||||||
|
// * call response callback (internal).
|
||||||
|
func (c *clientImpl) processResponseV2(res *processResponseV2Res, prm processResponseV2Prm) bool {
|
||||||
|
// verify response structure
|
||||||
|
if isInvalidSignatureV2(res, prm.resp) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle response meta info
|
||||||
|
if err := c.handleResponseInfoV2(prm.callOpts, prm.resp); err != nil {
|
||||||
|
res.cliErr = err
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// set result status
|
||||||
|
st := apistatus.FromStatusV2(prm.resp.GetMetaHeader().GetStatus())
|
||||||
|
|
||||||
|
res.statusRes.setStatus(st)
|
||||||
|
|
||||||
|
return !apistatus.IsSuccessful(st)
|
||||||
|
}
|
|
@ -2,7 +2,6 @@ package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
v2container "github.com/nspcc-dev/neofs-api-go/v2/container"
|
v2container "github.com/nspcc-dev/neofs-api-go/v2/container"
|
||||||
|
@ -10,6 +9,7 @@ import (
|
||||||
rpcapi "github.com/nspcc-dev/neofs-api-go/v2/rpc"
|
rpcapi "github.com/nspcc-dev/neofs-api-go/v2/rpc"
|
||||||
"github.com/nspcc-dev/neofs-api-go/v2/rpc/client"
|
"github.com/nspcc-dev/neofs-api-go/v2/rpc/client"
|
||||||
v2signature "github.com/nspcc-dev/neofs-api-go/v2/signature"
|
v2signature "github.com/nspcc-dev/neofs-api-go/v2/signature"
|
||||||
|
apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status"
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/container"
|
"github.com/nspcc-dev/neofs-sdk-go/container"
|
||||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/eacl"
|
"github.com/nspcc-dev/neofs-sdk-go/eacl"
|
||||||
|
@ -23,25 +23,25 @@ import (
|
||||||
// Container contains methods related to container and ACL.
|
// Container contains methods related to container and ACL.
|
||||||
type Container interface {
|
type Container interface {
|
||||||
// PutContainer creates new container in the NeoFS network.
|
// PutContainer creates new container in the NeoFS network.
|
||||||
PutContainer(context.Context, *container.Container, ...CallOption) (*cid.ID, error)
|
PutContainer(context.Context, *container.Container, ...CallOption) (*ContainerPutRes, error)
|
||||||
|
|
||||||
// GetContainer returns container by ID.
|
// GetContainer returns container by ID.
|
||||||
GetContainer(context.Context, *cid.ID, ...CallOption) (*container.Container, error)
|
GetContainer(context.Context, *cid.ID, ...CallOption) (*ContainerGetRes, error)
|
||||||
|
|
||||||
// ListContainers return container list with the provided owner.
|
// ListContainers return container list with the provided owner.
|
||||||
ListContainers(context.Context, *owner.ID, ...CallOption) ([]*cid.ID, error)
|
ListContainers(context.Context, *owner.ID, ...CallOption) (*ContainerListRes, error)
|
||||||
|
|
||||||
// DeleteContainer removes container from NeoFS network.
|
// DeleteContainer removes container from NeoFS network.
|
||||||
DeleteContainer(context.Context, *cid.ID, ...CallOption) error
|
DeleteContainer(context.Context, *cid.ID, ...CallOption) (*ContainerDeleteRes, error)
|
||||||
|
|
||||||
// GetEACL returns extended ACL for a given container.
|
// EACL returns extended ACL for a given container.
|
||||||
GetEACL(context.Context, *cid.ID, ...CallOption) (*EACLWithSignature, error)
|
EACL(context.Context, *cid.ID, ...CallOption) (*EACLRes, error)
|
||||||
|
|
||||||
// SetEACL sets extended ACL.
|
// SetEACL sets extended ACL.
|
||||||
SetEACL(context.Context, *eacl.Table, ...CallOption) error
|
SetEACL(context.Context, *eacl.Table, ...CallOption) (*SetEACLRes, error)
|
||||||
|
|
||||||
// AnnounceContainerUsedSpace announces amount of space which is taken by stored objects.
|
// AnnounceContainerUsedSpace announces amount of space which is taken by stored objects.
|
||||||
AnnounceContainerUsedSpace(context.Context, []container.UsedSpaceAnnouncement, ...CallOption) error
|
AnnounceContainerUsedSpace(context.Context, []container.UsedSpaceAnnouncement, ...CallOption) (*AnnounceSpaceRes, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type delContainerSignWrapper struct {
|
type delContainerSignWrapper struct {
|
||||||
|
@ -73,7 +73,21 @@ func (e EACLWithSignature) Signature() *signature.Signature {
|
||||||
return e.table.Signature()
|
return e.table.Signature()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *clientImpl) PutContainer(ctx context.Context, cnr *container.Container, opts ...CallOption) (*cid.ID, error) {
|
type ContainerPutRes struct {
|
||||||
|
statusRes
|
||||||
|
|
||||||
|
id *cid.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x ContainerPutRes) ID() *cid.ID {
|
||||||
|
return x.id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ContainerPutRes) setID(id *cid.ID) {
|
||||||
|
x.id = id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *clientImpl) PutContainer(ctx context.Context, cnr *container.Container, opts ...CallOption) (*ContainerPutRes, error) {
|
||||||
// apply all available options
|
// apply all available options
|
||||||
callOptions := c.defaultCallOptions()
|
callOptions := c.defaultCallOptions()
|
||||||
|
|
||||||
|
@ -131,23 +145,56 @@ func (c *clientImpl) PutContainer(ctx context.Context, cnr *container.Container,
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle response meta info
|
var (
|
||||||
if err := c.handleResponseInfoV2(callOptions, resp); err != nil {
|
res = new(ContainerPutRes)
|
||||||
return nil, err
|
procPrm processResponseV2Prm
|
||||||
|
procRes processResponseV2Res
|
||||||
|
)
|
||||||
|
|
||||||
|
procPrm.callOpts = callOptions
|
||||||
|
procPrm.resp = resp
|
||||||
|
|
||||||
|
procRes.statusRes = res
|
||||||
|
|
||||||
|
// process response in general
|
||||||
|
if c.processResponseV2(&procRes, procPrm) {
|
||||||
|
if procRes.cliErr != nil {
|
||||||
|
return nil, procRes.cliErr
|
||||||
}
|
}
|
||||||
|
|
||||||
err = v2signature.VerifyServiceMessage(resp)
|
return res, nil
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("can't verify response message: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return cid.NewFromV2(resp.GetBody().GetContainerID()), nil
|
// sets result status
|
||||||
|
st := apistatus.FromStatusV2(resp.GetMetaHeader().GetStatus())
|
||||||
|
|
||||||
|
res.setStatus(st)
|
||||||
|
|
||||||
|
if apistatus.IsSuccessful(st) {
|
||||||
|
res.setID(cid.NewFromV2(resp.GetBody().GetContainerID()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ContainerGetRes struct {
|
||||||
|
statusRes
|
||||||
|
|
||||||
|
cnr *container.Container
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x ContainerGetRes) Container() *container.Container {
|
||||||
|
return x.cnr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ContainerGetRes) setContainer(cnr *container.Container) {
|
||||||
|
x.cnr = cnr
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetContainer receives container structure through NeoFS API call.
|
// GetContainer receives container structure through NeoFS API call.
|
||||||
//
|
//
|
||||||
// Returns error if container structure is received but does not meet NeoFS API specification.
|
// Returns error if container structure is received but does not meet NeoFS API specification.
|
||||||
func (c *clientImpl) GetContainer(ctx context.Context, id *cid.ID, opts ...CallOption) (*container.Container, error) {
|
func (c *clientImpl) GetContainer(ctx context.Context, id *cid.ID, opts ...CallOption) (*ContainerGetRes, error) {
|
||||||
// apply all available options
|
// apply all available options
|
||||||
callOptions := c.defaultCallOptions()
|
callOptions := c.defaultCallOptions()
|
||||||
|
|
||||||
|
@ -172,43 +219,58 @@ func (c *clientImpl) GetContainer(ctx context.Context, id *cid.ID, opts ...CallO
|
||||||
return nil, fmt.Errorf("transport error: %w", err)
|
return nil, fmt.Errorf("transport error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle response meta info
|
var (
|
||||||
if err := c.handleResponseInfoV2(callOptions, resp); err != nil {
|
res = new(ContainerGetRes)
|
||||||
return nil, err
|
procPrm processResponseV2Prm
|
||||||
|
procRes processResponseV2Res
|
||||||
|
)
|
||||||
|
|
||||||
|
procPrm.callOpts = callOptions
|
||||||
|
procPrm.resp = resp
|
||||||
|
|
||||||
|
procRes.statusRes = res
|
||||||
|
|
||||||
|
// process response in general
|
||||||
|
if c.processResponseV2(&procRes, procPrm) {
|
||||||
|
if procRes.cliErr != nil {
|
||||||
|
return nil, procRes.cliErr
|
||||||
}
|
}
|
||||||
|
|
||||||
err = v2signature.VerifyServiceMessage(resp)
|
return res, nil
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("can't verify response message: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body := resp.GetBody()
|
body := resp.GetBody()
|
||||||
|
|
||||||
cnr := container.NewContainerFromV2(body.GetContainer())
|
cnr := container.NewContainerFromV2(body.GetContainer())
|
||||||
cnr.SetSessionToken(session.NewTokenFromV2(body.GetSessionToken()))
|
|
||||||
cnr.SetSignature(signature.NewFromV2(body.GetSignature()))
|
|
||||||
|
|
||||||
return cnr, nil
|
cnr.SetSessionToken(
|
||||||
|
session.NewTokenFromV2(body.GetSessionToken()),
|
||||||
|
)
|
||||||
|
|
||||||
|
cnr.SetSignature(
|
||||||
|
signature.NewFromV2(body.GetSignature()),
|
||||||
|
)
|
||||||
|
|
||||||
|
res.setContainer(cnr)
|
||||||
|
|
||||||
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetVerifiedContainerStructure is a wrapper over Client.GetContainer method
|
type ContainerListRes struct {
|
||||||
// which checks if the structure of the resulting container matches its identifier.
|
statusRes
|
||||||
//
|
|
||||||
// Returns an error if container does not match the identifier.
|
|
||||||
func GetVerifiedContainerStructure(ctx context.Context, c Client, id *cid.ID, opts ...CallOption) (*container.Container, error) {
|
|
||||||
cnr, err := c.GetContainer(ctx, id, opts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !container.CalculateID(cnr).Equal(id) {
|
ids []*cid.ID
|
||||||
return nil, errors.New("container structure does not match the identifier")
|
|
||||||
}
|
|
||||||
|
|
||||||
return cnr, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *clientImpl) ListContainers(ctx context.Context, ownerID *owner.ID, opts ...CallOption) ([]*cid.ID, error) {
|
func (x ContainerListRes) IDList() []*cid.ID {
|
||||||
|
return x.ids
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ContainerListRes) setIDList(ids []*cid.ID) {
|
||||||
|
x.ids = ids
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *clientImpl) ListContainers(ctx context.Context, ownerID *owner.ID, opts ...CallOption) (*ContainerListRes, error) {
|
||||||
// apply all available options
|
// apply all available options
|
||||||
callOptions := c.defaultCallOptions()
|
callOptions := c.defaultCallOptions()
|
||||||
|
|
||||||
|
@ -243,25 +305,42 @@ func (c *clientImpl) ListContainers(ctx context.Context, ownerID *owner.ID, opts
|
||||||
return nil, fmt.Errorf("transport error: %w", err)
|
return nil, fmt.Errorf("transport error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle response meta info
|
var (
|
||||||
if err := c.handleResponseInfoV2(callOptions, resp); err != nil {
|
res = new(ContainerListRes)
|
||||||
return nil, err
|
procPrm processResponseV2Prm
|
||||||
|
procRes processResponseV2Res
|
||||||
|
)
|
||||||
|
|
||||||
|
procPrm.callOpts = callOptions
|
||||||
|
procPrm.resp = resp
|
||||||
|
|
||||||
|
procRes.statusRes = res
|
||||||
|
|
||||||
|
// process response in general
|
||||||
|
if c.processResponseV2(&procRes, procPrm) {
|
||||||
|
if procRes.cliErr != nil {
|
||||||
|
return nil, procRes.cliErr
|
||||||
}
|
}
|
||||||
|
|
||||||
err = v2signature.VerifyServiceMessage(resp)
|
return res, nil
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("can't verify response message: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result := make([]*cid.ID, 0, len(resp.GetBody().GetContainerIDs()))
|
ids := make([]*cid.ID, 0, len(resp.GetBody().GetContainerIDs()))
|
||||||
|
|
||||||
for _, cidV2 := range resp.GetBody().GetContainerIDs() {
|
for _, cidV2 := range resp.GetBody().GetContainerIDs() {
|
||||||
result = append(result, cid.NewFromV2(cidV2))
|
ids = append(ids, cid.NewFromV2(cidV2))
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, nil
|
res.setIDList(ids)
|
||||||
|
|
||||||
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *clientImpl) DeleteContainer(ctx context.Context, id *cid.ID, opts ...CallOption) error {
|
type ContainerDeleteRes struct {
|
||||||
|
statusRes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *clientImpl) DeleteContainer(ctx context.Context, id *cid.ID, opts ...CallOption) (*ContainerDeleteRes, error) {
|
||||||
// apply all available options
|
// apply all available options
|
||||||
callOptions := c.defaultCallOptions()
|
callOptions := c.defaultCallOptions()
|
||||||
|
|
||||||
|
@ -285,7 +364,7 @@ func (c *clientImpl) DeleteContainer(ctx context.Context, id *cid.ID, opts ...Ca
|
||||||
},
|
},
|
||||||
sigutil.SignWithRFC6979())
|
sigutil.SignWithRFC6979())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
req := new(v2container.DeleteRequest)
|
req := new(v2container.DeleteRequest)
|
||||||
|
@ -294,27 +373,52 @@ func (c *clientImpl) DeleteContainer(ctx context.Context, id *cid.ID, opts ...Ca
|
||||||
|
|
||||||
err = v2signature.SignServiceMessage(callOptions.key, req)
|
err = v2signature.SignServiceMessage(callOptions.key, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := rpcapi.DeleteContainer(c.Raw(), req, client.WithContext(ctx))
|
resp, err := rpcapi.DeleteContainer(c.Raw(), req, client.WithContext(ctx))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("transport error: %w", err)
|
return nil, fmt.Errorf("transport error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle response meta info
|
var (
|
||||||
if err := c.handleResponseInfoV2(callOptions, resp); err != nil {
|
res = new(ContainerDeleteRes)
|
||||||
return err
|
procPrm processResponseV2Prm
|
||||||
|
procRes processResponseV2Res
|
||||||
|
)
|
||||||
|
|
||||||
|
procPrm.callOpts = callOptions
|
||||||
|
procPrm.resp = resp
|
||||||
|
|
||||||
|
procRes.statusRes = res
|
||||||
|
|
||||||
|
// process response in general
|
||||||
|
if c.processResponseV2(&procRes, procPrm) {
|
||||||
|
if procRes.cliErr != nil {
|
||||||
|
return nil, procRes.cliErr
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := v2signature.VerifyServiceMessage(resp); err != nil {
|
return res, nil
|
||||||
return fmt.Errorf("can't verify response message: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *clientImpl) GetEACL(ctx context.Context, id *cid.ID, opts ...CallOption) (*EACLWithSignature, error) {
|
type EACLRes struct {
|
||||||
|
statusRes
|
||||||
|
|
||||||
|
table *eacl.Table
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x EACLRes) Table() *eacl.Table {
|
||||||
|
return x.table
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *EACLRes) SetTable(table *eacl.Table) {
|
||||||
|
x.table = table
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *clientImpl) EACL(ctx context.Context, id *cid.ID, opts ...CallOption) (*EACLRes, error) {
|
||||||
// apply all available options
|
// apply all available options
|
||||||
callOptions := c.defaultCallOptions()
|
callOptions := c.defaultCallOptions()
|
||||||
|
|
||||||
|
@ -339,28 +443,48 @@ func (c *clientImpl) GetEACL(ctx context.Context, id *cid.ID, opts ...CallOption
|
||||||
return nil, fmt.Errorf("transport error: %w", err)
|
return nil, fmt.Errorf("transport error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle response meta info
|
var (
|
||||||
if err := c.handleResponseInfoV2(callOptions, resp); err != nil {
|
res = new(EACLRes)
|
||||||
return nil, err
|
procPrm processResponseV2Prm
|
||||||
|
procRes processResponseV2Res
|
||||||
|
)
|
||||||
|
|
||||||
|
procPrm.callOpts = callOptions
|
||||||
|
procPrm.resp = resp
|
||||||
|
|
||||||
|
procRes.statusRes = res
|
||||||
|
|
||||||
|
// process response in general
|
||||||
|
if c.processResponseV2(&procRes, procPrm) {
|
||||||
|
if procRes.cliErr != nil {
|
||||||
|
return nil, procRes.cliErr
|
||||||
}
|
}
|
||||||
|
|
||||||
err = v2signature.VerifyServiceMessage(resp)
|
return res, nil
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("can't verify response message: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body := resp.GetBody()
|
body := resp.GetBody()
|
||||||
|
|
||||||
table := eacl.NewTableFromV2(body.GetEACL())
|
table := eacl.NewTableFromV2(body.GetEACL())
|
||||||
table.SetSessionToken(session.NewTokenFromV2(body.GetSessionToken()))
|
|
||||||
table.SetSignature(signature.NewFromV2(body.GetSignature()))
|
|
||||||
|
|
||||||
return &EACLWithSignature{
|
table.SetSessionToken(
|
||||||
table: table,
|
session.NewTokenFromV2(body.GetSessionToken()),
|
||||||
}, nil
|
)
|
||||||
|
|
||||||
|
table.SetSignature(
|
||||||
|
signature.NewFromV2(body.GetSignature()),
|
||||||
|
)
|
||||||
|
|
||||||
|
res.SetTable(table)
|
||||||
|
|
||||||
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *clientImpl) SetEACL(ctx context.Context, eacl *eacl.Table, opts ...CallOption) error {
|
type SetEACLRes struct {
|
||||||
|
statusRes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *clientImpl) SetEACL(ctx context.Context, eacl *eacl.Table, opts ...CallOption) (*SetEACLRes, error) {
|
||||||
// apply all available options
|
// apply all available options
|
||||||
callOptions := c.defaultCallOptions()
|
callOptions := c.defaultCallOptions()
|
||||||
|
|
||||||
|
@ -380,37 +504,52 @@ func (c *clientImpl) SetEACL(ctx context.Context, eacl *eacl.Table, opts ...Call
|
||||||
reqBody.SetSignature(eaclSignature)
|
reqBody.SetSignature(eaclSignature)
|
||||||
}, sigutil.SignWithRFC6979())
|
}, sigutil.SignWithRFC6979())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
req := new(v2container.SetExtendedACLRequest)
|
||||||
|
req.SetBody(reqBody)
|
||||||
|
|
||||||
meta := v2MetaHeaderFromOpts(callOptions)
|
meta := v2MetaHeaderFromOpts(callOptions)
|
||||||
meta.SetSessionToken(eacl.SessionToken().ToV2())
|
meta.SetSessionToken(eacl.SessionToken().ToV2())
|
||||||
|
|
||||||
req := new(v2container.SetExtendedACLRequest)
|
|
||||||
req.SetBody(reqBody)
|
|
||||||
req.SetMetaHeader(meta)
|
req.SetMetaHeader(meta)
|
||||||
|
|
||||||
err = v2signature.SignServiceMessage(callOptions.key, req)
|
err = v2signature.SignServiceMessage(callOptions.key, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := rpcapi.SetEACL(c.Raw(), req, client.WithContext(ctx))
|
resp, err := rpcapi.SetEACL(c.Raw(), req, client.WithContext(ctx))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("transport error: %w", err)
|
return nil, fmt.Errorf("transport error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle response meta info
|
var (
|
||||||
if err := c.handleResponseInfoV2(callOptions, resp); err != nil {
|
res = new(SetEACLRes)
|
||||||
return err
|
procPrm processResponseV2Prm
|
||||||
|
procRes processResponseV2Res
|
||||||
|
)
|
||||||
|
|
||||||
|
procPrm.callOpts = callOptions
|
||||||
|
procPrm.resp = resp
|
||||||
|
|
||||||
|
procRes.statusRes = res
|
||||||
|
|
||||||
|
// process response in general
|
||||||
|
if c.processResponseV2(&procRes, procPrm) {
|
||||||
|
if procRes.cliErr != nil {
|
||||||
|
return nil, procRes.cliErr
|
||||||
}
|
}
|
||||||
|
|
||||||
err = v2signature.VerifyServiceMessage(resp)
|
return res, nil
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("can't verify response message: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type AnnounceSpaceRes struct {
|
||||||
|
statusRes
|
||||||
}
|
}
|
||||||
|
|
||||||
// AnnounceContainerUsedSpace used by storage nodes to estimate their container
|
// AnnounceContainerUsedSpace used by storage nodes to estimate their container
|
||||||
|
@ -418,7 +557,8 @@ func (c *clientImpl) SetEACL(ctx context.Context, eacl *eacl.Table, opts ...Call
|
||||||
func (c *clientImpl) AnnounceContainerUsedSpace(
|
func (c *clientImpl) AnnounceContainerUsedSpace(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
announce []container.UsedSpaceAnnouncement,
|
announce []container.UsedSpaceAnnouncement,
|
||||||
opts ...CallOption) error {
|
opts ...CallOption,
|
||||||
|
) (*AnnounceSpaceRes, error) {
|
||||||
callOptions := c.defaultCallOptions() // apply all available options
|
callOptions := c.defaultCallOptions() // apply all available options
|
||||||
|
|
||||||
for i := range opts {
|
for i := range opts {
|
||||||
|
@ -442,23 +582,33 @@ func (c *clientImpl) AnnounceContainerUsedSpace(
|
||||||
// sign the request
|
// sign the request
|
||||||
err := v2signature.SignServiceMessage(callOptions.key, req)
|
err := v2signature.SignServiceMessage(callOptions.key, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := rpcapi.AnnounceUsedSpace(c.Raw(), req, client.WithContext(ctx))
|
resp, err := rpcapi.AnnounceUsedSpace(c.Raw(), req, client.WithContext(ctx))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("transport error: %w", err)
|
return nil, fmt.Errorf("transport error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle response meta info
|
var (
|
||||||
if err := c.handleResponseInfoV2(callOptions, resp); err != nil {
|
res = new(AnnounceSpaceRes)
|
||||||
return err
|
procPrm processResponseV2Prm
|
||||||
|
procRes processResponseV2Res
|
||||||
|
)
|
||||||
|
|
||||||
|
procPrm.callOpts = callOptions
|
||||||
|
procPrm.resp = resp
|
||||||
|
|
||||||
|
procRes.statusRes = res
|
||||||
|
|
||||||
|
// process response in general
|
||||||
|
if c.processResponseV2(&procRes, procPrm) {
|
||||||
|
if procRes.cliErr != nil {
|
||||||
|
return nil, procRes.cliErr
|
||||||
}
|
}
|
||||||
|
|
||||||
err = v2signature.VerifyServiceMessage(resp)
|
return res, nil
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("can't verify response message: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,10 +17,10 @@ type Netmap interface {
|
||||||
// EndpointInfo returns attributes, address and public key of the node, specified
|
// EndpointInfo returns attributes, address and public key of the node, specified
|
||||||
// in client constructor via address or open connection. This can be used as a
|
// in client constructor via address or open connection. This can be used as a
|
||||||
// health check to see if node is alive and responses to requests.
|
// health check to see if node is alive and responses to requests.
|
||||||
EndpointInfo(context.Context, ...CallOption) (*EndpointInfo, error)
|
EndpointInfo(context.Context, ...CallOption) (*EndpointInfoRes, error)
|
||||||
|
|
||||||
// NetworkInfo returns information about the NeoFS network of which the remote server is a part.
|
// NetworkInfo returns information about the NeoFS network of which the remote server is a part.
|
||||||
NetworkInfo(context.Context, ...CallOption) (*netmap.NetworkInfo, error)
|
NetworkInfo(context.Context, ...CallOption) (*NetworkInfoRes, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EACLWithSignature represents eACL table/signature pair.
|
// EACLWithSignature represents eACL table/signature pair.
|
||||||
|
@ -40,10 +40,24 @@ func (e *EndpointInfo) NodeInfo() *netmap.NodeInfo {
|
||||||
return e.ni
|
return e.ni
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type EndpointInfoRes struct {
|
||||||
|
statusRes
|
||||||
|
|
||||||
|
info *EndpointInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x EndpointInfoRes) Info() *EndpointInfo {
|
||||||
|
return x.info
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *EndpointInfoRes) setInfo(info *EndpointInfo) {
|
||||||
|
x.info = info
|
||||||
|
}
|
||||||
|
|
||||||
// EndpointInfo returns attributes, address and public key of the node, specified
|
// EndpointInfo returns attributes, address and public key of the node, specified
|
||||||
// in client constructor via address or open connection. This can be used as a
|
// in client constructor via address or open connection. This can be used as a
|
||||||
// health check to see if node is alive and responses to requests.
|
// health check to see if node is alive and responses to requests.
|
||||||
func (c *clientImpl) EndpointInfo(ctx context.Context, opts ...CallOption) (*EndpointInfo, error) {
|
func (c *clientImpl) EndpointInfo(ctx context.Context, opts ...CallOption) (*EndpointInfoRes, error) {
|
||||||
// apply all available options
|
// apply all available options
|
||||||
callOptions := c.defaultCallOptions()
|
callOptions := c.defaultCallOptions()
|
||||||
|
|
||||||
|
@ -67,26 +81,52 @@ func (c *clientImpl) EndpointInfo(ctx context.Context, opts ...CallOption) (*End
|
||||||
return nil, fmt.Errorf("transport error: %w", err)
|
return nil, fmt.Errorf("transport error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle response meta info
|
var (
|
||||||
if err := c.handleResponseInfoV2(callOptions, resp); err != nil {
|
res = new(EndpointInfoRes)
|
||||||
return nil, err
|
procPrm processResponseV2Prm
|
||||||
|
procRes processResponseV2Res
|
||||||
|
)
|
||||||
|
|
||||||
|
procPrm.callOpts = callOptions
|
||||||
|
procPrm.resp = resp
|
||||||
|
|
||||||
|
procRes.statusRes = res
|
||||||
|
|
||||||
|
// process response in general
|
||||||
|
if c.processResponseV2(&procRes, procPrm) {
|
||||||
|
if procRes.cliErr != nil {
|
||||||
|
return nil, procRes.cliErr
|
||||||
}
|
}
|
||||||
|
|
||||||
err = v2signature.VerifyServiceMessage(resp)
|
return res, nil
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("can't verify response message: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body := resp.GetBody()
|
body := resp.GetBody()
|
||||||
|
|
||||||
return &EndpointInfo{
|
res.setInfo(&EndpointInfo{
|
||||||
version: version.NewFromV2(body.GetVersion()),
|
version: version.NewFromV2(body.GetVersion()),
|
||||||
ni: netmap.NewNodeInfoFromV2(body.GetNodeInfo()),
|
ni: netmap.NewNodeInfoFromV2(body.GetNodeInfo()),
|
||||||
}, nil
|
})
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type NetworkInfoRes struct {
|
||||||
|
statusRes
|
||||||
|
|
||||||
|
info *netmap.NetworkInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x NetworkInfoRes) Info() *netmap.NetworkInfo {
|
||||||
|
return x.info
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *NetworkInfoRes) setInfo(info *netmap.NetworkInfo) {
|
||||||
|
x.info = info
|
||||||
}
|
}
|
||||||
|
|
||||||
// NetworkInfo returns information about the NeoFS network of which the remote server is a part.
|
// NetworkInfo returns information about the NeoFS network of which the remote server is a part.
|
||||||
func (c *clientImpl) NetworkInfo(ctx context.Context, opts ...CallOption) (*netmap.NetworkInfo, error) {
|
func (c *clientImpl) NetworkInfo(ctx context.Context, opts ...CallOption) (*NetworkInfoRes, error) {
|
||||||
// apply all available options
|
// apply all available options
|
||||||
callOptions := c.defaultCallOptions()
|
callOptions := c.defaultCallOptions()
|
||||||
|
|
||||||
|
@ -110,15 +150,27 @@ func (c *clientImpl) NetworkInfo(ctx context.Context, opts ...CallOption) (*netm
|
||||||
return nil, fmt.Errorf("v2 NetworkInfo RPC failure: %w", err)
|
return nil, fmt.Errorf("v2 NetworkInfo RPC failure: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle response meta info
|
var (
|
||||||
if err := c.handleResponseInfoV2(callOptions, resp); err != nil {
|
res = new(NetworkInfoRes)
|
||||||
return nil, err
|
procPrm processResponseV2Prm
|
||||||
|
procRes processResponseV2Res
|
||||||
|
)
|
||||||
|
|
||||||
|
procPrm.callOpts = callOptions
|
||||||
|
procPrm.resp = resp
|
||||||
|
|
||||||
|
procRes.statusRes = res
|
||||||
|
|
||||||
|
// process response in general
|
||||||
|
if c.processResponseV2(&procRes, procPrm) {
|
||||||
|
if procRes.cliErr != nil {
|
||||||
|
return nil, procRes.cliErr
|
||||||
}
|
}
|
||||||
|
|
||||||
err = v2signature.VerifyServiceMessage(resp)
|
return res, nil
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("response message verification failed: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return netmap.NewNetworkInfoFromV2(resp.GetBody().GetNetworkInfo()), nil
|
res.setInfo(netmap.NewNetworkInfoFromV2(resp.GetBody().GetNetworkInfo()))
|
||||||
|
|
||||||
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
487
client/object.go
487
client/object.go
|
@ -4,7 +4,6 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"crypto/sha256"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
@ -15,6 +14,7 @@ import (
|
||||||
"github.com/nspcc-dev/neofs-api-go/v2/rpc/client"
|
"github.com/nspcc-dev/neofs-api-go/v2/rpc/client"
|
||||||
v2session "github.com/nspcc-dev/neofs-api-go/v2/session"
|
v2session "github.com/nspcc-dev/neofs-api-go/v2/session"
|
||||||
"github.com/nspcc-dev/neofs-api-go/v2/signature"
|
"github.com/nspcc-dev/neofs-api-go/v2/signature"
|
||||||
|
apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status"
|
||||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/object"
|
"github.com/nspcc-dev/neofs-sdk-go/object"
|
||||||
signer "github.com/nspcc-dev/neofs-sdk-go/util/signature"
|
signer "github.com/nspcc-dev/neofs-sdk-go/util/signature"
|
||||||
|
@ -23,28 +23,25 @@ import (
|
||||||
// Object contains methods for working with objects.
|
// Object contains methods for working with objects.
|
||||||
type Object interface {
|
type Object interface {
|
||||||
// PutObject puts new object to NeoFS.
|
// PutObject puts new object to NeoFS.
|
||||||
PutObject(context.Context, *PutObjectParams, ...CallOption) (*object.ID, error)
|
PutObject(context.Context, *PutObjectParams, ...CallOption) (*ObjectPutRes, error)
|
||||||
|
|
||||||
// DeleteObject deletes object to NeoFS.
|
// DeleteObject deletes object to NeoFS.
|
||||||
DeleteObject(context.Context, *DeleteObjectParams, ...CallOption) error
|
DeleteObject(context.Context, *DeleteObjectParams, ...CallOption) (*ObjectDeleteRes, error)
|
||||||
|
|
||||||
// GetObject returns object stored in NeoFS.
|
// GetObject returns object stored in NeoFS.
|
||||||
GetObject(context.Context, *GetObjectParams, ...CallOption) (*object.Object, error)
|
GetObject(context.Context, *GetObjectParams, ...CallOption) (*ObjectGetRes, error)
|
||||||
|
|
||||||
// GetObjectHeader returns object header.
|
// HeadObject returns object header.
|
||||||
GetObjectHeader(context.Context, *ObjectHeaderParams, ...CallOption) (*object.Object, error)
|
HeadObject(context.Context, *ObjectHeaderParams, ...CallOption) (*ObjectHeadRes, error)
|
||||||
|
|
||||||
// ObjectPayloadRangeData returns range of object payload.
|
// ObjectPayloadRangeData returns range of object payload.
|
||||||
ObjectPayloadRangeData(context.Context, *RangeDataParams, ...CallOption) ([]byte, error)
|
ObjectPayloadRangeData(context.Context, *RangeDataParams, ...CallOption) (*ObjectRangeRes, error)
|
||||||
|
|
||||||
// ObjectPayloadRangeSHA256 returns sha-256 hashes of object sub-ranges from NeoFS.
|
// HashObjectPayloadRanges returns hashes of the object payload ranges from NeoFS.
|
||||||
ObjectPayloadRangeSHA256(context.Context, *RangeChecksumParams, ...CallOption) ([][sha256.Size]byte, error)
|
HashObjectPayloadRanges(context.Context, *RangeChecksumParams, ...CallOption) (*ObjectRangeHashRes, error)
|
||||||
|
|
||||||
// ObjectPayloadRangeTZ returns homomorphic hashes of object sub-ranges from NeoFS.
|
// SearchObjects searches for objects in NeoFS using provided parameters.
|
||||||
ObjectPayloadRangeTZ(context.Context, *RangeChecksumParams, ...CallOption) ([][TZSize]byte, error)
|
SearchObjects(context.Context, *SearchObjectParams, ...CallOption) (*ObjectSearchRes, error)
|
||||||
|
|
||||||
// SearchObject searches for objects in NeoFS using provided parameters.
|
|
||||||
SearchObject(context.Context, *SearchObjectParams, ...CallOption) ([]*object.ID, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type PutObjectParams struct {
|
type PutObjectParams struct {
|
||||||
|
@ -59,10 +56,6 @@ type ObjectAddressWriter interface {
|
||||||
SetAddress(*object.Address)
|
SetAddress(*object.Address)
|
||||||
}
|
}
|
||||||
|
|
||||||
type objectAddressWriter struct {
|
|
||||||
addr *object.Address
|
|
||||||
}
|
|
||||||
|
|
||||||
type DeleteObjectParams struct {
|
type DeleteObjectParams struct {
|
||||||
addr *object.Address
|
addr *object.Address
|
||||||
|
|
||||||
|
@ -98,7 +91,7 @@ type RangeDataParams struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type RangeChecksumParams struct {
|
type RangeChecksumParams struct {
|
||||||
typ checksumType
|
tz bool
|
||||||
|
|
||||||
addr *object.Address
|
addr *object.Address
|
||||||
|
|
||||||
|
@ -141,17 +134,11 @@ const TZSize = 64
|
||||||
|
|
||||||
const searchQueryVersion uint32 = 1
|
const searchQueryVersion uint32 = 1
|
||||||
|
|
||||||
var errNilObjectPart = errors.New("received nil object part")
|
|
||||||
|
|
||||||
func (w *objectAddressWriter) SetAddress(addr *object.Address) {
|
|
||||||
w.addr = addr
|
|
||||||
}
|
|
||||||
|
|
||||||
func rangesToV2(rs []*object.Range) []*v2object.Range {
|
func rangesToV2(rs []*object.Range) []*v2object.Range {
|
||||||
r2 := make([]*v2object.Range, len(rs))
|
r2 := make([]*v2object.Range, 0, len(rs))
|
||||||
|
|
||||||
for i := range rs {
|
for i := range rs {
|
||||||
r2[i] = rs[i].ToV2()
|
r2 = append(r2, rs[i].ToV2())
|
||||||
}
|
}
|
||||||
|
|
||||||
return r2
|
return r2
|
||||||
|
@ -220,7 +207,21 @@ func (p *PutObjectParams) PayloadReader() io.Reader {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *clientImpl) PutObject(ctx context.Context, p *PutObjectParams, opts ...CallOption) (*object.ID, error) {
|
type ObjectPutRes struct {
|
||||||
|
statusRes
|
||||||
|
|
||||||
|
id *object.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ObjectPutRes) setID(id *object.ID) {
|
||||||
|
x.id = id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x ObjectPutRes) ID() *object.ID {
|
||||||
|
return x.id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *clientImpl) PutObject(ctx context.Context, p *PutObjectParams, opts ...CallOption) (*ObjectPutRes, error) {
|
||||||
callOpts := c.defaultCallOptions()
|
callOpts := c.defaultCallOptions()
|
||||||
|
|
||||||
for i := range opts {
|
for i := range opts {
|
||||||
|
@ -313,20 +314,32 @@ func (c *clientImpl) PutObject(ctx context.Context, p *PutObjectParams, opts ...
|
||||||
return nil, fmt.Errorf("closing the stream failed: %w", err)
|
return nil, fmt.Errorf("closing the stream failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle response meta info
|
var (
|
||||||
if err := c.handleResponseInfoV2(callOpts, resp); err != nil {
|
res = new(ObjectPutRes)
|
||||||
return nil, err
|
procPrm processResponseV2Prm
|
||||||
|
procRes processResponseV2Res
|
||||||
|
)
|
||||||
|
|
||||||
|
procPrm.callOpts = callOpts
|
||||||
|
procPrm.resp = resp
|
||||||
|
|
||||||
|
procRes.statusRes = res
|
||||||
|
|
||||||
|
// process response in general
|
||||||
|
if c.processResponseV2(&procRes, procPrm) {
|
||||||
|
if procRes.cliErr != nil {
|
||||||
|
return nil, procRes.cliErr
|
||||||
}
|
}
|
||||||
|
|
||||||
// verify response structure
|
return res, nil
|
||||||
if err := signature.VerifyServiceMessage(resp); err != nil {
|
|
||||||
return nil, fmt.Errorf("response verification failed: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// convert object identifier
|
// convert object identifier
|
||||||
id := object.NewIDFromV2(resp.GetBody().GetObjectID())
|
id := object.NewIDFromV2(resp.GetBody().GetObjectID())
|
||||||
|
|
||||||
return id, nil
|
res.setID(id)
|
||||||
|
|
||||||
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *DeleteObjectParams) WithAddress(v *object.Address) *DeleteObjectParams {
|
func (p *DeleteObjectParams) WithAddress(v *object.Address) *DeleteObjectParams {
|
||||||
|
@ -363,24 +376,24 @@ func (p *DeleteObjectParams) TombstoneAddressTarget() ObjectAddressWriter {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteObject is a wrapper over Client.DeleteObject method
|
type ObjectDeleteRes struct {
|
||||||
// that provides the ability to receive tombstone address
|
statusRes
|
||||||
// without setting a target in the parameters.
|
|
||||||
func DeleteObject(ctx context.Context, c Client, p *DeleteObjectParams, opts ...CallOption) (*object.Address, error) {
|
|
||||||
w := new(objectAddressWriter)
|
|
||||||
|
|
||||||
err := c.DeleteObject(ctx, p.WithTombstoneAddressTarget(w), opts...)
|
tombAddr *object.Address
|
||||||
if err != nil {
|
}
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return w.addr, nil
|
func (x ObjectDeleteRes) TombstoneAddress() *object.Address {
|
||||||
|
return x.tombAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ObjectDeleteRes) setTombstoneAddress(addr *object.Address) {
|
||||||
|
x.tombAddr = addr
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteObject removes object by address.
|
// DeleteObject removes object by address.
|
||||||
//
|
//
|
||||||
// If target of tombstone address is not set, the address is ignored.
|
// If target of tombstone address is not set, the address is ignored.
|
||||||
func (c *clientImpl) DeleteObject(ctx context.Context, p *DeleteObjectParams, opts ...CallOption) error {
|
func (c *clientImpl) DeleteObject(ctx context.Context, p *DeleteObjectParams, opts ...CallOption) (*ObjectDeleteRes, error) {
|
||||||
callOpts := c.defaultCallOptions()
|
callOpts := c.defaultCallOptions()
|
||||||
|
|
||||||
for i := range opts {
|
for i := range opts {
|
||||||
|
@ -403,7 +416,7 @@ func (c *clientImpl) DeleteObject(ctx context.Context, p *DeleteObjectParams, op
|
||||||
addr: p.addr.ToV2(),
|
addr: p.addr.ToV2(),
|
||||||
verb: v2session.ObjectVerbDelete,
|
verb: v2session.ObjectVerbDelete,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return fmt.Errorf("could not attach session token: %w", err)
|
return nil, fmt.Errorf("could not attach session token: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
req.SetMetaHeader(meta)
|
req.SetMetaHeader(meta)
|
||||||
|
@ -413,30 +426,40 @@ func (c *clientImpl) DeleteObject(ctx context.Context, p *DeleteObjectParams, op
|
||||||
|
|
||||||
// sign the request
|
// sign the request
|
||||||
if err := signature.SignServiceMessage(callOpts.key, req); err != nil {
|
if err := signature.SignServiceMessage(callOpts.key, req); err != nil {
|
||||||
return fmt.Errorf("signing the request failed: %w", err)
|
return nil, fmt.Errorf("signing the request failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// send request
|
// send request
|
||||||
resp, err := rpcapi.DeleteObject(c.Raw(), req, client.WithContext(ctx))
|
resp, err := rpcapi.DeleteObject(c.Raw(), req, client.WithContext(ctx))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("sending the request failed: %w", err)
|
return nil, fmt.Errorf("sending the request failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle response meta info
|
var (
|
||||||
if err := c.handleResponseInfoV2(callOpts, resp); err != nil {
|
res = new(ObjectDeleteRes)
|
||||||
return err
|
procPrm processResponseV2Prm
|
||||||
|
procRes processResponseV2Res
|
||||||
|
)
|
||||||
|
|
||||||
|
procPrm.callOpts = callOpts
|
||||||
|
procPrm.resp = resp
|
||||||
|
|
||||||
|
procRes.statusRes = res
|
||||||
|
|
||||||
|
// process response in general
|
||||||
|
if c.processResponseV2(&procRes, procPrm) {
|
||||||
|
if procRes.cliErr != nil {
|
||||||
|
return nil, procRes.cliErr
|
||||||
}
|
}
|
||||||
|
|
||||||
// verify response structure
|
return res, nil
|
||||||
if err := signature.VerifyServiceMessage(resp); err != nil {
|
|
||||||
return fmt.Errorf("response verification failed: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.tombTgt != nil {
|
addrv2 := resp.GetBody().GetTombstone()
|
||||||
p.tombTgt.SetAddress(object.NewAddressFromV2(resp.GetBody().GetTombstone()))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
res.setTombstoneAddress(object.NewAddressFromV2(addrv2))
|
||||||
|
|
||||||
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *GetObjectParams) WithAddress(v *object.Address) *GetObjectParams {
|
func (p *GetObjectParams) WithAddress(v *object.Address) *GetObjectParams {
|
||||||
|
@ -567,7 +590,32 @@ func (x *objectPayloadReader) Read(p []byte) (read int, err error) {
|
||||||
|
|
||||||
var errWrongMessageSeq = errors.New("incorrect message sequence")
|
var errWrongMessageSeq = errors.New("incorrect message sequence")
|
||||||
|
|
||||||
func (c *clientImpl) GetObject(ctx context.Context, p *GetObjectParams, opts ...CallOption) (*object.Object, error) {
|
type ObjectGetRes struct {
|
||||||
|
statusRes
|
||||||
|
objectRes
|
||||||
|
}
|
||||||
|
|
||||||
|
type objectRes struct {
|
||||||
|
obj *object.Object
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *objectRes) setObject(obj *object.Object) {
|
||||||
|
x.obj = obj
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x objectRes) Object() *object.Object {
|
||||||
|
return x.obj
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeUnexpectedMessageTypeErr(res resCommon, val interface{}) {
|
||||||
|
var st apistatus.ServerInternal // specific API status should be used
|
||||||
|
|
||||||
|
apistatus.WriteInternalServerErr(&st, fmt.Errorf("unexpected message type %T", val))
|
||||||
|
|
||||||
|
res.setStatus(st)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *clientImpl) GetObject(ctx context.Context, p *GetObjectParams, opts ...CallOption) (*ObjectGetRes, error) {
|
||||||
callOpts := c.defaultCallOptions()
|
callOpts := c.defaultCallOptions()
|
||||||
|
|
||||||
for i := range opts {
|
for i := range opts {
|
||||||
|
@ -615,16 +663,27 @@ func (c *clientImpl) GetObject(ctx context.Context, p *GetObjectParams, opts ...
|
||||||
payload []byte
|
payload []byte
|
||||||
obj = new(v2object.Object)
|
obj = new(v2object.Object)
|
||||||
resp = new(v2object.GetResponse)
|
resp = new(v2object.GetResponse)
|
||||||
|
|
||||||
|
messageWas bool
|
||||||
|
|
||||||
|
res = new(ObjectGetRes)
|
||||||
|
procPrm processResponseV2Prm
|
||||||
|
procRes processResponseV2Res
|
||||||
)
|
)
|
||||||
|
|
||||||
|
procPrm.callOpts = callOpts
|
||||||
|
procPrm.resp = resp
|
||||||
|
|
||||||
|
procRes.statusRes = res
|
||||||
|
|
||||||
loop:
|
loop:
|
||||||
for {
|
for {
|
||||||
// receive message from server stream
|
// receive message from server stream
|
||||||
err := stream.Read(resp)
|
err := stream.Read(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, io.EOF) {
|
if errors.Is(err, io.EOF) {
|
||||||
if !headWas {
|
if !messageWas {
|
||||||
return nil, io.ErrUnexpectedEOF
|
return nil, errWrongMessageSeq
|
||||||
}
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
|
@ -633,19 +692,20 @@ loop:
|
||||||
return nil, fmt.Errorf("reading the response failed: %w", err)
|
return nil, fmt.Errorf("reading the response failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle response meta info
|
messageWas = true
|
||||||
if err := c.handleResponseInfoV2(callOpts, resp); err != nil {
|
|
||||||
return nil, err
|
// process response in general
|
||||||
|
if c.processResponseV2(&procRes, procPrm) {
|
||||||
|
if procRes.cliErr != nil {
|
||||||
|
return nil, procRes.cliErr
|
||||||
}
|
}
|
||||||
|
|
||||||
// verify response structure
|
return res, nil
|
||||||
if err := signature.VerifyServiceMessage(resp); err != nil {
|
|
||||||
return nil, fmt.Errorf("response verification failed: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch v := resp.GetBody().GetObjectPart().(type) {
|
switch v := resp.GetBody().GetObjectPart().(type) {
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unexpected object part %T", v)
|
return nil, errWrongMessageSeq
|
||||||
case *v2object.GetObjectPartInit:
|
case *v2object.GetObjectPartInit:
|
||||||
if headWas {
|
if headWas {
|
||||||
return nil, errWrongMessageSeq
|
return nil, errWrongMessageSeq
|
||||||
|
@ -683,6 +743,10 @@ loop:
|
||||||
payload = append(payload, v.GetChunk()...)
|
payload = append(payload, v.GetChunk()...)
|
||||||
}
|
}
|
||||||
case *v2object.SplitInfo:
|
case *v2object.SplitInfo:
|
||||||
|
if headWas {
|
||||||
|
return nil, errWrongMessageSeq
|
||||||
|
}
|
||||||
|
|
||||||
si := object.NewSplitInfoFromV2(v)
|
si := object.NewSplitInfoFromV2(v)
|
||||||
return nil, object.NewSplitInfoError(si)
|
return nil, object.NewSplitInfoError(si)
|
||||||
}
|
}
|
||||||
|
@ -691,7 +755,9 @@ loop:
|
||||||
obj.SetPayload(payload)
|
obj.SetPayload(payload)
|
||||||
|
|
||||||
// convert the object
|
// convert the object
|
||||||
return object.NewFromV2(obj), nil
|
res.setObject(object.NewFromV2(obj))
|
||||||
|
|
||||||
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ObjectHeaderParams) WithAddress(v *object.Address) *ObjectHeaderParams {
|
func (p *ObjectHeaderParams) WithAddress(v *object.Address) *ObjectHeaderParams {
|
||||||
|
@ -752,7 +818,12 @@ func (p *ObjectHeaderParams) RawFlag() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *clientImpl) GetObjectHeader(ctx context.Context, p *ObjectHeaderParams, opts ...CallOption) (*object.Object, error) {
|
type ObjectHeadRes struct {
|
||||||
|
statusRes
|
||||||
|
objectRes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *clientImpl) HeadObject(ctx context.Context, p *ObjectHeaderParams, opts ...CallOption) (*ObjectHeadRes, error) {
|
||||||
callOpts := c.defaultCallOptions()
|
callOpts := c.defaultCallOptions()
|
||||||
|
|
||||||
for i := range opts {
|
for i := range opts {
|
||||||
|
@ -796,14 +867,24 @@ func (c *clientImpl) GetObjectHeader(ctx context.Context, p *ObjectHeaderParams,
|
||||||
return nil, fmt.Errorf("sending the request failed: %w", err)
|
return nil, fmt.Errorf("sending the request failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle response meta info
|
var (
|
||||||
if err := c.handleResponseInfoV2(callOpts, resp); err != nil {
|
res = new(ObjectHeadRes)
|
||||||
return nil, err
|
procPrm processResponseV2Prm
|
||||||
|
procRes processResponseV2Res
|
||||||
|
)
|
||||||
|
|
||||||
|
procPrm.callOpts = callOpts
|
||||||
|
procPrm.resp = resp
|
||||||
|
|
||||||
|
procRes.statusRes = res
|
||||||
|
|
||||||
|
// process response in general
|
||||||
|
if c.processResponseV2(&procRes, procPrm) {
|
||||||
|
if procRes.cliErr != nil {
|
||||||
|
return nil, procRes.cliErr
|
||||||
}
|
}
|
||||||
|
|
||||||
// verify response structure
|
return res, nil
|
||||||
if err := signature.VerifyServiceMessage(resp); err != nil {
|
|
||||||
return nil, fmt.Errorf("response verification failed: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -813,12 +894,12 @@ func (c *clientImpl) GetObjectHeader(ctx context.Context, p *ObjectHeaderParams,
|
||||||
|
|
||||||
switch v := resp.GetBody().GetHeaderPart().(type) {
|
switch v := resp.GetBody().GetHeaderPart().(type) {
|
||||||
case nil:
|
case nil:
|
||||||
return nil, fmt.Errorf("unexpected header type %T", v)
|
writeUnexpectedMessageTypeErr(res, v)
|
||||||
|
return res, nil
|
||||||
case *v2object.ShortHeader:
|
case *v2object.ShortHeader:
|
||||||
if !p.short {
|
if !p.short {
|
||||||
return nil, fmt.Errorf("wrong header part type: expected %T, received %T",
|
writeUnexpectedMessageTypeErr(res, v)
|
||||||
(*v2object.ShortHeader)(nil), (*v2object.HeaderWithSignature)(nil),
|
return res, nil
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
h := v
|
h := v
|
||||||
|
@ -833,29 +914,12 @@ func (c *clientImpl) GetObjectHeader(ctx context.Context, p *ObjectHeaderParams,
|
||||||
hdr.SetHomomorphicHash(h.GetHomomorphicHash())
|
hdr.SetHomomorphicHash(h.GetHomomorphicHash())
|
||||||
case *v2object.HeaderWithSignature:
|
case *v2object.HeaderWithSignature:
|
||||||
if p.short {
|
if p.short {
|
||||||
return nil, fmt.Errorf("wrong header part type: expected %T, received %T",
|
writeUnexpectedMessageTypeErr(res, v)
|
||||||
(*v2object.HeaderWithSignature)(nil), (*v2object.ShortHeader)(nil),
|
return res, nil
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hdrWithSig := v
|
hdr = v.GetHeader()
|
||||||
if hdrWithSig == nil {
|
idSig = v.GetSignature()
|
||||||
return nil, errNilObjectPart
|
|
||||||
}
|
|
||||||
|
|
||||||
hdr = hdrWithSig.GetHeader()
|
|
||||||
idSig = hdrWithSig.GetSignature()
|
|
||||||
|
|
||||||
if err := signer.VerifyDataWithSource(
|
|
||||||
signature.StableMarshalerWrapper{
|
|
||||||
SM: p.addr.ObjectID().ToV2(),
|
|
||||||
},
|
|
||||||
func() (key, sig []byte) {
|
|
||||||
return idSig.GetKey(), idSig.GetSign()
|
|
||||||
},
|
|
||||||
); err != nil {
|
|
||||||
return nil, fmt.Errorf("incorrect object header signature: %w", err)
|
|
||||||
}
|
|
||||||
case *v2object.SplitInfo:
|
case *v2object.SplitInfo:
|
||||||
si := object.NewSplitInfoFromV2(v)
|
si := object.NewSplitInfoFromV2(v)
|
||||||
|
|
||||||
|
@ -869,8 +933,9 @@ func (c *clientImpl) GetObjectHeader(ctx context.Context, p *ObjectHeaderParams,
|
||||||
raw := object.NewRawFromV2(obj)
|
raw := object.NewRawFromV2(obj)
|
||||||
raw.SetID(p.addr.ObjectID())
|
raw.SetID(p.addr.ObjectID())
|
||||||
|
|
||||||
// convert the object
|
res.setObject(raw.Object())
|
||||||
return raw.Object(), nil
|
|
||||||
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *RangeDataParams) WithAddress(v *object.Address) *RangeDataParams {
|
func (p *RangeDataParams) WithAddress(v *object.Address) *RangeDataParams {
|
||||||
|
@ -937,7 +1002,21 @@ func (p *RangeDataParams) DataWriter() io.Writer {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *clientImpl) ObjectPayloadRangeData(ctx context.Context, p *RangeDataParams, opts ...CallOption) ([]byte, error) {
|
type ObjectRangeRes struct {
|
||||||
|
statusRes
|
||||||
|
|
||||||
|
data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ObjectRangeRes) setData(data []byte) {
|
||||||
|
x.data = data
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x ObjectRangeRes) Data() []byte {
|
||||||
|
return x.data
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *clientImpl) ObjectPayloadRangeData(ctx context.Context, p *RangeDataParams, opts ...CallOption) (*ObjectRangeRes, error) {
|
||||||
callOpts := c.defaultCallOptions()
|
callOpts := c.defaultCallOptions()
|
||||||
|
|
||||||
for i := range opts {
|
for i := range opts {
|
||||||
|
@ -986,33 +1065,54 @@ func (c *clientImpl) ObjectPayloadRangeData(ctx context.Context, p *RangeDataPar
|
||||||
payload = make([]byte, 0, p.r.GetLength())
|
payload = make([]byte, 0, p.r.GetLength())
|
||||||
}
|
}
|
||||||
|
|
||||||
resp := new(v2object.GetRangeResponse)
|
var (
|
||||||
|
resp = new(v2object.GetRangeResponse)
|
||||||
|
|
||||||
|
chunkWas, messageWas bool
|
||||||
|
|
||||||
|
res = new(ObjectRangeRes)
|
||||||
|
procPrm processResponseV2Prm
|
||||||
|
procRes processResponseV2Res
|
||||||
|
)
|
||||||
|
|
||||||
|
procPrm.callOpts = callOpts
|
||||||
|
procPrm.resp = resp
|
||||||
|
|
||||||
|
procRes.statusRes = res
|
||||||
|
|
||||||
for {
|
for {
|
||||||
// receive message from server stream
|
// receive message from server stream
|
||||||
err := stream.Read(resp)
|
err := stream.Read(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, io.EOF) {
|
if errors.Is(err, io.EOF) {
|
||||||
|
if !messageWas {
|
||||||
|
return nil, errWrongMessageSeq
|
||||||
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("reading the response failed: %w", err)
|
return nil, fmt.Errorf("reading the response failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle response meta info
|
messageWas = true
|
||||||
if err := c.handleResponseInfoV2(callOpts, resp); err != nil {
|
|
||||||
return nil, err
|
// process response in general
|
||||||
|
if c.processResponseV2(&procRes, procPrm) {
|
||||||
|
if procRes.cliErr != nil {
|
||||||
|
return nil, procRes.cliErr
|
||||||
}
|
}
|
||||||
|
|
||||||
// verify response structure
|
return res, nil
|
||||||
if err := signature.VerifyServiceMessage(resp); err != nil {
|
|
||||||
return nil, fmt.Errorf("could not verify %T: %w", resp, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch v := resp.GetBody().GetRangePart().(type) {
|
switch v := resp.GetBody().GetRangePart().(type) {
|
||||||
case nil:
|
case nil:
|
||||||
return nil, fmt.Errorf("unexpected range type %T", v)
|
writeUnexpectedMessageTypeErr(res, v)
|
||||||
|
return res, nil
|
||||||
case *v2object.GetRangePartChunk:
|
case *v2object.GetRangePartChunk:
|
||||||
|
chunkWas = true
|
||||||
|
|
||||||
if p.w != nil {
|
if p.w != nil {
|
||||||
if _, err = p.w.Write(v.GetChunk()); err != nil {
|
if _, err = p.w.Write(v.GetChunk()); err != nil {
|
||||||
return nil, fmt.Errorf("could not write payload chunk: %w", err)
|
return nil, fmt.Errorf("could not write payload chunk: %w", err)
|
||||||
|
@ -1021,13 +1121,19 @@ func (c *clientImpl) ObjectPayloadRangeData(ctx context.Context, p *RangeDataPar
|
||||||
payload = append(payload, v.GetChunk()...)
|
payload = append(payload, v.GetChunk()...)
|
||||||
}
|
}
|
||||||
case *v2object.SplitInfo:
|
case *v2object.SplitInfo:
|
||||||
|
if chunkWas {
|
||||||
|
return nil, errWrongMessageSeq
|
||||||
|
}
|
||||||
|
|
||||||
si := object.NewSplitInfoFromV2(v)
|
si := object.NewSplitInfoFromV2(v)
|
||||||
|
|
||||||
return nil, object.NewSplitInfoError(si)
|
return nil, object.NewSplitInfoError(si)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return payload, nil
|
res.setData(payload)
|
||||||
|
|
||||||
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *RangeChecksumParams) WithAddress(v *object.Address) *RangeChecksumParams {
|
func (p *RangeChecksumParams) WithAddress(v *object.Address) *RangeChecksumParams {
|
||||||
|
@ -1078,33 +1184,26 @@ func (p *RangeChecksumParams) Salt() []byte {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *RangeChecksumParams) withChecksumType(t checksumType) *RangeChecksumParams {
|
func (p *RangeChecksumParams) TZ() *RangeChecksumParams {
|
||||||
if p != nil {
|
p.tz = true
|
||||||
p.typ = t
|
|
||||||
}
|
|
||||||
|
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *clientImpl) ObjectPayloadRangeSHA256(ctx context.Context, p *RangeChecksumParams, opts ...CallOption) ([][sha256.Size]byte, error) {
|
type ObjectRangeHashRes struct {
|
||||||
res, err := c.objectPayloadRangeHash(ctx, p.withChecksumType(checksumSHA256), opts...)
|
statusRes
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.([][sha256.Size]byte), nil
|
hashes [][]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *clientImpl) ObjectPayloadRangeTZ(ctx context.Context, p *RangeChecksumParams, opts ...CallOption) ([][TZSize]byte, error) {
|
func (x *ObjectRangeHashRes) setHashes(v [][]byte) {
|
||||||
res, err := c.objectPayloadRangeHash(ctx, p.withChecksumType(checksumTZ), opts...)
|
x.hashes = v
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.([][TZSize]byte), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *clientImpl) objectPayloadRangeHash(ctx context.Context, p *RangeChecksumParams, opts ...CallOption) (interface{}, error) {
|
func (x ObjectRangeHashRes) Hashes() [][]byte {
|
||||||
|
return x.hashes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *clientImpl) HashObjectPayloadRanges(ctx context.Context, p *RangeChecksumParams, opts ...CallOption) (*ObjectRangeHashRes, error) {
|
||||||
callOpts := c.defaultCallOptions()
|
callOpts := c.defaultCallOptions()
|
||||||
|
|
||||||
for i := range opts {
|
for i := range opts {
|
||||||
|
@ -1136,7 +1235,12 @@ func (c *clientImpl) objectPayloadRangeHash(ctx context.Context, p *RangeChecksu
|
||||||
body.SetAddress(p.addr.ToV2())
|
body.SetAddress(p.addr.ToV2())
|
||||||
body.SetSalt(p.salt)
|
body.SetSalt(p.salt)
|
||||||
|
|
||||||
typV2 := p.typ.toV2()
|
typ := checksumSHA256
|
||||||
|
if p.tz {
|
||||||
|
typ = checksumTZ
|
||||||
|
}
|
||||||
|
|
||||||
|
typV2 := typ.toV2()
|
||||||
body.SetType(typV2)
|
body.SetType(typV2)
|
||||||
|
|
||||||
rsV2 := rangesToV2(p.rs)
|
rsV2 := rangesToV2(p.rs)
|
||||||
|
@ -1153,60 +1257,27 @@ func (c *clientImpl) objectPayloadRangeHash(ctx context.Context, p *RangeChecksu
|
||||||
return nil, fmt.Errorf("sending the request failed: %w", err)
|
return nil, fmt.Errorf("sending the request failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle response meta info
|
var (
|
||||||
if err := c.handleResponseInfoV2(callOpts, resp); err != nil {
|
res = new(ObjectRangeHashRes)
|
||||||
return nil, err
|
procPrm processResponseV2Prm
|
||||||
|
procRes processResponseV2Res
|
||||||
|
)
|
||||||
|
|
||||||
|
procPrm.callOpts = callOpts
|
||||||
|
procPrm.resp = resp
|
||||||
|
|
||||||
|
procRes.statusRes = res
|
||||||
|
|
||||||
|
// process response in general
|
||||||
|
if c.processResponseV2(&procRes, procPrm) {
|
||||||
|
if procRes.cliErr != nil {
|
||||||
|
return nil, procRes.cliErr
|
||||||
}
|
}
|
||||||
|
|
||||||
// verify response structure
|
return res, nil
|
||||||
if err := signature.VerifyServiceMessage(resp); err != nil {
|
|
||||||
return nil, fmt.Errorf("response verification failed: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
respBody := resp.GetBody()
|
res.setHashes(resp.GetBody().GetHashList())
|
||||||
respType := respBody.GetType()
|
|
||||||
respHashes := respBody.GetHashList()
|
|
||||||
|
|
||||||
if t := p.typ.toV2(); respType != t {
|
|
||||||
return nil, fmt.Errorf("invalid checksum type: expected %v, received %v", t, respType)
|
|
||||||
} else if reqLn, respLn := len(rsV2), len(respHashes); reqLn != respLn {
|
|
||||||
return nil, fmt.Errorf("wrong checksum number: expected %d, received %d", reqLn, respLn)
|
|
||||||
}
|
|
||||||
|
|
||||||
var res interface{}
|
|
||||||
|
|
||||||
switch p.typ {
|
|
||||||
case checksumSHA256:
|
|
||||||
r := make([][sha256.Size]byte, 0, len(respHashes))
|
|
||||||
|
|
||||||
for i := range respHashes {
|
|
||||||
if ln := len(respHashes[i]); ln != sha256.Size {
|
|
||||||
return nil, fmt.Errorf("invalid checksum length: expected %d, received %d", sha256.Size, ln)
|
|
||||||
}
|
|
||||||
|
|
||||||
cs := [sha256.Size]byte{}
|
|
||||||
copy(cs[:], respHashes[i])
|
|
||||||
|
|
||||||
r = append(r, cs)
|
|
||||||
}
|
|
||||||
|
|
||||||
res = r
|
|
||||||
case checksumTZ:
|
|
||||||
r := make([][TZSize]byte, 0, len(respHashes))
|
|
||||||
|
|
||||||
for i := range respHashes {
|
|
||||||
if ln := len(respHashes[i]); ln != TZSize {
|
|
||||||
return nil, fmt.Errorf("invalid checksum length: expected %d, received %d", TZSize, ln)
|
|
||||||
}
|
|
||||||
|
|
||||||
cs := [TZSize]byte{}
|
|
||||||
copy(cs[:], respHashes[i])
|
|
||||||
|
|
||||||
r = append(r, cs)
|
|
||||||
}
|
|
||||||
|
|
||||||
res = r
|
|
||||||
}
|
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
@ -1243,7 +1314,21 @@ func (p *SearchObjectParams) SearchFilters() object.SearchFilters {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *clientImpl) SearchObject(ctx context.Context, p *SearchObjectParams, opts ...CallOption) ([]*object.ID, error) {
|
type ObjectSearchRes struct {
|
||||||
|
statusRes
|
||||||
|
|
||||||
|
ids []*object.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ObjectSearchRes) setIDList(v []*object.ID) {
|
||||||
|
x.ids = v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x ObjectSearchRes) IDList() []*object.ID {
|
||||||
|
return x.ids
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *clientImpl) SearchObjects(ctx context.Context, p *SearchObjectParams, opts ...CallOption) (*ObjectSearchRes, error) {
|
||||||
callOpts := c.defaultCallOptions()
|
callOpts := c.defaultCallOptions()
|
||||||
|
|
||||||
for i := range opts {
|
for i := range opts {
|
||||||
|
@ -1293,27 +1378,43 @@ func (c *clientImpl) SearchObject(ctx context.Context, p *SearchObjectParams, op
|
||||||
var (
|
var (
|
||||||
searchResult []*object.ID
|
searchResult []*object.ID
|
||||||
resp = new(v2object.SearchResponse)
|
resp = new(v2object.SearchResponse)
|
||||||
|
|
||||||
|
messageWas bool
|
||||||
|
|
||||||
|
res = new(ObjectSearchRes)
|
||||||
|
procPrm processResponseV2Prm
|
||||||
|
procRes processResponseV2Res
|
||||||
)
|
)
|
||||||
|
|
||||||
|
procPrm.callOpts = callOpts
|
||||||
|
procPrm.resp = resp
|
||||||
|
|
||||||
|
procRes.statusRes = res
|
||||||
|
|
||||||
for {
|
for {
|
||||||
// receive message from server stream
|
// receive message from server stream
|
||||||
err := stream.Read(resp)
|
err := stream.Read(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, io.EOF) {
|
if errors.Is(err, io.EOF) {
|
||||||
|
if !messageWas {
|
||||||
|
return nil, errWrongMessageSeq
|
||||||
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("reading the response failed: %w", err)
|
return nil, fmt.Errorf("reading the response failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle response meta info
|
messageWas = true
|
||||||
if err := c.handleResponseInfoV2(callOpts, resp); err != nil {
|
|
||||||
return nil, err
|
// process response in general
|
||||||
|
if c.processResponseV2(&procRes, procPrm) {
|
||||||
|
if procRes.cliErr != nil {
|
||||||
|
return nil, procRes.cliErr
|
||||||
}
|
}
|
||||||
|
|
||||||
// verify response structure
|
return res, nil
|
||||||
if err := signature.VerifyServiceMessage(resp); err != nil {
|
|
||||||
return nil, fmt.Errorf("could not verify %T: %w", resp, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
chunk := resp.GetBody().GetIDList()
|
chunk := resp.GetBody().GetIDList()
|
||||||
|
@ -1322,7 +1423,9 @@ func (c *clientImpl) SearchObject(ctx context.Context, p *SearchObjectParams, op
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return searchResult, nil
|
res.setIDList(searchResult)
|
||||||
|
|
||||||
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *clientImpl) attachV2SessionToken(opts *callOptions, hdr *v2session.RequestMetaHeader, info v2SessionReqInfo) error {
|
func (c *clientImpl) attachV2SessionToken(opts *callOptions, hdr *v2session.RequestMetaHeader, info v2SessionReqInfo) error {
|
||||||
|
|
|
@ -2,7 +2,6 @@ package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
v2reputation "github.com/nspcc-dev/neofs-api-go/v2/reputation"
|
v2reputation "github.com/nspcc-dev/neofs-api-go/v2/reputation"
|
||||||
rpcapi "github.com/nspcc-dev/neofs-api-go/v2/rpc"
|
rpcapi "github.com/nspcc-dev/neofs-api-go/v2/rpc"
|
||||||
|
@ -49,7 +48,9 @@ func (x *AnnounceLocalTrustPrm) SetTrusts(trusts []*reputation.Trust) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// AnnounceLocalTrustRes groups results of AnnounceLocalTrust operation.
|
// AnnounceLocalTrustRes groups results of AnnounceLocalTrust operation.
|
||||||
type AnnounceLocalTrustRes struct{}
|
type AnnounceLocalTrustRes struct {
|
||||||
|
statusRes
|
||||||
|
}
|
||||||
|
|
||||||
func (c *clientImpl) AnnounceLocalTrust(ctx context.Context, prm AnnounceLocalTrustPrm, opts ...CallOption) (*AnnounceLocalTrustRes, error) {
|
func (c *clientImpl) AnnounceLocalTrust(ctx context.Context, prm AnnounceLocalTrustPrm, opts ...CallOption) (*AnnounceLocalTrustRes, error) {
|
||||||
// apply all available options
|
// apply all available options
|
||||||
|
@ -77,17 +78,27 @@ func (c *clientImpl) AnnounceLocalTrust(ctx context.Context, prm AnnounceLocalTr
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle response meta info
|
var (
|
||||||
if err := c.handleResponseInfoV2(callOptions, resp); err != nil {
|
res = new(AnnounceLocalTrustRes)
|
||||||
return nil, err
|
procPrm processResponseV2Prm
|
||||||
|
procRes processResponseV2Res
|
||||||
|
)
|
||||||
|
|
||||||
|
procPrm.callOpts = callOptions
|
||||||
|
procPrm.resp = resp
|
||||||
|
|
||||||
|
procRes.statusRes = res
|
||||||
|
|
||||||
|
// process response in general
|
||||||
|
if c.processResponseV2(&procRes, procPrm) {
|
||||||
|
if procRes.cliErr != nil {
|
||||||
|
return nil, procRes.cliErr
|
||||||
}
|
}
|
||||||
|
|
||||||
err = v2signature.VerifyServiceMessage(resp)
|
return res, nil
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("can't verify response message: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new(AnnounceLocalTrustRes), nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AnnounceIntermediateTrustPrm groups parameters of AnnounceIntermediateTrust operation.
|
// AnnounceIntermediateTrustPrm groups parameters of AnnounceIntermediateTrust operation.
|
||||||
|
@ -128,7 +139,9 @@ func (x *AnnounceIntermediateTrustPrm) SetTrust(trust *reputation.PeerToPeerTrus
|
||||||
}
|
}
|
||||||
|
|
||||||
// AnnounceIntermediateTrustRes groups results of AnnounceIntermediateTrust operation.
|
// AnnounceIntermediateTrustRes groups results of AnnounceIntermediateTrust operation.
|
||||||
type AnnounceIntermediateTrustRes struct{}
|
type AnnounceIntermediateTrustRes struct {
|
||||||
|
statusRes
|
||||||
|
}
|
||||||
|
|
||||||
func (c *clientImpl) AnnounceIntermediateTrust(ctx context.Context, prm AnnounceIntermediateTrustPrm, opts ...CallOption) (*AnnounceIntermediateTrustRes, error) {
|
func (c *clientImpl) AnnounceIntermediateTrust(ctx context.Context, prm AnnounceIntermediateTrustPrm, opts ...CallOption) (*AnnounceIntermediateTrustRes, error) {
|
||||||
// apply all available options
|
// apply all available options
|
||||||
|
@ -157,15 +170,25 @@ func (c *clientImpl) AnnounceIntermediateTrust(ctx context.Context, prm Announce
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle response meta info
|
var (
|
||||||
if err := c.handleResponseInfoV2(callOptions, resp); err != nil {
|
res = new(AnnounceIntermediateTrustRes)
|
||||||
return nil, err
|
procPrm processResponseV2Prm
|
||||||
|
procRes processResponseV2Res
|
||||||
|
)
|
||||||
|
|
||||||
|
procPrm.callOpts = callOptions
|
||||||
|
procPrm.resp = resp
|
||||||
|
|
||||||
|
procRes.statusRes = res
|
||||||
|
|
||||||
|
// process response in general
|
||||||
|
if c.processResponseV2(&procRes, procPrm) {
|
||||||
|
if procRes.cliErr != nil {
|
||||||
|
return nil, procRes.cliErr
|
||||||
}
|
}
|
||||||
|
|
||||||
err = v2signature.VerifyServiceMessage(resp)
|
return res, nil
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("can't verify response message: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new(AnnounceIntermediateTrustRes), nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,8 @@ type ResponseMetaInfo struct {
|
||||||
key []byte
|
key []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
type verificationHeaderGetter interface {
|
type responseV2 interface {
|
||||||
|
GetMetaHeader() *session.ResponseMetaHeader
|
||||||
GetVerificationHeader() *session.ResponseVerificationHeader
|
GetVerificationHeader() *session.ResponseVerificationHeader
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +27,7 @@ func WithResponseInfoHandler(f func(ResponseMetaInfo) error) Option {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *clientImpl) handleResponseInfoV2(_ *callOptions, resp verificationHeaderGetter) error {
|
func (c *clientImpl) handleResponseInfoV2(opts *callOptions, resp responseV2) error {
|
||||||
if c.opts.cbRespInfo == nil {
|
if c.opts.cbRespInfo == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,18 +10,41 @@ import (
|
||||||
v2session "github.com/nspcc-dev/neofs-api-go/v2/session"
|
v2session "github.com/nspcc-dev/neofs-api-go/v2/session"
|
||||||
v2signature "github.com/nspcc-dev/neofs-api-go/v2/signature"
|
v2signature "github.com/nspcc-dev/neofs-api-go/v2/signature"
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/owner"
|
"github.com/nspcc-dev/neofs-sdk-go/owner"
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/session"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Session contains session-related methods.
|
// Session contains session-related methods.
|
||||||
type Session interface {
|
type Session interface {
|
||||||
// CreateSession creates session using provided expiration time.
|
// CreateSession creates session using provided expiration time.
|
||||||
CreateSession(context.Context, uint64, ...CallOption) (*session.Token, error)
|
CreateSession(context.Context, uint64, ...CallOption) (*CreateSessionRes, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
var errMalformedResponseBody = errors.New("malformed response body")
|
var errMalformedResponseBody = errors.New("malformed response body")
|
||||||
|
|
||||||
func (c *clientImpl) CreateSession(ctx context.Context, expiration uint64, opts ...CallOption) (*session.Token, error) {
|
type CreateSessionRes struct {
|
||||||
|
statusRes
|
||||||
|
|
||||||
|
id []byte
|
||||||
|
|
||||||
|
sessionKey []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CreateSessionRes) setID(id []byte) {
|
||||||
|
x.id = id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x CreateSessionRes) ID() []byte {
|
||||||
|
return x.id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CreateSessionRes) setSessionKey(key []byte) {
|
||||||
|
x.sessionKey = key
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x CreateSessionRes) SessionKey() []byte {
|
||||||
|
return x.sessionKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *clientImpl) CreateSession(ctx context.Context, expiration uint64, opts ...CallOption) (*CreateSessionRes, error) {
|
||||||
// apply all available options
|
// apply all available options
|
||||||
callOptions := c.defaultCallOptions()
|
callOptions := c.defaultCallOptions()
|
||||||
|
|
||||||
|
@ -55,25 +78,30 @@ func (c *clientImpl) CreateSession(ctx context.Context, expiration uint64, opts
|
||||||
return nil, fmt.Errorf("transport error: %w", err)
|
return nil, fmt.Errorf("transport error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle response meta info
|
var (
|
||||||
if err := c.handleResponseInfoV2(callOptions, resp); err != nil {
|
res = new(CreateSessionRes)
|
||||||
return nil, err
|
procPrm processResponseV2Prm
|
||||||
|
procRes processResponseV2Res
|
||||||
|
)
|
||||||
|
|
||||||
|
procPrm.callOpts = callOptions
|
||||||
|
procPrm.resp = resp
|
||||||
|
|
||||||
|
procRes.statusRes = res
|
||||||
|
|
||||||
|
// process response in general
|
||||||
|
if c.processResponseV2(&procRes, procPrm) {
|
||||||
|
if procRes.cliErr != nil {
|
||||||
|
return nil, procRes.cliErr
|
||||||
}
|
}
|
||||||
|
|
||||||
err = v2signature.VerifyServiceMessage(resp)
|
return res, nil
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("can't verify response message: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body := resp.GetBody()
|
body := resp.GetBody()
|
||||||
if body == nil {
|
|
||||||
return nil, errMalformedResponseBody
|
|
||||||
}
|
|
||||||
|
|
||||||
sessionToken := session.NewToken()
|
res.setID(body.GetID())
|
||||||
sessionToken.SetID(body.GetID())
|
res.setSessionKey(body.GetSessionKey())
|
||||||
sessionToken.SetSessionKey(body.GetSessionKey())
|
|
||||||
sessionToken.SetOwnerID(ownerID)
|
|
||||||
|
|
||||||
return sessionToken, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue