diff --git a/client/accounting.go b/client/accounting.go index 545dade..b32c8bc 100644 --- a/client/accounting.go +++ b/client/accounting.go @@ -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 + } + + return res, nil } - err = v2signature.VerifyServiceMessage(resp) - if err != nil { - return nil, fmt.Errorf("can't verify response message: %w", err) - } + res.setAmount(accounting.NewDecimalFromV2(resp.GetBody().GetBalance())) - return accounting.NewDecimalFromV2(resp.GetBody().GetBalance()), nil + return res, nil } diff --git a/client/common.go b/client/common.go new file mode 100644 index 0000000..b233a3f --- /dev/null +++ b/client/common.go @@ -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) +} diff --git a/client/container.go b/client/container.go index 425bed9..4f5a87e 100644 --- a/client/container.go +++ b/client/container.go @@ -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 + } + + return res, nil } - err = v2signature.VerifyServiceMessage(resp) - if err != nil { - return nil, fmt.Errorf("can't verify response message: %w", err) + // sets result status + st := apistatus.FromStatusV2(resp.GetMetaHeader().GetStatus()) + + res.setStatus(st) + + if apistatus.IsSuccessful(st) { + res.setID(cid.NewFromV2(resp.GetBody().GetContainerID())) } - return cid.NewFromV2(resp.GetBody().GetContainerID()), nil + 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 + ) - err = v2signature.VerifyServiceMessage(resp) - if err != nil { - return nil, fmt.Errorf("can't verify response message: %w", err) + 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 + } + + 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 + } + + return res, nil } - err = v2signature.VerifyServiceMessage(resp) - if err != nil { - return nil, fmt.Errorf("can't verify response message: %w", err) - } + ids := make([]*cid.ID, 0, len(resp.GetBody().GetContainerIDs())) - result := 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 + } + + return res, nil } - if err := v2signature.VerifyServiceMessage(resp); err != 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 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 + ) - err = v2signature.VerifyServiceMessage(resp) - if err != nil { - return nil, fmt.Errorf("can't verify response message: %w", err) + 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 + } + + 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 + } + + return res, nil } - err = v2signature.VerifyServiceMessage(resp) - if err != nil { - return fmt.Errorf("can't verify response message: %w", err) - } + return res, nil +} - return 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 + } + + return res, nil } - err = v2signature.VerifyServiceMessage(resp) - if err != nil { - return fmt.Errorf("can't verify response message: %w", err) - } - - return nil + return res, nil } diff --git a/client/netmap.go b/client/netmap.go index 2a430dc..9c00d91 100644 --- a/client/netmap.go +++ b/client/netmap.go @@ -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 + ) - err = v2signature.VerifyServiceMessage(resp) - if err != nil { - return nil, fmt.Errorf("can't verify response message: %w", err) + 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 + } + + 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 + } + + return res, nil } - err = v2signature.VerifyServiceMessage(resp) - if err != nil { - return nil, fmt.Errorf("response message verification failed: %w", err) - } + res.setInfo(netmap.NewNetworkInfoFromV2(resp.GetBody().GetNetworkInfo())) - return netmap.NewNetworkInfoFromV2(resp.GetBody().GetNetworkInfo()), nil + return res, nil } diff --git a/client/object.go b/client/object.go index a62c7ce..1d89f82 100644 --- a/client/object.go +++ b/client/object.go @@ -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 + ) - // verify response structure - if err := signature.VerifyServiceMessage(resp); err != nil { - return nil, fmt.Errorf("response verification failed: %w", err) + 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 + } + + 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 + } + + return res, nil } - // verify response structure - if err := signature.VerifyServiceMessage(resp); err != nil { - return fmt.Errorf("response verification failed: %w", err) - } + addrv2 := resp.GetBody().GetTombstone() - if p.tombTgt != nil { - p.tombTgt.SetAddress(object.NewAddressFromV2(resp.GetBody().GetTombstone())) - } + res.setTombstoneAddress(object.NewAddressFromV2(addrv2)) - return nil + 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 - // verify response structure - if err := signature.VerifyServiceMessage(resp); err != nil { - return nil, fmt.Errorf("response verification failed: %w", err) + // process response in general + if c.processResponseV2(&procRes, procPrm) { + if procRes.cliErr != nil { + return nil, procRes.cliErr + } + + 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 + ) - // verify response structure - if err := signature.VerifyServiceMessage(resp); err != nil { - return nil, fmt.Errorf("response verification failed: %w", err) + 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 + } + + 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 - // verify response structure - if err := signature.VerifyServiceMessage(resp); err != nil { - return nil, fmt.Errorf("could not verify %T: %w", resp, err) + // process response in general + if c.processResponseV2(&procRes, procPrm) { + if procRes.cliErr != nil { + return nil, procRes.cliErr + } + + 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,61 +1257,28 @@ 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 + ) - // verify response structure - if err := signature.VerifyServiceMessage(resp); err != nil { - return nil, fmt.Errorf("response verification failed: %w", err) - } + procPrm.callOpts = callOpts + procPrm.resp = resp - respBody := resp.GetBody() - respType := respBody.GetType() - respHashes := respBody.GetHashList() + procRes.statusRes = res - 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) + // process response in general + if c.processResponseV2(&procRes, procPrm) { + if procRes.cliErr != nil { + return nil, procRes.cliErr } - 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 } + 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 - // verify response structure - if err := signature.VerifyServiceMessage(resp); err != nil { - return nil, fmt.Errorf("could not verify %T: %w", resp, err) + // process response in general + if c.processResponseV2(&procRes, procPrm) { + if procRes.cliErr != nil { + return nil, procRes.cliErr + } + + 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 { diff --git a/client/reputation.go b/client/reputation.go index 71b619a..1f3aca7 100644 --- a/client/reputation.go +++ b/client/reputation.go @@ -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 + } + + return res, nil } - err = v2signature.VerifyServiceMessage(resp) - 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. @@ -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 + } + + return res, nil } - err = v2signature.VerifyServiceMessage(resp) - if err != nil { - return nil, fmt.Errorf("can't verify response message: %w", err) - } - - return new(AnnounceIntermediateTrustRes), nil + return res, nil } diff --git a/client/response.go b/client/response.go index f8bdbd4..f8182e7 100644 --- a/client/response.go +++ b/client/response.go @@ -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 } diff --git a/client/session.go b/client/session.go index 3578696..ce11489 100644 --- a/client/session.go +++ b/client/session.go @@ -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 + ) - err = v2signature.VerifyServiceMessage(resp) - if err != nil { - return nil, fmt.Errorf("can't verify response message: %w", err) + 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 + } + + 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 }