[#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:
Leonard Lyubich 2021-11-16 21:17:25 +03:00 committed by LeL
parent 9dcff95a29
commit bf78cddf69
8 changed files with 835 additions and 372 deletions

View file

@ -15,10 +15,24 @@ import (
// Accounting contains methods related to balance querying.
type Accounting interface {
// 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
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)
}
// handle response meta info
if err := c.handleResponseInfoV2(callOptions, resp); err != nil {
return nil, err
var (
res = new(BalanceOfRes)
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)
if err != nil {
return nil, fmt.Errorf("can't verify response message: %w", err)
return res, nil
}
return accounting.NewDecimalFromV2(resp.GetBody().GetBalance()), nil
res.setAmount(accounting.NewDecimalFromV2(resp.GetBody().GetBalance()))
return res, nil
}

80
client/common.go Normal file
View 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)
}

View file

@ -2,7 +2,6 @@ package client
import (
"context"
"errors"
"fmt"
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"
"github.com/nspcc-dev/neofs-api-go/v2/rpc/client"
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"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
"github.com/nspcc-dev/neofs-sdk-go/eacl"
@ -23,25 +23,25 @@ import (
// Container contains methods related to container and ACL.
type Container interface {
// 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(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(context.Context, *owner.ID, ...CallOption) ([]*cid.ID, error)
ListContainers(context.Context, *owner.ID, ...CallOption) (*ContainerListRes, error)
// 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.
GetEACL(context.Context, *cid.ID, ...CallOption) (*EACLWithSignature, error)
// EACL returns extended ACL for a given container.
EACL(context.Context, *cid.ID, ...CallOption) (*EACLRes, error)
// 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(context.Context, []container.UsedSpaceAnnouncement, ...CallOption) error
AnnounceContainerUsedSpace(context.Context, []container.UsedSpaceAnnouncement, ...CallOption) (*AnnounceSpaceRes, error)
}
type delContainerSignWrapper struct {
@ -73,7 +73,21 @@ func (e EACLWithSignature) Signature() *signature.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
callOptions := c.defaultCallOptions()
@ -131,23 +145,56 @@ func (c *clientImpl) PutContainer(ctx context.Context, cnr *container.Container,
return nil, err
}
// handle response meta info
if err := c.handleResponseInfoV2(callOptions, resp); err != nil {
return nil, err
var (
res = new(ContainerPutRes)
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)
if err != nil {
return nil, fmt.Errorf("can't verify response message: %w", err)
return res, nil
}
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.
//
// 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
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)
}
// handle response meta info
if err := c.handleResponseInfoV2(callOptions, resp); err != nil {
return nil, err
var (
res = new(ContainerGetRes)
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)
if err != nil {
return nil, fmt.Errorf("can't verify response message: %w", err)
return res, nil
}
body := resp.GetBody()
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
// which checks if the structure of the resulting container matches its identifier.
//
// 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
}
type ContainerListRes struct {
statusRes
if !container.CalculateID(cnr).Equal(id) {
return nil, errors.New("container structure does not match the identifier")
}
return cnr, nil
ids []*cid.ID
}
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
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)
}
// handle response meta info
if err := c.handleResponseInfoV2(callOptions, resp); err != nil {
return nil, err
var (
res = new(ContainerListRes)
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)
if err != nil {
return nil, fmt.Errorf("can't verify response message: %w", err)
return res, nil
}
result := make([]*cid.ID, 0, len(resp.GetBody().GetContainerIDs()))
ids := make([]*cid.ID, 0, len(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
callOptions := c.defaultCallOptions()
@ -285,7 +364,7 @@ func (c *clientImpl) DeleteContainer(ctx context.Context, id *cid.ID, opts ...Ca
},
sigutil.SignWithRFC6979())
if err != nil {
return err
return nil, err
}
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)
if err != nil {
return err
return nil, err
}
resp, err := rpcapi.DeleteContainer(c.Raw(), req, client.WithContext(ctx))
if err != nil {
return fmt.Errorf("transport error: %w", err)
return nil, fmt.Errorf("transport error: %w", err)
}
// handle response meta info
if err := c.handleResponseInfoV2(callOptions, resp); err != nil {
return err
var (
res = new(ContainerDeleteRes)
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 fmt.Errorf("can't verify response message: %w", err)
return res, nil
}
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
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)
}
// handle response meta info
if err := c.handleResponseInfoV2(callOptions, resp); err != nil {
return nil, err
var (
res = new(EACLRes)
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)
if err != nil {
return nil, fmt.Errorf("can't verify response message: %w", err)
return res, nil
}
body := resp.GetBody()
table := eacl.NewTableFromV2(body.GetEACL())
table.SetSessionToken(session.NewTokenFromV2(body.GetSessionToken()))
table.SetSignature(signature.NewFromV2(body.GetSignature()))
return &EACLWithSignature{
table: table,
}, nil
table.SetSessionToken(
session.NewTokenFromV2(body.GetSessionToken()),
)
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
callOptions := c.defaultCallOptions()
@ -380,37 +504,52 @@ func (c *clientImpl) SetEACL(ctx context.Context, eacl *eacl.Table, opts ...Call
reqBody.SetSignature(eaclSignature)
}, sigutil.SignWithRFC6979())
if err != nil {
return err
return nil, err
}
req := new(v2container.SetExtendedACLRequest)
req.SetBody(reqBody)
meta := v2MetaHeaderFromOpts(callOptions)
meta.SetSessionToken(eacl.SessionToken().ToV2())
req := new(v2container.SetExtendedACLRequest)
req.SetBody(reqBody)
req.SetMetaHeader(meta)
err = v2signature.SignServiceMessage(callOptions.key, req)
if err != nil {
return err
return nil, err
}
resp, err := rpcapi.SetEACL(c.Raw(), req, client.WithContext(ctx))
if err != nil {
return fmt.Errorf("transport error: %w", err)
return nil, fmt.Errorf("transport error: %w", err)
}
// handle response meta info
if err := c.handleResponseInfoV2(callOptions, resp); err != nil {
return err
var (
res = new(SetEACLRes)
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)
if err != nil {
return fmt.Errorf("can't verify response message: %w", err)
return res, nil
}
return nil
return res, nil
}
type AnnounceSpaceRes struct {
statusRes
}
// 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(
ctx context.Context,
announce []container.UsedSpaceAnnouncement,
opts ...CallOption) error {
opts ...CallOption,
) (*AnnounceSpaceRes, error) {
callOptions := c.defaultCallOptions() // apply all available options
for i := range opts {
@ -442,23 +582,33 @@ func (c *clientImpl) AnnounceContainerUsedSpace(
// sign the request
err := v2signature.SignServiceMessage(callOptions.key, req)
if err != nil {
return err
return nil, err
}
resp, err := rpcapi.AnnounceUsedSpace(c.Raw(), req, client.WithContext(ctx))
if err != nil {
return fmt.Errorf("transport error: %w", err)
return nil, fmt.Errorf("transport error: %w", err)
}
// handle response meta info
if err := c.handleResponseInfoV2(callOptions, resp); err != nil {
return err
var (
res = new(AnnounceSpaceRes)
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)
if err != nil {
return fmt.Errorf("can't verify response message: %w", err)
return res, nil
}
return nil
return res, nil
}

View file

@ -17,10 +17,10 @@ type Netmap interface {
// 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
// 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(context.Context, ...CallOption) (*netmap.NetworkInfo, error)
NetworkInfo(context.Context, ...CallOption) (*NetworkInfoRes, error)
}
// EACLWithSignature represents eACL table/signature pair.
@ -40,10 +40,24 @@ func (e *EndpointInfo) NodeInfo() *netmap.NodeInfo {
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
// 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.
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
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)
}
// handle response meta info
if err := c.handleResponseInfoV2(callOptions, resp); err != nil {
return nil, err
var (
res = new(EndpointInfoRes)
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)
if err != nil {
return nil, fmt.Errorf("can't verify response message: %w", err)
return res, nil
}
body := resp.GetBody()
return &EndpointInfo{
res.setInfo(&EndpointInfo{
version: version.NewFromV2(body.GetVersion()),
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.
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
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)
}
// handle response meta info
if err := c.handleResponseInfoV2(callOptions, resp); err != nil {
return nil, err
var (
res = new(NetworkInfoRes)
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)
if err != nil {
return nil, fmt.Errorf("response message verification failed: %w", err)
return res, nil
}
return netmap.NewNetworkInfoFromV2(resp.GetBody().GetNetworkInfo()), nil
res.setInfo(netmap.NewNetworkInfoFromV2(resp.GetBody().GetNetworkInfo()))
return res, nil
}

View file

@ -4,7 +4,6 @@ import (
"bytes"
"context"
"crypto/ecdsa"
"crypto/sha256"
"errors"
"fmt"
"io"
@ -15,6 +14,7 @@ import (
"github.com/nspcc-dev/neofs-api-go/v2/rpc/client"
v2session "github.com/nspcc-dev/neofs-api-go/v2/session"
"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"
"github.com/nspcc-dev/neofs-sdk-go/object"
signer "github.com/nspcc-dev/neofs-sdk-go/util/signature"
@ -23,28 +23,25 @@ import (
// Object contains methods for working with objects.
type Object interface {
// 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(context.Context, *DeleteObjectParams, ...CallOption) error
DeleteObject(context.Context, *DeleteObjectParams, ...CallOption) (*ObjectDeleteRes, error)
// 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.
GetObjectHeader(context.Context, *ObjectHeaderParams, ...CallOption) (*object.Object, error)
// HeadObject returns object header.
HeadObject(context.Context, *ObjectHeaderParams, ...CallOption) (*ObjectHeadRes, error)
// 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.
ObjectPayloadRangeSHA256(context.Context, *RangeChecksumParams, ...CallOption) ([][sha256.Size]byte, error)
// HashObjectPayloadRanges returns hashes of the object payload ranges from NeoFS.
HashObjectPayloadRanges(context.Context, *RangeChecksumParams, ...CallOption) (*ObjectRangeHashRes, error)
// ObjectPayloadRangeTZ returns homomorphic hashes of object sub-ranges from NeoFS.
ObjectPayloadRangeTZ(context.Context, *RangeChecksumParams, ...CallOption) ([][TZSize]byte, error)
// SearchObject searches for objects in NeoFS using provided parameters.
SearchObject(context.Context, *SearchObjectParams, ...CallOption) ([]*object.ID, error)
// SearchObjects searches for objects in NeoFS using provided parameters.
SearchObjects(context.Context, *SearchObjectParams, ...CallOption) (*ObjectSearchRes, error)
}
type PutObjectParams struct {
@ -59,10 +56,6 @@ type ObjectAddressWriter interface {
SetAddress(*object.Address)
}
type objectAddressWriter struct {
addr *object.Address
}
type DeleteObjectParams struct {
addr *object.Address
@ -98,7 +91,7 @@ type RangeDataParams struct {
}
type RangeChecksumParams struct {
typ checksumType
tz bool
addr *object.Address
@ -141,17 +134,11 @@ const TZSize = 64
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 {
r2 := make([]*v2object.Range, len(rs))
r2 := make([]*v2object.Range, 0, len(rs))
for i := range rs {
r2[i] = rs[i].ToV2()
r2 = append(r2, rs[i].ToV2())
}
return r2
@ -220,7 +207,21 @@ func (p *PutObjectParams) PayloadReader() io.Reader {
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()
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)
}
// handle response meta info
if err := c.handleResponseInfoV2(callOpts, resp); err != nil {
return nil, err
var (
res = new(ObjectPutRes)
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
if err := signature.VerifyServiceMessage(resp); err != nil {
return nil, fmt.Errorf("response verification failed: %w", err)
return res, nil
}
// convert object identifier
id := object.NewIDFromV2(resp.GetBody().GetObjectID())
return id, nil
res.setID(id)
return res, nil
}
func (p *DeleteObjectParams) WithAddress(v *object.Address) *DeleteObjectParams {
@ -363,24 +376,24 @@ func (p *DeleteObjectParams) TombstoneAddressTarget() ObjectAddressWriter {
return nil
}
// DeleteObject is a wrapper over Client.DeleteObject method
// that provides the ability to receive tombstone address
// without setting a target in the parameters.
func DeleteObject(ctx context.Context, c Client, p *DeleteObjectParams, opts ...CallOption) (*object.Address, error) {
w := new(objectAddressWriter)
type ObjectDeleteRes struct {
statusRes
err := c.DeleteObject(ctx, p.WithTombstoneAddressTarget(w), opts...)
if err != nil {
return nil, err
}
tombAddr *object.Address
}
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.
//
// 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()
for i := range opts {
@ -403,7 +416,7 @@ func (c *clientImpl) DeleteObject(ctx context.Context, p *DeleteObjectParams, op
addr: p.addr.ToV2(),
verb: v2session.ObjectVerbDelete,
}); 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)
@ -413,30 +426,40 @@ func (c *clientImpl) DeleteObject(ctx context.Context, p *DeleteObjectParams, op
// sign the request
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
resp, err := rpcapi.DeleteObject(c.Raw(), req, client.WithContext(ctx))
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
if err := c.handleResponseInfoV2(callOpts, resp); err != nil {
return err
var (
res = new(ObjectDeleteRes)
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
if err := signature.VerifyServiceMessage(resp); err != nil {
return fmt.Errorf("response verification failed: %w", err)
return res, nil
}
if p.tombTgt != nil {
p.tombTgt.SetAddress(object.NewAddressFromV2(resp.GetBody().GetTombstone()))
}
addrv2 := resp.GetBody().GetTombstone()
return nil
res.setTombstoneAddress(object.NewAddressFromV2(addrv2))
return res, nil
}
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")
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()
for i := range opts {
@ -615,16 +663,27 @@ func (c *clientImpl) GetObject(ctx context.Context, p *GetObjectParams, opts ...
payload []byte
obj = new(v2object.Object)
resp = new(v2object.GetResponse)
messageWas bool
res = new(ObjectGetRes)
procPrm processResponseV2Prm
procRes processResponseV2Res
)
procPrm.callOpts = callOpts
procPrm.resp = resp
procRes.statusRes = res
loop:
for {
// receive message from server stream
err := stream.Read(resp)
if err != nil {
if errors.Is(err, io.EOF) {
if !headWas {
return nil, io.ErrUnexpectedEOF
if !messageWas {
return nil, errWrongMessageSeq
}
break
@ -633,19 +692,20 @@ loop:
return nil, fmt.Errorf("reading the response failed: %w", err)
}
// handle response meta info
if err := c.handleResponseInfoV2(callOpts, resp); err != nil {
return nil, err
messageWas = true
// process response in general
if c.processResponseV2(&procRes, procPrm) {
if procRes.cliErr != nil {
return nil, procRes.cliErr
}
// verify response structure
if err := signature.VerifyServiceMessage(resp); err != nil {
return nil, fmt.Errorf("response verification failed: %w", err)
return res, nil
}
switch v := resp.GetBody().GetObjectPart().(type) {
default:
return nil, fmt.Errorf("unexpected object part %T", v)
return nil, errWrongMessageSeq
case *v2object.GetObjectPartInit:
if headWas {
return nil, errWrongMessageSeq
@ -683,6 +743,10 @@ loop:
payload = append(payload, v.GetChunk()...)
}
case *v2object.SplitInfo:
if headWas {
return nil, errWrongMessageSeq
}
si := object.NewSplitInfoFromV2(v)
return nil, object.NewSplitInfoError(si)
}
@ -691,7 +755,9 @@ loop:
obj.SetPayload(payload)
// convert the object
return object.NewFromV2(obj), nil
res.setObject(object.NewFromV2(obj))
return res, nil
}
func (p *ObjectHeaderParams) WithAddress(v *object.Address) *ObjectHeaderParams {
@ -752,7 +818,12 @@ func (p *ObjectHeaderParams) RawFlag() bool {
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()
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)
}
// handle response meta info
if err := c.handleResponseInfoV2(callOpts, resp); err != nil {
return nil, err
var (
res = new(ObjectHeadRes)
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
if err := signature.VerifyServiceMessage(resp); err != nil {
return nil, fmt.Errorf("response verification failed: %w", err)
return res, nil
}
var (
@ -813,12 +894,12 @@ func (c *clientImpl) GetObjectHeader(ctx context.Context, p *ObjectHeaderParams,
switch v := resp.GetBody().GetHeaderPart().(type) {
case nil:
return nil, fmt.Errorf("unexpected header type %T", v)
writeUnexpectedMessageTypeErr(res, v)
return res, nil
case *v2object.ShortHeader:
if !p.short {
return nil, fmt.Errorf("wrong header part type: expected %T, received %T",
(*v2object.ShortHeader)(nil), (*v2object.HeaderWithSignature)(nil),
)
writeUnexpectedMessageTypeErr(res, v)
return res, nil
}
h := v
@ -833,29 +914,12 @@ func (c *clientImpl) GetObjectHeader(ctx context.Context, p *ObjectHeaderParams,
hdr.SetHomomorphicHash(h.GetHomomorphicHash())
case *v2object.HeaderWithSignature:
if p.short {
return nil, fmt.Errorf("wrong header part type: expected %T, received %T",
(*v2object.HeaderWithSignature)(nil), (*v2object.ShortHeader)(nil),
)
writeUnexpectedMessageTypeErr(res, v)
return res, nil
}
hdrWithSig := v
if hdrWithSig == nil {
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)
}
hdr = v.GetHeader()
idSig = v.GetSignature()
case *v2object.SplitInfo:
si := object.NewSplitInfoFromV2(v)
@ -869,8 +933,9 @@ func (c *clientImpl) GetObjectHeader(ctx context.Context, p *ObjectHeaderParams,
raw := object.NewRawFromV2(obj)
raw.SetID(p.addr.ObjectID())
// convert the object
return raw.Object(), nil
res.setObject(raw.Object())
return res, nil
}
func (p *RangeDataParams) WithAddress(v *object.Address) *RangeDataParams {
@ -937,7 +1002,21 @@ func (p *RangeDataParams) DataWriter() io.Writer {
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()
for i := range opts {
@ -986,33 +1065,54 @@ func (c *clientImpl) ObjectPayloadRangeData(ctx context.Context, p *RangeDataPar
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 {
// receive message from server stream
err := stream.Read(resp)
if err != nil {
if errors.Is(err, io.EOF) {
if !messageWas {
return nil, errWrongMessageSeq
}
break
}
return nil, fmt.Errorf("reading the response failed: %w", err)
}
// handle response meta info
if err := c.handleResponseInfoV2(callOpts, resp); err != nil {
return nil, err
messageWas = true
// process response in general
if c.processResponseV2(&procRes, procPrm) {
if procRes.cliErr != nil {
return nil, procRes.cliErr
}
// verify response structure
if err := signature.VerifyServiceMessage(resp); err != nil {
return nil, fmt.Errorf("could not verify %T: %w", resp, err)
return res, nil
}
switch v := resp.GetBody().GetRangePart().(type) {
case nil:
return nil, fmt.Errorf("unexpected range type %T", v)
writeUnexpectedMessageTypeErr(res, v)
return res, nil
case *v2object.GetRangePartChunk:
chunkWas = true
if p.w != nil {
if _, err = p.w.Write(v.GetChunk()); err != nil {
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()...)
}
case *v2object.SplitInfo:
if chunkWas {
return nil, errWrongMessageSeq
}
si := object.NewSplitInfoFromV2(v)
return nil, object.NewSplitInfoError(si)
}
}
return payload, nil
res.setData(payload)
return res, nil
}
func (p *RangeChecksumParams) WithAddress(v *object.Address) *RangeChecksumParams {
@ -1078,33 +1184,26 @@ func (p *RangeChecksumParams) Salt() []byte {
return nil
}
func (p *RangeChecksumParams) withChecksumType(t checksumType) *RangeChecksumParams {
if p != nil {
p.typ = t
}
func (p *RangeChecksumParams) TZ() *RangeChecksumParams {
p.tz = true
return p
}
func (c *clientImpl) ObjectPayloadRangeSHA256(ctx context.Context, p *RangeChecksumParams, opts ...CallOption) ([][sha256.Size]byte, error) {
res, err := c.objectPayloadRangeHash(ctx, p.withChecksumType(checksumSHA256), opts...)
if err != nil {
return nil, err
}
type ObjectRangeHashRes struct {
statusRes
return res.([][sha256.Size]byte), nil
hashes [][]byte
}
func (c *clientImpl) ObjectPayloadRangeTZ(ctx context.Context, p *RangeChecksumParams, opts ...CallOption) ([][TZSize]byte, error) {
res, err := c.objectPayloadRangeHash(ctx, p.withChecksumType(checksumTZ), opts...)
if err != nil {
return nil, err
}
return res.([][TZSize]byte), nil
func (x *ObjectRangeHashRes) setHashes(v [][]byte) {
x.hashes = v
}
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()
for i := range opts {
@ -1136,7 +1235,12 @@ func (c *clientImpl) objectPayloadRangeHash(ctx context.Context, p *RangeChecksu
body.SetAddress(p.addr.ToV2())
body.SetSalt(p.salt)
typV2 := p.typ.toV2()
typ := checksumSHA256
if p.tz {
typ = checksumTZ
}
typV2 := typ.toV2()
body.SetType(typV2)
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)
}
// handle response meta info
if err := c.handleResponseInfoV2(callOpts, resp); err != nil {
return nil, err
var (
res = new(ObjectRangeHashRes)
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
if err := signature.VerifyServiceMessage(resp); err != nil {
return nil, fmt.Errorf("response verification failed: %w", err)
return res, nil
}
respBody := resp.GetBody()
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
}
res.setHashes(resp.GetBody().GetHashList())
return res, nil
}
@ -1243,7 +1314,21 @@ func (p *SearchObjectParams) SearchFilters() object.SearchFilters {
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()
for i := range opts {
@ -1293,27 +1378,43 @@ func (c *clientImpl) SearchObject(ctx context.Context, p *SearchObjectParams, op
var (
searchResult []*object.ID
resp = new(v2object.SearchResponse)
messageWas bool
res = new(ObjectSearchRes)
procPrm processResponseV2Prm
procRes processResponseV2Res
)
procPrm.callOpts = callOpts
procPrm.resp = resp
procRes.statusRes = res
for {
// receive message from server stream
err := stream.Read(resp)
if err != nil {
if errors.Is(err, io.EOF) {
if !messageWas {
return nil, errWrongMessageSeq
}
break
}
return nil, fmt.Errorf("reading the response failed: %w", err)
}
// handle response meta info
if err := c.handleResponseInfoV2(callOpts, resp); err != nil {
return nil, err
messageWas = true
// process response in general
if c.processResponseV2(&procRes, procPrm) {
if procRes.cliErr != nil {
return nil, procRes.cliErr
}
// verify response structure
if err := signature.VerifyServiceMessage(resp); err != nil {
return nil, fmt.Errorf("could not verify %T: %w", resp, err)
return res, nil
}
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 {

View file

@ -2,7 +2,6 @@ package client
import (
"context"
"fmt"
v2reputation "github.com/nspcc-dev/neofs-api-go/v2/reputation"
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.
type AnnounceLocalTrustRes struct{}
type AnnounceLocalTrustRes struct {
statusRes
}
func (c *clientImpl) AnnounceLocalTrust(ctx context.Context, prm AnnounceLocalTrustPrm, opts ...CallOption) (*AnnounceLocalTrustRes, error) {
// apply all available options
@ -77,17 +78,27 @@ func (c *clientImpl) AnnounceLocalTrust(ctx context.Context, prm AnnounceLocalTr
return nil, err
}
// handle response meta info
if err := c.handleResponseInfoV2(callOptions, resp); err != nil {
return nil, err
var (
res = new(AnnounceLocalTrustRes)
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)
if err != nil {
return nil, fmt.Errorf("can't verify response message: %w", err)
return res, nil
}
return new(AnnounceLocalTrustRes), nil
return res, nil
}
// AnnounceIntermediateTrustPrm groups parameters of AnnounceIntermediateTrust operation.
@ -128,7 +139,9 @@ func (x *AnnounceIntermediateTrustPrm) SetTrust(trust *reputation.PeerToPeerTrus
}
// 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) {
// apply all available options
@ -157,15 +170,25 @@ func (c *clientImpl) AnnounceIntermediateTrust(ctx context.Context, prm Announce
return nil, err
}
// handle response meta info
if err := c.handleResponseInfoV2(callOptions, resp); err != nil {
return nil, err
var (
res = new(AnnounceIntermediateTrustRes)
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)
if err != nil {
return nil, fmt.Errorf("can't verify response message: %w", err)
return res, nil
}
return new(AnnounceIntermediateTrustRes), nil
return res, nil
}

View file

@ -7,7 +7,8 @@ type ResponseMetaInfo struct {
key []byte
}
type verificationHeaderGetter interface {
type responseV2 interface {
GetMetaHeader() *session.ResponseMetaHeader
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 {
return nil
}

View file

@ -10,18 +10,41 @@ import (
v2session "github.com/nspcc-dev/neofs-api-go/v2/session"
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/session"
)
// Session contains session-related methods.
type Session interface {
// 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")
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
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)
}
// handle response meta info
if err := c.handleResponseInfoV2(callOptions, resp); err != nil {
return nil, err
var (
res = new(CreateSessionRes)
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)
if err != nil {
return nil, fmt.Errorf("can't verify response message: %w", err)
return res, nil
}
body := resp.GetBody()
if body == nil {
return nil, errMalformedResponseBody
}
sessionToken := session.NewToken()
sessionToken.SetID(body.GetID())
sessionToken.SetSessionKey(body.GetSessionKey())
sessionToken.SetOwnerID(ownerID)
res.setID(body.GetID())
res.setSessionKey(body.GetSessionKey())
return sessionToken, nil
return res, nil
}