diff --git a/client/common.go b/client/common.go index 5008a0d8..da93cc1e 100644 --- a/client/common.go +++ b/client/common.go @@ -65,6 +65,20 @@ func (x prmCommonMeta) writeToMetaHeader(h *v2session.RequestMetaHeader) { } } +func writeXHeadersToMeta(xHeaders []string, h *v2session.RequestMetaHeader) { + if len(xHeaders) == 0 { + return + } + + hs := make([]v2session.XHeader, len(xHeaders)/2) + for i := 0; i < len(xHeaders); i += 2 { + hs[i].SetKey(xHeaders[i]) + hs[i].SetValue(xHeaders[i+1]) + } + + h.SetXHeaders(hs) +} + // panic messages. const ( panicMsgMissingContext = "missing context" @@ -108,11 +122,7 @@ type contextCall struct { statusRes resCommon // request to be signed with a key and sent - req interface { - GetMetaHeader() *v2session.RequestMetaHeader - SetMetaHeader(*v2session.RequestMetaHeader) - SetVerificationHeader(*v2session.RequestVerificationHeader) - } + req request // function to send a request (unary) and receive a response call func() (responseV2, error) @@ -130,6 +140,12 @@ type contextCall struct { result func(v2 responseV2) } +type request interface { + GetMetaHeader() *v2session.RequestMetaHeader + SetMetaHeader(*v2session.RequestMetaHeader) + SetVerificationHeader(*v2session.RequestVerificationHeader) +} + // sets needed fields of the request meta header. func (x contextCall) prepareRequest() { meta := x.req.GetMetaHeader() @@ -153,6 +169,25 @@ func (x contextCall) prepareRequest() { x.meta.writeToMetaHeader(meta) } +func (c *Client) prepareRequest(req request, meta *v2session.RequestMetaHeader) { + ttl := meta.GetTTL() + if ttl == 0 { + ttl = 2 + } + + verV2 := meta.GetVersion() + if verV2 == nil { + verV2 = new(refs.Version) + version.Current().WriteToV2(verV2) + } + + meta.SetTTL(ttl) + meta.SetVersion(verV2) + meta.SetNetworkMagic(c.prm.netMagic) + + req.SetMetaHeader(meta) +} + // prepares, signs and writes the request. Result means success. // If failed, contextCall.err contains the reason. func (x *contextCall) writeRequest() bool { @@ -222,6 +257,20 @@ func (x *contextCall) processResponse() bool { return successfulStatus } +// processResponse verifies response signature and converts status to an error if needed. +func (c *Client) processResponse(resp responseV2) (apistatus.Status, error) { + err := signature.VerifyServiceMessage(resp) + if err != nil { + return nil, fmt.Errorf("invalid response signature: %w", err) + } + + st := apistatus.FromStatusV2(resp.GetMetaHeader().GetStatus()) + if c.prm.resolveNeoFSErrors { + return st, apistatus.ErrFromStatus(st) + } + return st, nil +} + // reads response (if rResp is set) and processes it. Result means success. // If failed, contextCall.err (or statusRes if resolveAPIFailures is set) contains the reason. func (x *contextCall) readResponse() bool { diff --git a/client/object_hash.go b/client/object_hash.go index 8f9561b3..0b0e047e 100644 --- a/client/object_hash.go +++ b/client/object_hash.go @@ -2,6 +2,7 @@ package client import ( "context" + "fmt" "github.com/nspcc-dev/neofs-api-go/v2/acl" v2object "github.com/nspcc-dev/neofs-api-go/v2/object" @@ -9,7 +10,9 @@ import ( rpcapi "github.com/nspcc-dev/neofs-api-go/v2/rpc" "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" "github.com/nspcc-dev/neofs-sdk-go/bearer" + apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" "github.com/nspcc-dev/neofs-sdk-go/session" @@ -117,7 +120,7 @@ func (x *PrmObjectHash) WithXHeaders(hs ...string) { panic("slice of X-Headers with odd length") } - prmCommonMeta{xHeaders: hs}.writeToMetaHeader(&x.meta) + writeXHeadersToMeta(hs, &x.meta) } // ResObjectHash groups resulting values of ObjectHash operation. @@ -166,43 +169,40 @@ func (c *Client) ObjectHash(ctx context.Context, prm PrmObjectHash) (*ResObjectH panic("missing ranges") } - // form request body prm.body.SetAddress(&prm.addr) - // ranges and salt are already by prm setters - if prm.tillichZemor { prm.body.SetType(v2refs.TillichZemor) } else { prm.body.SetType(v2refs.SHA256) } - // form request var req v2object.GetRangeHashRequest + c.prepareRequest(&req, &prm.meta) req.SetBody(&prm.body) - req.SetMetaHeader(&prm.meta) - // init call context - var ( - cc contextCall - res ResObjectHash - ) - - c.initCallContext(&cc) - cc.req = &req - cc.statusRes = &res - cc.call = func() (responseV2, error) { - return rpcapi.HashObjectRange(&c.c, &req, client.WithContext(ctx)) - } - cc.result = func(r responseV2) { - res.checksums = r.(*v2object.GetRangeHashResponse).GetBody().GetHashList() - if len(res.checksums) == 0 { - cc.err = newErrMissingResponseField("hash list") - } + err := signature.SignServiceMessage(&c.prm.key, req) + if err != nil { + return nil, fmt.Errorf("sign request: %w", err) } - // process call - if !cc.processCall() { - return nil, cc.err + resp, err := rpcapi.HashObjectRange(&c.c, &req, client.WithContext(ctx)) + if err != nil { + return nil, fmt.Errorf("write request: %w", err) + } + + var res ResObjectHash + res.st, err = c.processResponse(resp) + if err != nil { + return nil, err + } + + if !apistatus.IsSuccessful(res.st) { + return &res, nil + } + + res.checksums = resp.GetBody().GetHashList() + if len(res.checksums) == 0 { + return nil, newErrMissingResponseField("hash list") } return &res, nil