forked from TrueCloudLab/frostfs-sdk-go
[#62] client: move package from neofs-api-go
Signed-off-by: Evgenii Stratonikov <evgeniy@nspcc.ru>
This commit is contained in:
parent
6c55c0fd4b
commit
4855154b35
11 changed files with 2651 additions and 0 deletions
57
client/accounting.go
Normal file
57
client/accounting.go
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neofs-api-go/rpc/client"
|
||||||
|
v2accounting "github.com/nspcc-dev/neofs-api-go/v2/accounting"
|
||||||
|
rpcapi "github.com/nspcc-dev/neofs-api-go/v2/rpc"
|
||||||
|
v2signature "github.com/nspcc-dev/neofs-api-go/v2/signature"
|
||||||
|
"github.com/nspcc-dev/neofs-sdk-go/accounting"
|
||||||
|
"github.com/nspcc-dev/neofs-sdk-go/owner"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *clientImpl) GetBalance(ctx context.Context, owner *owner.ID, opts ...CallOption) (*accounting.Decimal, error) {
|
||||||
|
// apply all available options
|
||||||
|
callOptions := c.defaultCallOptions()
|
||||||
|
|
||||||
|
for i := range opts {
|
||||||
|
opts[i](callOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
reqBody := new(v2accounting.BalanceRequestBody)
|
||||||
|
reqBody.SetOwnerID(owner.ToV2())
|
||||||
|
|
||||||
|
req := new(v2accounting.BalanceRequest)
|
||||||
|
req.SetBody(reqBody)
|
||||||
|
req.SetMetaHeader(v2MetaHeaderFromOpts(callOptions))
|
||||||
|
|
||||||
|
err := v2signature.SignServiceMessage(callOptions.key, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := rpcapi.Balance(c.Raw(), req, client.WithContext(ctx))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("transport error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle response meta info
|
||||||
|
if err := c.handleResponseInfoV2(callOptions, resp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = v2signature.VerifyServiceMessage(resp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't verify response message: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return accounting.NewDecimalFromV2(resp.GetBody().GetBalance()), nil
|
||||||
|
}
|
45
client/client.go
Normal file
45
client/client.go
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neofs-api-go/rpc/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client represents NeoFS client.
|
||||||
|
type Client interface {
|
||||||
|
Accounting
|
||||||
|
Container
|
||||||
|
Netmap
|
||||||
|
Object
|
||||||
|
Session
|
||||||
|
Reputation
|
||||||
|
|
||||||
|
// Raw must return underlying raw protobuf client.
|
||||||
|
Raw() *client.Client
|
||||||
|
|
||||||
|
// Conn must return underlying connection.
|
||||||
|
//
|
||||||
|
// Must return a non-nil result after the first RPC call
|
||||||
|
// completed without a connection error.
|
||||||
|
Conn() io.Closer
|
||||||
|
}
|
||||||
|
|
||||||
|
type clientImpl struct {
|
||||||
|
raw *client.Client
|
||||||
|
|
||||||
|
opts *clientOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(opts ...Option) (Client, error) {
|
||||||
|
clientOptions := defaultClientOptions()
|
||||||
|
|
||||||
|
for i := range opts {
|
||||||
|
opts[i](clientOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &clientImpl{
|
||||||
|
opts: clientOptions,
|
||||||
|
raw: client.New(clientOptions.rawOpts...),
|
||||||
|
}, nil
|
||||||
|
}
|
465
client/container.go
Normal file
465
client/container.go
Normal file
|
@ -0,0 +1,465 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neofs-api-go/rpc/client"
|
||||||
|
v2container "github.com/nspcc-dev/neofs-api-go/v2/container"
|
||||||
|
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
||||||
|
rpcapi "github.com/nspcc-dev/neofs-api-go/v2/rpc"
|
||||||
|
v2signature "github.com/nspcc-dev/neofs-api-go/v2/signature"
|
||||||
|
"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"
|
||||||
|
"github.com/nspcc-dev/neofs-sdk-go/owner"
|
||||||
|
"github.com/nspcc-dev/neofs-sdk-go/session"
|
||||||
|
"github.com/nspcc-dev/neofs-sdk-go/signature"
|
||||||
|
sigutil "github.com/nspcc-dev/neofs-sdk-go/util/signature"
|
||||||
|
"github.com/nspcc-dev/neofs-sdk-go/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
|
||||||
|
// GetContainer returns container by ID.
|
||||||
|
GetContainer(context.Context, *cid.ID, ...CallOption) (*container.Container, error)
|
||||||
|
|
||||||
|
// ListContainers return container list with the provided owner.
|
||||||
|
ListContainers(context.Context, *owner.ID, ...CallOption) ([]*cid.ID, error)
|
||||||
|
|
||||||
|
// DeleteContainer removes container from NeoFS network.
|
||||||
|
DeleteContainer(context.Context, *cid.ID, ...CallOption) error
|
||||||
|
|
||||||
|
// GetEACL returns extended ACL for a given container.
|
||||||
|
GetEACL(context.Context, *cid.ID, ...CallOption) (*EACLWithSignature, error)
|
||||||
|
|
||||||
|
// SetEACL sets extended ACL.
|
||||||
|
SetEACL(context.Context, *eacl.Table, ...CallOption) error
|
||||||
|
|
||||||
|
// AnnounceContainerUsedSpace announces amount of space which is taken by stored objects.
|
||||||
|
AnnounceContainerUsedSpace(context.Context, []container.UsedSpaceAnnouncement, ...CallOption) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type delContainerSignWrapper struct {
|
||||||
|
body *v2container.DeleteRequestBody
|
||||||
|
}
|
||||||
|
|
||||||
|
// EACLWithSignature represents eACL table/signature pair.
|
||||||
|
type EACLWithSignature struct {
|
||||||
|
table *eacl.Table
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c delContainerSignWrapper) ReadSignedData(bytes []byte) ([]byte, error) {
|
||||||
|
return c.body.GetContainerID().GetValue(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c delContainerSignWrapper) SignedDataSize() int {
|
||||||
|
return len(c.body.GetContainerID().GetValue())
|
||||||
|
}
|
||||||
|
|
||||||
|
// EACL returns eACL table.
|
||||||
|
func (e EACLWithSignature) EACL() *eacl.Table {
|
||||||
|
return e.table
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signature returns table signature.
|
||||||
|
//
|
||||||
|
// Deprecated: use EACL().Signature() instead.
|
||||||
|
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) {
|
||||||
|
// apply all available options
|
||||||
|
callOptions := c.defaultCallOptions()
|
||||||
|
|
||||||
|
for i := range opts {
|
||||||
|
opts[i](callOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// set transport version
|
||||||
|
cnr.SetVersion(version.Current())
|
||||||
|
|
||||||
|
// if container owner is not set, then use client key as owner
|
||||||
|
if cnr.OwnerID() == nil {
|
||||||
|
w, err := owner.NEO3WalletFromPublicKey(&callOptions.key.PublicKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ownerID := new(owner.ID)
|
||||||
|
ownerID.SetNeo3Wallet(w)
|
||||||
|
|
||||||
|
cnr.SetOwnerID(ownerID)
|
||||||
|
}
|
||||||
|
|
||||||
|
reqBody := new(v2container.PutRequestBody)
|
||||||
|
reqBody.SetContainer(cnr.ToV2())
|
||||||
|
|
||||||
|
// sign container
|
||||||
|
signWrapper := v2signature.StableMarshalerWrapper{SM: reqBody.GetContainer()}
|
||||||
|
|
||||||
|
err := sigutil.SignDataWithHandler(callOptions.key, signWrapper, func(key []byte, sig []byte) {
|
||||||
|
containerSignature := new(refs.Signature)
|
||||||
|
containerSignature.SetKey(key)
|
||||||
|
containerSignature.SetSign(sig)
|
||||||
|
reqBody.SetSignature(containerSignature)
|
||||||
|
}, sigutil.SignWithRFC6979())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req := new(v2container.PutRequest)
|
||||||
|
req.SetBody(reqBody)
|
||||||
|
|
||||||
|
meta := v2MetaHeaderFromOpts(callOptions)
|
||||||
|
meta.SetSessionToken(cnr.SessionToken().ToV2())
|
||||||
|
|
||||||
|
req.SetMetaHeader(meta)
|
||||||
|
|
||||||
|
err = v2signature.SignServiceMessage(callOptions.key, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := rpcapi.PutContainer(c.Raw(), req, client.WithContext(ctx))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle response meta info
|
||||||
|
if err := c.handleResponseInfoV2(callOptions, resp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = v2signature.VerifyServiceMessage(resp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't verify response message: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cid.NewFromV2(resp.GetBody().GetContainerID()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
// apply all available options
|
||||||
|
callOptions := c.defaultCallOptions()
|
||||||
|
|
||||||
|
for i := range opts {
|
||||||
|
opts[i](callOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
reqBody := new(v2container.GetRequestBody)
|
||||||
|
reqBody.SetContainerID(id.ToV2())
|
||||||
|
|
||||||
|
req := new(v2container.GetRequest)
|
||||||
|
req.SetBody(reqBody)
|
||||||
|
req.SetMetaHeader(v2MetaHeaderFromOpts(callOptions))
|
||||||
|
|
||||||
|
err := v2signature.SignServiceMessage(callOptions.key, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := rpcapi.GetContainer(c.Raw(), req, client.WithContext(ctx))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("transport error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle response meta info
|
||||||
|
if err := c.handleResponseInfoV2(callOptions, resp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = v2signature.VerifyServiceMessage(resp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't verify response message: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
body := resp.GetBody()
|
||||||
|
|
||||||
|
cnr := container.NewContainerFromV2(body.GetContainer())
|
||||||
|
cnr.SetSessionToken(session.NewTokenFromV2(body.GetSessionToken()))
|
||||||
|
cnr.SetSignature(signature.NewFromV2(body.GetSignature()))
|
||||||
|
|
||||||
|
return cnr, 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
|
||||||
|
}
|
||||||
|
|
||||||
|
if !container.CalculateID(cnr).Equal(id) {
|
||||||
|
return nil, errors.New("container structure does not match the identifier")
|
||||||
|
}
|
||||||
|
|
||||||
|
return cnr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *clientImpl) ListContainers(ctx context.Context, ownerID *owner.ID, opts ...CallOption) ([]*cid.ID, error) {
|
||||||
|
// apply all available options
|
||||||
|
callOptions := c.defaultCallOptions()
|
||||||
|
|
||||||
|
for i := range opts {
|
||||||
|
opts[i](callOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ownerID == nil {
|
||||||
|
w, err := owner.NEO3WalletFromPublicKey(&callOptions.key.PublicKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ownerID = new(owner.ID)
|
||||||
|
ownerID.SetNeo3Wallet(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
reqBody := new(v2container.ListRequestBody)
|
||||||
|
reqBody.SetOwnerID(ownerID.ToV2())
|
||||||
|
|
||||||
|
req := new(v2container.ListRequest)
|
||||||
|
req.SetBody(reqBody)
|
||||||
|
req.SetMetaHeader(v2MetaHeaderFromOpts(callOptions))
|
||||||
|
|
||||||
|
err := v2signature.SignServiceMessage(callOptions.key, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := rpcapi.ListContainers(c.Raw(), req, client.WithContext(ctx))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("transport error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle response meta info
|
||||||
|
if err := c.handleResponseInfoV2(callOptions, resp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = v2signature.VerifyServiceMessage(resp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't verify response message: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]*cid.ID, 0, len(resp.GetBody().GetContainerIDs()))
|
||||||
|
for _, cidV2 := range resp.GetBody().GetContainerIDs() {
|
||||||
|
result = append(result, cid.NewFromV2(cidV2))
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *clientImpl) DeleteContainer(ctx context.Context, id *cid.ID, opts ...CallOption) error {
|
||||||
|
// apply all available options
|
||||||
|
callOptions := c.defaultCallOptions()
|
||||||
|
|
||||||
|
for i := range opts {
|
||||||
|
opts[i](callOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
reqBody := new(v2container.DeleteRequestBody)
|
||||||
|
reqBody.SetContainerID(id.ToV2())
|
||||||
|
|
||||||
|
// sign container
|
||||||
|
err := sigutil.SignDataWithHandler(callOptions.key,
|
||||||
|
delContainerSignWrapper{
|
||||||
|
body: reqBody,
|
||||||
|
},
|
||||||
|
func(key []byte, sig []byte) {
|
||||||
|
containerSignature := new(refs.Signature)
|
||||||
|
containerSignature.SetKey(key)
|
||||||
|
containerSignature.SetSign(sig)
|
||||||
|
reqBody.SetSignature(containerSignature)
|
||||||
|
},
|
||||||
|
sigutil.SignWithRFC6979())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req := new(v2container.DeleteRequest)
|
||||||
|
req.SetBody(reqBody)
|
||||||
|
req.SetMetaHeader(v2MetaHeaderFromOpts(callOptions))
|
||||||
|
|
||||||
|
err = v2signature.SignServiceMessage(callOptions.key, req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := rpcapi.DeleteContainer(c.Raw(), req, client.WithContext(ctx))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("transport error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle response meta info
|
||||||
|
if err := c.handleResponseInfoV2(callOptions, resp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := v2signature.VerifyServiceMessage(resp); err != nil {
|
||||||
|
return fmt.Errorf("can't verify response message: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *clientImpl) GetEACL(ctx context.Context, id *cid.ID, opts ...CallOption) (*EACLWithSignature, error) {
|
||||||
|
// apply all available options
|
||||||
|
callOptions := c.defaultCallOptions()
|
||||||
|
|
||||||
|
for i := range opts {
|
||||||
|
opts[i](callOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
reqBody := new(v2container.GetExtendedACLRequestBody)
|
||||||
|
reqBody.SetContainerID(id.ToV2())
|
||||||
|
|
||||||
|
req := new(v2container.GetExtendedACLRequest)
|
||||||
|
req.SetBody(reqBody)
|
||||||
|
req.SetMetaHeader(v2MetaHeaderFromOpts(callOptions))
|
||||||
|
|
||||||
|
err := v2signature.SignServiceMessage(callOptions.key, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := rpcapi.GetEACL(c.Raw(), req, client.WithContext(ctx))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("transport error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle response meta info
|
||||||
|
if err := c.handleResponseInfoV2(callOptions, resp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = v2signature.VerifyServiceMessage(resp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't verify response message: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *clientImpl) SetEACL(ctx context.Context, eacl *eacl.Table, opts ...CallOption) error {
|
||||||
|
// apply all available options
|
||||||
|
callOptions := c.defaultCallOptions()
|
||||||
|
|
||||||
|
for i := range opts {
|
||||||
|
opts[i](callOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
reqBody := new(v2container.SetExtendedACLRequestBody)
|
||||||
|
reqBody.SetEACL(eacl.ToV2())
|
||||||
|
reqBody.GetEACL().SetVersion(version.Current().ToV2())
|
||||||
|
|
||||||
|
signWrapper := v2signature.StableMarshalerWrapper{SM: reqBody.GetEACL()}
|
||||||
|
|
||||||
|
err := sigutil.SignDataWithHandler(callOptions.key, signWrapper, func(key []byte, sig []byte) {
|
||||||
|
eaclSignature := new(refs.Signature)
|
||||||
|
eaclSignature.SetKey(key)
|
||||||
|
eaclSignature.SetSign(sig)
|
||||||
|
reqBody.SetSignature(eaclSignature)
|
||||||
|
}, sigutil.SignWithRFC6979())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := rpcapi.SetEACL(c.Raw(), req, client.WithContext(ctx))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("transport error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle response meta info
|
||||||
|
if err := c.handleResponseInfoV2(callOptions, resp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = v2signature.VerifyServiceMessage(resp)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("can't verify response message: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnnounceContainerUsedSpace used by storage nodes to estimate their container
|
||||||
|
// sizes during lifetime. Use it only in storage node applications.
|
||||||
|
func (c *clientImpl) AnnounceContainerUsedSpace(
|
||||||
|
ctx context.Context,
|
||||||
|
announce []container.UsedSpaceAnnouncement,
|
||||||
|
opts ...CallOption) error {
|
||||||
|
callOptions := c.defaultCallOptions() // apply all available options
|
||||||
|
|
||||||
|
for i := range opts {
|
||||||
|
opts[i](callOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert list of SDK announcement structures into NeoFS-API v2 list
|
||||||
|
v2announce := make([]*v2container.UsedSpaceAnnouncement, 0, len(announce))
|
||||||
|
for i := range announce {
|
||||||
|
v2announce = append(v2announce, announce[i].ToV2())
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepare body of the NeoFS-API v2 request and request itself
|
||||||
|
reqBody := new(v2container.AnnounceUsedSpaceRequestBody)
|
||||||
|
reqBody.SetAnnouncements(v2announce)
|
||||||
|
|
||||||
|
req := new(v2container.AnnounceUsedSpaceRequest)
|
||||||
|
req.SetBody(reqBody)
|
||||||
|
req.SetMetaHeader(v2MetaHeaderFromOpts(callOptions))
|
||||||
|
|
||||||
|
// sign the request
|
||||||
|
err := v2signature.SignServiceMessage(callOptions.key, req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := rpcapi.AnnounceUsedSpace(c.Raw(), req, client.WithContext(ctx))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("transport error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle response meta info
|
||||||
|
if err := c.handleResponseInfoV2(callOptions, resp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = v2signature.VerifyServiceMessage(resp)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("can't verify response message: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
124
client/netmap.go
Normal file
124
client/netmap.go
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neofs-api-go/rpc/client"
|
||||||
|
v2netmap "github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
||||||
|
rpcapi "github.com/nspcc-dev/neofs-api-go/v2/rpc"
|
||||||
|
v2signature "github.com/nspcc-dev/neofs-api-go/v2/signature"
|
||||||
|
"github.com/nspcc-dev/neofs-sdk-go/netmap"
|
||||||
|
"github.com/nspcc-dev/neofs-sdk-go/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Netmap contains methods related to netmap.
|
||||||
|
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)
|
||||||
|
|
||||||
|
// NetworkInfo returns information about the NeoFS network of which the remote server is a part.
|
||||||
|
NetworkInfo(context.Context, ...CallOption) (*netmap.NetworkInfo, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EACLWithSignature represents eACL table/signature pair.
|
||||||
|
type EndpointInfo struct {
|
||||||
|
version *version.Version
|
||||||
|
|
||||||
|
ni *netmap.NodeInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// LatestVersion returns latest NeoFS API version in use.
|
||||||
|
func (e *EndpointInfo) LatestVersion() *version.Version {
|
||||||
|
return e.version
|
||||||
|
}
|
||||||
|
|
||||||
|
// NodeInfo returns returns information about the NeoFS node.
|
||||||
|
func (e *EndpointInfo) NodeInfo() *netmap.NodeInfo {
|
||||||
|
return e.ni
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
// apply all available options
|
||||||
|
callOptions := c.defaultCallOptions()
|
||||||
|
|
||||||
|
for i := range opts {
|
||||||
|
opts[i](callOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
reqBody := new(v2netmap.LocalNodeInfoRequestBody)
|
||||||
|
|
||||||
|
req := new(v2netmap.LocalNodeInfoRequest)
|
||||||
|
req.SetBody(reqBody)
|
||||||
|
req.SetMetaHeader(v2MetaHeaderFromOpts(callOptions))
|
||||||
|
|
||||||
|
err := v2signature.SignServiceMessage(callOptions.key, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := rpcapi.LocalNodeInfo(c.Raw(), req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("transport error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle response meta info
|
||||||
|
if err := c.handleResponseInfoV2(callOptions, resp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = v2signature.VerifyServiceMessage(resp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't verify response message: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
body := resp.GetBody()
|
||||||
|
|
||||||
|
return &EndpointInfo{
|
||||||
|
version: version.NewFromV2(body.GetVersion()),
|
||||||
|
ni: netmap.NewNodeInfoFromV2(body.GetNodeInfo()),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
// apply all available options
|
||||||
|
callOptions := c.defaultCallOptions()
|
||||||
|
|
||||||
|
for i := range opts {
|
||||||
|
opts[i](callOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
reqBody := new(v2netmap.NetworkInfoRequestBody)
|
||||||
|
|
||||||
|
req := new(v2netmap.NetworkInfoRequest)
|
||||||
|
req.SetBody(reqBody)
|
||||||
|
req.SetMetaHeader(v2MetaHeaderFromOpts(callOptions))
|
||||||
|
|
||||||
|
err := v2signature.SignServiceMessage(callOptions.key, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := rpcapi.NetworkInfo(c.Raw(), req, client.WithContext(ctx))
|
||||||
|
if err != nil {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
err = v2signature.VerifyServiceMessage(resp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("response message verification failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return netmap.NewNetworkInfoFromV2(resp.GetBody().GetNetworkInfo()), nil
|
||||||
|
}
|
1373
client/object.go
Normal file
1373
client/object.go
Normal file
File diff suppressed because it is too large
Load diff
94
client/object_test.go
Normal file
94
client/object_test.go
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neofs-api-go/v2/object"
|
||||||
|
"github.com/nspcc-dev/neofs-api-go/v2/signature"
|
||||||
|
"github.com/nspcc-dev/neofs-crypto/test"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
type singleResponseStream struct {
|
||||||
|
called bool
|
||||||
|
resp object.GetResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *singleResponseStream) Read(r *object.GetResponse) error {
|
||||||
|
if x.called {
|
||||||
|
return io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
x.called = true
|
||||||
|
|
||||||
|
*r = x.resp
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = test.DecodeKey(0)
|
||||||
|
|
||||||
|
func chunkResponse(c []byte) (r object.GetResponse) {
|
||||||
|
chunkPart := new(object.GetObjectPartChunk)
|
||||||
|
chunkPart.SetChunk(c)
|
||||||
|
|
||||||
|
body := new(object.GetResponseBody)
|
||||||
|
body.SetObjectPart(chunkPart)
|
||||||
|
|
||||||
|
r.SetBody(body)
|
||||||
|
|
||||||
|
if err := signature.SignServiceMessage(key, &r); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func data(sz int) []byte {
|
||||||
|
data := make([]byte, sz)
|
||||||
|
|
||||||
|
for i := range data {
|
||||||
|
data[i] = byte(i) % ^byte(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkFullRead(t *testing.T, r io.Reader, buf, payload []byte) {
|
||||||
|
var (
|
||||||
|
restored []byte
|
||||||
|
read int
|
||||||
|
)
|
||||||
|
|
||||||
|
for {
|
||||||
|
n, err := r.Read(buf)
|
||||||
|
|
||||||
|
read += n
|
||||||
|
restored = append(restored, buf[:n]...)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
require.Equal(t, err, io.EOF)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Equal(t, payload, restored)
|
||||||
|
require.EqualValues(t, len(payload), read)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestObjectPayloadReader_Read(t *testing.T) {
|
||||||
|
t.Run("read with tail", func(t *testing.T) {
|
||||||
|
payload := data(10)
|
||||||
|
|
||||||
|
buf := make([]byte, len(payload)-1)
|
||||||
|
|
||||||
|
var r io.Reader = &objectPayloadReader{
|
||||||
|
stream: &singleResponseStream{
|
||||||
|
resp: chunkResponse(payload),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
checkFullRead(t, r, buf, payload)
|
||||||
|
})
|
||||||
|
}
|
189
client/opts.go
Normal file
189
client/opts.go
Normal file
|
@ -0,0 +1,189 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/tls"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neofs-api-go/rpc/client"
|
||||||
|
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
||||||
|
v2session "github.com/nspcc-dev/neofs-api-go/v2/session"
|
||||||
|
"github.com/nspcc-dev/neofs-sdk-go/session"
|
||||||
|
"github.com/nspcc-dev/neofs-sdk-go/token"
|
||||||
|
"github.com/nspcc-dev/neofs-sdk-go/version"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
CallOption func(*callOptions)
|
||||||
|
|
||||||
|
Option func(*clientOptions)
|
||||||
|
|
||||||
|
callOptions struct {
|
||||||
|
version *version.Version
|
||||||
|
xHeaders []*session.XHeader
|
||||||
|
ttl uint32
|
||||||
|
epoch uint64
|
||||||
|
key *ecdsa.PrivateKey
|
||||||
|
session *session.Token
|
||||||
|
bearer *token.BearerToken
|
||||||
|
}
|
||||||
|
|
||||||
|
clientOptions struct {
|
||||||
|
key *ecdsa.PrivateKey
|
||||||
|
|
||||||
|
rawOpts []client.Option
|
||||||
|
|
||||||
|
cbRespInfo func(ResponseMetaInfo) error
|
||||||
|
}
|
||||||
|
|
||||||
|
v2SessionReqInfo struct {
|
||||||
|
addr *refs.Address
|
||||||
|
verb v2session.ObjectSessionVerb
|
||||||
|
|
||||||
|
exp, nbf, iat uint64
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *clientImpl) defaultCallOptions() *callOptions {
|
||||||
|
return &callOptions{
|
||||||
|
version: version.Current(),
|
||||||
|
ttl: 2,
|
||||||
|
key: c.opts.key,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithXHeader(x *session.XHeader) CallOption {
|
||||||
|
return func(opts *callOptions) {
|
||||||
|
opts.xHeaders = append(opts.xHeaders, x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithTTL(ttl uint32) CallOption {
|
||||||
|
return func(opts *callOptions) {
|
||||||
|
opts.ttl = ttl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithKey sets client's key for the next request.
|
||||||
|
func WithKey(key *ecdsa.PrivateKey) CallOption {
|
||||||
|
return func(opts *callOptions) {
|
||||||
|
opts.key = key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithEpoch(epoch uint64) CallOption {
|
||||||
|
return func(opts *callOptions) {
|
||||||
|
opts.epoch = epoch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithSession(token *session.Token) CallOption {
|
||||||
|
return func(opts *callOptions) {
|
||||||
|
opts.session = token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithBearer(token *token.BearerToken) CallOption {
|
||||||
|
return func(opts *callOptions) {
|
||||||
|
opts.bearer = token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func v2MetaHeaderFromOpts(options *callOptions) *v2session.RequestMetaHeader {
|
||||||
|
meta := new(v2session.RequestMetaHeader)
|
||||||
|
meta.SetVersion(options.version.ToV2())
|
||||||
|
meta.SetTTL(options.ttl)
|
||||||
|
meta.SetEpoch(options.epoch)
|
||||||
|
|
||||||
|
xhdrs := make([]*v2session.XHeader, len(options.xHeaders))
|
||||||
|
for i := range options.xHeaders {
|
||||||
|
xhdrs[i] = options.xHeaders[i].ToV2()
|
||||||
|
}
|
||||||
|
|
||||||
|
meta.SetXHeaders(xhdrs)
|
||||||
|
|
||||||
|
if options.bearer != nil {
|
||||||
|
meta.SetBearerToken(options.bearer.ToV2())
|
||||||
|
}
|
||||||
|
|
||||||
|
meta.SetSessionToken(options.session.ToV2())
|
||||||
|
|
||||||
|
return meta
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultClientOptions() *clientOptions {
|
||||||
|
return &clientOptions{
|
||||||
|
rawOpts: make([]client.Option, 0, 4),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithAddress returns option to specify
|
||||||
|
// network address of the remote server.
|
||||||
|
//
|
||||||
|
// Ignored if WithGRPCConnection is provided.
|
||||||
|
func WithAddress(addr string) Option {
|
||||||
|
return func(opts *clientOptions) {
|
||||||
|
opts.rawOpts = append(opts.rawOpts, client.WithNetworkAddress(addr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDialTimeout returns option to set connection timeout to the remote node.
|
||||||
|
//
|
||||||
|
// Ignored if WithGRPCConn is provided.
|
||||||
|
func WithDialTimeout(dur time.Duration) Option {
|
||||||
|
return func(opts *clientOptions) {
|
||||||
|
opts.rawOpts = append(opts.rawOpts, client.WithDialTimeout(dur))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTLSConfig returns option to set connection's TLS config to the remote node.
|
||||||
|
//
|
||||||
|
// Ignored if WithGRPCConnection is provided.
|
||||||
|
func WithTLSConfig(cfg *tls.Config) Option {
|
||||||
|
return func(opts *clientOptions) {
|
||||||
|
opts.rawOpts = append(opts.rawOpts, client.WithTLSCfg(cfg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDefaultPrivateKey returns option to set default private key
|
||||||
|
// used for the work.
|
||||||
|
func WithDefaultPrivateKey(key *ecdsa.PrivateKey) Option {
|
||||||
|
return func(opts *clientOptions) {
|
||||||
|
opts.key = key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithURIAddress returns option to specify
|
||||||
|
// network address of a remote server and connection
|
||||||
|
// scheme for it.
|
||||||
|
//
|
||||||
|
// Format of the URI:
|
||||||
|
//
|
||||||
|
// [scheme://]host:port
|
||||||
|
//
|
||||||
|
// Supported schemes:
|
||||||
|
// - grpc;
|
||||||
|
// - grpcs.
|
||||||
|
//
|
||||||
|
// tls.Cfg second argument is optional and is taken into
|
||||||
|
// account only in case of `grpcs` scheme.
|
||||||
|
//
|
||||||
|
// Falls back to WithNetworkAddress if address is not a valid URI.
|
||||||
|
//
|
||||||
|
// Do not use along with WithAddress and WithTLSConfig.
|
||||||
|
//
|
||||||
|
// Ignored if WithGRPCConnection is provided.
|
||||||
|
func WithURIAddress(addr string, tlsCfg *tls.Config) Option {
|
||||||
|
return func(opts *clientOptions) {
|
||||||
|
opts.rawOpts = append(opts.rawOpts, client.WithNetworkURIAddress(addr, tlsCfg)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithGRPCConnection returns option to set GRPC connection to
|
||||||
|
// the remote node.
|
||||||
|
func WithGRPCConnection(grpcConn *grpc.ClientConn) Option {
|
||||||
|
return func(opts *clientOptions) {
|
||||||
|
opts.rawOpts = append(opts.rawOpts, client.WithGRPCConn(grpcConn))
|
||||||
|
}
|
||||||
|
}
|
17
client/raw.go
Normal file
17
client/raw.go
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neofs-api-go/rpc/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Raw returns underlying raw protobuf client.
|
||||||
|
func (c *clientImpl) Raw() *client.Client {
|
||||||
|
return c.raw
|
||||||
|
}
|
||||||
|
|
||||||
|
// implements Client.Conn method.
|
||||||
|
func (c *clientImpl) Conn() io.Closer {
|
||||||
|
return c.raw.Conn()
|
||||||
|
}
|
171
client/reputation.go
Normal file
171
client/reputation.go
Normal file
|
@ -0,0 +1,171 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neofs-api-go/rpc/client"
|
||||||
|
v2reputation "github.com/nspcc-dev/neofs-api-go/v2/reputation"
|
||||||
|
rpcapi "github.com/nspcc-dev/neofs-api-go/v2/rpc"
|
||||||
|
v2signature "github.com/nspcc-dev/neofs-api-go/v2/signature"
|
||||||
|
"github.com/nspcc-dev/neofs-sdk-go/reputation"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reputation contains methods for working with Reputation system values.
|
||||||
|
type Reputation interface {
|
||||||
|
// AnnounceLocalTrust announces local trust values of local peer.
|
||||||
|
AnnounceLocalTrust(context.Context, AnnounceLocalTrustPrm, ...CallOption) (*AnnounceLocalTrustRes, error)
|
||||||
|
|
||||||
|
// AnnounceIntermediateTrust announces the intermediate result of the iterative algorithm for calculating
|
||||||
|
// the global reputation of the node.
|
||||||
|
AnnounceIntermediateTrust(context.Context, AnnounceIntermediateTrustPrm, ...CallOption) (*AnnounceIntermediateTrustRes, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnnounceLocalTrustPrm groups parameters of AnnounceLocalTrust operation.
|
||||||
|
type AnnounceLocalTrustPrm struct {
|
||||||
|
epoch uint64
|
||||||
|
|
||||||
|
trusts []*reputation.Trust
|
||||||
|
}
|
||||||
|
|
||||||
|
// Epoch returns epoch in which the trust was assessed.
|
||||||
|
func (x AnnounceLocalTrustPrm) Epoch() uint64 {
|
||||||
|
return x.epoch
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetEpoch sets epoch in which the trust was assessed.
|
||||||
|
func (x *AnnounceLocalTrustPrm) SetEpoch(epoch uint64) {
|
||||||
|
x.epoch = epoch
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trusts returns list of local trust values.
|
||||||
|
func (x AnnounceLocalTrustPrm) Trusts() []*reputation.Trust {
|
||||||
|
return x.trusts
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTrusts sets list of local trust values.
|
||||||
|
func (x *AnnounceLocalTrustPrm) SetTrusts(trusts []*reputation.Trust) {
|
||||||
|
x.trusts = trusts
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnnounceLocalTrustRes groups results of AnnounceLocalTrust operation.
|
||||||
|
type AnnounceLocalTrustRes struct{}
|
||||||
|
|
||||||
|
func (c *clientImpl) AnnounceLocalTrust(ctx context.Context, prm AnnounceLocalTrustPrm, opts ...CallOption) (*AnnounceLocalTrustRes, error) {
|
||||||
|
// apply all available options
|
||||||
|
callOptions := c.defaultCallOptions()
|
||||||
|
|
||||||
|
for i := range opts {
|
||||||
|
opts[i](callOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
reqBody := new(v2reputation.AnnounceLocalTrustRequestBody)
|
||||||
|
reqBody.SetEpoch(prm.Epoch())
|
||||||
|
reqBody.SetTrusts(reputation.TrustsToV2(prm.Trusts()))
|
||||||
|
|
||||||
|
req := new(v2reputation.AnnounceLocalTrustRequest)
|
||||||
|
req.SetBody(reqBody)
|
||||||
|
req.SetMetaHeader(v2MetaHeaderFromOpts(callOptions))
|
||||||
|
|
||||||
|
err := v2signature.SignServiceMessage(callOptions.key, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := rpcapi.AnnounceLocalTrust(c.Raw(), req, client.WithContext(ctx))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle response meta info
|
||||||
|
if err := c.handleResponseInfoV2(callOptions, resp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = v2signature.VerifyServiceMessage(resp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't verify response message: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return new(AnnounceLocalTrustRes), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnnounceIntermediateTrustPrm groups parameters of AnnounceIntermediateTrust operation.
|
||||||
|
type AnnounceIntermediateTrustPrm struct {
|
||||||
|
epoch uint64
|
||||||
|
|
||||||
|
iter uint32
|
||||||
|
|
||||||
|
trust *reputation.PeerToPeerTrust
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *AnnounceIntermediateTrustPrm) Epoch() uint64 {
|
||||||
|
return x.epoch
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *AnnounceIntermediateTrustPrm) SetEpoch(epoch uint64) {
|
||||||
|
x.epoch = epoch
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iteration returns sequence number of the iteration.
|
||||||
|
func (x AnnounceIntermediateTrustPrm) Iteration() uint32 {
|
||||||
|
return x.iter
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetIteration sets sequence number of the iteration.
|
||||||
|
func (x *AnnounceIntermediateTrustPrm) SetIteration(iter uint32) {
|
||||||
|
x.iter = iter
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trust returns current global trust value computed at the specified iteration.
|
||||||
|
func (x AnnounceIntermediateTrustPrm) Trust() *reputation.PeerToPeerTrust {
|
||||||
|
return x.trust
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTrust sets current global trust value computed at the specified iteration.
|
||||||
|
func (x *AnnounceIntermediateTrustPrm) SetTrust(trust *reputation.PeerToPeerTrust) {
|
||||||
|
x.trust = trust
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnnounceIntermediateTrustRes groups results of AnnounceIntermediateTrust operation.
|
||||||
|
type AnnounceIntermediateTrustRes struct{}
|
||||||
|
|
||||||
|
func (c *clientImpl) AnnounceIntermediateTrust(ctx context.Context, prm AnnounceIntermediateTrustPrm, opts ...CallOption) (*AnnounceIntermediateTrustRes, error) {
|
||||||
|
// apply all available options
|
||||||
|
callOptions := c.defaultCallOptions()
|
||||||
|
|
||||||
|
for i := range opts {
|
||||||
|
opts[i](callOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
reqBody := new(v2reputation.AnnounceIntermediateResultRequestBody)
|
||||||
|
reqBody.SetEpoch(prm.Epoch())
|
||||||
|
reqBody.SetIteration(prm.Iteration())
|
||||||
|
reqBody.SetTrust(prm.Trust().ToV2())
|
||||||
|
|
||||||
|
req := new(v2reputation.AnnounceIntermediateResultRequest)
|
||||||
|
req.SetBody(reqBody)
|
||||||
|
req.SetMetaHeader(v2MetaHeaderFromOpts(callOptions))
|
||||||
|
|
||||||
|
err := v2signature.SignServiceMessage(callOptions.key, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := rpcapi.AnnounceIntermediateResult(c.Raw(), req, client.WithContext(ctx))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle response meta info
|
||||||
|
if err := c.handleResponseInfoV2(callOptions, resp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = v2signature.VerifyServiceMessage(resp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't verify response message: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return new(AnnounceIntermediateTrustRes), nil
|
||||||
|
}
|
37
client/response.go
Normal file
37
client/response.go
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import "github.com/nspcc-dev/neofs-api-go/v2/session"
|
||||||
|
|
||||||
|
// ResponseMetaInfo groups meta information about any NeoFS API response.
|
||||||
|
type ResponseMetaInfo struct {
|
||||||
|
key []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type verificationHeaderGetter interface {
|
||||||
|
GetVerificationHeader() *session.ResponseVerificationHeader
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResponderKey returns responder's public key in a binary format.
|
||||||
|
//
|
||||||
|
// Result must not be mutated.
|
||||||
|
func (x ResponseMetaInfo) ResponderKey() []byte {
|
||||||
|
return x.key
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithResponseInfoHandler allows to specify handler of response meta information for the all Client operations.
|
||||||
|
// The handler is called right after the response is received. Client returns handler's error immediately.
|
||||||
|
func WithResponseInfoHandler(f func(ResponseMetaInfo) error) Option {
|
||||||
|
return func(opts *clientOptions) {
|
||||||
|
opts.cbRespInfo = f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *clientImpl) handleResponseInfoV2(_ *callOptions, resp verificationHeaderGetter) error {
|
||||||
|
if c.opts.cbRespInfo == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.opts.cbRespInfo(ResponseMetaInfo{
|
||||||
|
key: resp.GetVerificationHeader().GetBodySignature().GetKey(),
|
||||||
|
})
|
||||||
|
}
|
79
client/session.go
Normal file
79
client/session.go
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neofs-api-go/rpc/client"
|
||||||
|
rpcapi "github.com/nspcc-dev/neofs-api-go/v2/rpc"
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
var errMalformedResponseBody = errors.New("malformed response body")
|
||||||
|
|
||||||
|
func (c *clientImpl) CreateSession(ctx context.Context, expiration uint64, opts ...CallOption) (*session.Token, error) {
|
||||||
|
// apply all available options
|
||||||
|
callOptions := c.defaultCallOptions()
|
||||||
|
|
||||||
|
for i := range opts {
|
||||||
|
opts[i](callOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
w, err := owner.NEO3WalletFromPublicKey(&callOptions.key.PublicKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ownerID := new(owner.ID)
|
||||||
|
ownerID.SetNeo3Wallet(w)
|
||||||
|
|
||||||
|
reqBody := new(v2session.CreateRequestBody)
|
||||||
|
reqBody.SetOwnerID(ownerID.ToV2())
|
||||||
|
reqBody.SetExpiration(expiration)
|
||||||
|
|
||||||
|
req := new(v2session.CreateRequest)
|
||||||
|
req.SetBody(reqBody)
|
||||||
|
req.SetMetaHeader(v2MetaHeaderFromOpts(callOptions))
|
||||||
|
|
||||||
|
err = v2signature.SignServiceMessage(callOptions.key, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := rpcapi.CreateSession(c.Raw(), req, client.WithContext(ctx))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("transport error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle response meta info
|
||||||
|
if err := c.handleResponseInfoV2(callOptions, resp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = v2signature.VerifyServiceMessage(resp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't verify response message: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
body := resp.GetBody()
|
||||||
|
if body == nil {
|
||||||
|
return nil, errMalformedResponseBody
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionToken := session.NewToken()
|
||||||
|
sessionToken.SetID(body.GetID())
|
||||||
|
sessionToken.SetSessionKey(body.GetSessionKey())
|
||||||
|
sessionToken.SetOwnerID(ownerID)
|
||||||
|
|
||||||
|
return sessionToken, nil
|
||||||
|
}
|
Loading…
Reference in a new issue