From 1efaa3ab761c8f1bc8dd439c61a28afefe0cab58 Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Wed, 25 Dec 2024 17:27:56 +0300 Subject: [PATCH] *: Replace `github.com/nspcc-dev/neofs-api-go/v2` module Continues 8a26264f1d7d85acef7c2f0f0b407c6966e4a58b and finalizes deprecation of `neofs-api-go/v2` module in favor of `proto/` packages. Request/response signing and verification functions have been added to `crypto` package A. They are used by the API client and will also be used by https://github.com/nspcc-dev/neofs-node. API client unit tests and AIO integration ones PASS, therefore, there is no more way to track more regressions for now. If problems arise in apps when updating the lib, fixes will be added later before the major release. Refs #270. Closes #452. Refs #532. Closes #551 (it's almost impossible to do now). Signed-off-by: Leonard Lyubich --- README.md | 2 +- client/accounting.go | 97 +-- client/accounting_test.go | 11 +- client/client.go | 14 +- client/client_test.go | 225 +++---- client/common.go | 285 +-------- client/container.go | 619 +++++++++---------- client/container_test.go | 149 ++--- client/crypto_test.go | 31 +- client/errors.go | 4 + client/example_container_put_test.go | 13 +- client/example_test.go | 80 --- client/messages_test.go | 47 +- client/netmap.go | 234 +++---- client/netmap_test.go | 30 +- client/object_delete.go | 84 ++- client/object_delete_test.go | 14 +- client/object_get.go | 316 +++++----- client/object_get_test.go | 46 +- client/object_hash.go | 119 ++-- client/object_hash_test.go | 14 +- client/object_put.go | 127 ++-- client/object_put_test.go | 14 +- client/object_replicate.go | 25 +- client/object_replicate_test.go | 11 +- client/object_search.go | 88 +-- client/object_search_test.go | 16 +- client/object_test.go | 38 +- client/reputation.go | 141 +++-- client/reputation_test.go | 19 +- client/response.go | 7 - client/session.go | 107 ++-- client/session_container.go | 20 +- client/session_test.go | 11 +- client/sign.go | 390 ------------ client/sign_test.go | 243 -------- client/status/common.go | 186 +++--- client/status/common_test.go | 54 +- client/status/container.go | 77 +-- client/status/object.go | 245 ++++---- client/status/object_test.go | 6 +- client/status/session.go | 77 +-- client/status/unrecognized.go | 35 +- client/status/v2.go | 178 +++--- client/status/v2_test.go | 127 ++-- crypto/proto.go | 293 +++++++++ crypto/proto_test.go | 878 +++++++++++++++++++++++++++ go.mod | 1 - go.sum | 2 - 49 files changed, 2800 insertions(+), 3050 deletions(-) delete mode 100644 client/sign.go delete mode 100644 client/sign_test.go create mode 100644 crypto/proto.go create mode 100644 crypto/proto_test.go diff --git a/README.md b/README.md index cb8e1994..4213f0f2 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ # neofs-sdk-go Go implementation of NeoFS SDK. It contains high-level version-independent wrappers -for structures from [neofs-api-go](https://github.com/nspcc-dev/neofs-api-go) as well as +for structures from [proto](https://github.com/nspcc-dev/neofs-sdk-go/proto) packages as well as helper functions for simplifying node/dApp implementations. ## Repository structure diff --git a/client/accounting.go b/client/accounting.go index 8be2ec4e..e0372035 100644 --- a/client/accounting.go +++ b/client/accounting.go @@ -2,14 +2,17 @@ package client import ( "context" + "fmt" "time" - v2accounting "github.com/nspcc-dev/neofs-api-go/v2/accounting" - protoaccounting "github.com/nspcc-dev/neofs-api-go/v2/accounting/grpc" - "github.com/nspcc-dev/neofs-api-go/v2/refs" "github.com/nspcc-dev/neofs-sdk-go/accounting" + apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status" + neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" + protoaccounting "github.com/nspcc-dev/neofs-sdk-go/proto/accounting" + protosession "github.com/nspcc-dev/neofs-sdk-go/proto/session" "github.com/nspcc-dev/neofs-sdk-go/stat" "github.com/nspcc-dev/neofs-sdk-go/user" + "github.com/nspcc-dev/neofs-sdk-go/version" ) // PrmBalanceGet groups parameters of BalanceGet operation. @@ -49,60 +52,66 @@ func (c *Client) BalanceGet(ctx context.Context, prm PrmBalanceGet) (accounting. return accounting.Decimal{}, err } - // form request body - var accountV2 refs.OwnerID - prm.account.WriteToV2(&accountV2) - - var body v2accounting.BalanceRequestBody - body.SetOwnerID(&accountV2) + req := &protoaccounting.BalanceRequest{ + Body: &protoaccounting.BalanceRequest_Body{ + OwnerId: prm.account.ProtoMessage(), + }, + MetaHeader: &protosession.RequestMetaHeader{ + Version: version.Current().ProtoMessage(), + Ttl: defaultRequestTTL, + }, + } + writeXHeadersToMeta(prm.xHeaders, req.MetaHeader) - // form request - var req v2accounting.BalanceRequest + var res accounting.Decimal - req.SetBody(&body) + buf := c.buffers.Get().(*[]byte) + defer func() { c.buffers.Put(buf) }() - // init call context + req.VerifyHeader, err = neofscrypto.SignRequestWithBuffer[*protoaccounting.BalanceRequest_Body](c.prm.signer, req, *buf) + if err != nil { + err = fmt.Errorf("%w: %w", errSignRequest, err) + return res, err + } - var ( - cc contextCall - res accounting.Decimal - ) + resp, err := c.accounting.Balance(ctx, req) + if err != nil { + err = rpcErr(err) + return res, err + } - c.initCallContext(&cc) - cc.meta = prm.prmCommonMeta - cc.req = &req - cc.call = func() (responseV2, error) { - resp, err := c.accounting.Balance(ctx, req.ToGRPCMessage().(*protoaccounting.BalanceRequest)) + if c.prm.cbRespInfo != nil { + err = c.prm.cbRespInfo(ResponseMetaInfo{ + key: resp.GetVerifyHeader().GetBodySignature().GetKey(), + epoch: resp.GetMetaHeader().GetEpoch(), + }) if err != nil { - return nil, rpcErr(err) - } - var respV2 v2accounting.BalanceResponse - if err = respV2.FromGRPCMessage(resp); err != nil { - return nil, err + err = fmt.Errorf("%w: %w", errResponseCallback, err) + return res, err } - return &respV2, nil } - cc.result = func(r responseV2) { - resp := r.(*v2accounting.BalanceResponse) - const fieldBalance = "balance" + if err = neofscrypto.VerifyResponseWithBuffer[*protoaccounting.BalanceResponse_Body](resp, *buf); err != nil { + err = fmt.Errorf("%w: %w", errResponseSignatures, err) + return res, err + } - bal := resp.GetBody().GetBalance() - if bal == nil { - cc.err = newErrMissingResponseField(fieldBalance) - return - } + if err = apistatus.ToError(resp.GetMetaHeader().GetStatus()); err != nil { + return res, err + } - cc.err = res.ReadFromV2(*bal) - if cc.err != nil { - cc.err = newErrInvalidResponseField(fieldBalance, cc.err) - } + const fieldBalance = "balance" + + bal := resp.GetBody().GetBalance() + if bal == nil { + err = newErrMissingResponseField(fieldBalance) + return res, err } - // process call - if !cc.processCall() { - err = cc.err - return accounting.Decimal{}, cc.err + err = res.FromProtoMessage(bal) + if err != nil { + err = newErrInvalidResponseField(fieldBalance, err) + return res, err } return res, nil diff --git a/client/accounting_test.go b/client/accounting_test.go index 5abc2f00..db85b525 100644 --- a/client/accounting_test.go +++ b/client/accounting_test.go @@ -7,8 +7,7 @@ import ( "testing" "time" - v2accounting "github.com/nspcc-dev/neofs-api-go/v2/accounting" - protoaccounting "github.com/nspcc-dev/neofs-api-go/v2/accounting/grpc" + protoaccounting "github.com/nspcc-dev/neofs-sdk-go/proto/accounting" "github.com/nspcc-dev/neofs-sdk-go/stat" "github.com/nspcc-dev/neofs-sdk-go/user" usertest "github.com/nspcc-dev/neofs-sdk-go/user/test" @@ -35,17 +34,9 @@ type testGetBalanceServer struct { protoaccounting.UnimplementedAccountingServiceServer testCommonUnaryServerSettings[ *protoaccounting.BalanceRequest_Body, - v2accounting.BalanceRequestBody, - *v2accounting.BalanceRequestBody, *protoaccounting.BalanceRequest, - v2accounting.BalanceRequest, - *v2accounting.BalanceRequest, *protoaccounting.BalanceResponse_Body, - v2accounting.BalanceResponseBody, - *v2accounting.BalanceResponseBody, *protoaccounting.BalanceResponse, - v2accounting.BalanceResponse, - *v2accounting.BalanceResponse, ] reqAcc *user.ID } diff --git a/client/client.go b/client/client.go index 6e6a6d42..02441748 100644 --- a/client/client.go +++ b/client/client.go @@ -9,15 +9,15 @@ import ( "time" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" - protoaccounting "github.com/nspcc-dev/neofs-api-go/v2/accounting/grpc" - protocontainer "github.com/nspcc-dev/neofs-api-go/v2/container/grpc" - protonetmap "github.com/nspcc-dev/neofs-api-go/v2/netmap/grpc" - protoobject "github.com/nspcc-dev/neofs-api-go/v2/object/grpc" - protoreputation "github.com/nspcc-dev/neofs-api-go/v2/reputation/grpc" - protosession "github.com/nspcc-dev/neofs-api-go/v2/session/grpc" neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa" "github.com/nspcc-dev/neofs-sdk-go/internal/uriutil" + protoaccounting "github.com/nspcc-dev/neofs-sdk-go/proto/accounting" + protocontainer "github.com/nspcc-dev/neofs-sdk-go/proto/container" + protonetmap "github.com/nspcc-dev/neofs-sdk-go/proto/netmap" + protoobject "github.com/nspcc-dev/neofs-sdk-go/proto/object" + protoreputation "github.com/nspcc-dev/neofs-sdk-go/proto/reputation" + protosession "github.com/nspcc-dev/neofs-sdk-go/proto/session" "github.com/nspcc-dev/neofs-sdk-go/stat" "google.golang.org/grpc" "google.golang.org/grpc/credentials" @@ -231,7 +231,7 @@ type PrmInit struct { cbRespInfo func(ResponseMetaInfo) error - netMagic uint64 + netMagic uint64 //nolint:unused // https://github.com/nspcc-dev/neofs-sdk-go/issues/671 statisticCallback stat.OperationCallback diff --git a/client/client_test.go b/client/client_test.go index 2d26d6f9..f58144e9 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -13,20 +13,17 @@ import ( "testing" "time" - protonetmap "github.com/nspcc-dev/neofs-api-go/v2/netmap/grpc" - protorefs "github.com/nspcc-dev/neofs-api-go/v2/refs/grpc" - apigrpc "github.com/nspcc-dev/neofs-api-go/v2/rpc/grpc" - apisession "github.com/nspcc-dev/neofs-api-go/v2/session" - protosession "github.com/nspcc-dev/neofs-api-go/v2/session/grpc" - protostatus "github.com/nspcc-dev/neofs-api-go/v2/status/grpc" apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" - cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test" neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" neofscryptotest "github.com/nspcc-dev/neofs-sdk-go/crypto/test" - "github.com/nspcc-dev/neofs-sdk-go/eacl" + eacltest "github.com/nspcc-dev/neofs-sdk-go/eacl/test" + neofsproto "github.com/nspcc-dev/neofs-sdk-go/internal/proto" + protonetmap "github.com/nspcc-dev/neofs-sdk-go/proto/netmap" + protorefs "github.com/nspcc-dev/neofs-sdk-go/proto/refs" + protosession "github.com/nspcc-dev/neofs-sdk-go/proto/session" + protostatus "github.com/nspcc-dev/neofs-sdk-go/proto/status" "github.com/nspcc-dev/neofs-sdk-go/stat" - usertest "github.com/nspcc-dev/neofs-sdk-go/user/test" "github.com/nspcc-dev/neofs-sdk-go/version" "github.com/stretchr/testify/require" "google.golang.org/grpc" @@ -321,17 +318,7 @@ var ( } ) -// TODO: use eacltest.Table() after https://github.com/nspcc-dev/neofs-sdk-go/issues/606 -var anyValidEACL = eacl.NewTableForContainer(cidtest.ID(), []eacl.Record{ - eacl.ConstructRecord(eacl.ActionDeny, eacl.OperationPut, - []eacl.Target{ - eacl.NewTargetByRole(eacl.RoleOthers), - eacl.NewTargetByAccounts(usertest.IDs(3)), - }, - eacl.NewFilterObjectOwnerEquals(usertest.ID()), - eacl.NewObjectPropertyFilter("attr1", eacl.MatchStringEqual, "val1"), - ), -}) +var anyValidEACL = eacltest.Table() type ( invalidSessionTokenProtoTestcase = struct { @@ -434,10 +421,9 @@ var ( name, msg string corrupt func(valid *protorefs.Checksum) }{ - // TODO: uncomment after https://github.com/nspcc-dev/neofs-sdk-go/issues/606 - // {name: "negative scheme", msg: "negative type -1", corrupt: func(valid *protorefs.Checksum) { - // valid.Type = -1 - // }}, + {name: "negative scheme", msg: "negative type -1", corrupt: func(valid *protorefs.Checksum) { + valid.Type = -1 + }}, {name: "value/nil", msg: "missing value", corrupt: func(valid *protorefs.Checksum) { valid.Sum = nil }}, @@ -449,10 +435,9 @@ var ( name, msg string corrupt func(valid *protorefs.Signature) }{ - // TODO: uncomment after https://github.com/nspcc-dev/neofs-sdk-go/issues/606 - // {name: "negative scheme", msg: "negative scheme -1", corrupt: func(valid *protorefs.Signature) { - // valid.Scheme = -1 - // }}, + {name: "negative scheme", msg: "negative scheme -1", corrupt: func(valid *protorefs.Signature) { + valid.Scheme = -1 + }}, } invalidCommonSessionTokenProtoTestcases = []invalidSessionTokenProtoTestcase{ {name: "body/nil", msg: "missing token body", corrupt: func(valid *protosession.SessionToken) { @@ -551,91 +536,55 @@ func (x *testCommonServerSettings) setSleepDuration(dur time.Duration) { x.handl // provides generic server code for various NeoFS API unary RPC servers. type testCommonUnaryServerSettings[ - REQBODY apigrpc.Message, - REQBODYV2 any, - REQBODYV2PTR interface { - *REQBODYV2 - signedMessageV2 - }, + REQBODY neofsproto.Message, REQ interface { GetBody() REQBODY GetMetaHeader() *protosession.RequestMetaHeader GetVerifyHeader() *protosession.RequestVerificationHeader }, - REQV2 any, - REQV2PTR interface { - *REQV2 - FromGRPCMessage(apigrpc.Message) error - }, - RESPBODY proto.Message, - RESPBODYV2 any, - RESPBODYV2PTR interface { - *RESPBODYV2 - signedMessageV2 + RESPBODY interface { + proto.Message + neofsproto.Message }, RESP interface { GetBody() RESPBODY GetMetaHeader() *protosession.ResponseMetaHeader }, - RESPV2 any, - RESPV2PTR interface { - *RESPV2 - ToGRPCMessage() apigrpc.Message - FromGRPCMessage(apigrpc.Message) error - }, ] struct { testCommonServerSettings - testCommonRequestServerSettings[REQBODY, REQBODYV2, REQBODYV2PTR, REQ, REQV2, REQV2PTR] - testCommonResponseServerSettings[RESPBODY, RESPBODYV2, RESPBODYV2PTR, RESP, RESPV2, RESPV2PTR] + testCommonRequestServerSettings[REQBODY, REQ] + testCommonResponseServerSettings[RESPBODY, RESP] } // provides generic server code for various NeoFS API server-side stream RPC // servers. type testCommonServerStreamServerSettings[ - REQBODY apigrpc.Message, - REQBODYV2 any, - REQBODYV2PTR interface { - *REQBODYV2 - signedMessageV2 - }, + REQBODY neofsproto.Message, REQ interface { GetBody() REQBODY GetMetaHeader() *protosession.RequestMetaHeader GetVerifyHeader() *protosession.RequestVerificationHeader }, - REQV2 any, - REQV2PTR interface { - *REQV2 - FromGRPCMessage(apigrpc.Message) error - }, - RESPBODY proto.Message, - RESPBODYV2 any, - RESPBODYV2PTR interface { - *RESPBODYV2 - signedMessageV2 + RESPBODY interface { + proto.Message + neofsproto.Message }, RESP interface { GetBody() RESPBODY GetMetaHeader() *protosession.ResponseMetaHeader }, - RESPV2 any, - RESPV2PTR interface { - *RESPV2 - ToGRPCMessage() apigrpc.Message - FromGRPCMessage(apigrpc.Message) error - }, ] struct { testCommonServerSettings - testCommonRequestServerSettings[REQBODY, REQBODYV2, REQBODYV2PTR, REQ, REQV2, REQV2PTR] - resps map[uint]testCommonResponseServerSettings[RESPBODY, RESPBODYV2, RESPBODYV2PTR, RESP, RESPV2, RESPV2PTR] + testCommonRequestServerSettings[REQBODY, REQ] + resps map[uint]testCommonResponseServerSettings[RESPBODY, RESP] respErrN uint respErr error } // tunes processing of N-th response starting from 0. -func (x *testCommonServerStreamServerSettings[_, _, _, _, _, _, RESPBODY, RESPBODYV2, RESPBODYV2PTR, RESP, RESPV2, RESPV2PTR]) tuneNResp(n uint, - tune func(*testCommonResponseServerSettings[RESPBODY, RESPBODYV2, RESPBODYV2PTR, RESP, RESPV2, RESPV2PTR])) { - type t = testCommonResponseServerSettings[RESPBODY, RESPBODYV2, RESPBODYV2PTR, RESP, RESPV2, RESPV2PTR] +func (x *testCommonServerStreamServerSettings[_, _, RESPBODY, RESP]) tuneNResp(n uint, + tune func(*testCommonResponseServerSettings[RESPBODY, RESP])) { + type t = testCommonResponseServerSettings[RESPBODY, RESP] if x.resps == nil { x.resps = make(map[uint]t, 1) } @@ -648,8 +597,8 @@ func (x *testCommonServerStreamServerSettings[_, _, _, _, _, _, RESPBODY, RESPBO // response is signed. // // Overrides signResponsesBy. -func (x *testCommonServerStreamServerSettings[_, _, _, _, _, _, RESPBODY, RESPBODYV2, RESPBODYV2PTR, RESP, RESPV2, RESPV2PTR]) respondWithoutSigning(n uint) { - x.tuneNResp(n, func(s *testCommonResponseServerSettings[RESPBODY, RESPBODYV2, RESPBODYV2PTR, RESP, RESPV2, RESPV2PTR]) { +func (x *testCommonServerStreamServerSettings[_, _, RESPBODY, RESP]) respondWithoutSigning(n uint) { + x.tuneNResp(n, func(s *testCommonResponseServerSettings[RESPBODY, RESP]) { s.respondWithoutSigning() }) } @@ -659,8 +608,8 @@ func (x *testCommonServerStreamServerSettings[_, _, _, _, _, _, RESPBODY, RESPBO // // No-op if signing is disabled using respondWithoutSigning. // nolint:unused // will be needed for https://github.com/nspcc-dev/neofs-sdk-go/issues/653 -func (x *testCommonServerStreamServerSettings[_, _, _, _, _, _, RESPBODY, RESPBODYV2, RESPBODYV2PTR, RESP, RESPV2, RESPV2PTR]) signResponsesBy(n uint, signer ecdsa.PrivateKey) { - x.tuneNResp(n, func(s *testCommonResponseServerSettings[RESPBODY, RESPBODYV2, RESPBODYV2PTR, RESP, RESPV2, RESPV2PTR]) { +func (x *testCommonServerStreamServerSettings[_, _, RESPBODY, RESP]) signResponsesBy(n uint, signer ecdsa.PrivateKey) { + x.tuneNResp(n, func(s *testCommonResponseServerSettings[RESPBODY, RESP]) { s.signResponsesBy(signer) }) } @@ -670,8 +619,8 @@ func (x *testCommonServerStreamServerSettings[_, _, _, _, _, _, RESPBODY, RESPBO // // Overrides respondWithStatus. // nolint:unused // will be needed for https://github.com/nspcc-dev/neofs-sdk-go/issues/653 -func (x *testCommonServerStreamServerSettings[_, _, _, _, _, _, RESPBODY, RESPBODYV2, RESPBODYV2PTR, RESP, RESPV2, RESPV2PTR]) respondWithMeta(n uint, meta *protosession.ResponseMetaHeader) { - x.tuneNResp(n, func(s *testCommonResponseServerSettings[RESPBODY, RESPBODYV2, RESPBODYV2PTR, RESP, RESPV2, RESPV2PTR]) { +func (x *testCommonServerStreamServerSettings[_, _, RESPBODY, RESP]) respondWithMeta(n uint, meta *protosession.ResponseMetaHeader) { + x.tuneNResp(n, func(s *testCommonResponseServerSettings[RESPBODY, RESP]) { s.respondWithMeta(meta) }) } @@ -680,16 +629,16 @@ func (x *testCommonServerStreamServerSettings[_, _, _, _, _, _, RESPBODY, RESPBO // status OK is returned. // // Overrides respondWithMeta. -func (x *testCommonServerStreamServerSettings[_, _, _, _, _, _, RESPBODY, RESPBODYV2, RESPBODYV2PTR, RESP, RESPV2, RESPV2PTR]) respondWithStatus(n uint, st *protostatus.Status) { - x.tuneNResp(n, func(s *testCommonResponseServerSettings[RESPBODY, RESPBODYV2, RESPBODYV2PTR, RESP, RESPV2, RESPV2PTR]) { +func (x *testCommonServerStreamServerSettings[_, _, RESPBODY, RESP]) respondWithStatus(n uint, st *protostatus.Status) { + x.tuneNResp(n, func(s *testCommonResponseServerSettings[RESPBODY, RESP]) { s.respondWithStatus(st) }) } // makes the server to return n-th request with the given body. By default, any // valid body is returned. -func (x *testCommonServerStreamServerSettings[_, _, _, _, _, _, RESPBODY, RESPBODYV2, RESPBODYV2PTR, RESP, RESPV2, RESPV2PTR]) respondWithBody(n uint, body RESPBODY) { - x.tuneNResp(n, func(s *testCommonResponseServerSettings[RESPBODY, RESPBODYV2, RESPBODYV2PTR, RESP, RESPV2, RESPV2PTR]) { +func (x *testCommonServerStreamServerSettings[_, _, RESPBODY, RESP]) respondWithBody(n uint, body RESPBODY) { + x.tuneNResp(n, func(s *testCommonResponseServerSettings[RESPBODY, RESP]) { s.respondWithBody(body) }) } @@ -700,7 +649,7 @@ func (x *testCommonServerStreamServerSettings[_, _, _, _, _, _, RESPBODY, RESPBO // returned since it leads to a particular gRPC status. // // Overrides respondWithStatus. -func (x *testCommonServerStreamServerSettings[_, _, _, _, _, _, _, _, _, _, _, _]) abortHandlerAfterResponse(n uint, err error) { +func (x *testCommonServerStreamServerSettings[_, _, _, _]) abortHandlerAfterResponse(n uint, err error) { if n == 0 { x.setHandlerError(err) } else { @@ -711,42 +660,24 @@ func (x *testCommonServerStreamServerSettings[_, _, _, _, _, _, _, _, _, _, _, _ // provides generic server code for various NeoFS API client-side stream RPC // servers. type testCommonClientStreamServerSettings[ - REQBODY apigrpc.Message, - REQBODYV2 any, - REQBODYV2PTR interface { - *REQBODYV2 - signedMessageV2 - }, + REQBODY neofsproto.Message, REQ interface { GetBody() REQBODY GetMetaHeader() *protosession.RequestMetaHeader GetVerifyHeader() *protosession.RequestVerificationHeader }, - REQV2 any, - REQV2PTR interface { - *REQV2 - FromGRPCMessage(apigrpc.Message) error - }, - RESPBODY proto.Message, - RESPBODYV2 any, - RESPBODYV2PTR interface { - *RESPBODYV2 - signedMessageV2 + RESPBODY interface { + proto.Message + neofsproto.Message }, RESP interface { GetBody() RESPBODY GetMetaHeader() *protosession.ResponseMetaHeader }, - RESPV2 any, - RESPV2PTR interface { - *RESPV2 - ToGRPCMessage() apigrpc.Message - FromGRPCMessage(apigrpc.Message) error - }, ] struct { testCommonServerSettings - testCommonRequestServerSettings[REQBODY, REQBODYV2, REQBODYV2PTR, REQ, REQV2, REQV2PTR] - testCommonResponseServerSettings[RESPBODY, RESPBODYV2, RESPBODYV2PTR, RESP, RESPV2, RESPV2PTR] + testCommonRequestServerSettings[REQBODY, REQ] + testCommonResponseServerSettings[RESPBODY, RESP] reqCounter uint reqErrN uint reqErr error @@ -759,7 +690,7 @@ type testCommonClientStreamServerSettings[ // that nil error is also returned since it leads to a particular gRPC status. // // Overrides respondWithStatusOnRequest. -func (x *testCommonClientStreamServerSettings[_, _, _, _, _, _, _, _, _, _, _, _]) abortHandlerAfterRequest(n uint, err error) { +func (x *testCommonClientStreamServerSettings[_, _, _, _]) abortHandlerAfterRequest(n uint, err error) { if n == 0 { x.setHandlerError(err) } else { @@ -769,27 +700,17 @@ func (x *testCommonClientStreamServerSettings[_, _, _, _, _, _, _, _, _, _, _, _ // makes the server to immediately respond right after the n-th request // received. -func (x *testCommonClientStreamServerSettings[_, _, _, _, _, _, _, _, _, _, _, _]) respondAfterRequest(n uint) { +func (x *testCommonClientStreamServerSettings[_, _, _, _]) respondAfterRequest(n uint) { x.respN = n } type testCommonRequestServerSettings[ - REQBODY apigrpc.Message, - REQBODYV2 any, - REQBODYV2PTR interface { - *REQBODYV2 - signedMessageV2 - }, + REQBODY neofsproto.Message, REQ interface { GetBody() REQBODY GetMetaHeader() *protosession.RequestMetaHeader GetVerifyHeader() *protosession.RequestVerificationHeader }, - REQV2 any, - REQV2PTR interface { - *REQV2 - FromGRPCMessage(apigrpc.Message) error - }, ] struct { reqCreds *authCredentials reqXHdrs []string @@ -797,7 +718,7 @@ type testCommonRequestServerSettings[ // makes the server to assert that any request has given X-headers. By default, // and if empty, no headers are expected. -func (x *testCommonRequestServerSettings[_, _, _, _, _, _]) checkRequestXHeaders(xhdrs []string) { +func (x *testCommonRequestServerSettings[_, _]) checkRequestXHeaders(xhdrs []string) { if len(xhdrs)%2 != 0 { panic("odd number of elements") } @@ -808,12 +729,12 @@ func (x *testCommonRequestServerSettings[_, _, _, _, _, _]) checkRequestXHeaders // signer is accepted. // // Has no effect with checkRequestDataSignature. -func (x *testCommonRequestServerSettings[_, _, _, _, _, _]) authenticateRequest(s neofscrypto.Signer) { +func (x *testCommonRequestServerSettings[_, _]) authenticateRequest(s neofscrypto.Signer) { c := authCredentialsFromSigner(s) x.reqCreds = &c } -func (x testCommonRequestServerSettings[REQBODY, REQBODYV2, REQBODYV2PTR, REQ, _, _]) verifyRequest(req REQ) error { +func (x testCommonRequestServerSettings[REQBODY, REQ]) verifyRequest(req REQ) error { body := req.GetBody() metaHdr := req.GetMetaHeader() verifyHdr := req.GetVerifyHeader() @@ -825,16 +746,16 @@ func (x testCommonRequestServerSettings[REQBODY, REQBODYV2, REQBODYV2PTR, REQ, _ if verifyHdr.Origin != nil { return newInvalidRequestVerificationHeaderErr(errors.New("origin field is set while should not be")) } - if err := verifyMessageSignature[REQBODY, REQBODYV2, REQBODYV2PTR]( - body, verifyHdr.BodySignature, x.reqCreds); err != nil { + if err := verifyDataSignature( + neofsproto.MarshalMessage(body), verifyHdr.BodySignature, x.reqCreds); err != nil { return newInvalidRequestVerificationHeaderErr(fmt.Errorf("body signature: %w", err)) } - if err := verifyMessageSignature[*protosession.RequestMetaHeader, apisession.RequestMetaHeader, *apisession.RequestMetaHeader]( - metaHdr, verifyHdr.MetaSignature, x.reqCreds); err != nil { + if err := verifyDataSignature( + neofsproto.MarshalMessage(metaHdr), verifyHdr.MetaSignature, x.reqCreds); err != nil { return newInvalidRequestVerificationHeaderErr(fmt.Errorf("meta signature: %w", err)) } - if err := verifyMessageSignature[*protosession.RequestVerificationHeader, apisession.RequestVerificationHeader, *apisession.RequestVerificationHeader]( - verifyHdr.Origin, verifyHdr.OriginSignature, x.reqCreds); err != nil { + if err := verifyDataSignature( + neofsproto.MarshalMessage(verifyHdr.Origin), verifyHdr.OriginSignature, x.reqCreds); err != nil { return newInvalidRequestVerificationHeaderErr(fmt.Errorf("verification header's origin signature: %w", err)) } // meta header @@ -871,22 +792,14 @@ func (x testCommonRequestServerSettings[REQBODY, REQBODYV2, REQBODYV2PTR, REQ, _ } type testCommonResponseServerSettings[ - RESPBODY proto.Message, - RESPBODYV2 any, - RESPBODYV2PTR interface { - *RESPBODYV2 - signedMessageV2 + RESPBODY interface { + proto.Message + neofsproto.Message }, RESP interface { GetBody() RESPBODY GetMetaHeader() *protosession.ResponseMetaHeader }, - RESPV2 any, - RESPV2PTR interface { - *RESPV2 - ToGRPCMessage() apigrpc.Message - FromGRPCMessage(apigrpc.Message) error - }, ] struct { respUnsigned bool respSigner *ecdsa.PrivateKey @@ -899,7 +812,7 @@ type testCommonResponseServerSettings[ // response is signed. // // Overrides signResponsesBy. -func (x *testCommonResponseServerSettings[_, _, _, _, _, _]) respondWithoutSigning() { +func (x *testCommonResponseServerSettings[_, _]) respondWithoutSigning() { x.respUnsigned = true } @@ -907,7 +820,7 @@ func (x *testCommonResponseServerSettings[_, _, _, _, _, _]) respondWithoutSigni // if nil, random signer is used. // // No-op if signing is disabled using respondWithoutSigning. -func (x *testCommonResponseServerSettings[_, _, _, _, _, _]) signResponsesBy(key ecdsa.PrivateKey) { +func (x *testCommonResponseServerSettings[_, _]) signResponsesBy(key ecdsa.PrivateKey) { x.respSigner = &key } @@ -915,7 +828,7 @@ func (x *testCommonResponseServerSettings[_, _, _, _, _, _]) signResponsesBy(key // and if nil, no header is attached. // // Overrides respondWithStatus. -func (x *testCommonResponseServerSettings[_, _, _, _, _, _]) respondWithMeta(meta *protosession.ResponseMetaHeader) { +func (x *testCommonResponseServerSettings[_, _]) respondWithMeta(meta *protosession.ResponseMetaHeader) { x.respMeta = meta } @@ -923,18 +836,18 @@ func (x *testCommonResponseServerSettings[_, _, _, _, _, _]) respondWithMeta(met // OK is returned. // // Overrides respondWithMeta. -func (x *testCommonResponseServerSettings[_, _, _, _, _, _]) respondWithStatus(st *protostatus.Status) { +func (x *testCommonResponseServerSettings[_, _]) respondWithStatus(st *protostatus.Status) { x.respondWithMeta(&protosession.ResponseMetaHeader{Status: st}) } // makes the server to always respond with the given body. By default, any valid // body is returned. -func (x *testCommonResponseServerSettings[RESPBODY, _, _, _, _, _]) respondWithBody(body RESPBODY) { +func (x *testCommonResponseServerSettings[RESPBODY, _]) respondWithBody(body RESPBODY) { x.respBody = proto.Clone(body).(RESPBODY) x.respBodyForced = true } -func (x testCommonResponseServerSettings[_, RESPBODYV2, RESPBODYV2PTR, RESP, RESPV2, RESPV2PTR]) signResponse(resp RESP) (*protosession.ResponseVerificationHeader, error) { +func (x testCommonResponseServerSettings[_, RESP]) signResponse(resp RESP) (*protosession.ResponseVerificationHeader, error) { if x.respUnsigned { return nil, nil } @@ -945,17 +858,17 @@ func (x testCommonResponseServerSettings[_, RESPBODYV2, RESPBODYV2PTR, RESP, RES signer = neofscryptotest.ECDSAPrivateKey() } // body - bs, err := signMessage(signer, resp.GetBody(), RESPBODYV2PTR(nil)) + bs, err := signMessage(signer, resp.GetBody()) if err != nil { return nil, fmt.Errorf("sign body: %w", err) } // meta - ms, err := signMessage(signer, resp.GetMetaHeader(), (*apisession.ResponseMetaHeader)(nil)) + ms, err := signMessage(signer, resp.GetMetaHeader()) if err != nil { return nil, fmt.Errorf("sign meta: %w", err) } // origin - ors, err := signMessage(signer, (*protosession.ResponseVerificationHeader)(nil), (*apisession.ResponseVerificationHeader)(nil)) + ors, err := signMessage(signer, (*protosession.ResponseVerificationHeader)(nil)) if err != nil { return nil, fmt.Errorf("sign verification header's origin: %w", err) } diff --git a/client/common.go b/client/common.go index c48cd846..bfe56223 100644 --- a/client/common.go +++ b/client/common.go @@ -5,12 +5,7 @@ import ( "fmt" "time" - "github.com/nspcc-dev/neofs-api-go/v2/refs" - "github.com/nspcc-dev/neofs-api-go/v2/rpc/client" - v2session "github.com/nspcc-dev/neofs-api-go/v2/session" - apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status" - neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" - "github.com/nspcc-dev/neofs-sdk-go/version" + protosession "github.com/nspcc-dev/neofs-sdk-go/proto/session" "google.golang.org/grpc/encoding" "google.golang.org/grpc/encoding/proto" ) @@ -22,6 +17,11 @@ const ( fieldNumSigScheme = 3 ) +const ( + localRequestTTL = 1 + defaultRequestTTL = 2 +) + // groups meta parameters shared between all Client operations. type prmCommonMeta struct { // NeoFS request X-Headers @@ -40,7 +40,7 @@ func (x *prmCommonMeta) WithXHeaders(hs ...string) { x.xHeaders = hs } -func writeXHeadersToMeta(xHeaders []string, h *v2session.RequestMetaHeader) { +func writeXHeadersToMeta(xHeaders []string, h *protosession.RequestMetaHeader) { if len(xHeaders) == 0 { return } @@ -49,280 +49,13 @@ func writeXHeadersToMeta(xHeaders []string, h *v2session.RequestMetaHeader) { panic("slice of X-Headers with odd length") } - hs := make([]v2session.XHeader, len(xHeaders)/2) + h.XHeaders = make([]*protosession.XHeader, len(xHeaders)/2) j := 0 for i := 0; i < len(xHeaders); i += 2 { - hs[j].SetKey(xHeaders[i]) - hs[j].SetValue(xHeaders[i+1]) + h.XHeaders[j] = &protosession.XHeader{Key: xHeaders[i], Value: xHeaders[i+1]} j++ } - - h.SetXHeaders(hs) -} - -// groups all the details required to send a single request and process a response to it. -type contextCall struct { - // ================================================== - // state vars that do not require explicit initialization - - // final error to be returned from client method - err error - - // received response - resp responseV2 - - // ================================================== - // shared parameters which are set uniformly on all calls - - // request signer - signer neofscrypto.Signer - - // callback prior to processing the response by the client - callbackResp func(ResponseMetaInfo) error - - // NeoFS network magic - netMagic uint64 - - // Meta parameters - meta prmCommonMeta - - // ================================================== - // custom call parameters - - // request to be signed with a signer and sent - req request - - // function to send a request (unary) and receive a response - call func() (responseV2, error) - - // function to send the request (req field) - wReq func() error - - // function to recv the response (resp field) - rResp func() error - - // function to close the message stream - closer func() error - - // function of writing response fields to the resulting structure (optional) - result func(v2 responseV2) - - buf []byte - bufCleanCallback func() -} - -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() - if meta == nil { - meta = new(v2session.RequestMetaHeader) - x.req.SetMetaHeader(meta) - } - - if meta.GetTTL() == 0 { - meta.SetTTL(2) - } - - if meta.GetVersion() == nil { - var verV2 refs.Version - version.Current().WriteToV2(&verV2) - meta.SetVersion(&verV2) - } - - meta.SetNetworkMagic(x.netMagic) - - writeXHeadersToMeta(x.meta.xHeaders, 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 { - x.prepareRequest() - - x.req.SetVerificationHeader(nil) - - // sign the request - x.err = signServiceMessage(x.signer, x.req, x.buf) - if x.err != nil { - x.err = fmt.Errorf("sign request: %w", x.err) - return false - } - - x.err = x.wReq() - if x.err != nil { - x.err = fmt.Errorf("write request: %w", x.err) - return false - } - - return true -} - -// performs common actions of response processing and writes any problem as a result status or client error -// (in both cases returns false). -// -// Actions: -// - verify signature (internal); -// - call response callback (internal); -// - unwrap status error (optional). -func (x *contextCall) processResponse() bool { - // call response callback if set - if x.callbackResp != nil { - x.err = x.callbackResp(ResponseMetaInfo{ - key: x.resp.GetVerificationHeader().GetBodySignature().GetKey(), - epoch: x.resp.GetMetaHeader().GetEpoch(), - }) - if x.err != nil { - x.err = fmt.Errorf("response callback error: %w", x.err) - return false - } - } - - // note that we call response callback before signature check since it is expected more lightweight - // while verification needs marshaling - - // verify response signature - x.err = verifyServiceMessage(x.resp) - if x.err != nil { - x.err = fmt.Errorf("invalid response signature: %w", x.err) - return false - } - - // get result status - x.err = apistatus.ErrorFromV2(x.resp.GetMetaHeader().GetStatus()) - return x.err == nil -} - -// processResponse verifies response signature. -func (c *Client) processResponse(resp responseV2) error { - if err := verifyServiceMessage(resp); err != nil { - return fmt.Errorf("invalid response signature: %w", err) - } - - return apistatus.ErrorFromV2(resp.GetMetaHeader().GetStatus()) -} - -// reads response (if rResp is set) and processes it. Result means success. -// If failed, contextCall.err contains the reason. -func (x *contextCall) readResponse() bool { - if x.rResp != nil { - x.err = x.rResp() - if x.err != nil { - x.err = fmt.Errorf("read response: %w", x.err) - return false - } - } - - return x.processResponse() -} - -// closes the message stream (if closer is set) and writes the results (if result is set). -// Return means success. If failed, contextCall.err contains the reason. -func (x *contextCall) close() bool { - if x.closer != nil { - x.err = x.closer() - if x.err != nil { - x.err = fmt.Errorf("close RPC: %w", x.err) - return false - } - } - - // write response to resulting structure - if x.result != nil { - x.result(x.resp) - } - - return x.err == nil -} - -// goes through all stages of sending a request and processing a response. Returns true if successful. -// If failed, contextCall.err contains the reason. -func (x *contextCall) processCall() bool { - // set request writer - x.wReq = func() error { - var err error - x.resp, err = x.call() - return err - } - - // write request - ok := x.writeRequest() - if x.bufCleanCallback != nil { - x.bufCleanCallback() - } - - if !ok { - return false - } - - // read response - ok = x.readResponse() - if !ok { - return x.err == nil - } - - // close and write response to resulting structure - ok = x.close() - if !ok { - return false - } - - return x.err == nil -} - -// initializes static cross-call parameters inherited from client. -func (c *Client) initCallContext(ctx *contextCall) { - ctx.signer = c.prm.signer - ctx.callbackResp = c.prm.cbRespInfo - ctx.netMagic = c.prm.netMagic - - buf := c.buffers.Get().(*[]byte) - ctx.buf = *buf - ctx.bufCleanCallback = func() { - c.buffers.Put(buf) - } -} - -// ExecRaw executes f with underlying github.com/nspcc-dev/neofs-api-go/v2/rpc/client.Client -// instance. Communicate over the Protocol Buffers protocol in a more flexible way: -// most often used to transmit data over a fixed version of the NeoFS protocol, as well -// as to support custom services. -// -// The f must not manipulate the client connection passed into it. -// -// Like all other operations, must be called after connecting to the server and -// before closing the connection. -// -// See also Dial and Close. -// See also github.com/nspcc-dev/neofs-api-go/v2/rpc/client package docs. -// Deprecated: use [Client.Conn] instead. -func (c *Client) ExecRaw(f func(client *client.Client) error) error { - return f(client.New(client.WithGRPCConn(c.conn), client.WithRWTimeout(c.streamTimeout))) } type onlyBinarySendingCodec struct{} diff --git a/client/container.go b/client/container.go index e81f94a0..c10487fe 100644 --- a/client/container.go +++ b/client/container.go @@ -6,17 +6,19 @@ import ( "fmt" "time" - v2container "github.com/nspcc-dev/neofs-api-go/v2/container" - protocontainer "github.com/nspcc-dev/neofs-api-go/v2/container/grpc" - "github.com/nspcc-dev/neofs-api-go/v2/refs" - v2session "github.com/nspcc-dev/neofs-api-go/v2/session" + 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" neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" "github.com/nspcc-dev/neofs-sdk-go/eacl" + neofsproto "github.com/nspcc-dev/neofs-sdk-go/internal/proto" + protocontainer "github.com/nspcc-dev/neofs-sdk-go/proto/container" + "github.com/nspcc-dev/neofs-sdk-go/proto/refs" + protosession "github.com/nspcc-dev/neofs-sdk-go/proto/session" "github.com/nspcc-dev/neofs-sdk-go/session" "github.com/nspcc-dev/neofs-sdk-go/stat" "github.com/nspcc-dev/neofs-sdk-go/user" + "github.com/nspcc-dev/neofs-sdk-go/version" ) // PrmContainerPut groups optional parameters of ContainerPut operation. @@ -83,9 +85,6 @@ func (c *Client) ContainerPut(ctx context.Context, cont container.Container, sig return cid.ID{}, ErrMissingSigner } - var cnr v2container.Container - cont.WriteToV2(&cnr) - if !prm.sigSet { if err = cont.CalculateSignature(&prm.sig, signer); err != nil { err = fmt.Errorf("calculate container signature: %w", err) @@ -93,73 +92,73 @@ func (c *Client) ContainerPut(ctx context.Context, cont container.Container, sig } } - var sigv2 refs.Signature - - prm.sig.WriteToV2(&sigv2) - - // form request body - reqBody := new(v2container.PutRequestBody) - reqBody.SetContainer(&cnr) - reqBody.SetSignature(&sigv2) - - // form meta header - var meta v2session.RequestMetaHeader - writeXHeadersToMeta(prm.prmCommonMeta.xHeaders, &meta) - + req := &protocontainer.PutRequest{ + Body: &protocontainer.PutRequest_Body{ + Container: cont.ProtoMessage(), + Signature: &refs.SignatureRFC6979{ + Key: prm.sig.PublicKeyBytes(), + Sign: prm.sig.Value(), + }, + }, + MetaHeader: &protosession.RequestMetaHeader{ + Version: version.Current().ProtoMessage(), + Ttl: defaultRequestTTL, + }, + } + writeXHeadersToMeta(prm.xHeaders, req.MetaHeader) if prm.sessionSet { - var tokv2 v2session.Token - prm.session.WriteToV2(&tokv2) - - meta.SetSessionToken(&tokv2) + req.MetaHeader.SessionToken = prm.session.ProtoMessage() } - // form request - var req v2container.PutRequest + var res cid.ID - req.SetBody(reqBody) - req.SetMetaHeader(&meta) + buf := c.buffers.Get().(*[]byte) + defer func() { c.buffers.Put(buf) }() - // init call context + req.VerifyHeader, err = neofscrypto.SignRequestWithBuffer[*protocontainer.PutRequest_Body](c.prm.signer, req, *buf) + if err != nil { + err = fmt.Errorf("%w: %w", errSignRequest, err) + return res, err + } - var ( - cc contextCall - res cid.ID - ) + resp, err := c.container.Put(ctx, req) + if err != nil { + err = rpcErr(err) + return res, err + } - c.initCallContext(&cc) - cc.req = &req - cc.call = func() (responseV2, error) { - resp, err := c.container.Put(ctx, req.ToGRPCMessage().(*protocontainer.PutRequest)) + if c.prm.cbRespInfo != nil { + err = c.prm.cbRespInfo(ResponseMetaInfo{ + key: resp.GetVerifyHeader().GetBodySignature().GetKey(), + epoch: resp.GetMetaHeader().GetEpoch(), + }) if err != nil { - return nil, rpcErr(err) + err = fmt.Errorf("%w: %w", errResponseCallback, err) + return res, err } - var respV2 v2container.PutResponse - if err = respV2.FromGRPCMessage(resp); err != nil { - return nil, err - } - return &respV2, nil } - cc.result = func(r responseV2) { - resp := r.(*v2container.PutResponse) - const fieldCnrID = "container ID" + if err = neofscrypto.VerifyResponseWithBuffer[*protocontainer.PutResponse_Body](resp, *buf); err != nil { + err = fmt.Errorf("%w: %w", errResponseSignatures, err) + return res, err + } - cidV2 := resp.GetBody().GetContainerID() - if cidV2 == nil { - cc.err = newErrMissingResponseField(fieldCnrID) - return - } + if err = apistatus.ToError(resp.GetMetaHeader().GetStatus()); err != nil { + return res, err + } - cc.err = res.ReadFromV2(*cidV2) - if cc.err != nil { - cc.err = newErrInvalidResponseField(fieldCnrID, cc.err) - } + const fieldCnrID = "container ID" + + mCID := resp.GetBody().GetContainerId() + if mCID == nil { + err = newErrMissingResponseField(fieldCnrID) + return res, err } - // process call - if !cc.processCall() { - err = cc.err - return cid.ID{}, cc.err + err = res.FromProtoMessage(mCID) + if err != nil { + err = newErrInvalidResponseField(fieldCnrID, err) + return res, err } return res, nil @@ -185,58 +184,63 @@ func (c *Client) ContainerGet(ctx context.Context, id cid.ID, prm PrmContainerGe }() } - var cidV2 refs.ContainerID - id.WriteToV2(&cidV2) - - // form request body - reqBody := new(v2container.GetRequestBody) - reqBody.SetContainerID(&cidV2) - - // form request - var req v2container.GetRequest + req := &protocontainer.GetRequest{ + Body: &protocontainer.GetRequest_Body{ + ContainerId: id.ProtoMessage(), + }, + MetaHeader: &protosession.RequestMetaHeader{ + Version: version.Current().ProtoMessage(), + Ttl: 2, + }, + } + writeXHeadersToMeta(prm.xHeaders, req.MetaHeader) - req.SetBody(reqBody) + var res container.Container + buf := c.buffers.Get().(*[]byte) + defer func() { c.buffers.Put(buf) }() - // init call context + req.VerifyHeader, err = neofscrypto.SignRequestWithBuffer[*protocontainer.GetRequest_Body](c.prm.signer, req, *buf) + if err != nil { + err = fmt.Errorf("%w: %w", errSignRequest, err) + return res, err + } - var ( - cc contextCall - res container.Container - ) + resp, err := c.container.Get(ctx, req) + if err != nil { + err = rpcErr(err) + return res, err + } - c.initCallContext(&cc) - cc.meta = prm.prmCommonMeta - cc.req = &req - cc.call = func() (responseV2, error) { - resp, err := c.container.Get(ctx, req.ToGRPCMessage().(*protocontainer.GetRequest)) + if c.prm.cbRespInfo != nil { + err = c.prm.cbRespInfo(ResponseMetaInfo{ + key: resp.GetVerifyHeader().GetBodySignature().GetKey(), + epoch: resp.GetMetaHeader().GetEpoch(), + }) if err != nil { - return nil, rpcErr(err) + err = fmt.Errorf("%w: %w", errResponseCallback, err) + return res, err } - var respV2 v2container.GetResponse - if err = respV2.FromGRPCMessage(resp); err != nil { - return nil, err - } - return &respV2, nil } - cc.result = func(r responseV2) { - resp := r.(*v2container.GetResponse) - cnrV2 := resp.GetBody().GetContainer() - if cnrV2 == nil { - cc.err = errors.New("missing container in response") - return - } + if err = neofscrypto.VerifyResponseWithBuffer[*protocontainer.GetResponse_Body](resp, *buf); err != nil { + err = fmt.Errorf("%w: %w", errResponseSignatures, err) + return res, err + } - cc.err = res.ReadFromV2(*cnrV2) - if cc.err != nil { - cc.err = fmt.Errorf("invalid container in response: %w", cc.err) - } + if err = apistatus.ToError(resp.GetMetaHeader().GetStatus()); err != nil { + return res, err } - // process call - if !cc.processCall() { - err = cc.err - return container.Container{}, cc.err + mc := resp.GetBody().GetContainer() + if mc == nil { + err = errors.New("missing container in response") + return res, err + } + + err = res.FromProtoMessage(mc) + if err != nil { + err = fmt.Errorf("invalid container in response: %w", err) + return res, err } return res, nil @@ -262,57 +266,63 @@ func (c *Client) ContainerList(ctx context.Context, ownerID user.ID, prm PrmCont }() } - // form request body - var ownerV2 refs.OwnerID - ownerID.WriteToV2(&ownerV2) - - reqBody := new(v2container.ListRequestBody) - reqBody.SetOwnerID(&ownerV2) - - // form request - var req v2container.ListRequest + req := &protocontainer.ListRequest{ + Body: &protocontainer.ListRequest_Body{ + OwnerId: ownerID.ProtoMessage(), + }, + MetaHeader: &protosession.RequestMetaHeader{ + Version: version.Current().ProtoMessage(), + Ttl: defaultRequestTTL, + }, + } + writeXHeadersToMeta(prm.xHeaders, req.MetaHeader) - req.SetBody(reqBody) + buf := c.buffers.Get().(*[]byte) + defer func() { c.buffers.Put(buf) }() - // init call context + req.VerifyHeader, err = neofscrypto.SignRequestWithBuffer[*protocontainer.ListRequest_Body](c.prm.signer, req, *buf) + if err != nil { + err = fmt.Errorf("%w: %w", errSignRequest, err) + return nil, err + } - var ( - cc contextCall - res []cid.ID - ) + resp, err := c.container.List(ctx, req) + if err != nil { + err = rpcErr(err) + return nil, err + } - c.initCallContext(&cc) - cc.meta = prm.prmCommonMeta - cc.req = &req - cc.call = func() (responseV2, error) { - resp, err := c.container.List(ctx, req.ToGRPCMessage().(*protocontainer.ListRequest)) + if c.prm.cbRespInfo != nil { + err = c.prm.cbRespInfo(ResponseMetaInfo{ + key: resp.GetVerifyHeader().GetBodySignature().GetKey(), + epoch: resp.GetMetaHeader().GetEpoch(), + }) if err != nil { - return nil, rpcErr(err) - } - var respV2 v2container.ListResponse - if err = respV2.FromGRPCMessage(resp); err != nil { + err = fmt.Errorf("%w: %w", errResponseCallback, err) return nil, err } - return &respV2, nil } - cc.result = func(r responseV2) { - resp := r.(*v2container.ListResponse) - res = make([]cid.ID, len(resp.GetBody().GetContainerIDs())) + if err = neofscrypto.VerifyResponseWithBuffer[*protocontainer.ListResponse_Body](resp, *buf); err != nil { + err = fmt.Errorf("%w: %w", errResponseSignatures, err) + return nil, err + } - for i, cidV2 := range resp.GetBody().GetContainerIDs() { - cc.err = res[i].ReadFromV2(cidV2) - if cc.err != nil { - cc.err = fmt.Errorf("invalid ID in the response: %w", cc.err) - return - } - } + if err = apistatus.ToError(resp.GetMetaHeader().GetStatus()); err != nil { + return nil, err } - // process call - if !cc.processCall() { - err = cc.err - return nil, cc.err + ms := resp.GetBody().GetContainerIds() + res := make([]cid.ID, len(ms)) + for i := range ms { + if ms[i] == nil { + err = newErrInvalidResponseField("ID list", fmt.Errorf("nil element #%d", i)) + return nil, err + } + if err = res[i].FromProtoMessage(ms[i]); err != nil { + err = fmt.Errorf("invalid ID in the response: %w", err) + return nil, err + } } return res, nil @@ -383,74 +393,66 @@ func (c *Client) ContainerDelete(ctx context.Context, id cid.ID, signer neofscry return fmt.Errorf("%w: expected ECDSA_DETERMINISTIC_SHA256 scheme", neofscrypto.ErrIncorrectSigner) } - // sign container ID - var cidV2 refs.ContainerID - id.WriteToV2(&cidV2) - - // container contract expects signature of container ID value - // don't get confused with stable marshaled protobuf container.ID structure - data := cidV2.GetValue() - if !prm.sigSet { - if err = prm.sig.Calculate(signer, data); err != nil { + // container contract expects signature of container ID value + // don't get confused with stable marshaled protobuf container.ID structure + if err = prm.sig.Calculate(signer, id[:]); err != nil { err = fmt.Errorf("calculate container ID signature: %w", err) return err } } - var sigv2 refs.Signature - - prm.sig.WriteToV2(&sigv2) - - // form request body - reqBody := new(v2container.DeleteRequestBody) - reqBody.SetContainerID(&cidV2) - reqBody.SetSignature(&sigv2) - - // form meta header - var meta v2session.RequestMetaHeader - writeXHeadersToMeta(prm.prmCommonMeta.xHeaders, &meta) - + req := &protocontainer.DeleteRequest{ + Body: &protocontainer.DeleteRequest_Body{ + ContainerId: id.ProtoMessage(), + Signature: &refs.SignatureRFC6979{ + Key: prm.sig.PublicKeyBytes(), + Sign: prm.sig.Value(), + }, + }, + MetaHeader: &protosession.RequestMetaHeader{ + Version: version.Current().ProtoMessage(), + Ttl: defaultRequestTTL, + }, + } + writeXHeadersToMeta(prm.prmCommonMeta.xHeaders, req.MetaHeader) if prm.tokSet { - var tokv2 v2session.Token - prm.tok.WriteToV2(&tokv2) - - meta.SetSessionToken(&tokv2) + req.MetaHeader.SessionToken = prm.tok.ProtoMessage() } - // form request - var req v2container.DeleteRequest - - req.SetBody(reqBody) - req.SetMetaHeader(&meta) + buf := c.buffers.Get().(*[]byte) + defer func() { c.buffers.Put(buf) }() - // init call context + req.VerifyHeader, err = neofscrypto.SignRequestWithBuffer[*protocontainer.DeleteRequest_Body](c.prm.signer, req, *buf) + if err != nil { + err = fmt.Errorf("%w: %w", errSignRequest, err) + return err + } - var ( - cc contextCall - ) + resp, err := c.container.Delete(ctx, req) + if err != nil { + err = rpcErr(err) + return err + } - c.initCallContext(&cc) - cc.req = &req - cc.call = func() (responseV2, error) { - resp, err := c.container.Delete(ctx, req.ToGRPCMessage().(*protocontainer.DeleteRequest)) + if c.prm.cbRespInfo != nil { + err = c.prm.cbRespInfo(ResponseMetaInfo{ + key: resp.GetVerifyHeader().GetBodySignature().GetKey(), + epoch: resp.GetMetaHeader().GetEpoch(), + }) if err != nil { - return nil, rpcErr(err) - } - var respV2 v2container.DeleteResponse - if err = respV2.FromGRPCMessage(resp); err != nil { - return nil, err + err = fmt.Errorf("%w: %w", errResponseCallback, err) + return err } - return &respV2, nil } - // process call - if !cc.processCall() { - err = cc.err - return cc.err + if err = neofscrypto.VerifyResponseWithBuffer[*protocontainer.DeleteResponse_Body](resp, *buf); err != nil { + err = fmt.Errorf("%w: %w", errResponseSignatures, err) + return err } - return nil + err = apistatus.ToError(resp.GetMetaHeader().GetStatus()) + return err } // PrmContainerEACL groups optional parameters of ContainerEACL operation. @@ -473,56 +475,63 @@ func (c *Client) ContainerEACL(ctx context.Context, id cid.ID, prm PrmContainerE }() } - var cidV2 refs.ContainerID - id.WriteToV2(&cidV2) - - // form request body - reqBody := new(v2container.GetExtendedACLRequestBody) - reqBody.SetContainerID(&cidV2) + req := &protocontainer.GetExtendedACLRequest{ + Body: &protocontainer.GetExtendedACLRequest_Body{ + ContainerId: id.ProtoMessage(), + }, + MetaHeader: &protosession.RequestMetaHeader{ + Version: version.Current().ProtoMessage(), + Ttl: defaultRequestTTL, + }, + } + writeXHeadersToMeta(prm.xHeaders, req.MetaHeader) - // form request - var req v2container.GetExtendedACLRequest + var res eacl.Table - req.SetBody(reqBody) + buf := c.buffers.Get().(*[]byte) + defer func() { c.buffers.Put(buf) }() - // init call context + req.VerifyHeader, err = neofscrypto.SignRequestWithBuffer[*protocontainer.GetExtendedACLRequest_Body](c.prm.signer, req, *buf) + if err != nil { + err = fmt.Errorf("%w: %w", errSignRequest, err) + return res, err + } - var ( - cc contextCall - res eacl.Table - ) + resp, err := c.container.GetExtendedACL(ctx, req) + if err != nil { + err = rpcErr(err) + return res, err + } - c.initCallContext(&cc) - cc.meta = prm.prmCommonMeta - cc.req = &req - cc.call = func() (responseV2, error) { - resp, err := c.container.GetExtendedACL(ctx, req.ToGRPCMessage().(*protocontainer.GetExtendedACLRequest)) + if c.prm.cbRespInfo != nil { + err = c.prm.cbRespInfo(ResponseMetaInfo{ + key: resp.GetVerifyHeader().GetBodySignature().GetKey(), + epoch: resp.GetMetaHeader().GetEpoch(), + }) if err != nil { - return nil, rpcErr(err) - } - var respV2 v2container.GetExtendedACLResponse - if err = respV2.FromGRPCMessage(resp); err != nil { - return nil, err - } - return &respV2, nil - } - cc.result = func(r responseV2) { - resp := r.(*v2container.GetExtendedACLResponse) - const fieldEACL = "eACL" - eACL := resp.GetBody().GetEACL() - if eACL == nil { - cc.err = newErrMissingResponseField(fieldEACL) - return - } - if cc.err = res.ReadFromV2(*eACL); cc.err != nil { - cc.err = newErrInvalidResponseField(fieldEACL, cc.err) + err = fmt.Errorf("%w: %w", errResponseCallback, err) + return res, err } } - // process call - if !cc.processCall() { - err = cc.err - return eacl.Table{}, cc.err + if err = neofscrypto.VerifyResponseWithBuffer[*protocontainer.GetExtendedACLResponse_Body](resp, *buf); err != nil { + err = fmt.Errorf("%w: %w", errResponseSignatures, err) + return res, err + } + + if err = apistatus.ToError(resp.GetMetaHeader().GetStatus()); err != nil { + return res, err + } + + const fieldEACL = "eACL" + eACL := resp.GetBody().GetEacl() + if eACL == nil { + err = newErrMissingResponseField(fieldEACL) + return res, err + } + if err = res.FromProtoMessage(eACL); err != nil { + err = newErrInvalidResponseField(fieldEACL, err) + return res, err } return res, nil @@ -604,67 +613,65 @@ func (c *Client) ContainerSetEACL(ctx context.Context, table eacl.Table, signer } // sign the eACL table - eaclV2 := table.ToV2() + mEACL := table.ProtoMessage() if !prm.sigSet { - if err = prm.sig.CalculateMarshalled(signer, eaclV2, nil); err != nil { + if err = prm.sig.Calculate(signer, neofsproto.MarshalMessage(mEACL)); err != nil { err = fmt.Errorf("calculate eACL signature: %w", err) return err } } - var sigv2 refs.Signature - - prm.sig.WriteToV2(&sigv2) - - // form request body - reqBody := new(v2container.SetExtendedACLRequestBody) - reqBody.SetEACL(eaclV2) - reqBody.SetSignature(&sigv2) - - // form meta header - var meta v2session.RequestMetaHeader - writeXHeadersToMeta(prm.prmCommonMeta.xHeaders, &meta) - + req := &protocontainer.SetExtendedACLRequest{ + Body: &protocontainer.SetExtendedACLRequest_Body{ + Eacl: mEACL, + Signature: &refs.SignatureRFC6979{ + Key: prm.sig.PublicKeyBytes(), + Sign: prm.sig.Value(), + }, + }, + MetaHeader: &protosession.RequestMetaHeader{ + Version: version.Current().ProtoMessage(), + Ttl: defaultRequestTTL, + }, + } + writeXHeadersToMeta(prm.prmCommonMeta.xHeaders, req.MetaHeader) if prm.sessionSet { - var tokv2 v2session.Token - prm.session.WriteToV2(&tokv2) - - meta.SetSessionToken(&tokv2) + req.MetaHeader.SessionToken = prm.session.ProtoMessage() } - // form request - var req v2container.SetExtendedACLRequest + buf := c.buffers.Get().(*[]byte) + defer func() { c.buffers.Put(buf) }() - req.SetBody(reqBody) - req.SetMetaHeader(&meta) - - // init call context + req.VerifyHeader, err = neofscrypto.SignRequestWithBuffer[*protocontainer.SetExtendedACLRequest_Body](c.prm.signer, req, *buf) + if err != nil { + err = fmt.Errorf("%w: %w", errSignRequest, err) + return err + } - var ( - cc contextCall - ) + resp, err := c.container.SetExtendedACL(ctx, req) + if err != nil { + err = rpcErr(err) + return err + } - c.initCallContext(&cc) - cc.req = &req - cc.call = func() (responseV2, error) { - resp, err := c.container.SetExtendedACL(ctx, req.ToGRPCMessage().(*protocontainer.SetExtendedACLRequest)) + if c.prm.cbRespInfo != nil { + err = c.prm.cbRespInfo(ResponseMetaInfo{ + key: resp.GetVerifyHeader().GetBodySignature().GetKey(), + epoch: resp.GetMetaHeader().GetEpoch(), + }) if err != nil { - return nil, rpcErr(err) - } - var respV2 v2container.SetExtendedACLResponse - if err = respV2.FromGRPCMessage(resp); err != nil { - return nil, err + err = fmt.Errorf("%w: %w", errResponseCallback, err) + return err } - return &respV2, nil } - // process call - if !cc.processCall() { - err = cc.err - return cc.err + if err = neofscrypto.VerifyResponseWithBuffer[*protocontainer.SetExtendedACLResponse_Body](resp, *buf); err != nil { + err = fmt.Errorf("%w: %w", errResponseSignatures, err) + return err } - return nil + err = apistatus.ToError(resp.GetMetaHeader().GetStatus()) + return err } // PrmAnnounceSpace groups optional parameters of ContainerAnnounceUsedSpace operation. @@ -702,49 +709,53 @@ func (c *Client) ContainerAnnounceUsedSpace(ctx context.Context, announcements [ return err } - // convert list of SDK announcement structures into NeoFS-API v2 list - v2announce := make([]v2container.UsedSpaceAnnouncement, len(announcements)) + req := &protocontainer.AnnounceUsedSpaceRequest{ + Body: &protocontainer.AnnounceUsedSpaceRequest_Body{ + Announcements: make([]*protocontainer.AnnounceUsedSpaceRequest_Body_Announcement, len(announcements)), + }, + MetaHeader: &protosession.RequestMetaHeader{ + Version: version.Current().ProtoMessage(), + Ttl: defaultRequestTTL, + }, + } for i := range announcements { - announcements[i].WriteToV2(&v2announce[i]) + req.Body.Announcements[i] = announcements[i].ProtoMessage() } + writeXHeadersToMeta(prm.xHeaders, req.MetaHeader) - // prepare body of the NeoFS-API v2 request and request itself - reqBody := new(v2container.AnnounceUsedSpaceRequestBody) - reqBody.SetAnnouncements(v2announce) - - // form request - var req v2container.AnnounceUsedSpaceRequest + buf := c.buffers.Get().(*[]byte) + defer func() { c.buffers.Put(buf) }() - req.SetBody(reqBody) - - // init call context + req.VerifyHeader, err = neofscrypto.SignRequestWithBuffer[*protocontainer.AnnounceUsedSpaceRequest_Body](c.prm.signer, req, *buf) + if err != nil { + err = fmt.Errorf("%w: %w", errSignRequest, err) + return err + } - var ( - cc contextCall - ) + resp, err := c.container.AnnounceUsedSpace(ctx, req) + if err != nil { + err = rpcErr(err) + return err + } - c.initCallContext(&cc) - cc.meta = prm.prmCommonMeta - cc.req = &req - cc.call = func() (responseV2, error) { - resp, err := c.container.AnnounceUsedSpace(ctx, req.ToGRPCMessage().(*protocontainer.AnnounceUsedSpaceRequest)) + if c.prm.cbRespInfo != nil { + err = c.prm.cbRespInfo(ResponseMetaInfo{ + key: resp.GetVerifyHeader().GetBodySignature().GetKey(), + epoch: resp.GetMetaHeader().GetEpoch(), + }) if err != nil { - return nil, rpcErr(err) - } - var respV2 v2container.AnnounceUsedSpaceResponse - if err = respV2.FromGRPCMessage(resp); err != nil { - return nil, err + err = fmt.Errorf("%w: %w", errResponseCallback, err) + return err } - return &respV2, nil } - // process call - if !cc.processCall() { - err = cc.err - return cc.err + if err = neofscrypto.VerifyResponseWithBuffer[*protocontainer.AnnounceUsedSpaceResponse_Body](resp, *buf); err != nil { + err = fmt.Errorf("%w: %w", errResponseSignatures, err) + return err } - return nil + err = apistatus.ToError(resp.GetMetaHeader().GetStatus()) + return err } // SyncContainerWithNetwork requests network configuration using passed [NetworkInfoExecutor] diff --git a/client/container_test.go b/client/container_test.go index 451bedd7..9466c06c 100644 --- a/client/container_test.go +++ b/client/container_test.go @@ -7,15 +7,6 @@ import ( "testing" "time" - v2acl "github.com/nspcc-dev/neofs-api-go/v2/acl" - protoacl "github.com/nspcc-dev/neofs-api-go/v2/acl/grpc" - apicontainer "github.com/nspcc-dev/neofs-api-go/v2/container" - protocontainer "github.com/nspcc-dev/neofs-api-go/v2/container/grpc" - protonetmap "github.com/nspcc-dev/neofs-api-go/v2/netmap/grpc" - "github.com/nspcc-dev/neofs-api-go/v2/refs" - protorefs "github.com/nspcc-dev/neofs-api-go/v2/refs/grpc" - apigrpc "github.com/nspcc-dev/neofs-api-go/v2/rpc/grpc" - protosession "github.com/nspcc-dev/neofs-api-go/v2/session/grpc" "github.com/nspcc-dev/neofs-sdk-go/container" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test" @@ -24,6 +15,12 @@ import ( neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa" neofscryptotest "github.com/nspcc-dev/neofs-sdk-go/crypto/test" "github.com/nspcc-dev/neofs-sdk-go/eacl" + neofsproto "github.com/nspcc-dev/neofs-sdk-go/internal/proto" + protoacl "github.com/nspcc-dev/neofs-sdk-go/proto/acl" + protocontainer "github.com/nspcc-dev/neofs-sdk-go/proto/container" + protonetmap "github.com/nspcc-dev/neofs-sdk-go/proto/netmap" + protorefs "github.com/nspcc-dev/neofs-sdk-go/proto/refs" + protosession "github.com/nspcc-dev/neofs-sdk-go/proto/session" "github.com/nspcc-dev/neofs-sdk-go/session" sessiontest "github.com/nspcc-dev/neofs-sdk-go/session/test" "github.com/nspcc-dev/neofs-sdk-go/stat" @@ -51,12 +48,8 @@ func newTestContainerClient(t testing.TB, srv any) *Client { // for sharing between servers of requests with RFC 6979 signature of particular // data. type testRFC6979DataSignatureServerSettings[ - SIGNED apigrpc.Message, - SIGNEDV2 any, - SIGNEDV2PTR interface { - *SIGNEDV2 - signedMessageV2 - }, + SIGNED neofsproto.Message, + ] struct { reqCreds *authCredentials reqDataSignature *neofscrypto.Signature @@ -66,7 +59,7 @@ type testRFC6979DataSignatureServerSettings[ // default, any signer is accepted. // // Has no effect with checkRequestDataSignature. -func (x *testRFC6979DataSignatureServerSettings[_, _, _]) authenticateRequestPayload(s neofscrypto.Signer) { +func (x *testRFC6979DataSignatureServerSettings[_]) authenticateRequestPayload(s neofscrypto.Signer) { c := authCredentialsFromSigner(s) x.reqCreds = &c } @@ -75,11 +68,11 @@ func (x *testRFC6979DataSignatureServerSettings[_, _, _]) authenticateRequestPay // verification. By default, any signature matching the data is accepted. // // Overrides checkRequestDataSignerKey. -func (x *testRFC6979DataSignatureServerSettings[_, _, _]) checkRequestDataSignature(s neofscrypto.Signature) { +func (x *testRFC6979DataSignatureServerSettings[_]) checkRequestDataSignature(s neofscrypto.Signature) { x.reqDataSignature = &s } -func (x testRFC6979DataSignatureServerSettings[_, _, _]) verifyDataSignature(signedField string, data []byte, m *protorefs.SignatureRFC6979) error { +func (x testRFC6979DataSignatureServerSettings[_]) verifyDataSignature(signedField string, data []byte, m *protorefs.SignatureRFC6979) error { field := signedField + " signature" if m == nil { return newErrMissingRequestBodyField(field) @@ -100,12 +93,8 @@ func (x testRFC6979DataSignatureServerSettings[_, _, _]) verifyDataSignature(sig return nil } -func (x testRFC6979DataSignatureServerSettings[SIGNED, SIGNEDV2, SIGNEDV2PTR]) verifyMessageSignature(signedField string, signed SIGNED, m *protorefs.SignatureRFC6979) error { - mV2 := SIGNEDV2PTR(new(SIGNEDV2)) - if err := mV2.FromGRPCMessage(signed); err != nil { - panic(err) - } - return x.verifyDataSignature(signedField, mV2.StableMarshal(nil), m) +func (x testRFC6979DataSignatureServerSettings[SIGNED]) verifyMessageSignature(signedField string, signed SIGNED, m *protorefs.SignatureRFC6979) error { + return x.verifyDataSignature(signedField, neofsproto.MarshalMessage(signed), m) } // for sharing between servers of requests with a container session token. @@ -139,20 +128,12 @@ type testPutContainerServer struct { protocontainer.UnimplementedContainerServiceServer testCommonUnaryServerSettings[ *protocontainer.PutRequest_Body, - apicontainer.PutRequestBody, - *apicontainer.PutRequestBody, *protocontainer.PutRequest, - apicontainer.PutRequest, - *apicontainer.PutRequest, *protocontainer.PutResponse_Body, - apicontainer.PutResponseBody, - *apicontainer.PutResponseBody, *protocontainer.PutResponse, - apicontainer.PutResponse, - *apicontainer.PutResponse, ] testContainerSessionServerSettings - testRFC6979DataSignatureServerSettings[*protocontainer.Container, apicontainer.Container, *apicontainer.Container] + testRFC6979DataSignatureServerSettings[*protocontainer.Container] reqContainer *container.Container } @@ -229,17 +210,9 @@ type testGetContainerServer struct { protocontainer.UnimplementedContainerServiceServer testCommonUnaryServerSettings[ *protocontainer.GetRequest_Body, - apicontainer.GetRequestBody, - *apicontainer.GetRequestBody, *protocontainer.GetRequest, - apicontainer.GetRequest, - *apicontainer.GetRequest, *protocontainer.GetResponse_Body, - apicontainer.GetResponseBody, - *apicontainer.GetResponseBody, *protocontainer.GetResponse, - apicontainer.GetResponse, - *apicontainer.GetResponse, ] testRequiredContainerIDServerSettings } @@ -304,17 +277,9 @@ type testListContainersServer struct { protocontainer.UnimplementedContainerServiceServer testCommonUnaryServerSettings[ *protocontainer.ListRequest_Body, - apicontainer.ListRequestBody, - *apicontainer.ListRequestBody, *protocontainer.ListRequest, - apicontainer.ListRequest, - *apicontainer.ListRequest, *protocontainer.ListResponse_Body, - apicontainer.ListResponseBody, - *apicontainer.ListResponseBody, *protocontainer.ListResponse, - apicontainer.ListResponse, - *apicontainer.ListResponse, ] reqOwner *user.ID } @@ -388,21 +353,13 @@ type testDeleteContainerServer struct { protocontainer.UnimplementedContainerServiceServer testCommonUnaryServerSettings[ *protocontainer.DeleteRequest_Body, - apicontainer.DeleteRequestBody, - *apicontainer.DeleteRequestBody, *protocontainer.DeleteRequest, - apicontainer.DeleteRequest, - *apicontainer.DeleteRequest, *protocontainer.DeleteResponse_Body, - apicontainer.DeleteResponseBody, - *apicontainer.DeleteResponseBody, *protocontainer.DeleteResponse, - apicontainer.DeleteResponse, - *apicontainer.DeleteResponse, ] testContainerSessionServerSettings testRequiredContainerIDServerSettings - testRFC6979DataSignatureServerSettings[*protorefs.ContainerID, refs.ContainerID, *refs.ContainerID] + testRFC6979DataSignatureServerSettings[*protorefs.ContainerID] } // returns [protocontainer.ContainerServiceServer] supporting Delete method only. @@ -472,17 +429,9 @@ type testGetEACLServer struct { protocontainer.UnimplementedContainerServiceServer testCommonUnaryServerSettings[ *protocontainer.GetExtendedACLRequest_Body, - apicontainer.GetExtendedACLRequestBody, - *apicontainer.GetExtendedACLRequestBody, *protocontainer.GetExtendedACLRequest, - apicontainer.GetExtendedACLRequest, - *apicontainer.GetExtendedACLRequest, *protocontainer.GetExtendedACLResponse_Body, - apicontainer.GetExtendedACLResponseBody, - *apicontainer.GetExtendedACLResponseBody, *protocontainer.GetExtendedACLResponse, - apicontainer.GetExtendedACLResponse, - *apicontainer.GetExtendedACLResponse, ] testRequiredContainerIDServerSettings } @@ -545,20 +494,12 @@ type testSetEACLServer struct { protocontainer.UnimplementedContainerServiceServer testCommonUnaryServerSettings[ *protocontainer.SetExtendedACLRequest_Body, - apicontainer.SetExtendedACLRequestBody, - *apicontainer.SetExtendedACLRequestBody, *protocontainer.SetExtendedACLRequest, - apicontainer.SetExtendedACLRequest, - *apicontainer.SetExtendedACLRequest, *protocontainer.SetExtendedACLResponse_Body, - apicontainer.SetExtendedACLResponseBody, - *apicontainer.SetExtendedACLResponseBody, *protocontainer.SetExtendedACLResponse, - apicontainer.SetExtendedACLResponse, - *apicontainer.SetExtendedACLResponse, ] testContainerSessionServerSettings - testRFC6979DataSignatureServerSettings[*protoacl.EACLTable, v2acl.Table, *v2acl.Table] + testRFC6979DataSignatureServerSettings[*protoacl.EACLTable] reqEACL *eacl.Table } @@ -637,17 +578,9 @@ type testAnnounceContainerSpaceServer struct { protocontainer.UnimplementedContainerServiceServer testCommonUnaryServerSettings[ *protocontainer.AnnounceUsedSpaceRequest_Body, - apicontainer.AnnounceUsedSpaceRequestBody, - *apicontainer.AnnounceUsedSpaceRequestBody, *protocontainer.AnnounceUsedSpaceRequest, - apicontainer.AnnounceUsedSpaceRequest, - *apicontainer.AnnounceUsedSpaceRequest, *protocontainer.AnnounceUsedSpaceResponse_Body, - apicontainer.AnnounceUsedSpaceResponseBody, - *apicontainer.AnnounceUsedSpaceResponseBody, *protocontainer.AnnounceUsedSpaceResponse, - apicontainer.AnnounceUsedSpaceResponse, - *apicontainer.AnnounceUsedSpaceResponse, ] reqAnnouncements []container.SizeEstimation } @@ -752,8 +685,10 @@ func TestClient_ContainerPut(t *testing.T) { }) t.Run("options", func(t *testing.T) { t.Run("X-headers", func(t *testing.T) { - testStatusResponses(t, newTestPutContainerServer, newTestContainerClient, func(c *Client) error { - _, err := c.ContainerPut(ctx, anyValidContainer, anyValidSigner, anyValidOpts) + testRequestXHeaders(t, newTestPutContainerServer, newTestContainerClient, func(c *Client, xhs []string) error { + opts := anyValidOpts + opts.WithXHeaders(xhs...) + _, err := c.ContainerPut(ctx, anyValidContainer, anyValidSigner, opts) return err }) }) @@ -1067,13 +1002,12 @@ func TestClient_ContainerGet(t *testing.T) { {name: "missing replicas", msg: "missing replicas", corrupt: func(valid *protonetmap.PlacementPolicy) { valid.Replicas = nil }}, - // TODO: uncomment after https://github.com/nspcc-dev/neofs-sdk-go/issues/606 - // {name: "selectors/clause/negative", msg: "invalid selector #1: negative clause -1", corrupt: func(valid *protonetmap.PlacementPolicy) { - // valid.Selectors[1].Clause = -1 - // }}, - // {name: "filters/op/negative", msg: "invalid filter #1: negative op -1", corrupt: func(valid *protonetmap.PlacementPolicy) { - // valid.Filters[1].Op = -1 - // }}, + {name: "selectors/clause/negative", msg: "invalid selector #1: negative clause -1", corrupt: func(valid *protonetmap.PlacementPolicy) { + valid.Selectors[1].Clause = -1 + }}, + {name: "filters/op/negative", msg: "invalid filter #1: negative op -1", corrupt: func(valid *protonetmap.PlacementPolicy) { + valid.Filters[1].Op = -1 + }}, } { ctcs = append(ctcs, invalidContainerTestcase{ name: "policy" + tc.name, msg: "invalid placement policy: " + tc.msg, @@ -1542,22 +1476,21 @@ func TestClient_ContainerEACL(t *testing.T) { name, msg string corrupt func(valid *protoacl.EACLRecord) }{ - // TODO: uncomment after https://github.com/nspcc-dev/neofs-sdk-go/issues/606 - // {name: "op/negative", msg: "negative op -1", corrupt: func(valid *protoacl.EACLRecord) { - // valid.Operation = -1 - // }}, - // {name: "action/negative", msg: "negative action -1", corrupt: func(valid *protoacl.EACLRecord) { - // valid.Action = -1 - // }}, - // {name: "filters/header type/negative", msg: "invalid filter #1: negative header type -1", corrupt: func(valid *protoacl.EACLRecord) { - // valid.Filters = []*protoacl.EACLRecord_Filter{{}, {HeaderType: -1}} - // }}, - // {name: "filters/matcher/negative", msg: "invalid filter #1: negative matcher -1", corrupt: func(valid *protoacl.EACLRecord) { - // valid.Filters = []*protoacl.EACLRecord_Filter{{}, {MatchType: -1}} - // }}, - // {name: "targets/role/negative", msg: "invalid target #1: negative role -1", corrupt: func(valid *protoacl.EACLRecord) { - // valid.Targets = []*protoacl.EACLRecord_Target{{}, {Role: -1}} - // }}, + {name: "op/negative", msg: "negative op -1", corrupt: func(valid *protoacl.EACLRecord) { + valid.Operation = -1 + }}, + {name: "action/negative", msg: "negative action -1", corrupt: func(valid *protoacl.EACLRecord) { + valid.Action = -1 + }}, + {name: "filters/header type/negative", msg: "invalid filter #1: negative header type -1", corrupt: func(valid *protoacl.EACLRecord) { + valid.Filters = []*protoacl.EACLRecord_Filter{{}, {HeaderType: -1}} + }}, + {name: "filters/matcher/negative", msg: "invalid filter #1: negative match type -1", corrupt: func(valid *protoacl.EACLRecord) { + valid.Filters = []*protoacl.EACLRecord_Filter{{}, {MatchType: -1}} + }}, + {name: "targets/role/negative", msg: "invalid subject descriptor #1: negative role -1", corrupt: func(valid *protoacl.EACLRecord) { + valid.Targets = []*protoacl.EACLRecord_Target{{}, {Role: -1}} + }}, } { etcs = append(etcs, invalidEACLTestcase{ name: "records/" + tc.name, msg: "invalid record #1: " + tc.msg, diff --git a/client/crypto_test.go b/client/crypto_test.go index f5f74817..ec1fe02d 100644 --- a/client/crypto_test.go +++ b/client/crypto_test.go @@ -15,18 +15,13 @@ import ( "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/io" - protorefs "github.com/nspcc-dev/neofs-api-go/v2/refs/grpc" - apigrpc "github.com/nspcc-dev/neofs-api-go/v2/rpc/grpc" neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" + neofsproto "github.com/nspcc-dev/neofs-sdk-go/internal/proto" + protorefs "github.com/nspcc-dev/neofs-sdk-go/proto/refs" ) var p256Curve = elliptic.P256() -type signedMessageV2 interface { - FromGRPCMessage(apigrpc.Message) error - StableMarshal([]byte) []byte -} - // represents tested NeoFS authentication credentials. type authCredentials struct { scheme protorefs.SignatureScheme @@ -59,15 +54,8 @@ func checkAuthCredendials(exp, act authCredentials) error { return nil } -func signMessage[MESSAGE apigrpc.Message, MESSAGEV2 any, MESSAGEV2PTR interface { - *MESSAGEV2 - signedMessageV2 -}](key ecdsa.PrivateKey, m MESSAGE, _ MESSAGEV2PTR) (*protorefs.Signature, error) { - mV2 := MESSAGEV2PTR(new(MESSAGEV2)) - if err := mV2.FromGRPCMessage(m); err != nil { - panic(err) - } - b := mV2.StableMarshal(nil) +func signMessage[MESSAGE neofsproto.Message](key ecdsa.PrivateKey, m MESSAGE) (*protorefs.Signature, error) { + b := neofsproto.MarshalMessage(m) h := sha512.Sum512(b) r, s, err := ecdsa.Sign(rand.Reader, &key, h[:]) if err != nil { @@ -80,17 +68,6 @@ func signMessage[MESSAGE apigrpc.Message, MESSAGEV2 any, MESSAGEV2PTR interface return &protorefs.Signature{Key: elliptic.MarshalCompressed(p256Curve, key.X, key.Y), Sign: sig}, nil } -func verifyMessageSignature[MESSAGE apigrpc.Message, MESSAGEV2 any, MESSAGEV2PTR interface { - *MESSAGEV2 - signedMessageV2 -}](m MESSAGE, s *protorefs.Signature, expectedCreds *authCredentials) error { - mV2 := MESSAGEV2PTR(new(MESSAGEV2)) - if err := mV2.FromGRPCMessage(m); err != nil { - panic(err) - } - return verifyDataSignature(mV2.StableMarshal(nil), s, expectedCreds) -} - func verifyDataSignature(data []byte, s *protorefs.Signature, expectedCreds *authCredentials) error { if s == nil { return errors.New("missing") diff --git a/client/errors.go b/client/errors.go index d51cc7fc..0448f288 100644 --- a/client/errors.go +++ b/client/errors.go @@ -36,6 +36,10 @@ var ( // ErrMissingResponseField is returned when required field is not exists in NeoFS api response. ErrMissingResponseField MissingResponseFieldErr + + errSignRequest = errors.New("sign request") + errResponseCallback = errors.New("response callback error") + errResponseSignatures = errors.New("invalid response signature") ) // MissingResponseFieldErr contains field name which should be in NeoFS API response. diff --git a/client/example_container_put_test.go b/client/example_container_put_test.go index 75d40faf..32e99b72 100644 --- a/client/example_container_put_test.go +++ b/client/example_container_put_test.go @@ -6,7 +6,6 @@ import ( "time" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" - netmapv2 "github.com/nspcc-dev/neofs-api-go/v2/netmap" "github.com/nspcc-dev/neofs-sdk-go/client" "github.com/nspcc-dev/neofs-sdk-go/container" "github.com/nspcc-dev/neofs-sdk-go/container/acl" @@ -62,18 +61,12 @@ func ExampleClient_ContainerPut() { // init placement policy var containerID cid.ID - var placementPolicyV2 netmapv2.PlacementPolicy - var replicas []netmapv2.Replica - replica := netmapv2.Replica{} - replica.SetCount(1) - replicas = append(replicas, replica) - placementPolicyV2.SetReplicas(replicas) + replica := netmap.ReplicaDescriptor{} + replica.SetNumberOfObjects(1) var placementPolicy netmap.PlacementPolicy - if err = placementPolicy.ReadFromV2(placementPolicyV2); err != nil { - panic(fmt.Errorf("ReadFromV2 %w", err)) - } + placementPolicy.SetReplicas([]netmap.ReplicaDescriptor{replica}) placementPolicy.SetContainerBackupFactor(1) cont.SetPlacementPolicy(placementPolicy) diff --git a/client/example_test.go b/client/example_test.go index 60651820..df8cad5a 100644 --- a/client/example_test.go +++ b/client/example_test.go @@ -8,9 +8,6 @@ import ( "time" "github.com/google/uuid" - rpcClient "github.com/nspcc-dev/neofs-api-go/v2/rpc/client" - "github.com/nspcc-dev/neofs-api-go/v2/rpc/common" - "github.com/nspcc-dev/neofs-api-go/v2/rpc/grpc" "github.com/nspcc-dev/neofs-sdk-go/client" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa" @@ -33,83 +30,6 @@ func ExampleClient_createInstance() { _ = c.Dial(prmDial) } -type CustomRPCRequest struct { -} - -type CustomRPCResponse struct { -} - -func (a *CustomRPCRequest) ToGRPCMessage() grpc.Message { - return nil -} - -func (a *CustomRPCRequest) FromGRPCMessage(grpc.Message) error { - return nil -} - -func (a *CustomRPCResponse) ToGRPCMessage() grpc.Message { - return nil -} - -func (a *CustomRPCResponse) FromGRPCMessage(grpc.Message) error { - return nil -} - -// Consume custom service of the server. -func Example_customService() { - // syntax = "proto3"; - // - // service CustomService { - // rpc CustomRPC(CustomRPCRequest) returns (CustomRPCResponse); - // } - - // import "github.com/nspcc-dev/neofs-api-go/v2/rpc/client" - // import "github.com/nspcc-dev/neofs-api-go/v2/rpc/common" - - var prmInit client.PrmInit - // ... - - c, _ := client.New(prmInit) - - req := &CustomRPCRequest{} - resp := &CustomRPCResponse{} - - err := c.ExecRaw(func(c *rpcClient.Client) error { - return rpcClient.SendUnary(c, common.CallMethodInfo{ - Service: "CustomService", - Name: "CustomRPC", - }, req, resp) - }) - - _ = err - - // ... - - // Close the connection - _ = c.Close() - - // Note that it's not allowed to override Client behaviour directly: the parameters - // for the all operations are write-only and the results of the all operations are - // read-only. To be able to override client behavior (e.g. for tests), abstract it - // with an interface: - // - // import "github.com/nspcc-dev/neofs-sdk-go/client" - // - // type NeoFSClient interface { - // // Operations according to the application needs - // CreateContainer(context.Context, container.Container) error - // // ... - // } - // - // type client struct { - // c *client.Client - // } - // - // func (x *client) CreateContainer(context.Context, container.Container) error { - // // ... - // } -} - // Session created for the one node, and it will work only for this node. Other nodes don't have info about this session. // That is why session can't be created with Pool API. func ExampleClient_SessionCreate() { diff --git a/client/messages_test.go b/client/messages_test.go index ff29b5b2..4a7a2102 100644 --- a/client/messages_test.go +++ b/client/messages_test.go @@ -11,16 +11,6 @@ import ( "strings" "github.com/google/uuid" - protoaccounting "github.com/nspcc-dev/neofs-api-go/v2/accounting/grpc" - protoacl "github.com/nspcc-dev/neofs-api-go/v2/acl/grpc" - apicontainer "github.com/nspcc-dev/neofs-api-go/v2/container" - protocontainer "github.com/nspcc-dev/neofs-api-go/v2/container/grpc" - apinetmap "github.com/nspcc-dev/neofs-api-go/v2/netmap" - protonetmap "github.com/nspcc-dev/neofs-api-go/v2/netmap/grpc" - protoobject "github.com/nspcc-dev/neofs-api-go/v2/object/grpc" - protorefs "github.com/nspcc-dev/neofs-api-go/v2/refs/grpc" - protoreputation "github.com/nspcc-dev/neofs-api-go/v2/reputation/grpc" - protosession "github.com/nspcc-dev/neofs-api-go/v2/session/grpc" "github.com/nspcc-dev/neofs-sdk-go/accounting" "github.com/nspcc-dev/neofs-sdk-go/bearer" "github.com/nspcc-dev/neofs-sdk-go/checksum" @@ -33,6 +23,14 @@ import ( "github.com/nspcc-dev/neofs-sdk-go/netmap" "github.com/nspcc-dev/neofs-sdk-go/object" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" + protoaccounting "github.com/nspcc-dev/neofs-sdk-go/proto/accounting" + protoacl "github.com/nspcc-dev/neofs-sdk-go/proto/acl" + protocontainer "github.com/nspcc-dev/neofs-sdk-go/proto/container" + protonetmap "github.com/nspcc-dev/neofs-sdk-go/proto/netmap" + protoobject "github.com/nspcc-dev/neofs-sdk-go/proto/object" + protorefs "github.com/nspcc-dev/neofs-sdk-go/proto/refs" + protoreputation "github.com/nspcc-dev/neofs-sdk-go/proto/reputation" + protosession "github.com/nspcc-dev/neofs-sdk-go/proto/session" "github.com/nspcc-dev/neofs-sdk-go/reputation" "github.com/nspcc-dev/neofs-sdk-go/session" "github.com/nspcc-dev/neofs-sdk-go/user" @@ -150,30 +148,29 @@ var ( {}, {Operation: 1, Action: 1}, {Operation: 2, Action: 2}, - // TODO: uncomment after https://github.com/nspcc-dev/neofs-sdk-go/issues/606 - // {Operation: 3, Action: 3}, - // {Operation: 4, Action: math.MaxInt32}, + {Operation: 3, Action: 3}, + {Operation: 4, Action: math.MaxInt32}, {Operation: 5}, {Operation: 6}, {Operation: 7}, - // {Operation: math.MaxInt32}, + {Operation: math.MaxInt32}, {Filters: []*protoacl.EACLRecord_Filter{ {HeaderType: 0, MatchType: 0, Key: "key1", Value: "val1"}, {HeaderType: 1, MatchType: 1}, {HeaderType: 2, MatchType: 2}, {HeaderType: 3, MatchType: 3}, - // {HeaderType: math.MaxInt32, MatchType: 4}, + {HeaderType: math.MaxInt32, MatchType: 4}, {MatchType: 5}, {MatchType: 6}, {MatchType: 7}, - // {MatchType: math.MaxInt32}, + {MatchType: math.MaxInt32}, }}, {Targets: []*protoacl.EACLRecord_Target{ {Role: 0, Keys: [][]byte{[]byte("key1"), []byte("key2")}}, {Role: 1}, {Role: 2}, {Role: 3}, - // {Role: math.MaxInt32}, + {Role: math.MaxInt32}, }}, }, } @@ -342,8 +339,7 @@ var ( 4, 124, 162, 237, 187, 141, 28, 109, 121, 22, 77, 77}, Context: &protosession.SessionToken_Body_Object{ Object: &protosession.ObjectSessionContext{ - // TODO: must work with big verb (e.g. 1849442930) after https://github.com/nspcc-dev/neofs-sdk-go/issues/606 - Verb: 3, + Verb: 1849442930, Target: &protosession.ObjectSessionContext_Target{ Container: &protorefs.ContainerID{Value: []byte{43, 155, 220, 2, 70, 86, 249, 4, 211, 12, 14, 152, 15, 165, 141, 240, 15, 199, 82, 245, 32, 86, 49, 60, 3, 15, 235, 107, 227, 21, 201, 226}}, @@ -954,9 +950,7 @@ func checkStoragePolicyTransport(p netmap.PlacementPolicy, m *protonetmap.Placem actClause := ms.GetClause() switch { default: - var pV2 apinetmap.PlacementPolicy - p.WriteToV2(&pV2) - expClause = pV2.ToGRPCMessage().(*protonetmap.PlacementPolicy).Selectors[i].Clause + expClause = p.ProtoMessage().Selectors[i].Clause case cs.IsSame(): expClause = protonetmap.Clause_SAME case cs.IsDistinct(): @@ -998,9 +992,8 @@ func checkContainerTransport(c container.Container, m *protocontainer.Container) } // 3. nonce // TODO(https://github.com/nspcc-dev/neofs-sdk-go/issues/664): access nonce from c directly - var cV2 apicontainer.Container - c.WriteToV2(&cV2) - if v1, v2 := cV2.GetNonce(), m.GetNonce(); !bytes.Equal(v1, v2) { + mc := c.ProtoMessage() + if v1, v2 := mc.GetNonce(), m.GetNonce(); !bytes.Equal(v1, v2) { return fmt.Errorf("nonce field (client: %x, message: %x)", v1, v2) } // 4. basic ACL @@ -1303,9 +1296,7 @@ func checkNodeInfoTransport(n netmap.NodeInfo, m *protonetmap.NodeInfo) error { var expState protonetmap.NodeInfo_State switch { default: - var pV2 apinetmap.NodeInfo - n.WriteToV2(&pV2) - expState = pV2.ToGRPCMessage().(*protonetmap.NodeInfo).State + expState = n.ProtoMessage().State case n.IsOnline(): expState = protonetmap.NodeInfo_ONLINE case n.IsOffline(): diff --git a/client/netmap.go b/client/netmap.go index eb0a391b..1e17a87c 100644 --- a/client/netmap.go +++ b/client/netmap.go @@ -5,10 +5,11 @@ import ( "fmt" "time" - v2netmap "github.com/nspcc-dev/neofs-api-go/v2/netmap" - protonetmap "github.com/nspcc-dev/neofs-api-go/v2/netmap/grpc" - v2session "github.com/nspcc-dev/neofs-api-go/v2/session" + apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status" + neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" "github.com/nspcc-dev/neofs-sdk-go/netmap" + protonetmap "github.com/nspcc-dev/neofs-sdk-go/proto/netmap" + protosession "github.com/nspcc-dev/neofs-sdk-go/proto/session" "github.com/nspcc-dev/neofs-sdk-go/stat" "github.com/nspcc-dev/neofs-sdk-go/version" ) @@ -68,67 +69,78 @@ func (c *Client) EndpointInfo(ctx context.Context, prm PrmEndpointInfo) (*ResEnd }() } - // form request - var req v2netmap.LocalNodeInfoRequest + req := &protonetmap.LocalNodeInfoRequest{ + MetaHeader: &protosession.RequestMetaHeader{ + Version: version.Current().ProtoMessage(), + Ttl: defaultRequestTTL, + }, + } + writeXHeadersToMeta(prm.xHeaders, req.MetaHeader) - // init call context + buf := c.buffers.Get().(*[]byte) + defer func() { c.buffers.Put(buf) }() - var ( - cc contextCall - res ResEndpointInfo - ) + req.VerifyHeader, err = neofscrypto.SignRequestWithBuffer[*protonetmap.LocalNodeInfoRequest_Body](c.prm.signer, req, *buf) + if err != nil { + err = fmt.Errorf("%w: %w", errSignRequest, err) + return nil, err + } + + resp, err := c.netmap.LocalNodeInfo(ctx, req) + if err != nil { + err = rpcErr(err) + return nil, err + } - c.initCallContext(&cc) - cc.meta = prm.prmCommonMeta - cc.req = &req - cc.call = func() (responseV2, error) { - resp, err := c.netmap.LocalNodeInfo(ctx, req.ToGRPCMessage().(*protonetmap.LocalNodeInfoRequest)) + if c.prm.cbRespInfo != nil { + err = c.prm.cbRespInfo(ResponseMetaInfo{ + key: resp.GetVerifyHeader().GetBodySignature().GetKey(), + epoch: resp.GetMetaHeader().GetEpoch(), + }) if err != nil { - return nil, rpcErr(err) - } - var respV2 v2netmap.LocalNodeInfoResponse - if err = respV2.FromGRPCMessage(resp); err != nil { + err = fmt.Errorf("%w: %w", errResponseCallback, err) return nil, err } - return &respV2, nil } - cc.result = func(r responseV2) { - resp := r.(*v2netmap.LocalNodeInfoResponse) - body := resp.GetBody() + if err = neofscrypto.VerifyResponseWithBuffer[*protonetmap.LocalNodeInfoResponse_Body](resp, *buf); err != nil { + err = fmt.Errorf("%w: %w", errResponseSignatures, err) + return nil, err + } - const fieldVersion = "version" + if err = apistatus.ToError(resp.GetMetaHeader().GetStatus()); err != nil { + return nil, err + } - verV2 := body.GetVersion() - if verV2 == nil { - cc.err = newErrMissingResponseField(fieldVersion) - return - } + body := resp.GetBody() - cc.err = res.version.ReadFromV2(*verV2) - if cc.err != nil { - cc.err = newErrInvalidResponseField(fieldVersion, cc.err) - return - } + const fieldVersion = "version" - const fieldNodeInfo = "node info" + mv := body.GetVersion() + if mv == nil { + err = newErrMissingResponseField(fieldVersion) + return nil, err + } - nodeInfoV2 := body.GetNodeInfo() - if nodeInfoV2 == nil { - cc.err = newErrMissingResponseField(fieldNodeInfo) - return - } + var res ResEndpointInfo - cc.err = res.ni.ReadFromV2(*nodeInfoV2) - if cc.err != nil { - cc.err = newErrInvalidResponseField(fieldNodeInfo, cc.err) - return - } + err = res.version.FromProtoMessage(mv) + if err != nil { + err = newErrInvalidResponseField(fieldVersion, err) + return nil, err + } + + const fieldNodeInfo = "node info" + + mn := body.GetNodeInfo() + if mn == nil { + err = newErrMissingResponseField(fieldNodeInfo) + return nil, err } - // process call - if !cc.processCall() { - err = cc.err + err = res.ni.FromProtoMessage(mn) + if err != nil { + err = newErrInvalidResponseField(fieldNodeInfo, err) return nil, err } @@ -157,52 +169,63 @@ func (c *Client) NetworkInfo(ctx context.Context, prm PrmNetworkInfo) (netmap.Ne }() } - // form request - var req v2netmap.NetworkInfoRequest + req := &protonetmap.NetworkInfoRequest{ + MetaHeader: &protosession.RequestMetaHeader{ + Version: version.Current().ProtoMessage(), + Ttl: defaultRequestTTL, + }, + } + writeXHeadersToMeta(prm.xHeaders, req.MetaHeader) + + var res netmap.NetworkInfo - // init call context + buf := c.buffers.Get().(*[]byte) + defer func() { c.buffers.Put(buf) }() + + req.VerifyHeader, err = neofscrypto.SignRequestWithBuffer[*protonetmap.NetworkInfoRequest_Body](c.prm.signer, req, *buf) + if err != nil { + err = fmt.Errorf("%w: %w", errSignRequest, err) + return res, err + } - var ( - cc contextCall - res netmap.NetworkInfo - ) + resp, err := c.netmap.NetworkInfo(ctx, req) + if err != nil { + err = rpcErr(err) + return res, err + } - c.initCallContext(&cc) - cc.meta = prm.prmCommonMeta - cc.req = &req - cc.call = func() (responseV2, error) { - resp, err := c.netmap.NetworkInfo(ctx, req.ToGRPCMessage().(*protonetmap.NetworkInfoRequest)) + if c.prm.cbRespInfo != nil { + err = c.prm.cbRespInfo(ResponseMetaInfo{ + key: resp.GetVerifyHeader().GetBodySignature().GetKey(), + epoch: resp.GetMetaHeader().GetEpoch(), + }) if err != nil { - return nil, rpcErr(err) + err = fmt.Errorf("%w: %w", errResponseCallback, err) + return res, err } - var respV2 v2netmap.NetworkInfoResponse - if err = respV2.FromGRPCMessage(resp); err != nil { - return nil, err - } - return &respV2, nil } - cc.result = func(r responseV2) { - resp := r.(*v2netmap.NetworkInfoResponse) - const fieldNetInfo = "network info" + if err = neofscrypto.VerifyResponseWithBuffer[*protonetmap.NetworkInfoResponse_Body](resp, *buf); err != nil { + err = fmt.Errorf("%w: %w", errResponseSignatures, err) + return res, err + } - netInfoV2 := resp.GetBody().GetNetworkInfo() - if netInfoV2 == nil { - cc.err = newErrMissingResponseField(fieldNetInfo) - return - } + if err = apistatus.ToError(resp.GetMetaHeader().GetStatus()); err != nil { + return res, err + } - cc.err = res.ReadFromV2(*netInfoV2) - if cc.err != nil { - cc.err = newErrInvalidResponseField(fieldNetInfo, cc.err) - return - } + const fieldNetInfo = "network info" + + mn := resp.GetBody().GetNetworkInfo() + if mn == nil { + err = newErrMissingResponseField(fieldNetInfo) + return res, err } - // process call - if !cc.processCall() { - err = cc.err - return netmap.NetworkInfo{}, cc.err + err = res.FromProtoMessage(mn) + if err != nil { + err = newErrInvalidResponseField(fieldNetInfo, err) + return res, err } return res, nil @@ -229,49 +252,48 @@ func (c *Client) NetMapSnapshot(ctx context.Context, _ PrmNetMapSnapshot) (netma }() } - // form request body - var body v2netmap.SnapshotRequestBody - - // form meta header - var meta v2session.RequestMetaHeader + req := &protonetmap.NetmapSnapshotRequest{ + MetaHeader: &protosession.RequestMetaHeader{ + Version: version.Current().ProtoMessage(), + Ttl: defaultRequestTTL, + }, + } - // form request - var req v2netmap.SnapshotRequest - req.SetBody(&body) - c.prepareRequest(&req, &meta) + var res netmap.NetMap buf := c.buffers.Get().(*[]byte) - err = signServiceMessage(c.prm.signer, &req, *buf) - c.buffers.Put(buf) + defer func() { c.buffers.Put(buf) }() + + req.VerifyHeader, err = neofscrypto.SignRequestWithBuffer[*protonetmap.NetmapSnapshotRequest_Body](c.prm.signer, req, *buf) if err != nil { - err = fmt.Errorf("sign request: %w", err) - return netmap.NetMap{}, err + err = fmt.Errorf("%w: %w", errSignRequest, err) + return res, err } - resp, err := c.netmap.NetmapSnapshot(ctx, req.ToGRPCMessage().(*protonetmap.NetmapSnapshotRequest)) + resp, err := c.netmap.NetmapSnapshot(ctx, req) if err != nil { err = rpcErr(err) return netmap.NetMap{}, err } - var respV2 v2netmap.SnapshotResponse - if err = respV2.FromGRPCMessage(resp); err != nil { - return netmap.NetMap{}, err + + if err = neofscrypto.VerifyResponseWithBuffer[*protonetmap.NetmapSnapshotResponse_Body](resp, *buf); err != nil { + err = fmt.Errorf("%w: %w", errResponseSignatures, err) + return res, err } - var res netmap.NetMap - if err = c.processResponse(&respV2); err != nil { - return netmap.NetMap{}, err + if err = apistatus.ToError(resp.GetMetaHeader().GetStatus()); err != nil { + return res, err } const fieldNetMap = "network map" - netMapV2 := respV2.GetBody().NetMap() - if netMapV2 == nil { + mn := resp.GetBody().GetNetmap() + if mn == nil { err = newErrMissingResponseField(fieldNetMap) return netmap.NetMap{}, err } - err = res.ReadFromV2(*netMapV2) + err = res.FromProtoMessage(mn) if err != nil { err = newErrInvalidResponseField(fieldNetMap, err) return netmap.NetMap{}, err diff --git a/client/netmap_test.go b/client/netmap_test.go index 4d1ac42a..3ae0e78c 100644 --- a/client/netmap_test.go +++ b/client/netmap_test.go @@ -7,8 +7,7 @@ import ( "testing" "time" - v2netmap "github.com/nspcc-dev/neofs-api-go/v2/netmap" - protonetmap "github.com/nspcc-dev/neofs-api-go/v2/netmap/grpc" + protonetmap "github.com/nspcc-dev/neofs-sdk-go/proto/netmap" "github.com/nspcc-dev/neofs-sdk-go/stat" "github.com/stretchr/testify/require" "google.golang.org/protobuf/proto" @@ -54,8 +53,7 @@ var ( {Key: "k1", Value: "v1"}, {Key: "Price", Value: "foo"}, {Key: "k3", Value: "v3"}, } }}, - // TODO: uncomment after https://github.com/nspcc-dev/neofs-sdk-go/issues/606 - // {name: "state/negative", msg: "negative state -1", corrupt: func(valid *protonetmap.NodeInfo) { valid.State = -1 }}, + {name: "state/negative", msg: "negative state -1", corrupt: func(valid *protonetmap.NodeInfo) { valid.State = -1 }}, } invalidNetInfoProtoTestcases = []struct { name, msg string @@ -183,17 +181,9 @@ type testNetmapSnapshotServer struct { protonetmap.UnimplementedNetmapServiceServer testCommonUnaryServerSettings[ *protonetmap.NetmapSnapshotRequest_Body, - v2netmap.SnapshotRequestBody, - *v2netmap.SnapshotRequestBody, *protonetmap.NetmapSnapshotRequest, - v2netmap.SnapshotRequest, - *v2netmap.SnapshotRequest, *protonetmap.NetmapSnapshotResponse_Body, - v2netmap.SnapshotResponseBody, - *v2netmap.SnapshotResponseBody, *protonetmap.NetmapSnapshotResponse, - v2netmap.SnapshotResponse, - *v2netmap.SnapshotResponse, ] } @@ -253,17 +243,9 @@ type testGetNetworkInfoServer struct { protonetmap.UnimplementedNetmapServiceServer testCommonUnaryServerSettings[ *protonetmap.NetworkInfoRequest_Body, - v2netmap.NetworkInfoRequestBody, - *v2netmap.NetworkInfoRequestBody, *protonetmap.NetworkInfoRequest, - v2netmap.NetworkInfoRequest, - *v2netmap.NetworkInfoRequest, *protonetmap.NetworkInfoResponse_Body, - v2netmap.NetworkInfoResponseBody, - *v2netmap.NetworkInfoResponseBody, *protonetmap.NetworkInfoResponse, - v2netmap.NetworkInfoResponse, - *v2netmap.NetworkInfoResponse, ] } @@ -317,17 +299,9 @@ type testGetNodeInfoServer struct { protonetmap.UnimplementedNetmapServiceServer testCommonUnaryServerSettings[ *protonetmap.LocalNodeInfoRequest_Body, - v2netmap.LocalNodeInfoRequestBody, - *v2netmap.LocalNodeInfoRequestBody, *protonetmap.LocalNodeInfoRequest, - v2netmap.LocalNodeInfoRequest, - *v2netmap.LocalNodeInfoRequest, *protonetmap.LocalNodeInfoResponse_Body, - v2netmap.LocalNodeInfoResponseBody, - *v2netmap.LocalNodeInfoResponseBody, *protonetmap.LocalNodeInfoResponse, - v2netmap.LocalNodeInfoResponse, - *v2netmap.LocalNodeInfoResponse, ] } diff --git a/client/object_delete.go b/client/object_delete.go index dc9a5353..e65029cb 100644 --- a/client/object_delete.go +++ b/client/object_delete.go @@ -6,15 +6,16 @@ import ( "fmt" "time" - "github.com/nspcc-dev/neofs-api-go/v2/acl" - v2object "github.com/nspcc-dev/neofs-api-go/v2/object" - protoobject "github.com/nspcc-dev/neofs-api-go/v2/object/grpc" - v2refs "github.com/nspcc-dev/neofs-api-go/v2/refs" "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" + neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" + protoobject "github.com/nspcc-dev/neofs-sdk-go/proto/object" + protosession "github.com/nspcc-dev/neofs-sdk-go/proto/session" "github.com/nspcc-dev/neofs-sdk-go/stat" "github.com/nspcc-dev/neofs-sdk-go/user" + "github.com/nspcc-dev/neofs-sdk-go/version" ) var ( @@ -24,7 +25,9 @@ var ( // PrmObjectDelete groups optional parameters of ObjectDelete operation. type PrmObjectDelete struct { + prmCommonMeta sessionContainer + bearerToken *bearer.Token } // WithBearerToken attaches bearer token to be used for the operation. @@ -33,17 +36,7 @@ type PrmObjectDelete struct { // // Must be signed. func (x *PrmObjectDelete) WithBearerToken(t bearer.Token) { - var v2token acl.BearerToken - t.WriteToV2(&v2token) - x.meta.SetBearerToken(&v2token) -} - -// WithXHeaders specifies list of extended headers (string key-value pairs) -// to be attached to the request. Must have an even length. -// -// Slice must not be mutated until the operation completes. -func (x *PrmObjectDelete) WithXHeaders(hs ...string) { - writeXHeadersToMeta(hs, &x.meta) + x.bearerToken = &t } // ObjectDelete marks an object for deletion from the container using NeoFS API protocol. @@ -67,13 +60,7 @@ func (x *PrmObjectDelete) WithXHeaders(hs ...string) { // - [apistatus.ErrObjectLocked] // - [apistatus.ErrSessionTokenExpired] func (c *Client) ObjectDelete(ctx context.Context, containerID cid.ID, objectID oid.ID, signer user.Signer, prm PrmObjectDelete) (oid.ID, error) { - var ( - addr v2refs.Address - cidV2 v2refs.ContainerID - oidV2 v2refs.ObjectID - body v2object.DeleteRequestBody - err error - ) + var err error if c.prm.statisticCallback != nil { startTime := time.Now() @@ -82,56 +69,61 @@ func (c *Client) ObjectDelete(ctx context.Context, containerID cid.ID, objectID }() } - containerID.WriteToV2(&cidV2) - addr.SetContainerID(&cidV2) - - objectID.WriteToV2(&oidV2) - addr.SetObjectID(&oidV2) - if signer == nil { return oid.ID{}, ErrMissingSigner } - // form request body - body.SetAddress(&addr) - - // form request - var req v2object.DeleteRequest - req.SetBody(&body) - c.prepareRequest(&req, &prm.meta) + req := &protoobject.DeleteRequest{ + Body: &protoobject.DeleteRequest_Body{ + Address: oid.NewAddress(containerID, objectID).ProtoMessage(), + }, + MetaHeader: &protosession.RequestMetaHeader{ + Version: version.Current().ProtoMessage(), + Ttl: defaultRequestTTL, + }, + } + writeXHeadersToMeta(prm.xHeaders, req.MetaHeader) + if prm.session != nil { + req.MetaHeader.SessionToken = prm.session.ProtoMessage() + } + if prm.bearerToken != nil { + req.MetaHeader.BearerToken = prm.bearerToken.ProtoMessage() + } buf := c.buffers.Get().(*[]byte) - err = signServiceMessage(signer, &req, *buf) - c.buffers.Put(buf) + defer func() { c.buffers.Put(buf) }() + + req.VerifyHeader, err = neofscrypto.SignRequestWithBuffer[*protoobject.DeleteRequest_Body](signer, req, *buf) if err != nil { - err = fmt.Errorf("sign request: %w", err) + err = fmt.Errorf("%w: %w", errSignRequest, err) return oid.ID{}, err } - resp, err := c.object.Delete(ctx, req.ToGRPCMessage().(*protoobject.DeleteRequest)) + resp, err := c.object.Delete(ctx, req) if err != nil { err = rpcErr(err) return oid.ID{}, err } - var respV2 v2object.DeleteResponse - if err = respV2.FromGRPCMessage(resp); err != nil { + + if err = neofscrypto.VerifyResponseWithBuffer[*protoobject.DeleteResponse_Body](resp, *buf); err != nil { + err = fmt.Errorf("%w: %w", errResponseSignatures, err) return oid.ID{}, err } - var res oid.ID - if err = c.processResponse(&respV2); err != nil { + if err = apistatus.ToError(resp.GetMetaHeader().GetStatus()); err != nil { return oid.ID{}, err } const fieldTombstone = "tombstone" - idTombV2 := respV2.GetBody().GetTombstone().GetObjectID() - if idTombV2 == nil { + mt := resp.GetBody().GetTombstone().GetObjectId() + if mt == nil { err = newErrMissingResponseField(fieldTombstone) return oid.ID{}, err } - err = res.ReadFromV2(*idTombV2) + var res oid.ID + err = res.FromProtoMessage(mt) if err != nil { err = newErrInvalidResponseField(fieldTombstone, err) return oid.ID{}, err diff --git a/client/object_delete_test.go b/client/object_delete_test.go index 2a41642e..fe29de6d 100644 --- a/client/object_delete_test.go +++ b/client/object_delete_test.go @@ -7,12 +7,11 @@ import ( "testing" "time" - apiobject "github.com/nspcc-dev/neofs-api-go/v2/object" - protoobject "github.com/nspcc-dev/neofs-api-go/v2/object/grpc" - protorefs "github.com/nspcc-dev/neofs-api-go/v2/refs/grpc" bearertest "github.com/nspcc-dev/neofs-sdk-go/bearer/test" cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test" oidtest "github.com/nspcc-dev/neofs-sdk-go/object/id/test" + protoobject "github.com/nspcc-dev/neofs-sdk-go/proto/object" + protorefs "github.com/nspcc-dev/neofs-sdk-go/proto/refs" sessiontest "github.com/nspcc-dev/neofs-sdk-go/session/test" "github.com/nspcc-dev/neofs-sdk-go/stat" usertest "github.com/nspcc-dev/neofs-sdk-go/user/test" @@ -24,17 +23,9 @@ type testDeleteObjectServer struct { protoobject.UnimplementedObjectServiceServer testCommonUnaryServerSettings[ *protoobject.DeleteRequest_Body, - apiobject.DeleteRequestBody, - *apiobject.DeleteRequestBody, *protoobject.DeleteRequest, - apiobject.DeleteRequest, - *apiobject.DeleteRequest, *protoobject.DeleteResponse_Body, - apiobject.DeleteResponseBody, - *apiobject.DeleteResponseBody, *protoobject.DeleteResponse, - apiobject.DeleteResponse, - *apiobject.DeleteResponse, ] testObjectSessionServerSettings testBearerTokenServerSettings @@ -147,7 +138,6 @@ func TestClient_ObjectDelete(t *testing.T) { c := newTestObjectClient(t, srv) bt := bearertest.Token() - bt.SetEACLTable(anyValidEACL) // TODO: drop after https://github.com/nspcc-dev/neofs-sdk-go/issues/606 require.NoError(t, bt.Sign(usertest.User())) opts := anyValidOpts opts.WithBearerToken(bt) diff --git a/client/object_get.go b/client/object_get.go index 1679d3ad..161fa218 100644 --- a/client/object_get.go +++ b/client/object_get.go @@ -7,35 +7,31 @@ import ( "io" "time" - "github.com/nspcc-dev/neofs-api-go/v2/acl" - v2object "github.com/nspcc-dev/neofs-api-go/v2/object" - protoobject "github.com/nspcc-dev/neofs-api-go/v2/object/grpc" - v2refs "github.com/nspcc-dev/neofs-api-go/v2/refs" "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" + neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" "github.com/nspcc-dev/neofs-sdk-go/object" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" + protoobject "github.com/nspcc-dev/neofs-sdk-go/proto/object" + protosession "github.com/nspcc-dev/neofs-sdk-go/proto/session" "github.com/nspcc-dev/neofs-sdk-go/stat" "github.com/nspcc-dev/neofs-sdk-go/user" + "github.com/nspcc-dev/neofs-sdk-go/version" ) var errInvalidSplitInfo = errors.New("invalid split info") // shared parameters of GET/HEAD/RANGE. type prmObjectRead struct { + prmCommonMeta sessionContainer + bearerToken *bearer.Token + local bool raw bool } -// WithXHeaders specifies list of extended headers (string key-value pairs) -// to be attached to the request. Must have an even length. -// -// Slice must not be mutated until the operation completes. -func (x *prmObjectRead) WithXHeaders(hs ...string) { - writeXHeadersToMeta(hs, &x.meta) -} - // MarkRaw marks an intent to read physically stored object. func (x *prmObjectRead) MarkRaw() { x.raw = true @@ -43,7 +39,7 @@ func (x *prmObjectRead) MarkRaw() { // MarkLocal tells the server to execute the operation locally. func (x *prmObjectRead) MarkLocal() { - x.meta.SetTTL(1) + x.local = true } // WithBearerToken attaches bearer token to be used for the operation. @@ -52,9 +48,7 @@ func (x *prmObjectRead) MarkLocal() { // // Must be signed. func (x *prmObjectRead) WithBearerToken(t bearer.Token) { - var v2token acl.BearerToken - t.WriteToV2(&v2token) - x.meta.SetBearerToken(&v2token) + x.bearerToken = &t } // PrmObjectGet groups optional parameters of ObjectGetInit operation. @@ -79,7 +73,6 @@ type getObjectResponseStream interface { type PayloadReader struct { cancelCtxStream context.CancelFunc - client *Client stream getObjectResponseStream singleMsgTimeout time.Duration @@ -105,67 +98,62 @@ func (x *PayloadReader) readHeader(dst *object.Object) bool { if x.err != nil { return false } - var respV2 v2object.GetResponse - if x.err = respV2.FromGRPCMessage(resp); x.err != nil { + + if x.err = neofscrypto.VerifyResponseWithBuffer[*protoobject.GetResponse_Body](resp, nil); x.err != nil { + x.err = fmt.Errorf("%w: %w", errResponseSignatures, x.err) return false } - x.err = x.client.processResponse(&respV2) - if x.err != nil { + if x.err = apistatus.ToError(resp.GetMetaHeader().GetStatus()); x.err != nil { return false } - var partInit *v2object.GetObjectPartInit + var partInit *protoobject.GetResponse_Body_Init - switch v := respV2.GetBody().GetObjectPart().(type) { + switch v := resp.GetBody().GetObjectPart().(type) { default: x.err = fmt.Errorf("unexpected message instead of heading part: %T", v) return false - case *v2object.SplitInfo: - if v == nil { + case *protoobject.GetResponse_Body_SplitInfo: + if v == nil || v.SplitInfo == nil { x.err = fmt.Errorf("%w: nil split info field", errInvalidSplitInfo) return false } var si object.SplitInfo - if x.err = si.ReadFromV2(*v); x.err != nil { + if x.err = si.FromProtoMessage(v.SplitInfo); x.err != nil { x.err = fmt.Errorf("%w: %w", errInvalidSplitInfo, x.err) return false } x.err = object.NewSplitInfoError(&si) return false - case *v2object.GetObjectPartInit: - if v == nil { - x.err = newErrMissingResponseField("init") + case *protoobject.GetResponse_Body_Init_: + if v == nil || v.Init == nil { + x.err = errors.New("nil header oneof field") return false } - partInit = v + partInit = v.Init } - id := partInit.GetObjectID() - if id == nil { + if partInit.ObjectId == nil { x.err = newErrMissingResponseField("object ID") return false } - sig := partInit.GetSignature() - if sig == nil { + if partInit.Signature == nil { x.err = newErrMissingResponseField("signature") return false } - hdr := partInit.GetHeader() - if hdr == nil { + if partInit.Header == nil { x.err = newErrMissingResponseField("header") return false } - var objv2 v2object.Object + x.remainingPayloadLen = int(partInit.Header.GetPayloadLength()) - objv2.SetObjectID(id) - objv2.SetHeader(hdr) - objv2.SetSignature(sig) - - x.remainingPayloadLen = int(hdr.GetPayloadLength()) - - x.err = dst.ReadFromV2(objv2) + x.err = dst.FromProtoMessage(&protoobject.Object{ + ObjectId: partInit.ObjectId, + Signature: partInit.Signature, + Header: partInit.Header, + }) return x.err == nil } @@ -194,25 +182,29 @@ func (x *PayloadReader) readChunk(buf []byte) (int, bool) { if x.err != nil { return read, false } - var respV2 v2object.GetResponse - if x.err = respV2.FromGRPCMessage(resp); x.err != nil { + + if x.err = neofscrypto.VerifyResponseWithBuffer[*protoobject.GetResponse_Body](resp, nil); x.err != nil { + x.err = fmt.Errorf("%w: %w", errResponseSignatures, x.err) return read, false } - x.err = x.client.processResponse(&respV2) - if x.err != nil { + if x.err = apistatus.ToError(resp.GetMetaHeader().GetStatus()); x.err != nil { return read, false } - part := respV2.GetBody().GetObjectPart() - partChunk, ok := part.(*v2object.GetObjectPartChunk) + part := resp.GetBody().GetObjectPart() + partChunk, ok := part.(*protoobject.GetResponse_Body_Chunk) if !ok { x.err = fmt.Errorf("unexpected message instead of chunk part: %T", part) return read, false } + if partChunk == nil { + x.err = errors.New("nil chunk oneof field") + return read, false + } // read new chunk - chunk = partChunk.GetChunk() + chunk = partChunk.Chunk if len(chunk) == 0 { // just skip empty chunks since they are not prohibited by protocol continue @@ -300,12 +292,8 @@ func (x *PayloadReader) Read(p []byte) (int, error) { // - [apistatus.ErrSessionTokenExpired] func (c *Client) ObjectGetInit(ctx context.Context, containerID cid.ID, objectID oid.ID, signer user.Signer, prm PrmObjectGet) (object.Object, *PayloadReader, error) { var ( - addr v2refs.Address - cidV2 v2refs.ContainerID - oidV2 v2refs.ObjectID - body v2object.GetRequestBody - hdr object.Object - err error + hdr object.Object + err error ) if c.prm.statisticCallback != nil { @@ -319,31 +307,40 @@ func (c *Client) ObjectGetInit(ctx context.Context, containerID cid.ID, objectID return hdr, nil, ErrMissingSigner } - containerID.WriteToV2(&cidV2) - addr.SetContainerID(&cidV2) - - objectID.WriteToV2(&oidV2) - addr.SetObjectID(&oidV2) - - body.SetRaw(prm.raw) - body.SetAddress(&addr) - - // form request - var req v2object.GetRequest + req := &protoobject.GetRequest{ + Body: &protoobject.GetRequest_Body{ + Address: oid.NewAddress(containerID, objectID).ProtoMessage(), + Raw: prm.raw, + }, + MetaHeader: &protosession.RequestMetaHeader{ + Version: version.Current().ProtoMessage(), + }, + } + writeXHeadersToMeta(prm.xHeaders, req.MetaHeader) + if prm.local { + req.MetaHeader.Ttl = localRequestTTL + } else { + req.MetaHeader.Ttl = defaultRequestTTL + } + if prm.session != nil { + req.MetaHeader.SessionToken = prm.session.ProtoMessage() + } + if prm.bearerToken != nil { + req.MetaHeader.BearerToken = prm.bearerToken.ProtoMessage() + } - req.SetBody(&body) - c.prepareRequest(&req, &prm.meta) buf := c.buffers.Get().(*[]byte) - err = signServiceMessage(signer, &req, *buf) - c.buffers.Put(buf) + defer func() { c.buffers.Put(buf) }() + + req.VerifyHeader, err = neofscrypto.SignRequestWithBuffer[*protoobject.GetRequest_Body](signer, req, *buf) if err != nil { - err = fmt.Errorf("sign request: %w", err) + err = fmt.Errorf("%w: %w", errSignRequest, err) return hdr, nil, err } ctx, cancel := context.WithCancel(ctx) - stream, err := c.object.Get(ctx, req.ToGRPCMessage().(*protoobject.GetRequest)) + stream, err := c.object.Get(ctx, req) if err != nil { cancel() err = fmt.Errorf("open stream: %w", err) @@ -354,7 +351,6 @@ func (c *Client) ObjectGetInit(ctx context.Context, containerID cid.ID, objectID r.cancelCtxStream = cancel r.stream = stream r.singleMsgTimeout = c.streamTimeout - r.client = c if c.prm.statisticCallback != nil { r.startTime = time.Now() r.statisticCallback = func(dur time.Duration, err error) { @@ -396,13 +392,7 @@ type PrmObjectHead struct { // - [apistatus.ErrObjectAlreadyRemoved] // - [apistatus.ErrSessionTokenExpired] func (c *Client) ObjectHead(ctx context.Context, containerID cid.ID, objectID oid.ID, signer user.Signer, prm PrmObjectHead) (*object.Object, error) { - var ( - addr v2refs.Address - cidV2 v2refs.ContainerID - oidV2 v2refs.ObjectID - body v2object.HeadRequestBody - err error - ) + var err error if c.prm.statisticCallback != nil { startTime := time.Now() @@ -415,78 +405,86 @@ func (c *Client) ObjectHead(ctx context.Context, containerID cid.ID, objectID oi return nil, ErrMissingSigner } - containerID.WriteToV2(&cidV2) - addr.SetContainerID(&cidV2) - - objectID.WriteToV2(&oidV2) - addr.SetObjectID(&oidV2) - - body.SetRaw(prm.raw) - body.SetAddress(&addr) - - var req v2object.HeadRequest - req.SetBody(&body) - c.prepareRequest(&req, &prm.meta) + req := &protoobject.HeadRequest{ + Body: &protoobject.HeadRequest_Body{ + Address: oid.NewAddress(containerID, objectID).ProtoMessage(), + Raw: prm.raw, + }, + MetaHeader: &protosession.RequestMetaHeader{ + Version: version.Current().ProtoMessage(), + }, + } + writeXHeadersToMeta(prm.xHeaders, req.MetaHeader) + if prm.local { + req.MetaHeader.Ttl = localRequestTTL + } else { + req.MetaHeader.Ttl = defaultRequestTTL + } + if prm.session != nil { + req.MetaHeader.SessionToken = prm.session.ProtoMessage() + } + if prm.bearerToken != nil { + req.MetaHeader.BearerToken = prm.bearerToken.ProtoMessage() + } buf := c.buffers.Get().(*[]byte) - err = signServiceMessage(signer, &req, *buf) - c.buffers.Put(buf) + defer func() { c.buffers.Put(buf) }() + + req.VerifyHeader, err = neofscrypto.SignRequestWithBuffer[*protoobject.HeadRequest_Body](signer, req, *buf) if err != nil { - err = fmt.Errorf("sign request: %w", err) + err = fmt.Errorf("%w: %w", errSignRequest, err) return nil, err } - resp, err := c.object.Head(ctx, req.ToGRPCMessage().(*protoobject.HeadRequest)) + resp, err := c.object.Head(ctx, req) if err != nil { err = rpcErr(err) return nil, err } - var respV2 v2object.HeadResponse - if err = respV2.FromGRPCMessage(resp); err != nil { + + if err = neofscrypto.VerifyResponseWithBuffer[*protoobject.HeadResponse_Body](resp, *buf); err != nil { + err = fmt.Errorf("%w: %w", errResponseSignatures, err) return nil, err } - if err = c.processResponse(&respV2); err != nil { + if err = apistatus.ToError(resp.GetMetaHeader().GetStatus()); err != nil { return nil, err } - switch v := respV2.GetBody().GetHeaderPart().(type) { + switch v := resp.GetBody().GetHead().(type) { default: err = fmt.Errorf("unexpected header type %T", v) return nil, err - case *v2object.SplitInfo: - if v == nil { + case *protoobject.HeadResponse_Body_SplitInfo: + if v == nil || v.SplitInfo == nil { err = fmt.Errorf("%w: nil split info field", errInvalidSplitInfo) return nil, err } var si object.SplitInfo - if err = si.ReadFromV2(*v); err != nil { + if err = si.FromProtoMessage(v.SplitInfo); err != nil { err = fmt.Errorf("%w: %w", errInvalidSplitInfo, err) return nil, err } err = object.NewSplitInfoError(&si) return nil, err - case *v2object.HeaderWithSignature: + case *protoobject.HeadResponse_Body_Header: if v == nil { return nil, errors.New("empty header") } - sig := v.GetSignature() - if sig == nil { + if v.Header.Signature == nil { err = newErrMissingResponseField("signature") return nil, err } - hdr := v.GetHeader() - if hdr == nil { + if v.Header.Header == nil { err = newErrMissingResponseField("header") return nil, err } - var objv2 v2object.Object - objv2.SetHeader(hdr) - objv2.SetSignature(sig) - var obj object.Object - if err = obj.ReadFromV2(objv2); err != nil { + if err = obj.FromProtoMessage(&protoobject.Object{ + Signature: v.Header.Signature, + Header: v.Header.Header, + }); err != nil { return nil, fmt.Errorf("invalid header response: %w", err) } return &obj, nil @@ -515,8 +513,6 @@ type getObjectPayloadRangeResponseStream interface { type ObjectRangeReader struct { cancelCtxStream context.CancelFunc - client *Client - err error stream getObjectPayloadRangeResponseStream @@ -542,7 +538,6 @@ func (x *ObjectRangeReader) readChunk(buf []byte) (int, bool) { return read, true } - var partChunk *v2object.GetRangePartChunk var chunk []byte var lastRead int @@ -556,38 +551,41 @@ func (x *ObjectRangeReader) readChunk(buf []byte) (int, bool) { if x.err != nil { return read, false } - var respV2 v2object.GetRangeResponse - if x.err = respV2.FromGRPCMessage(resp); x.err != nil { + + if x.err = neofscrypto.VerifyResponseWithBuffer[*protoobject.GetRangeResponse_Body](resp, nil); x.err != nil { + x.err = fmt.Errorf("%w: %w", errResponseSignatures, x.err) return read, false } - x.err = x.client.processResponse(&respV2) - if x.err != nil { + if x.err = apistatus.ToError(resp.GetMetaHeader().GetStatus()); x.err != nil { return read, false } // get chunk message - switch v := respV2.GetBody().GetRangePart().(type) { + switch v := resp.GetBody().GetRangePart().(type) { default: x.err = fmt.Errorf("unexpected message received: %T", v) return read, false - case *v2object.SplitInfo: - if v == nil { + case *protoobject.GetRangeResponse_Body_SplitInfo: + if v == nil || v.SplitInfo == nil { x.err = fmt.Errorf("%w: nil split info field", errInvalidSplitInfo) return read, false } var si object.SplitInfo - if x.err = si.ReadFromV2(*v); x.err != nil { + if x.err = si.FromProtoMessage(v.SplitInfo); x.err != nil { x.err = fmt.Errorf("%w: %w", errInvalidSplitInfo, x.err) return read, false } x.err = object.NewSplitInfoError(&si) return read, false - case *v2object.GetRangePartChunk: - partChunk = v + case *protoobject.GetRangeResponse_Body_Chunk: + if v == nil { + x.err = errors.New("nil header oneof field") + return read, false + } + chunk = v.Chunk } - chunk = partChunk.GetChunk() if len(chunk) == 0 { // just skip empty chunks since they are not prohibited by protocol continue @@ -684,14 +682,7 @@ func (x *ObjectRangeReader) Read(p []byte) (int, error) { // - [ErrZeroRangeLength] // - [ErrMissingSigner] func (c *Client) ObjectRangeInit(ctx context.Context, containerID cid.ID, objectID oid.ID, offset, length uint64, signer user.Signer, prm PrmObjectRange) (*ObjectRangeReader, error) { - var ( - addr v2refs.Address - cidV2 v2refs.ContainerID - oidV2 v2refs.ObjectID - rngV2 v2object.Range - body v2object.GetRangeRequestBody - err error - ) + var err error if c.prm.statisticCallback != nil { startTime := time.Now() @@ -709,37 +700,41 @@ func (c *Client) ObjectRangeInit(ctx context.Context, containerID cid.ID, object return nil, ErrMissingSigner } - containerID.WriteToV2(&cidV2) - addr.SetContainerID(&cidV2) - - objectID.WriteToV2(&oidV2) - addr.SetObjectID(&oidV2) - - rngV2.SetOffset(offset) - rngV2.SetLength(length) - - // form request body - body.SetRaw(prm.raw) - body.SetAddress(&addr) - body.SetRange(&rngV2) - - // form request - var req v2object.GetRangeRequest - - req.SetBody(&body) - c.prepareRequest(&req, &prm.meta) + req := &protoobject.GetRangeRequest{ + Body: &protoobject.GetRangeRequest_Body{ + Address: oid.NewAddress(containerID, objectID).ProtoMessage(), + Range: &protoobject.Range{Offset: offset, Length: length}, + Raw: prm.raw, + }, + MetaHeader: &protosession.RequestMetaHeader{ + Version: version.Current().ProtoMessage(), + }, + } + writeXHeadersToMeta(prm.xHeaders, req.MetaHeader) + if prm.local { + req.MetaHeader.Ttl = localRequestTTL + } else { + req.MetaHeader.Ttl = defaultRequestTTL + } + if prm.session != nil { + req.MetaHeader.SessionToken = prm.session.ProtoMessage() + } + if prm.bearerToken != nil { + req.MetaHeader.BearerToken = prm.bearerToken.ProtoMessage() + } buf := c.buffers.Get().(*[]byte) - err = signServiceMessage(signer, &req, *buf) - c.buffers.Put(buf) + defer func() { c.buffers.Put(buf) }() + + req.VerifyHeader, err = neofscrypto.SignRequestWithBuffer[*protoobject.GetRangeRequest_Body](signer, req, *buf) if err != nil { - err = fmt.Errorf("sign request: %w", err) + err = fmt.Errorf("%w: %w", errSignRequest, err) return nil, err } ctx, cancel := context.WithCancel(ctx) - stream, err := c.object.GetRange(ctx, req.ToGRPCMessage().(*protoobject.GetRangeRequest)) + stream, err := c.object.GetRange(ctx, req) if err != nil { cancel() err = fmt.Errorf("open stream: %w", err) @@ -751,7 +746,6 @@ func (c *Client) ObjectRangeInit(ctx context.Context, containerID cid.ID, object r.cancelCtxStream = cancel r.stream = stream r.singleMsgTimeout = c.streamTimeout - r.client = c if c.prm.statisticCallback != nil { r.startTime = time.Now() r.statisticCallback = func(dur time.Duration, err error) { diff --git a/client/object_get_test.go b/client/object_get_test.go index d79364a9..2001408d 100644 --- a/client/object_get_test.go +++ b/client/object_get_test.go @@ -11,16 +11,15 @@ import ( "testing/iotest" "time" - apiobject "github.com/nspcc-dev/neofs-api-go/v2/object" - protoobject "github.com/nspcc-dev/neofs-api-go/v2/object/grpc" - protorefs "github.com/nspcc-dev/neofs-api-go/v2/refs/grpc" - protosession "github.com/nspcc-dev/neofs-api-go/v2/session/grpc" - protostatus "github.com/nspcc-dev/neofs-api-go/v2/status/grpc" bearertest "github.com/nspcc-dev/neofs-sdk-go/bearer/test" apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status" cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test" "github.com/nspcc-dev/neofs-sdk-go/object" oidtest "github.com/nspcc-dev/neofs-sdk-go/object/id/test" + protoobject "github.com/nspcc-dev/neofs-sdk-go/proto/object" + protorefs "github.com/nspcc-dev/neofs-sdk-go/proto/refs" + protosession "github.com/nspcc-dev/neofs-sdk-go/proto/session" + protostatus "github.com/nspcc-dev/neofs-sdk-go/proto/status" sessiontest "github.com/nspcc-dev/neofs-sdk-go/session/test" "github.com/nspcc-dev/neofs-sdk-go/stat" usertest "github.com/nspcc-dev/neofs-sdk-go/user/test" @@ -111,17 +110,9 @@ type testGetObjectServer struct { protoobject.UnimplementedObjectServiceServer testCommonServerStreamServerSettings[ *protoobject.GetRequest_Body, - apiobject.GetRequestBody, - *apiobject.GetRequestBody, *protoobject.GetRequest, - apiobject.GetRequest, - *apiobject.GetRequest, *protoobject.GetResponse_Body, - apiobject.GetResponseBody, - *apiobject.GetResponseBody, *protoobject.GetResponse, - apiobject.GetResponse, - *apiobject.GetResponse, ] testCommonReadObjectRequestServerSettings chunk []byte @@ -235,17 +226,9 @@ type testGetObjectPayloadRangeServer struct { protoobject.UnimplementedObjectServiceServer testCommonServerStreamServerSettings[ *protoobject.GetRangeRequest_Body, - apiobject.GetRangeRequestBody, - *apiobject.GetRangeRequestBody, *protoobject.GetRangeRequest, - apiobject.GetRangeRequest, - *apiobject.GetRangeRequest, *protoobject.GetRangeResponse_Body, - apiobject.GetRangeResponseBody, - *apiobject.GetRangeResponseBody, *protoobject.GetRangeResponse, - apiobject.GetRangeResponse, - *apiobject.GetRangeResponse, ] testCommonReadObjectRequestServerSettings chunk []byte @@ -369,17 +352,9 @@ type testHeadObjectServer struct { protoobject.UnimplementedObjectServiceServer testCommonUnaryServerSettings[ *protoobject.HeadRequest_Body, - apiobject.HeadRequestBody, - *apiobject.HeadRequestBody, *protoobject.HeadRequest, - apiobject.HeadRequest, - *apiobject.HeadRequest, *protoobject.HeadResponse_Body, - apiobject.HeadResponseBody, - *apiobject.HeadResponseBody, *protoobject.HeadResponse, - apiobject.HeadResponse, - *apiobject.HeadResponse, ] testCommonReadObjectRequestServerSettings } @@ -513,7 +488,6 @@ func TestClient_ObjectHead(t *testing.T) { c := newTestObjectClient(t, srv) bt := bearertest.Token() - bt.SetEACLTable(anyValidEACL) // TODO: drop after https://github.com/nspcc-dev/neofs-sdk-go/issues/606 require.NoError(t, bt.Sign(usertest.User())) opts := anyValidOpts opts.WithBearerToken(bt) @@ -612,7 +586,7 @@ func TestClient_ObjectHead(t *testing.T) { }}, {name: "short header oneof/empty", body: &protoobject.HeadResponse_Body{Head: new(protoobject.HeadResponse_Body_ShortHeader)}, assertErr: func(t testing.TB, err error) { - require.EqualError(t, err, "unexpected header type *object.ShortHeader") + require.EqualError(t, err, "unexpected header type *object.HeadResponse_Body_ShortHeader") }}, {name: "split info oneof/nil", body: &protoobject.HeadResponse_Body{Head: (*protoobject.HeadResponse_Body_SplitInfo)(nil)}, assertErr: func(t testing.TB, err error) { @@ -679,7 +653,7 @@ func TestClient_ObjectHead(t *testing.T) { }, }}, assertErr: func(t testing.TB, err error) { - require.EqualError(t, err, "invalid header response: invalid header: "+tc.msg) + require.EqualError(t, err, "invalid header response: invalid signature: "+tc.msg) }, }) } @@ -827,7 +801,6 @@ func TestClient_ObjectGetInit(t *testing.T) { c := newTestObjectClient(t, srv) bt := bearertest.Token() - bt.SetEACLTable(anyValidEACL) // TODO: drop after https://github.com/nspcc-dev/neofs-sdk-go/issues/606 require.NoError(t, bt.Sign(usertest.User())) opts := anyValidOpts opts.WithBearerToken(bt) @@ -1241,7 +1214,7 @@ func TestClient_ObjectGetInit(t *testing.T) { srv.respondWithBody(0, proto.Clone(validFullChunkObjectGetResponseBody).(*protoobject.GetResponse_Body)) _, _, err := c.ObjectGetInit(ctx, anyCID, anyOID, anyValidSigner, anyValidOpts) - require.EqualError(t, err, "read header: unexpected message instead of heading part: *object.GetObjectPartChunk") + require.EqualError(t, err, "read header: unexpected message instead of heading part: *object.GetResponse_Body_Chunk") }) t.Run("repeated heading message", func(t *testing.T) { srv := newTestGetObjectServer() @@ -1251,7 +1224,7 @@ func TestClient_ObjectGetInit(t *testing.T) { _, r, err := c.ObjectGetInit(ctx, anyCID, anyOID, anyValidSigner, anyValidOpts) require.NoError(t, err) _, err = io.Copy(io.Discard, r) - require.EqualError(t, err, "unexpected message instead of chunk part: *object.GetObjectPartInit") + require.EqualError(t, err, "unexpected message instead of chunk part: *object.GetResponse_Body_Init_") }) t.Run("non-first split info message", func(t *testing.T) { srv := newTestGetObjectServer() @@ -1261,7 +1234,7 @@ func TestClient_ObjectGetInit(t *testing.T) { _, r, err := c.ObjectGetInit(ctx, anyCID, anyOID, anyValidSigner, anyValidOpts) require.NoError(t, err) _, err = io.Copy(io.Discard, r) - require.EqualError(t, err, "unexpected message instead of chunk part: *object.SplitInfo") + require.EqualError(t, err, "unexpected message instead of chunk part: *object.GetResponse_Body_SplitInfo") }) t.Run("chunk after split info", func(t *testing.T) { srv := newTestGetObjectServer() @@ -1494,7 +1467,6 @@ func TestClient_ObjectRangeInit(t *testing.T) { c := newTestObjectClient(t, srv) bt := bearertest.Token() - bt.SetEACLTable(anyValidEACL) // TODO: drop after https://github.com/nspcc-dev/neofs-sdk-go/issues/606 require.NoError(t, bt.Sign(usertest.User())) opts := anyValidOpts opts.WithBearerToken(bt) diff --git a/client/object_hash.go b/client/object_hash.go index 192f30e8..5bcd6cbe 100644 --- a/client/object_hash.go +++ b/client/object_hash.go @@ -5,29 +5,34 @@ import ( "fmt" "time" - "github.com/nspcc-dev/neofs-api-go/v2/acl" - v2object "github.com/nspcc-dev/neofs-api-go/v2/object" - protoobject "github.com/nspcc-dev/neofs-api-go/v2/object/grpc" - v2refs "github.com/nspcc-dev/neofs-api-go/v2/refs" "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" + neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" + protoobject "github.com/nspcc-dev/neofs-sdk-go/proto/object" + "github.com/nspcc-dev/neofs-sdk-go/proto/refs" + protosession "github.com/nspcc-dev/neofs-sdk-go/proto/session" "github.com/nspcc-dev/neofs-sdk-go/stat" "github.com/nspcc-dev/neofs-sdk-go/user" + "github.com/nspcc-dev/neofs-sdk-go/version" ) // PrmObjectHash groups parameters of ObjectHash operation. type PrmObjectHash struct { + prmCommonMeta sessionContainer + bearerToken *bearer.Token + local bool - body v2object.GetRangeHashRequestBody - - csAlgo v2refs.ChecksumType + tz bool + rs []uint64 + salt []byte } // MarkLocal tells the server to execute the operation locally. func (x *PrmObjectHash) MarkLocal() { - x.meta.SetTTL(1) + x.local = true } // WithBearerToken attaches bearer token to be used for the operation. @@ -36,9 +41,7 @@ func (x *PrmObjectHash) MarkLocal() { // // Must be signed. func (x *PrmObjectHash) WithBearerToken(t bearer.Token) { - var v2token acl.BearerToken - t.WriteToV2(&v2token) - x.meta.SetBearerToken(&v2token) + x.bearerToken = &t } // SetRangeList sets list of ranges in (offset, length) pair format. @@ -51,14 +54,7 @@ func (x *PrmObjectHash) SetRangeList(r ...uint64) { panic("odd number of range parameters") } - rs := make([]v2object.Range, ln/2) - - for i := range ln / 2 { - rs[i].SetOffset(r[2*i]) - rs[i].SetLength(r[2*i+1]) - } - - x.body.SetRanges(rs) + x.rs = r } // TillichZemorAlgo changes the hash function to Tillich-Zemor @@ -66,22 +62,14 @@ func (x *PrmObjectHash) SetRangeList(r ...uint64) { // // By default, SHA256 hash function is used. func (x *PrmObjectHash) TillichZemorAlgo() { - x.csAlgo = v2refs.TillichZemor + x.tz = true } // UseSalt sets the salt to XOR the data range before hashing. // // Must not be mutated before the operation completes. func (x *PrmObjectHash) UseSalt(salt []byte) { - x.body.SetSalt(salt) -} - -// WithXHeaders specifies list of extended headers (string key-value pairs) -// to be attached to the request. Must have an even length. -// -// Slice must not be mutated until the operation completes. -func (x *PrmObjectHash) WithXHeaders(hs ...string) { - writeXHeadersToMeta(hs, &x.meta) + x.salt = salt } // ObjectHash requests checksum of the range list of the object payload using @@ -103,12 +91,7 @@ func (x *PrmObjectHash) WithXHeaders(hs ...string) { // - [ErrMissingRanges] // - [ErrMissingSigner] func (c *Client) ObjectHash(ctx context.Context, containerID cid.ID, objectID oid.ID, signer user.Signer, prm PrmObjectHash) ([][]byte, error) { - var ( - addr v2refs.Address - cidV2 v2refs.ContainerID - oidV2 v2refs.ObjectID - err error - ) + var err error if c.prm.statisticCallback != nil { startTime := time.Now() @@ -117,56 +100,74 @@ func (c *Client) ObjectHash(ctx context.Context, containerID cid.ID, objectID oi }() } - if len(prm.body.GetRanges()) == 0 { + if len(prm.rs) == 0 { err = ErrMissingRanges return nil, err } - containerID.WriteToV2(&cidV2) - addr.SetContainerID(&cidV2) - - objectID.WriteToV2(&oidV2) - addr.SetObjectID(&oidV2) - if signer == nil { return nil, ErrMissingSigner } - prm.body.SetAddress(&addr) - if prm.csAlgo == v2refs.UnknownChecksum { - prm.body.SetType(v2refs.SHA256) + req := &protoobject.GetRangeHashRequest{ + Body: &protoobject.GetRangeHashRequest_Body{ + Address: oid.NewAddress(containerID, objectID).ProtoMessage(), + Ranges: make([]*protoobject.Range, len(prm.rs)/2), + Salt: prm.salt, + }, + MetaHeader: &protosession.RequestMetaHeader{ + Version: version.Current().ProtoMessage(), + }, + } + if prm.tz { + req.Body.Type = refs.ChecksumType_TZ } else { - prm.body.SetType(prm.csAlgo) + req.Body.Type = refs.ChecksumType_SHA256 + } + for i := range len(prm.rs) / 2 { + req.Body.Ranges[i] = &protoobject.Range{ + Offset: prm.rs[2*i], + Length: prm.rs[2*i+1], + } + } + writeXHeadersToMeta(prm.xHeaders, req.MetaHeader) + if prm.local { + req.MetaHeader.Ttl = localRequestTTL + } else { + req.MetaHeader.Ttl = defaultRequestTTL + } + if prm.session != nil { + req.MetaHeader.SessionToken = prm.session.ProtoMessage() + } + if prm.bearerToken != nil { + req.MetaHeader.BearerToken = prm.bearerToken.ProtoMessage() } - - var req v2object.GetRangeHashRequest - c.prepareRequest(&req, &prm.meta) - req.SetBody(&prm.body) buf := c.buffers.Get().(*[]byte) - err = signServiceMessage(signer, &req, *buf) - c.buffers.Put(buf) + defer func() { c.buffers.Put(buf) }() + + req.VerifyHeader, err = neofscrypto.SignRequestWithBuffer[*protoobject.GetRangeHashRequest_Body](signer, req, *buf) if err != nil { - err = fmt.Errorf("sign request: %w", err) + err = fmt.Errorf("%w: %w", errSignRequest, err) return nil, err } - resp, err := c.object.GetRangeHash(ctx, req.ToGRPCMessage().(*protoobject.GetRangeHashRequest)) + resp, err := c.object.GetRangeHash(ctx, req) if err != nil { err = rpcErr(err) return nil, err } - var respV2 v2object.GetRangeHashResponse - if err = respV2.FromGRPCMessage(resp); err != nil { + + if err = neofscrypto.VerifyResponseWithBuffer[*protoobject.GetRangeHashResponse_Body](resp, *buf); err != nil { + err = fmt.Errorf("%w: %w", errResponseSignatures, err) return nil, err } - var res [][]byte - if err = c.processResponse(&respV2); err != nil { + if err = apistatus.ToError(resp.GetMetaHeader().GetStatus()); err != nil { return nil, err } - res = resp.GetBody().GetHashList() + res := resp.GetBody().GetHashList() if len(res) == 0 { err = newErrMissingResponseField("hash list") return nil, err diff --git a/client/object_hash_test.go b/client/object_hash_test.go index d618952a..7ba474dd 100644 --- a/client/object_hash_test.go +++ b/client/object_hash_test.go @@ -9,12 +9,11 @@ import ( "testing" "time" - v2object "github.com/nspcc-dev/neofs-api-go/v2/object" - protoobject "github.com/nspcc-dev/neofs-api-go/v2/object/grpc" - protorefs "github.com/nspcc-dev/neofs-api-go/v2/refs/grpc" bearertest "github.com/nspcc-dev/neofs-sdk-go/bearer/test" cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test" oidtest "github.com/nspcc-dev/neofs-sdk-go/object/id/test" + protoobject "github.com/nspcc-dev/neofs-sdk-go/proto/object" + protorefs "github.com/nspcc-dev/neofs-sdk-go/proto/refs" sessiontest "github.com/nspcc-dev/neofs-sdk-go/session/test" "github.com/nspcc-dev/neofs-sdk-go/stat" usertest "github.com/nspcc-dev/neofs-sdk-go/user/test" @@ -26,17 +25,9 @@ type testHashObjectPayloadRangesServer struct { protoobject.UnimplementedObjectServiceServer testCommonUnaryServerSettings[ *protoobject.GetRangeHashRequest_Body, - v2object.GetRangeHashRequestBody, - *v2object.GetRangeHashRequestBody, *protoobject.GetRangeHashRequest, - v2object.GetRangeHashRequest, - *v2object.GetRangeHashRequest, *protoobject.GetRangeHashResponse_Body, - v2object.GetRangeHashResponseBody, - *v2object.GetRangeHashResponseBody, *protoobject.GetRangeHashResponse, - v2object.GetRangeHashResponse, - *v2object.GetRangeHashResponse, ] testCommonReadObjectRequestServerSettings reqHomo bool @@ -233,7 +224,6 @@ func TestClient_ObjectHash(t *testing.T) { c := newTestObjectClient(t, srv) bt := bearertest.Token() - bt.SetEACLTable(anyValidEACL) // TODO: drop after https://github.com/nspcc-dev/neofs-sdk-go/issues/606 require.NoError(t, bt.Sign(usertest.User())) opts := anyValidOpts opts.WithBearerToken(bt) diff --git a/client/object_put.go b/client/object_put.go index e02c1b34..d9d0f756 100644 --- a/client/object_put.go +++ b/client/object_put.go @@ -7,15 +7,16 @@ import ( "io" "time" - "github.com/nspcc-dev/neofs-api-go/v2/acl" - v2object "github.com/nspcc-dev/neofs-api-go/v2/object" - protoobject "github.com/nspcc-dev/neofs-api-go/v2/object/grpc" "github.com/nspcc-dev/neofs-sdk-go/bearer" + apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status" neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" "github.com/nspcc-dev/neofs-sdk-go/object" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" + protoobject "github.com/nspcc-dev/neofs-sdk-go/proto/object" + protosession "github.com/nspcc-dev/neofs-sdk-go/proto/session" "github.com/nspcc-dev/neofs-sdk-go/stat" "github.com/nspcc-dev/neofs-sdk-go/user" + "github.com/nspcc-dev/neofs-sdk-go/version" ) var ( @@ -42,7 +43,10 @@ type shortStatisticCallback func(dur time.Duration, err error) // PrmObjectPutInit groups parameters of ObjectPutInit operation. type PrmObjectPutInit struct { + prmCommonMeta sessionContainer + bearerToken *bearer.Token + local bool copyNum uint32 } @@ -76,7 +80,6 @@ type ObjectWriter interface { type DefaultObjectWriter struct { cancelCtxStream context.CancelFunc - client *Client stream putObjectStream singleMsgTimeout time.Duration streamClosed bool @@ -87,9 +90,7 @@ type DefaultObjectWriter struct { chunkCalled bool - req v2object.PutRequest - partInit v2object.PutObjectPartInit - partChunk v2object.PutObjectPartChunk + opts PrmObjectPutInit statisticCallback shortStatisticCallback startTime time.Time // if statisticCallback is set only @@ -101,44 +102,54 @@ type DefaultObjectWriter struct { // WithBearerToken attaches bearer token to be used for the operation. // Should be called once before any writing steps. func (x *PrmObjectPutInit) WithBearerToken(t bearer.Token) { - var v2token acl.BearerToken - t.WriteToV2(&v2token) - x.meta.SetBearerToken(&v2token) + x.bearerToken = &t } // MarkLocal tells the server to execute the operation locally. func (x *PrmObjectPutInit) MarkLocal() { - x.meta.SetTTL(1) -} - -// WithXHeaders specifies list of extended headers (string key-value pairs) -// to be attached to the request. Must have an even length. -// -// Slice must not be mutated until the operation completes. -func (x *PrmObjectPutInit) WithXHeaders(hs ...string) { - writeXHeadersToMeta(hs, &x.meta) + x.local = true } // writeHeader writes header of the object. Result means success. // Failure reason can be received via [DefaultObjectWriter.Close]. -func (x *DefaultObjectWriter) writeHeader(hdr object.Object) error { - v2Hdr := hdr.ToV2() - - x.partInit.SetObjectID(v2Hdr.GetObjectID()) - x.partInit.SetHeader(v2Hdr.GetHeader()) - x.partInit.SetSignature(v2Hdr.GetSignature()) - - x.req.GetBody().SetObjectPart(&x.partInit) - x.req.SetVerificationHeader(nil) +func (x *DefaultObjectWriter) writeHeader(hdr object.Object, copyNum uint32) error { + mh := hdr.ProtoMessage() + req := &protoobject.PutRequest{ + Body: &protoobject.PutRequest_Body{ + ObjectPart: &protoobject.PutRequest_Body_Init_{ + Init: &protoobject.PutRequest_Body_Init{ + ObjectId: mh.ObjectId, + Signature: mh.Signature, + Header: mh.Header, + CopiesNumber: copyNum, + }, + }, + }, + MetaHeader: &protosession.RequestMetaHeader{ + Version: version.Current().ProtoMessage(), + }, + } + writeXHeadersToMeta(x.opts.xHeaders, req.MetaHeader) + if x.opts.local { + req.MetaHeader.Ttl = localRequestTTL + } else { + req.MetaHeader.Ttl = defaultRequestTTL + } + if x.opts.session != nil { + req.MetaHeader.SessionToken = x.opts.session.ProtoMessage() + } + if x.opts.bearerToken != nil { + req.MetaHeader.BearerToken = x.opts.bearerToken.ProtoMessage() + } - x.err = signServiceMessage(x.signer, &x.req, x.buf) + req.VerifyHeader, x.err = neofscrypto.SignRequestWithBuffer[*protoobject.PutRequest_Body](x.signer, req, x.buf) if x.err != nil { x.err = fmt.Errorf("sign message: %w", x.err) return x.err } x.err = dowithTimeout(x.singleMsgTimeout, x.cancelCtxStream, func() error { - return x.stream.Send(x.req.ToGRPCMessage().(*protoobject.PutRequest)) + return x.stream.Send(req) }) return x.err } @@ -148,7 +159,6 @@ func (x *DefaultObjectWriter) writeHeader(hdr object.Object) error { func (x *DefaultObjectWriter) Write(chunk []byte) (n int, err error) { if !x.chunkCalled { x.chunkCalled = true - x.req.GetBody().SetObjectPart(&x.partChunk) } var writtenBytes int @@ -174,17 +184,37 @@ func (x *DefaultObjectWriter) Write(chunk []byte) (n int, err error) { // the allocated buffer is filled, or when the last chunk is received. // It is mentally assumed that allocating and filling the buffer is better than // synchronous sending, but this needs to be tested. - x.partChunk.SetChunk(chunk[:ln]) - x.req.SetVerificationHeader(nil) + req := &protoobject.PutRequest{ + Body: &protoobject.PutRequest_Body{ + ObjectPart: &protoobject.PutRequest_Body_Chunk{ + Chunk: chunk[:ln], + }, + }, + MetaHeader: &protosession.RequestMetaHeader{ + Version: version.Current().ProtoMessage(), + }, + } + writeXHeadersToMeta(x.opts.xHeaders, req.MetaHeader) + if x.opts.local { + req.MetaHeader.Ttl = localRequestTTL + } else { + req.MetaHeader.Ttl = defaultRequestTTL + } + if x.opts.session != nil { + req.MetaHeader.SessionToken = x.opts.session.ProtoMessage() + } + if x.opts.bearerToken != nil { + req.MetaHeader.BearerToken = x.opts.bearerToken.ProtoMessage() + } - x.err = signServiceMessage(x.signer, &x.req, x.buf) + req.VerifyHeader, x.err = neofscrypto.SignRequestWithBuffer[*protoobject.PutRequest_Body](x.signer, req, x.buf) if x.err != nil { x.err = fmt.Errorf("sign message: %w", x.err) return writtenBytes, x.err } x.err = dowithTimeout(x.singleMsgTimeout, x.cancelCtxStream, func() error { - return x.stream.Send(x.req.ToGRPCMessage().(*protoobject.PutRequest)) + return x.stream.Send(req) }) if x.err != nil { if errors.Is(x.err, io.EOF) { @@ -197,11 +227,11 @@ func (x *DefaultObjectWriter) Write(chunk []byte) (n int, err error) { if x.err != nil { return writtenBytes, x.err } - var respV2 v2object.PutResponse - if x.err = respV2.FromGRPCMessage(resp); x.err != nil { - return writtenBytes, x.err + if x.err = neofscrypto.VerifyResponseWithBuffer[*protoobject.PutResponse_Body](resp, nil); x.err != nil { + x.err = fmt.Errorf("%w: %w", errResponseSignatures, x.err) + } else { + x.err = apistatus.ToError(resp.GetMetaHeader().GetStatus()) } - x.err = x.client.processResponse(&respV2) x.streamClosed = true x.cancelCtxStream() } @@ -264,24 +294,25 @@ func (x *DefaultObjectWriter) Close() error { }); x.err != nil { return x.err } - var respV2 v2object.PutResponse - if x.err = respV2.FromGRPCMessage(resp); x.err != nil { + + if x.err = neofscrypto.VerifyResponseWithBuffer[*protoobject.PutResponse_Body](resp, nil); x.err != nil { + x.err = fmt.Errorf("%w: %w", errResponseSignatures, x.err) return x.err } - if x.err = x.client.processResponse(&respV2); x.err != nil { + if x.err = apistatus.ToError(resp.GetMetaHeader().GetStatus()); x.err != nil { return x.err } const fieldID = "ID" - idV2 := respV2.GetBody().GetObjectID() + idV2 := resp.GetBody().GetObjectId() if idV2 == nil { x.err = newErrMissingResponseField(fieldID) return x.err } - x.err = x.res.obj.ReadFromV2(*idV2) + x.err = x.res.obj.FromProtoMessage(idV2) if x.err != nil { x.err = newErrInvalidResponseField(fieldID, x.err) } @@ -344,14 +375,10 @@ func (c *Client) ObjectPutInit(ctx context.Context, hdr object.Object, signer us w.signer = signer w.cancelCtxStream = cancel - w.client = c w.stream = stream w.singleMsgTimeout = c.streamTimeout - w.partInit.SetCopiesNumber(prm.copyNum) - w.req.SetBody(new(v2object.PutRequestBody)) - c.prepareRequest(&w.req, &prm.meta) - - if err = w.writeHeader(hdr); err != nil { + w.opts = prm + if err = w.writeHeader(hdr, prm.copyNum); err != nil { _ = w.Close() err = fmt.Errorf("header write: %w", err) return nil, err diff --git a/client/object_put_test.go b/client/object_put_test.go index bca0c1ba..4d4c7b33 100644 --- a/client/object_put_test.go +++ b/client/object_put_test.go @@ -10,13 +10,12 @@ import ( "testing" "time" - v2object "github.com/nspcc-dev/neofs-api-go/v2/object" - protoobject "github.com/nspcc-dev/neofs-api-go/v2/object/grpc" - protostatus "github.com/nspcc-dev/neofs-api-go/v2/status/grpc" bearertest "github.com/nspcc-dev/neofs-sdk-go/bearer/test" apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status" "github.com/nspcc-dev/neofs-sdk-go/object" objecttest "github.com/nspcc-dev/neofs-sdk-go/object/test" + protoobject "github.com/nspcc-dev/neofs-sdk-go/proto/object" + protostatus "github.com/nspcc-dev/neofs-sdk-go/proto/status" sessiontest "github.com/nspcc-dev/neofs-sdk-go/session/test" "github.com/nspcc-dev/neofs-sdk-go/stat" "github.com/nspcc-dev/neofs-sdk-go/user" @@ -50,17 +49,9 @@ type testPutObjectServer struct { protoobject.UnimplementedObjectServiceServer testCommonClientStreamServerSettings[ *protoobject.PutRequest_Body, - v2object.PutRequestBody, - *v2object.PutRequestBody, *protoobject.PutRequest, - v2object.PutRequest, - *v2object.PutRequest, *protoobject.PutResponse_Body, - v2object.PutResponseBody, - *v2object.PutResponseBody, *protoobject.PutResponse, - v2object.PutResponse, - *v2object.PutResponse, ] testObjectSessionServerSettings testBearerTokenServerSettings @@ -364,7 +355,6 @@ func TestClient_ObjectPut(t *testing.T) { c := newTestObjectClient(t, srv) bt := bearertest.Token() - bt.SetEACLTable(anyValidEACL) // TODO: drop after https://github.com/nspcc-dev/neofs-sdk-go/issues/606 require.NoError(t, bt.Sign(usertest.User())) opts := anyValidOpts opts.WithBearerToken(bt) diff --git a/client/object_replicate.go b/client/object_replicate.go index b298bdfb..0c9db02a 100644 --- a/client/object_replicate.go +++ b/client/object_replicate.go @@ -9,12 +9,10 @@ import ( "os" "sync" - objectgrpc "github.com/nspcc-dev/neofs-api-go/v2/object/grpc" - "github.com/nspcc-dev/neofs-api-go/v2/refs" - "github.com/nspcc-dev/neofs-api-go/v2/status" apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status" neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" + protoobject "github.com/nspcc-dev/neofs-sdk-go/proto/object" "google.golang.org/grpc" "google.golang.org/protobuf/encoding/protowire" ) @@ -56,21 +54,13 @@ func (c *Client) ReplicateObject(ctx context.Context, id oid.ID, src io.ReadSeek return nil, err } - var resp objectgrpc.ReplicateResponse - err = c.conn.Invoke(ctx, objectgrpc.ObjectService_Replicate_FullMethodName, msg, &resp, grpc.ForceCodec(onlyBinarySendingCodec{})) + var resp protoobject.ReplicateResponse + err = c.conn.Invoke(ctx, protoobject.ObjectService_Replicate_FullMethodName, msg, &resp, grpc.ForceCodec(onlyBinarySendingCodec{})) if err != nil { return nil, fmt.Errorf("send request over gRPC: %w", err) } - var st *status.Status - if mst := resp.GetStatus(); mst != nil { - st = new(status.Status) - err := st.FromGRPCMessage(mst) - if err != nil { - return nil, fmt.Errorf("decode response status: %w", err) - } - } - if err = apistatus.ErrorFromV2(st); err != nil { + if err = apistatus.ToError(resp.GetStatus()); err != nil { return nil, err } @@ -83,13 +73,8 @@ func (c *Client) ReplicateObject(ctx context.Context, id oid.ID, src io.ReadSeek return nil, errors.New("requested but missing signature") } - var sigV2 refs.Signature - if err := sigV2.Unmarshal(sigBin); err != nil { - return nil, fmt.Errorf("decoding signature from proto message: %w", err) - } - var sig neofscrypto.Signature - if err = sig.ReadFromV2(sigV2); err != nil { + if err = sig.Unmarshal(sigBin); err != nil { return nil, fmt.Errorf("invalid signature: %w", err) } diff --git a/client/object_replicate_test.go b/client/object_replicate_test.go index 003a9869..efb38814 100644 --- a/client/object_replicate_test.go +++ b/client/object_replicate_test.go @@ -8,15 +8,15 @@ import ( "sync" "testing" - objectgrpc "github.com/nspcc-dev/neofs-api-go/v2/object/grpc" - "github.com/nspcc-dev/neofs-api-go/v2/refs" - status "github.com/nspcc-dev/neofs-api-go/v2/status/grpc" apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status" neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" neofscryptotest "github.com/nspcc-dev/neofs-sdk-go/crypto/test" + neofsproto "github.com/nspcc-dev/neofs-sdk-go/internal/proto" "github.com/nspcc-dev/neofs-sdk-go/object" oidtest "github.com/nspcc-dev/neofs-sdk-go/object/id/test" objecttest "github.com/nspcc-dev/neofs-sdk-go/object/test" + objectgrpc "github.com/nspcc-dev/neofs-sdk-go/proto/object" + "github.com/nspcc-dev/neofs-sdk-go/proto/status" "github.com/stretchr/testify/require" "google.golang.org/protobuf/proto" ) @@ -130,10 +130,7 @@ func (x *testReplicationServer) Replicate(_ context.Context, req *objectgrpc.Rep return &resp, nil } - var sigV2 refs.Signature - sig.WriteToV2(&sigV2) - - resp.ObjectSignature = sigV2.StableMarshal(nil) + resp.ObjectSignature = neofsproto.Marshal(sig) } resp.Status = &status.Status{Code: x.respStatusCode} diff --git a/client/object_search.go b/client/object_search.go index 4cdf7598..80984c18 100644 --- a/client/object_search.go +++ b/client/object_search.go @@ -7,28 +7,33 @@ import ( "io" "time" - "github.com/nspcc-dev/neofs-api-go/v2/acl" - v2object "github.com/nspcc-dev/neofs-api-go/v2/object" - protoobject "github.com/nspcc-dev/neofs-api-go/v2/object/grpc" - v2refs "github.com/nspcc-dev/neofs-api-go/v2/refs" "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" + neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" "github.com/nspcc-dev/neofs-sdk-go/object" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" + protoobject "github.com/nspcc-dev/neofs-sdk-go/proto/object" + "github.com/nspcc-dev/neofs-sdk-go/proto/refs" + protosession "github.com/nspcc-dev/neofs-sdk-go/proto/session" "github.com/nspcc-dev/neofs-sdk-go/stat" "github.com/nspcc-dev/neofs-sdk-go/user" + "github.com/nspcc-dev/neofs-sdk-go/version" ) // PrmObjectSearch groups optional parameters of ObjectSearch operation. type PrmObjectSearch struct { sessionContainer + prmCommonMeta + bearerToken *bearer.Token + local bool filters object.SearchFilters } // MarkLocal tells the server to execute the operation locally. func (x *PrmObjectSearch) MarkLocal() { - x.meta.SetTTL(1) + x.local = true } // WithBearerToken attaches bearer token to be used for the operation. @@ -37,17 +42,7 @@ func (x *PrmObjectSearch) MarkLocal() { // // Must be signed. func (x *PrmObjectSearch) WithBearerToken(t bearer.Token) { - var v2token acl.BearerToken - t.WriteToV2(&v2token) - x.meta.SetBearerToken(&v2token) -} - -// WithXHeaders specifies list of extended headers (string key-value pairs) -// to be attached to the request. Must have an even length. -// -// Slice must not be mutated until the operation completes. -func (x *PrmObjectSearch) WithXHeaders(hs ...string) { - writeXHeadersToMeta(hs, &x.meta) + x.bearerToken = &t } // SetFilters sets filters by which to select objects. All container objects @@ -69,12 +64,11 @@ type searchObjectsResponseStream interface { // // Must be initialized using Client.ObjectSearch, any other usage is unsafe. type ObjectListReader struct { - client *Client cancelCtxStream context.CancelFunc err error stream searchObjectsResponseStream singleMsgTimeout time.Duration - tail []v2refs.ObjectID + tail []*refs.ObjectID statisticCallback shortStatisticCallback startTime time.Time // if statisticCallback is set only @@ -108,18 +102,18 @@ func (x *ObjectListReader) Read(buf []oid.ID) (int, error) { if x.err != nil { return read, x.err } - var respV2 v2object.SearchResponse - if x.err = respV2.FromGRPCMessage(resp); x.err != nil { + + if x.err = neofscrypto.VerifyResponseWithBuffer[*protoobject.SearchResponse_Body](resp, nil); x.err != nil { + x.err = fmt.Errorf("%w: %w", errResponseSignatures, x.err) return read, x.err } - x.err = x.client.processResponse(&respV2) - if x.err != nil { + if x.err = apistatus.ToError(resp.GetMetaHeader().GetStatus()); x.err != nil { return read, x.err } // read new chunk of objects - ids := respV2.GetBody().GetIDList() + ids := resp.GetBody().GetIdList() if len(ids) == 0 { // just skip empty lists since they are not prohibited by protocol continue @@ -137,10 +131,10 @@ func (x *ObjectListReader) Read(buf []oid.ID) (int, error) { } } -func copyIDBuffers(dst []oid.ID, src []v2refs.ObjectID) int { +func copyIDBuffers(dst []oid.ID, src []*refs.ObjectID) int { var i int for ; i < len(dst) && i < len(src); i++ { - _ = dst[i].ReadFromV2(src[i]) + copy(dst[i][:], src[i].GetValue()) } return i } @@ -219,37 +213,47 @@ func (c *Client) ObjectSearchInit(ctx context.Context, containerID cid.ID, signe return nil, ErrMissingSigner } - var cidV2 v2refs.ContainerID - containerID.WriteToV2(&cidV2) - - var body v2object.SearchRequestBody - body.SetVersion(1) - body.SetContainerID(&cidV2) - body.SetFilters(prm.filters.ToV2()) - - // init reader - var req v2object.SearchRequest - req.SetBody(&body) - c.prepareRequest(&req, &prm.meta) + req := &protoobject.SearchRequest{ + Body: &protoobject.SearchRequest_Body{ + ContainerId: containerID.ProtoMessage(), + Version: 1, + Filters: prm.filters.ProtoMessage(), + }, + MetaHeader: &protosession.RequestMetaHeader{ + Version: version.Current().ProtoMessage(), + }, + } + writeXHeadersToMeta(prm.xHeaders, req.MetaHeader) + if prm.local { + req.MetaHeader.Ttl = localRequestTTL + } else { + req.MetaHeader.Ttl = defaultRequestTTL + } + if prm.session != nil { + req.MetaHeader.SessionToken = prm.session.ProtoMessage() + } + if prm.bearerToken != nil { + req.MetaHeader.BearerToken = prm.bearerToken.ProtoMessage() + } buf := c.buffers.Get().(*[]byte) - err = signServiceMessage(signer, &req, *buf) - c.buffers.Put(buf) + defer func() { c.buffers.Put(buf) }() + + req.VerifyHeader, err = neofscrypto.SignRequestWithBuffer[*protoobject.SearchRequest_Body](signer, req, *buf) if err != nil { - err = fmt.Errorf("sign request: %w", err) + err = fmt.Errorf("%w: %w", errSignRequest, err) return nil, err } var r ObjectListReader ctx, r.cancelCtxStream = context.WithCancel(ctx) - r.stream, err = c.object.Search(ctx, req.ToGRPCMessage().(*protoobject.SearchRequest)) + r.stream, err = c.object.Search(ctx, req) if err != nil { err = fmt.Errorf("open stream: %w", err) return nil, err } r.singleMsgTimeout = c.streamTimeout - r.client = c if c.prm.statisticCallback != nil { r.startTime = time.Now() r.statisticCallback = func(dur time.Duration, err error) { diff --git a/client/object_search_test.go b/client/object_search_test.go index c74956a6..f2130601 100644 --- a/client/object_search_test.go +++ b/client/object_search_test.go @@ -10,16 +10,15 @@ import ( "testing" "time" - v2object "github.com/nspcc-dev/neofs-api-go/v2/object" - protoobject "github.com/nspcc-dev/neofs-api-go/v2/object/grpc" - protorefs "github.com/nspcc-dev/neofs-api-go/v2/refs/grpc" - protostatus "github.com/nspcc-dev/neofs-api-go/v2/status/grpc" bearertest "github.com/nspcc-dev/neofs-sdk-go/bearer/test" apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status" cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test" "github.com/nspcc-dev/neofs-sdk-go/object" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" oidtest "github.com/nspcc-dev/neofs-sdk-go/object/id/test" + protoobject "github.com/nspcc-dev/neofs-sdk-go/proto/object" + protorefs "github.com/nspcc-dev/neofs-sdk-go/proto/refs" + protostatus "github.com/nspcc-dev/neofs-sdk-go/proto/status" sessiontest "github.com/nspcc-dev/neofs-sdk-go/session/test" "github.com/nspcc-dev/neofs-sdk-go/stat" usertest "github.com/nspcc-dev/neofs-sdk-go/user/test" @@ -57,17 +56,9 @@ type testSearchObjectsServer struct { protoobject.UnimplementedObjectServiceServer testCommonServerStreamServerSettings[ *protoobject.SearchRequest_Body, - v2object.SearchRequestBody, - *v2object.SearchRequestBody, *protoobject.SearchRequest, - v2object.SearchRequest, - *v2object.SearchRequest, *protoobject.SearchResponse_Body, - v2object.SearchResponseBody, - *v2object.SearchResponseBody, *protoobject.SearchResponse, - v2object.SearchResponse, - *v2object.SearchResponse, ] testObjectSessionServerSettings testBearerTokenServerSettings @@ -317,7 +308,6 @@ func TestClient_ObjectSearch(t *testing.T) { c := newTestObjectClient(t, srv) bt := bearertest.Token() - bt.SetEACLTable(anyValidEACL) // TODO: drop after https://github.com/nspcc-dev/neofs-sdk-go/issues/606 require.NoError(t, bt.Sign(usertest.User())) opts := anyValidOpts opts.WithBearerToken(bt) diff --git a/client/object_test.go b/client/object_test.go index ccd1b907..1f5b36d5 100644 --- a/client/object_test.go +++ b/client/object_test.go @@ -6,13 +6,13 @@ import ( "strings" "testing" - protoacl "github.com/nspcc-dev/neofs-api-go/v2/acl/grpc" - protoobject "github.com/nspcc-dev/neofs-api-go/v2/object/grpc" - protorefs "github.com/nspcc-dev/neofs-api-go/v2/refs/grpc" - protosession "github.com/nspcc-dev/neofs-api-go/v2/session/grpc" "github.com/nspcc-dev/neofs-sdk-go/bearer" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" + protoacl "github.com/nspcc-dev/neofs-sdk-go/proto/acl" + protoobject "github.com/nspcc-dev/neofs-sdk-go/proto/object" + protorefs "github.com/nspcc-dev/neofs-sdk-go/proto/refs" + protosession "github.com/nspcc-dev/neofs-sdk-go/proto/session" "github.com/nspcc-dev/neofs-sdk-go/session" "github.com/stretchr/testify/require" "google.golang.org/grpc/codes" @@ -40,18 +40,17 @@ var ( // + other cases in init } invalidObjectSessionTokenProtoTestcases = append(invalidCommonSessionTokenProtoTestcases, invalidSessionTokenProtoTestcase{ - name: "context/wrong", msg: "invalid context: invalid context *session.ContainerSessionContext", + name: "context/wrong", msg: "invalid context: invalid context *session.SessionToken_Body_Container", corrupt: func(valid *protosession.SessionToken) { valid.Body.Context = new(protosession.SessionToken_Body_Container) }}, - // TODO: uncomment after https://github.com/nspcc-dev/neofs-sdk-go/issues/606 - // invalidSessionTokenProtoTestcase{ - // name: "context/verb/negative", msg: "invalid context: negative verb -1", - // corrupt: func(valid *protosession.SessionToken) { - // c := valid.Body.Context.(*protosession.SessionToken_Body_Object).Object - // c.Verb = -1 - // }, - // }, + invalidSessionTokenProtoTestcase{ + name: "context/verb/negative", msg: "invalid context: negative verb -1", + corrupt: func(valid *protosession.SessionToken) { + c := valid.Body.Context.(*protosession.SessionToken_Body_Object).Object + c.Verb = -1 + }, + }, invalidSessionTokenProtoTestcase{ name: "context/container/nil", msg: "invalid context: missing target container", corrupt: func(valid *protosession.SessionToken) { @@ -66,19 +65,18 @@ var ( // 4. creation epoch (any accepted) // 5. payload length (any accepted) // 6. payload checksum (init) - // TODO: uncomment after https://github.com/nspcc-dev/neofs-sdk-go/issues/606 - // {name: "type/negative", msg: "negative type -1", corrupt: func(valid *protoobject.Header) { - // valid.ObjectType = -1 - // }}, + {name: "type/negative", msg: "negative type -1", corrupt: func(valid *protoobject.Header) { + valid.ObjectType = -1 + }}, // 8. homomorphic payload checksum (init) // 9. session token (init) - {name: "attributes/no key", msg: "empty key of the attribute #1", + {name: "attributes/no key", msg: "invalid attribute #1: missing key", corrupt: func(valid *protoobject.Header) { valid.Attributes = []*protoobject.Header_Attribute{ {Key: "k1", Value: "v1"}, {Key: "", Value: "v2"}, {Key: "k3", Value: "v3"}, } }}, - {name: "attributes/no value", msg: "empty value of the attribute #1 (k2)", + {name: "attributes/no value", msg: "invalid attribute #1: missing value", corrupt: func(valid *protoobject.Header) { valid.Attributes = []*protoobject.Header_Attribute{ {Key: "k1", Value: "v1"}, {Key: "k2", Value: ""}, {Key: "k3", Value: "v3"}, @@ -90,7 +88,7 @@ var ( {Key: "k1", Value: "v1"}, {Key: "k2", Value: "v2"}, {Key: "k1", Value: "v3"}, } }}, - {name: "attributes/expiration", msg: `invalid expiration attribute (must be a uint): strconv.ParseUint: parsing "foo": invalid syntax`, + {name: "attributes/expiration", msg: `invalid attribute #1: invalid expiration epoch (must be a uint): strconv.ParseUint: parsing "foo": invalid syntax`, corrupt: func(valid *protoobject.Header) { valid.Attributes = []*protoobject.Header_Attribute{ {Key: "k1", Value: "v1"}, {Key: "__NEOFS__EXPIRATION_EPOCH", Value: "foo"}, {Key: "k3", Value: "v3"}, diff --git a/client/reputation.go b/client/reputation.go index d96a5496..91e84b4f 100644 --- a/client/reputation.go +++ b/client/reputation.go @@ -2,12 +2,16 @@ package client import ( "context" + "fmt" "time" - v2reputation "github.com/nspcc-dev/neofs-api-go/v2/reputation" - protoreputation "github.com/nspcc-dev/neofs-api-go/v2/reputation/grpc" + apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status" + neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" + protoreputation "github.com/nspcc-dev/neofs-sdk-go/proto/reputation" + protosession "github.com/nspcc-dev/neofs-sdk-go/proto/session" "github.com/nspcc-dev/neofs-sdk-go/reputation" "github.com/nspcc-dev/neofs-sdk-go/stat" + "github.com/nspcc-dev/neofs-sdk-go/version" ) // PrmAnnounceLocalTrust groups optional parameters of AnnounceLocalTrust operation. @@ -47,51 +51,54 @@ func (c *Client) AnnounceLocalTrust(ctx context.Context, epoch uint64, trusts [] return err } - // form request body - reqBody := new(v2reputation.AnnounceLocalTrustRequestBody) - reqBody.SetEpoch(epoch) - - trustList := make([]v2reputation.Trust, len(trusts)) - + req := &protoreputation.AnnounceLocalTrustRequest{ + Body: &protoreputation.AnnounceLocalTrustRequest_Body{ + Epoch: epoch, + Trusts: make([]*protoreputation.Trust, len(trusts)), + }, + MetaHeader: &protosession.RequestMetaHeader{ + Version: version.Current().ProtoMessage(), + Ttl: defaultRequestTTL, + }, + } for i := range trusts { - trusts[i].WriteToV2(&trustList[i]) + req.Body.Trusts[i] = trusts[i].ProtoMessage() } + writeXHeadersToMeta(prm.xHeaders, req.MetaHeader) - reqBody.SetTrusts(trustList) - - // form request - var req v2reputation.AnnounceLocalTrustRequest - - req.SetBody(reqBody) + buf := c.buffers.Get().(*[]byte) + defer func() { c.buffers.Put(buf) }() - // init call context + req.VerifyHeader, err = neofscrypto.SignRequestWithBuffer[*protoreputation.AnnounceLocalTrustRequest_Body](c.prm.signer, req, *buf) + if err != nil { + err = fmt.Errorf("%w: %w", errSignRequest, err) + return err + } - var ( - cc contextCall - ) + resp, err := c.reputation.AnnounceLocalTrust(ctx, req) + if err != nil { + err = rpcErr(err) + return err + } - c.initCallContext(&cc) - cc.meta = prm.prmCommonMeta - cc.req = &req - cc.call = func() (responseV2, error) { - resp, err := c.reputation.AnnounceLocalTrust(ctx, req.ToGRPCMessage().(*protoreputation.AnnounceLocalTrustRequest)) + if c.prm.cbRespInfo != nil { + err = c.prm.cbRespInfo(ResponseMetaInfo{ + key: resp.GetVerifyHeader().GetBodySignature().GetKey(), + epoch: resp.GetMetaHeader().GetEpoch(), + }) if err != nil { - return nil, rpcErr(err) + err = fmt.Errorf("%w: %w", errResponseCallback, err) + return err } - var respV2 v2reputation.AnnounceLocalTrustResponse - if err = respV2.FromGRPCMessage(resp); err != nil { - return nil, err - } - return &respV2, nil } - // process call - if !cc.processCall() { - err = cc.err + if err = neofscrypto.VerifyResponseWithBuffer[*protoreputation.AnnounceLocalTrustResponse_Body](resp, *buf); err != nil { + err = fmt.Errorf("%w: %w", errResponseSignatures, err) return err } - return nil + err = apistatus.ToError(resp.GetMetaHeader().GetStatus()) + return err } // PrmAnnounceIntermediateTrust groups optional parameters of AnnounceIntermediateTrust operation. @@ -133,46 +140,50 @@ func (c *Client) AnnounceIntermediateTrust(ctx context.Context, epoch uint64, tr return err } - var v2Trust v2reputation.PeerToPeerTrust - trust.WriteToV2(&v2Trust) - - // form request body - reqBody := new(v2reputation.AnnounceIntermediateResultRequestBody) - reqBody.SetEpoch(epoch) - reqBody.SetIteration(prm.iter) - reqBody.SetTrust(&v2Trust) - - // form request - var req v2reputation.AnnounceIntermediateResultRequest + req := &protoreputation.AnnounceIntermediateResultRequest{ + Body: &protoreputation.AnnounceIntermediateResultRequest_Body{ + Epoch: epoch, + Iteration: prm.iter, + Trust: trust.ProtoMessage(), + }, + MetaHeader: &protosession.RequestMetaHeader{ + Version: version.Current().ProtoMessage(), + Ttl: defaultRequestTTL, + }, + } + writeXHeadersToMeta(prm.xHeaders, req.MetaHeader) - req.SetBody(reqBody) + buf := c.buffers.Get().(*[]byte) + defer func() { c.buffers.Put(buf) }() - // init call context + req.VerifyHeader, err = neofscrypto.SignRequestWithBuffer[*protoreputation.AnnounceIntermediateResultRequest_Body](c.prm.signer, req, *buf) + if err != nil { + err = fmt.Errorf("%w: %w", errSignRequest, err) + return err + } - var ( - cc contextCall - ) + resp, err := c.reputation.AnnounceIntermediateResult(ctx, req) + if err != nil { + err = rpcErr(err) + return err + } - c.initCallContext(&cc) - cc.meta = prm.prmCommonMeta - cc.req = &req - cc.call = func() (responseV2, error) { - resp, err := c.reputation.AnnounceIntermediateResult(ctx, req.ToGRPCMessage().(*protoreputation.AnnounceIntermediateResultRequest)) + if c.prm.cbRespInfo != nil { + err = c.prm.cbRespInfo(ResponseMetaInfo{ + key: resp.GetVerifyHeader().GetBodySignature().GetKey(), + epoch: resp.GetMetaHeader().GetEpoch(), + }) if err != nil { - return nil, rpcErr(err) - } - var respV2 v2reputation.AnnounceIntermediateResultResponse - if err = respV2.FromGRPCMessage(resp); err != nil { - return nil, err + err = fmt.Errorf("%w: %w", errResponseCallback, err) + return err } - return &respV2, nil } - // process call - if !cc.processCall() { - err = cc.err + if err = neofscrypto.VerifyResponseWithBuffer[*protoreputation.AnnounceIntermediateResultResponse_Body](resp, *buf); err != nil { + err = fmt.Errorf("%w: %w", errResponseSignatures, err) return err } - return nil + err = apistatus.ToError(resp.GetMetaHeader().GetStatus()) + return err } diff --git a/client/reputation_test.go b/client/reputation_test.go index ebe53e5e..874b46ca 100644 --- a/client/reputation_test.go +++ b/client/reputation_test.go @@ -8,8 +8,7 @@ import ( "testing" "time" - apireputation "github.com/nspcc-dev/neofs-api-go/v2/reputation" - protoreputation "github.com/nspcc-dev/neofs-api-go/v2/reputation/grpc" + protoreputation "github.com/nspcc-dev/neofs-sdk-go/proto/reputation" "github.com/nspcc-dev/neofs-sdk-go/reputation" reputationtest "github.com/nspcc-dev/neofs-sdk-go/reputation/test" "github.com/nspcc-dev/neofs-sdk-go/stat" @@ -36,17 +35,9 @@ type testAnnounceIntermediateReputationServer struct { protoreputation.UnimplementedReputationServiceServer testCommonUnaryServerSettings[ *protoreputation.AnnounceIntermediateResultRequest_Body, - apireputation.AnnounceIntermediateResultRequestBody, - *apireputation.AnnounceIntermediateResultRequestBody, *protoreputation.AnnounceIntermediateResultRequest, - apireputation.AnnounceIntermediateResultRequest, - *apireputation.AnnounceIntermediateResultRequest, *protoreputation.AnnounceIntermediateResultResponse_Body, - apireputation.AnnounceIntermediateResultResponseBody, - *apireputation.AnnounceIntermediateResultResponseBody, *protoreputation.AnnounceIntermediateResultResponse, - apireputation.AnnounceIntermediateResultResponse, - *apireputation.AnnounceIntermediateResultResponse, ] reqEpoch *uint64 reqIter uint32 @@ -151,17 +142,9 @@ type testAnnounceLocalTrustServer struct { protoreputation.UnimplementedReputationServiceServer testCommonUnaryServerSettings[ *protoreputation.AnnounceLocalTrustRequest_Body, - apireputation.AnnounceLocalTrustRequestBody, - *apireputation.AnnounceLocalTrustRequestBody, *protoreputation.AnnounceLocalTrustRequest, - apireputation.AnnounceLocalTrustRequest, - *apireputation.AnnounceLocalTrustRequest, *protoreputation.AnnounceLocalTrustResponse_Body, - apireputation.AnnounceLocalTrustResponseBody, - *apireputation.AnnounceLocalTrustResponseBody, *protoreputation.AnnounceLocalTrustResponse, - apireputation.AnnounceLocalTrustResponse, - *apireputation.AnnounceLocalTrustResponse, ] reqEpoch *uint64 reqTrusts []reputation.Trust diff --git a/client/response.go b/client/response.go index 934864eb..f809e49e 100644 --- a/client/response.go +++ b/client/response.go @@ -1,7 +1,5 @@ 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 @@ -9,11 +7,6 @@ type ResponseMetaInfo struct { epoch uint64 } -type responseV2 interface { - GetMetaHeader() *session.ResponseMetaHeader - GetVerificationHeader() *session.ResponseVerificationHeader -} - // ResponderKey returns responder's public key in a binary format. // // The resulting slice of bytes is a serialized compressed public key. See [elliptic.MarshalCompressed]. diff --git a/client/session.go b/client/session.go index 46930e11..2f06bf09 100644 --- a/client/session.go +++ b/client/session.go @@ -2,13 +2,15 @@ package client import ( "context" + "fmt" "time" - "github.com/nspcc-dev/neofs-api-go/v2/refs" - v2session "github.com/nspcc-dev/neofs-api-go/v2/session" - protosession "github.com/nspcc-dev/neofs-api-go/v2/session/grpc" + apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status" + neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" + protosession "github.com/nspcc-dev/neofs-sdk-go/proto/session" "github.com/nspcc-dev/neofs-sdk-go/stat" "github.com/nspcc-dev/neofs-sdk-go/user" + "github.com/nspcc-dev/neofs-sdk-go/version" ) // PrmSessionCreate groups parameters of SessionCreate operation. @@ -38,10 +40,6 @@ func NewResSessionCreate(id []byte, sessionKey []byte) ResSessionCreate { } } -func (x *ResSessionCreate) setID(id []byte) { - x.id = id -} - // ID returns identifier of the opened session in a binary NeoFS API protocol format. // // Client doesn't retain value so modification is safe. @@ -49,10 +47,6 @@ func (x ResSessionCreate) ID() []byte { return x.id } -func (x *ResSessionCreate) setSessionKey(key []byte) { - x.sessionKey = key -} - // PublicKey returns public key of the opened session in a binary NeoFS API protocol format. // // The resulting slice of bytes is a serialized compressed public key. See [elliptic.MarshalCompressed]. @@ -90,66 +84,63 @@ func (c *Client) SessionCreate(ctx context.Context, signer user.Signer, prm PrmS return nil, ErrMissingSigner } - ownerID := signer.UserID() - - var ownerIDV2 refs.OwnerID - ownerID.WriteToV2(&ownerIDV2) - - // form request body - reqBody := new(v2session.CreateRequestBody) - reqBody.SetOwnerID(&ownerIDV2) - reqBody.SetExpiration(prm.exp) - - // for request - var req v2session.CreateRequest + req := &protosession.CreateRequest{ + Body: &protosession.CreateRequest_Body{ + OwnerId: signer.UserID().ProtoMessage(), + Expiration: prm.exp, + }, + MetaHeader: &protosession.RequestMetaHeader{ + Version: version.Current().ProtoMessage(), + Ttl: defaultRequestTTL, + }, + } + writeXHeadersToMeta(prm.xHeaders, req.MetaHeader) - req.SetBody(reqBody) + buf := c.buffers.Get().(*[]byte) + defer func() { c.buffers.Put(buf) }() - // init call context + req.VerifyHeader, err = neofscrypto.SignRequestWithBuffer[*protosession.CreateRequest_Body](signer, req, *buf) + if err != nil { + err = fmt.Errorf("%w: %w", errSignRequest, err) + return nil, err + } - var ( - cc contextCall - res ResSessionCreate - ) + resp, err := c.session.Create(ctx, req) + if err != nil { + err = rpcErr(err) + return nil, err + } - c.initCallContext(&cc) - cc.signer = signer - cc.meta = prm.prmCommonMeta - cc.req = &req - cc.call = func() (responseV2, error) { - resp, err := c.session.Create(ctx, req.ToGRPCMessage().(*protosession.CreateRequest)) + if c.prm.cbRespInfo != nil { + err = c.prm.cbRespInfo(ResponseMetaInfo{ + key: resp.GetVerifyHeader().GetBodySignature().GetKey(), + epoch: resp.GetMetaHeader().GetEpoch(), + }) if err != nil { - return nil, rpcErr(err) - } - var respV2 v2session.CreateResponse - if err = respV2.FromGRPCMessage(resp); err != nil { + err = fmt.Errorf("%w: %w", errResponseCallback, err) return nil, err } - return &respV2, nil } - cc.result = func(r responseV2) { - resp := r.(*v2session.CreateResponse) - - body := resp.GetBody() - if len(body.GetID()) == 0 { - cc.err = newErrMissingResponseField("session id") - return - } + if err = neofscrypto.VerifyResponseWithBuffer[*protosession.CreateResponse_Body](resp, *buf); err != nil { + err = fmt.Errorf("%w: %w", errResponseSignatures, err) + return nil, err + } - if len(body.GetSessionKey()) == 0 { - cc.err = newErrMissingResponseField("session key") - return - } + if err = apistatus.ToError(resp.GetMetaHeader().GetStatus()); err != nil { + return nil, err + } - res.setID(body.GetID()) - res.setSessionKey(body.GetSessionKey()) + body := resp.GetBody() + var res ResSessionCreate + if res.id = body.GetId(); len(res.id) == 0 { + err = newErrMissingResponseField("session id") + return nil, err } - // process call - if !cc.processCall() { - err = cc.err - return nil, cc.err + if res.sessionKey = body.GetSessionKey(); len(res.sessionKey) == 0 { + err = newErrMissingResponseField("session key") + return nil, err } return &res, nil diff --git a/client/session_container.go b/client/session_container.go index df8773d5..59b4e09a 100644 --- a/client/session_container.go +++ b/client/session_container.go @@ -1,7 +1,6 @@ package client import ( - v2session "github.com/nspcc-dev/neofs-api-go/v2/session" "github.com/nspcc-dev/neofs-sdk-go/session" ) @@ -9,7 +8,7 @@ import ( // All methods make public, because sessionContainer is included in Prm* structs. type sessionContainer struct { isSessionIgnored bool - meta v2session.RequestMetaHeader + session *session.Object } // GetSession returns session object. @@ -22,17 +21,10 @@ func (x *sessionContainer) GetSession() (*session.Object, error) { return nil, ErrNoSessionExplicitly } - token := x.meta.GetSessionToken() - if token == nil { + if x.session == nil { return nil, ErrNoSession } - - var sess session.Object - if err := sess.ReadFromV2(*token); err != nil { - return nil, err - } - - return &sess, nil + return x.session, nil } // WithinSession specifies session within which the query must be executed. @@ -44,9 +36,7 @@ func (x *sessionContainer) GetSession() (*session.Object, error) { // // Must be signed. func (x *sessionContainer) WithinSession(t session.Object) { - var tokv2 v2session.Token - t.WriteToV2(&tokv2) - x.meta.SetSessionToken(&tokv2) + x.session = &t x.isSessionIgnored = false } @@ -55,5 +45,5 @@ func (x *sessionContainer) WithinSession(t session.Object) { // See also WithinSession. func (x *sessionContainer) IgnoreSession() { x.isSessionIgnored = true - x.meta.SetSessionToken(nil) + x.session = nil } diff --git a/client/session_test.go b/client/session_test.go index f4304c59..fb8bfa32 100644 --- a/client/session_test.go +++ b/client/session_test.go @@ -8,8 +8,7 @@ import ( "testing" "time" - apisession "github.com/nspcc-dev/neofs-api-go/v2/session" - protosession "github.com/nspcc-dev/neofs-api-go/v2/session/grpc" + protosession "github.com/nspcc-dev/neofs-sdk-go/proto/session" "github.com/nspcc-dev/neofs-sdk-go/stat" "github.com/nspcc-dev/neofs-sdk-go/user" usertest "github.com/nspcc-dev/neofs-sdk-go/user/test" @@ -36,17 +35,9 @@ type testCreateSessionServer struct { protosession.UnimplementedSessionServiceServer testCommonUnaryServerSettings[ *protosession.CreateRequest_Body, - apisession.CreateRequestBody, - *apisession.CreateRequestBody, *protosession.CreateRequest, - apisession.CreateRequest, - *apisession.CreateRequest, *protosession.CreateResponse_Body, - apisession.CreateResponseBody, - *apisession.CreateResponseBody, *protosession.CreateResponse, - apisession.CreateResponse, - *apisession.CreateResponse, ] reqUsr *user.ID reqExp uint64 diff --git a/client/sign.go b/client/sign.go deleted file mode 100644 index 55bbf0a2..00000000 --- a/client/sign.go +++ /dev/null @@ -1,390 +0,0 @@ -package client - -import ( - "errors" - "fmt" - - "github.com/nspcc-dev/neofs-api-go/v2/accounting" - "github.com/nspcc-dev/neofs-api-go/v2/container" - "github.com/nspcc-dev/neofs-api-go/v2/netmap" - "github.com/nspcc-dev/neofs-api-go/v2/object" - "github.com/nspcc-dev/neofs-api-go/v2/refs" - "github.com/nspcc-dev/neofs-api-go/v2/reputation" - "github.com/nspcc-dev/neofs-api-go/v2/session" - "github.com/nspcc-dev/neofs-api-go/v2/util/signature" - neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" -) - -type serviceRequest interface { - GetMetaHeader() *session.RequestMetaHeader - GetVerificationHeader() *session.RequestVerificationHeader - SetVerificationHeader(*session.RequestVerificationHeader) -} - -type serviceResponse interface { - GetMetaHeader() *session.ResponseMetaHeader - GetVerificationHeader() *session.ResponseVerificationHeader - SetVerificationHeader(*session.ResponseVerificationHeader) -} - -type stableMarshaler interface { - StableMarshal([]byte) []byte - StableSize() int -} - -type stableMarshalerWrapper struct { - SM stableMarshaler -} - -type metaHeader interface { - stableMarshaler - getOrigin() metaHeader -} - -type verificationHeader interface { - stableMarshaler - - GetBodySignature() *refs.Signature - SetBodySignature(*refs.Signature) - GetMetaSignature() *refs.Signature - SetMetaSignature(*refs.Signature) - GetOriginSignature() *refs.Signature - SetOriginSignature(*refs.Signature) - - setOrigin(stableMarshaler) - getOrigin() verificationHeader -} - -type requestMetaHeader struct { - *session.RequestMetaHeader -} - -type responseMetaHeader struct { - *session.ResponseMetaHeader -} - -type requestVerificationHeader struct { - *session.RequestVerificationHeader -} - -type responseVerificationHeader struct { - *session.ResponseVerificationHeader -} - -func (h *requestMetaHeader) getOrigin() metaHeader { - return &requestMetaHeader{ - RequestMetaHeader: h.GetOrigin(), - } -} - -func (h *responseMetaHeader) getOrigin() metaHeader { - return &responseMetaHeader{ - ResponseMetaHeader: h.GetOrigin(), - } -} - -func (h *requestVerificationHeader) getOrigin() verificationHeader { - if origin := h.GetOrigin(); origin != nil { - return &requestVerificationHeader{ - RequestVerificationHeader: origin, - } - } - - return nil -} - -func (h *requestVerificationHeader) setOrigin(m stableMarshaler) { - if m != nil { - h.SetOrigin(m.(*session.RequestVerificationHeader)) - } -} - -func (r *responseVerificationHeader) getOrigin() verificationHeader { - if origin := r.GetOrigin(); origin != nil { - return &responseVerificationHeader{ - ResponseVerificationHeader: origin, - } - } - - return nil -} - -func (r *responseVerificationHeader) setOrigin(m stableMarshaler) { - if m != nil { - r.SetOrigin(m.(*session.ResponseVerificationHeader)) - } -} - -func (s stableMarshalerWrapper) ReadSignedData(buf []byte) ([]byte, error) { - if s.SM != nil { - return s.SM.StableMarshal(buf), nil - } - - return nil, nil -} - -func (s stableMarshalerWrapper) SignedDataSize() int { - if s.SM != nil { - return s.SM.StableSize() - } - - return 0 -} - -// signServiceMessage signing request or response messages which can be sent or received from neofs endpoint. -// Return errors: -// - [ErrSign] -func signServiceMessage(signer neofscrypto.Signer, msg any, buf []byte) error { - var ( - body, meta, verifyOrigin stableMarshaler - verifyHdr verificationHeader - verifyHdrSetter func(verificationHeader) - ) - - switch v := msg.(type) { - case nil: - return nil - case serviceRequest: - body = serviceMessageBody(v) - meta = v.GetMetaHeader() - verifyHdr = &requestVerificationHeader{new(session.RequestVerificationHeader)} - verifyHdrSetter = func(h verificationHeader) { - v.SetVerificationHeader(h.(*requestVerificationHeader).RequestVerificationHeader) - } - - if h := v.GetVerificationHeader(); h != nil { - verifyOrigin = h - } - case serviceResponse: - body = serviceMessageBody(v) - meta = v.GetMetaHeader() - verifyHdr = &responseVerificationHeader{new(session.ResponseVerificationHeader)} - verifyHdrSetter = func(h verificationHeader) { - v.SetVerificationHeader(h.(*responseVerificationHeader).ResponseVerificationHeader) - } - - if h := v.GetVerificationHeader(); h != nil { - verifyOrigin = h - } - default: - return NewSignError(fmt.Errorf("unsupported session message %T", v)) - } - - if verifyOrigin == nil { - // sign session message body - if err := signServiceMessagePart(signer, body, verifyHdr.SetBodySignature, buf); err != nil { - return NewSignError(fmt.Errorf("body: %w", err)) - } - } - - // sign meta header - if err := signServiceMessagePart(signer, meta, verifyHdr.SetMetaSignature, buf); err != nil { - return NewSignError(fmt.Errorf("meta header: %w", err)) - } - - // sign verification header origin - if err := signServiceMessagePart(signer, verifyOrigin, verifyHdr.SetOriginSignature, buf); err != nil { - return NewSignError(fmt.Errorf("origin of verification header: %w", err)) - } - - // wrap origin verification header - verifyHdr.setOrigin(verifyOrigin) - - // update matryoshka verification header - verifyHdrSetter(verifyHdr) - - return nil -} - -func signServiceMessagePart(signer neofscrypto.Signer, part stableMarshaler, sigWrite func(*refs.Signature), buf []byte) error { - var sig neofscrypto.Signature - var sigv2 refs.Signature - - if err := sig.CalculateMarshalled(signer, part, buf); err != nil { - return fmt.Errorf("calculate %w", err) - } - - sig.WriteToV2(&sigv2) - sigWrite(&sigv2) - - return nil -} - -func verifyServiceMessage(msg any) error { - var ( - meta metaHeader - verify verificationHeader - ) - - switch v := msg.(type) { - case nil: - return nil - case serviceRequest: - meta = &requestMetaHeader{ - RequestMetaHeader: v.GetMetaHeader(), - } - - verify = &requestVerificationHeader{ - RequestVerificationHeader: v.GetVerificationHeader(), - } - case serviceResponse: - meta = &responseMetaHeader{ - ResponseMetaHeader: v.GetMetaHeader(), - } - - verify = &responseVerificationHeader{ - ResponseVerificationHeader: v.GetVerificationHeader(), - } - default: - return fmt.Errorf("unsupported session message %T", v) - } - - body := serviceMessageBody(msg) - size := body.StableSize() - if sz := meta.StableSize(); sz > size { - size = sz - } - if sz := verify.StableSize(); sz > size { - size = sz - } - - buf := make([]byte, 0, size) - return verifyMatryoshkaLevel(body, meta, verify, buf) -} - -func verifyMatryoshkaLevel(body stableMarshaler, meta metaHeader, verify verificationHeader, buf []byte) error { - if err := verifyServiceMessagePart(meta, verify.GetMetaSignature, buf); err != nil { - return fmt.Errorf("could not verify meta header: %w", err) - } - - origin := verify.getOrigin() - - if err := verifyServiceMessagePart(origin, verify.GetOriginSignature, buf); err != nil { - return fmt.Errorf("could not verify origin of verification header: %w", err) - } - - if origin == nil { - if err := verifyServiceMessagePart(body, verify.GetBodySignature, buf); err != nil { - return fmt.Errorf("could not verify body: %w", err) - } - - return nil - } - - if verify.GetBodySignature() != nil { - return errors.New("body signature at the matryoshka upper level") - } - - return verifyMatryoshkaLevel(body, meta.getOrigin(), origin, buf) -} - -func verifyServiceMessagePart(part stableMarshaler, sigRdr func() *refs.Signature, buf []byte) error { - return signature.VerifyDataWithSource( - &stableMarshalerWrapper{part}, - sigRdr, - signature.WithBuffer(buf), - ) -} - -func serviceMessageBody(req any) stableMarshaler { - switch v := req.(type) { - default: - panic(fmt.Sprintf("unsupported session message %T", req)) - - /* Accounting */ - case *accounting.BalanceRequest: - return v.GetBody() - case *accounting.BalanceResponse: - return v.GetBody() - - /* Session */ - case *session.CreateRequest: - return v.GetBody() - case *session.CreateResponse: - return v.GetBody() - - /* Container */ - case *container.PutRequest: - return v.GetBody() - case *container.PutResponse: - return v.GetBody() - case *container.DeleteRequest: - return v.GetBody() - case *container.DeleteResponse: - return v.GetBody() - case *container.GetRequest: - return v.GetBody() - case *container.GetResponse: - return v.GetBody() - case *container.ListRequest: - return v.GetBody() - case *container.ListResponse: - return v.GetBody() - case *container.SetExtendedACLRequest: - return v.GetBody() - case *container.SetExtendedACLResponse: - return v.GetBody() - case *container.GetExtendedACLRequest: - return v.GetBody() - case *container.GetExtendedACLResponse: - return v.GetBody() - case *container.AnnounceUsedSpaceRequest: - return v.GetBody() - case *container.AnnounceUsedSpaceResponse: - return v.GetBody() - - /* Object */ - case *object.PutRequest: - return v.GetBody() - case *object.PutResponse: - return v.GetBody() - case *object.GetRequest: - return v.GetBody() - case *object.GetResponse: - return v.GetBody() - case *object.HeadRequest: - return v.GetBody() - case *object.HeadResponse: - return v.GetBody() - case *object.SearchRequest: - return v.GetBody() - case *object.SearchResponse: - return v.GetBody() - case *object.DeleteRequest: - return v.GetBody() - case *object.DeleteResponse: - return v.GetBody() - case *object.GetRangeRequest: - return v.GetBody() - case *object.GetRangeResponse: - return v.GetBody() - case *object.GetRangeHashRequest: - return v.GetBody() - case *object.GetRangeHashResponse: - return v.GetBody() - - /* Netmap */ - case *netmap.LocalNodeInfoRequest: - return v.GetBody() - case *netmap.LocalNodeInfoResponse: - return v.GetBody() - case *netmap.NetworkInfoRequest: - return v.GetBody() - case *netmap.NetworkInfoResponse: - return v.GetBody() - case *netmap.SnapshotRequest: - return v.GetBody() - case *netmap.SnapshotResponse: - return v.GetBody() - - /* Reputation */ - case *reputation.AnnounceLocalTrustRequest: - return v.GetBody() - case *reputation.AnnounceLocalTrustResponse: - return v.GetBody() - case *reputation.AnnounceIntermediateResultRequest: - return v.GetBody() - case *reputation.AnnounceIntermediateResultResponse: - return v.GetBody() - } -} diff --git a/client/sign_test.go b/client/sign_test.go deleted file mode 100644 index 9b7fc421..00000000 --- a/client/sign_test.go +++ /dev/null @@ -1,243 +0,0 @@ -package client - -import ( - "crypto/rand" - "testing" - - "github.com/nspcc-dev/neofs-api-go/v2/accounting" - "github.com/nspcc-dev/neofs-api-go/v2/refs" - "github.com/nspcc-dev/neofs-api-go/v2/session" - neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" - neofscryptotest "github.com/nspcc-dev/neofs-sdk-go/crypto/test" - usertest "github.com/nspcc-dev/neofs-sdk-go/user/test" - "github.com/stretchr/testify/require" -) - -type testResponse interface { - SetMetaHeader(*session.ResponseMetaHeader) - GetMetaHeader() *session.ResponseMetaHeader -} - -func testOwner(t *testing.T, owner *refs.OwnerID, req any) { - originalValue := owner.GetValue() - owner.SetValue([]byte{1, 2, 3}) - // verification must fail - require.Error(t, verifyServiceMessage(req)) - owner.SetValue(originalValue) - require.NoError(t, verifyServiceMessage(req)) -} - -func testRequestSign(t *testing.T, signer neofscrypto.Signer, meta *session.RequestMetaHeader, req request) { - require.Error(t, verifyServiceMessage(req)) - - // sign request - require.NoError(t, signServiceMessage(signer, req, nil)) - - // verification must pass - require.NoError(t, verifyServiceMessage(req)) - - meta.SetOrigin(req.GetMetaHeader()) - req.SetMetaHeader(meta) - - // sign request - require.NoError(t, signServiceMessage(signer, req, nil)) - - // verification must pass - require.NoError(t, verifyServiceMessage(req)) -} - -func testRequestMeta(t *testing.T, meta *session.RequestMetaHeader, req serviceRequest) { - // corrupt meta header - meta.SetTTL(meta.GetTTL() + 1) - - // verification must fail - require.Error(t, verifyServiceMessage(req)) - - // restore meta header - meta.SetTTL(meta.GetTTL() - 1) - - // corrupt origin verification header - req.GetVerificationHeader().SetOrigin(nil) - - // verification must fail - require.Error(t, verifyServiceMessage(req)) -} - -func testResponseSign(t *testing.T, signer neofscrypto.Signer, meta *session.ResponseMetaHeader, resp testResponse) { - require.Error(t, verifyServiceMessage(resp)) - - // sign request - require.NoError(t, signServiceMessage(signer, resp, nil)) - - // verification must pass - require.NoError(t, verifyServiceMessage(resp)) - - meta.SetOrigin(resp.GetMetaHeader()) - resp.SetMetaHeader(meta) - - // sign request - require.NoError(t, signServiceMessage(signer, resp, nil)) - - // verification must pass - require.NoError(t, verifyServiceMessage(resp)) -} - -func testResponseMeta(t *testing.T, meta *session.ResponseMetaHeader, req serviceResponse) { - // corrupt meta header - meta.SetTTL(meta.GetTTL() + 1) - - // verification must fail - require.Error(t, verifyServiceMessage(req)) - - // restore meta header - meta.SetTTL(meta.GetTTL() - 1) - - // corrupt origin verification header - req.GetVerificationHeader().SetOrigin(nil) - - // verification must fail - require.Error(t, verifyServiceMessage(req)) -} - -func TestEmptyMessage(t *testing.T) { - signer := neofscryptotest.Signer() - - require.NoError(t, verifyServiceMessage(nil)) - require.NoError(t, signServiceMessage(signer, nil, nil)) -} - -func TestBalanceRequest(t *testing.T) { - signer := neofscryptotest.Signer() - id := usertest.ID() - - var ownerID refs.OwnerID - id.WriteToV2(&ownerID) - - body := accounting.BalanceRequestBody{} - body.SetOwnerID(&ownerID) - - meta := &session.RequestMetaHeader{} - meta.SetTTL(1) - - req := &accounting.BalanceRequest{} - req.SetBody(&body) - req.SetMetaHeader(meta) - - // add level to meta header matryoshka - meta = &session.RequestMetaHeader{} - testRequestSign(t, signer, meta, req) - - testOwner(t, &ownerID, req) - testRequestMeta(t, meta, req) -} - -func TestBalanceResponse(t *testing.T) { - signer := neofscryptotest.Signer() - - dec := new(accounting.Decimal) - dec.SetValue(100) - - body := new(accounting.BalanceResponseBody) - body.SetBalance(dec) - - meta := new(session.ResponseMetaHeader) - meta.SetTTL(1) - - resp := new(accounting.BalanceResponse) - resp.SetBody(body) - resp.SetMetaHeader(meta) - - // add level to meta header matryoshka - meta = new(session.ResponseMetaHeader) - testResponseSign(t, signer, meta, resp) - - // corrupt body - dec.SetValue(dec.GetValue() + 1) - - // verification must fail - require.Error(t, verifyServiceMessage(resp)) - - // restore body - dec.SetValue(dec.GetValue() - 1) - - testResponseMeta(t, meta, resp) -} - -func TestCreateRequest(t *testing.T) { - signer := neofscryptotest.Signer() - id := usertest.ID() - - var ownerID refs.OwnerID - id.WriteToV2(&ownerID) - - body := session.CreateRequestBody{} - body.SetOwnerID(&ownerID) - body.SetExpiration(100) - - meta := &session.RequestMetaHeader{} - meta.SetTTL(1) - - req := &session.CreateRequest{} - req.SetBody(&body) - req.SetMetaHeader(meta) - - // add level to meta header matryoshka - meta = &session.RequestMetaHeader{} - testRequestSign(t, signer, meta, req) - - testOwner(t, &ownerID, req) - - // corrupt body - body.SetExpiration(body.GetExpiration() + 1) - - // verification must fail - require.Error(t, verifyServiceMessage(req)) - - // restore body - body.SetExpiration(body.GetExpiration() - 1) - - testRequestMeta(t, meta, req) -} - -func TestCreateResponse(t *testing.T) { - signer := neofscryptotest.Signer() - - id := make([]byte, 8) - _, err := rand.Read(id) - require.NoError(t, err) - - sessionKey := make([]byte, 8) - _, err = rand.Read(sessionKey) - require.NoError(t, err) - - body := session.CreateResponseBody{} - body.SetID(id) - body.SetSessionKey(sessionKey) - - meta := &session.ResponseMetaHeader{} - meta.SetTTL(1) - - req := &session.CreateResponse{} - req.SetBody(&body) - req.SetMetaHeader(meta) - - // add level to meta header matryoshka - meta = &session.ResponseMetaHeader{} - testResponseSign(t, signer, meta, req) - - // corrupt body - body.SetID([]byte{1}) - // verification must fail - require.Error(t, verifyServiceMessage(req)) - // restore body - body.SetID(id) - - // corrupt body - body.SetSessionKey([]byte{1}) - // verification must fail - require.Error(t, verifyServiceMessage(req)) - // restore body - body.SetSessionKey(id) - - testResponseMeta(t, meta, req) -} diff --git a/client/status/common.go b/client/status/common.go index e3e5b046..71d63f3d 100644 --- a/client/status/common.go +++ b/client/status/common.go @@ -4,7 +4,7 @@ import ( "encoding/binary" "errors" - "github.com/nspcc-dev/neofs-api-go/v2/status" + protostatus "github.com/nspcc-dev/neofs-sdk-go/proto/status" ) // Error describes common error which is a grouping type for any [apistatus] errors. Any [apistatus] error may be checked @@ -27,18 +27,15 @@ var ( ) // ServerInternal describes failure statuses related to internal server errors. -// Instances provide [StatusV2] and error interfaces. // // The status is purely informative, the client should not go into details of the error except for debugging needs. type ServerInternal struct { - v2 status.Status + msg string + dts []*protostatus.Status_Detail } func (x ServerInternal) Error() string { - return errMessageStatusV2( - globalizeCodeV2(status.Internal, status.GlobalizeCommonFail), - x.v2.Message(), - ) + return errMessageStatus(protostatus.InternalServerError, x.msg) } // Is implements interface for correct checking current error type with [errors.Is]. @@ -51,34 +48,29 @@ func (x ServerInternal) Is(target error) bool { } } -// implements local interface defined in [ErrorFromV2] func. -func (x *ServerInternal) fromStatusV2(st *status.Status) { - x.v2 = *st +// implements local interface defined in [ToError] func. +func (x *ServerInternal) fromProtoMessage(st *protostatus.Status) { + x.msg = st.Message + x.dts = st.Details } -// ErrorToV2 implements [StatusV2] interface method. -// If the value was returned by [ErrorFromV2], returns the source message. -// Otherwise, returns message with -// - code: INTERNAL; -// - string message: empty; -// - details: empty. -func (x ServerInternal) ErrorToV2() *status.Status { - x.v2.SetCode(globalizeCodeV2(status.Internal, status.GlobalizeCommonFail)) - return &x.v2 +// implements local interface defined in [FromError] func. +func (x ServerInternal) protoMessage() *protostatus.Status { + return &protostatus.Status{Code: protostatus.InternalServerError, Message: x.msg, Details: x.dts} } // SetMessage sets message describing internal error. // // Message should be used for debug purposes only. func (x *ServerInternal) SetMessage(msg string) { - x.v2.SetMessage(msg) + x.msg = msg } // Message returns message describing internal server error. // // Message should be used for debug purposes only. By default, it is empty. func (x ServerInternal) Message() string { - return x.v2.Message() + return x.msg } // WriteInternalServerErr writes err message to ServerInternal instance. @@ -89,14 +81,12 @@ func WriteInternalServerErr(x *ServerInternal, err error) { // WrongMagicNumber describes failure status related to incorrect network magic. // Instances provide [StatusV2] and error interfaces. type WrongMagicNumber struct { - v2 status.Status + msg string + dts []*protostatus.Status_Detail } func (x WrongMagicNumber) Error() string { - return errMessageStatusV2( - globalizeCodeV2(status.WrongMagicNumber, status.GlobalizeCommonFail), - x.v2.Message(), - ) + return errMessageStatus(protostatus.WrongNetMagic, x.msg) } // Is implements interface for correct checking current error type with [errors.Is]. @@ -109,20 +99,15 @@ func (x WrongMagicNumber) Is(target error) bool { } } -// implements local interface defined in [ErrorFromV2] func. -func (x *WrongMagicNumber) fromStatusV2(st *status.Status) { - x.v2 = *st +// implements local interface defined in [ToError] func. +func (x *WrongMagicNumber) fromProtoMessage(st *protostatus.Status) { + x.msg = st.Message + x.dts = st.Details } -// ErrorToV2 implements [StatusV2] interface method. -// If the value was returned by [ErrorFromV2], returns the source message. -// Otherwise, returns message with -// - code: WRONG_MAGIC_NUMBER; -// - string message: empty; -// - details: empty. -func (x WrongMagicNumber) ErrorToV2() *status.Status { - x.v2.SetCode(globalizeCodeV2(status.WrongMagicNumber, status.GlobalizeCommonFail)) - return &x.v2 +// implements local interface defined in [FromError] func. +func (x WrongMagicNumber) protoMessage() *protostatus.Status { + return &protostatus.Status{Code: protostatus.WrongNetMagic, Message: x.msg, Details: x.dts} } // WriteCorrectMagic writes correct network magic. @@ -132,14 +117,16 @@ func (x *WrongMagicNumber) WriteCorrectMagic(magic uint64) { binary.BigEndian.PutUint64(buf, magic) - // create corresponding detail - var d status.Detail - - d.SetID(status.DetailIDCorrectMagic) - d.SetValue(buf) - - // attach the detail - x.v2.AppendDetails(d) + for i := range x.dts { + if x.dts[i].Id == protostatus.DetailCorrectNetMagic { + x.dts[i].Value = buf + return + } + } + x.dts = append(x.dts, &protostatus.Status_Detail{ + Id: protostatus.DetailCorrectNetMagic, + Value: buf, + }) } // CorrectMagic returns network magic returned by the server. @@ -147,41 +134,32 @@ func (x *WrongMagicNumber) WriteCorrectMagic(magic uint64) { // - -1 if number is presented in incorrect format // - 0 if number is not presented // - +1 otherwise -func (x WrongMagicNumber) CorrectMagic() (magic uint64, ok int8) { - x.v2.IterateDetails(func(d *status.Detail) bool { - if d.ID() == status.DetailIDCorrectMagic { - if val := d.Value(); len(val) == 8 { - magic = binary.BigEndian.Uint64(val) - ok = 1 - } else { - ok = -1 +func (x WrongMagicNumber) CorrectMagic() (uint64, int8) { + for i := range x.dts { + if x.dts[i].Id == protostatus.DetailCorrectNetMagic { + if len(x.dts[i].Value) == 8 { + return binary.BigEndian.Uint64(x.dts[i].Value), 1 } + return 0, -1 } - - return ok != 0 - }) - - return + } + return 0, 0 } // SignatureVerification describes failure status related to signature verification. -// Instances provide [StatusV2] and error interfaces. type SignatureVerification struct { - v2 status.Status + msg string + dts []*protostatus.Status_Detail } const defaultSignatureVerificationMsg = "signature verification failed" func (x SignatureVerification) Error() string { - msg := x.v2.Message() - if msg == "" { - msg = defaultSignatureVerificationMsg + if x.msg == "" { + x.msg = defaultSignatureVerificationMsg } - return errMessageStatusV2( - globalizeCodeV2(status.SignatureVerificationFail, status.GlobalizeCommonFail), - msg, - ) + return errMessageStatus(protostatus.SignatureVerificationFail, x.msg) } // Is implements interface for correct checking current error type with [errors.Is]. @@ -194,26 +172,18 @@ func (x SignatureVerification) Is(target error) bool { } } -// implements local interface defined in [ErrorFromV2] func. -func (x *SignatureVerification) fromStatusV2(st *status.Status) { - x.v2 = *st +// implements local interface defined in [ToError] func. +func (x *SignatureVerification) fromProtoMessage(st *protostatus.Status) { + x.msg = st.Message + x.dts = st.Details } -// ErrorToV2 implements [StatusV2] interface method. -// If the value was returned by [ErrorFromV2], returns the source message. -// Otherwise, returns message with -// - code: SIGNATURE_VERIFICATION_FAIL; -// - string message: written message via [SignatureVerification.SetMessage] or -// "signature verification failed" as a default message; -// - details: empty. -func (x SignatureVerification) ErrorToV2() *status.Status { - x.v2.SetCode(globalizeCodeV2(status.SignatureVerificationFail, status.GlobalizeCommonFail)) - - if x.v2.Message() == "" { - x.v2.SetMessage(defaultSignatureVerificationMsg) +// implements local interface defined in [FromError] func. +func (x SignatureVerification) protoMessage() *protostatus.Status { + if x.msg == "" { + x.msg = defaultSignatureVerificationMsg } - - return &x.v2 + return &protostatus.Status{Code: protostatus.SignatureVerificationFail, Message: x.msg, Details: x.dts} } // SetMessage writes signature verification failure message. @@ -221,7 +191,7 @@ func (x SignatureVerification) ErrorToV2() *status.Status { // // See also Message. func (x *SignatureVerification) SetMessage(v string) { - x.v2.SetMessage(v) + x.msg = v } // Message returns status message. Zero status returns empty message. @@ -229,28 +199,24 @@ func (x *SignatureVerification) SetMessage(v string) { // // See also SetMessage. func (x SignatureVerification) Message() string { - return x.v2.Message() + return x.msg } // NodeUnderMaintenance describes failure status for nodes being under maintenance. -// Instances provide [StatusV2] and error interfaces. type NodeUnderMaintenance struct { - v2 status.Status + msg string + dts []*protostatus.Status_Detail } const defaultNodeUnderMaintenanceMsg = "node is under maintenance" // Error implements the error interface. func (x NodeUnderMaintenance) Error() string { - msg := x.Message() - if msg == "" { - msg = defaultNodeUnderMaintenanceMsg + if x.msg == "" { + x.msg = defaultNodeUnderMaintenanceMsg } - return errMessageStatusV2( - globalizeCodeV2(status.NodeUnderMaintenance, status.GlobalizeCommonFail), - msg, - ) + return errMessageStatus(protostatus.NodeUnderMaintenance, x.msg) } // Is implements interface for correct checking current error type with [errors.Is]. @@ -263,24 +229,18 @@ func (x NodeUnderMaintenance) Is(target error) bool { } } -func (x *NodeUnderMaintenance) fromStatusV2(st *status.Status) { - x.v2 = *st +// implements local interface defined in [ToError] func. +func (x *NodeUnderMaintenance) fromProtoMessage(st *protostatus.Status) { + x.msg = st.Message + x.dts = st.Details } -// ErrorToV2 implements [StatusV2] interface method. -// If the value was returned by [ErrorFromV2], returns the source message. -// Otherwise, returns message with -// - code: NODE_UNDER_MAINTENANCE; -// - string message: written message via [NodeUnderMaintenance.SetMessage] or -// "node is under maintenance" as a default message; -// - details: empty. -func (x NodeUnderMaintenance) ErrorToV2() *status.Status { - x.v2.SetCode(globalizeCodeV2(status.NodeUnderMaintenance, status.GlobalizeCommonFail)) - if x.v2.Message() == "" { - x.v2.SetMessage(defaultNodeUnderMaintenanceMsg) +// implements local interface defined in [FromError] func. +func (x NodeUnderMaintenance) protoMessage() *protostatus.Status { + if x.msg == "" { + x.msg = defaultNodeUnderMaintenanceMsg } - - return &x.v2 + return &protostatus.Status{Code: protostatus.NodeUnderMaintenance, Message: x.msg, Details: x.dts} } // SetMessage writes signature verification failure message. @@ -288,7 +248,7 @@ func (x NodeUnderMaintenance) ErrorToV2() *status.Status { // // See also Message. func (x *NodeUnderMaintenance) SetMessage(v string) { - x.v2.SetMessage(v) + x.msg = v } // Message returns status message. Zero status returns empty message. @@ -296,5 +256,5 @@ func (x *NodeUnderMaintenance) SetMessage(v string) { // // See also SetMessage. func (x NodeUnderMaintenance) Message() string { - return x.v2.Message() + return x.msg } diff --git a/client/status/common_test.go b/client/status/common_test.go index 5576f480..1341d783 100644 --- a/client/status/common_test.go +++ b/client/status/common_test.go @@ -3,7 +3,6 @@ package apistatus_test import ( "testing" - "github.com/nspcc-dev/neofs-api-go/v2/status" apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status" "github.com/stretchr/testify/require" ) @@ -13,17 +12,13 @@ func TestServerInternal_Message(t *testing.T) { var st apistatus.ServerInternal - res := st.Message() - resv2 := apistatus.ErrorToV2(st).Message() - require.Empty(t, res) - require.Empty(t, resv2) + require.Empty(t, st.Message()) + require.Empty(t, apistatus.FromError(st).Message) st.SetMessage(msg) - res = st.Message() - resv2 = apistatus.ErrorToV2(st).Message() - require.Equal(t, msg, res) - require.Equal(t, msg, resv2) + require.Equal(t, msg, st.Message()) + require.Equal(t, msg, apistatus.FromError(st).Message) } func TestWrongMagicNumber_CorrectMagic(t *testing.T) { @@ -42,10 +37,9 @@ func TestWrongMagicNumber_CorrectMagic(t *testing.T) { require.EqualValues(t, 1, ok) // corrupt the value - apistatus.ErrorToV2(st).IterateDetails(func(d *status.Detail) bool { - d.SetValue([]byte{1, 2, 3}) // any slice with len != 8 - return true - }) + m := apistatus.FromError(st) + require.Len(t, m.Details, 1) + m.Details[0].Value = []byte{1, 2, 3} // any slice with len != 8 _, ok = st.CorrectMagic() require.EqualValues(t, -1, ok) @@ -64,29 +58,26 @@ func TestSignatureVerification(t *testing.T) { st.SetMessage(msg) - stV2 := st.ErrorToV2() + m := apistatus.FromError(st) require.Equal(t, msg, st.Message()) - require.Equal(t, msg, stV2.Message()) + require.Equal(t, msg, m.Message) }) - t.Run("empty to V2", func(t *testing.T) { + t.Run("proto", func(t *testing.T) { var st apistatus.SignatureVerification - stV2 := st.ErrorToV2() + m := apistatus.FromError(st) - require.Equal(t, "signature verification failed", stV2.Message()) - }) + require.Equal(t, "signature verification failed", m.Message) - t.Run("non-empty to V2", func(t *testing.T) { - var st apistatus.SignatureVerification msg := "some other msg" st.SetMessage(msg) - stV2 := st.ErrorToV2() + m = apistatus.FromError(st) - require.Equal(t, msg, stV2.Message()) + require.Equal(t, msg, m.Message) }) } @@ -103,28 +94,25 @@ func TestNodeUnderMaintenance(t *testing.T) { st.SetMessage(msg) - stV2 := st.ErrorToV2() + m := apistatus.FromError(st) require.Equal(t, msg, st.Message()) - require.Equal(t, msg, stV2.Message()) + require.Equal(t, msg, m.Message) }) - t.Run("empty to V2", func(t *testing.T) { + t.Run("proto", func(t *testing.T) { var st apistatus.NodeUnderMaintenance - stV2 := st.ErrorToV2() + m := apistatus.FromError(st) - require.Empty(t, "", stV2.Message()) - }) + require.Equal(t, "node is under maintenance", m.Message) - t.Run("non-empty to V2", func(t *testing.T) { - var st apistatus.NodeUnderMaintenance msg := "some other msg" st.SetMessage(msg) - stV2 := st.ErrorToV2() + m = apistatus.FromError(st) - require.Equal(t, msg, stV2.Message()) + require.Equal(t, msg, m.Message) }) } diff --git a/client/status/container.go b/client/status/container.go index 526d46fe..eb31a660 100644 --- a/client/status/container.go +++ b/client/status/container.go @@ -3,8 +3,7 @@ package apistatus import ( "errors" - "github.com/nspcc-dev/neofs-api-go/v2/container" - "github.com/nspcc-dev/neofs-api-go/v2/status" + protostatus "github.com/nspcc-dev/neofs-sdk-go/proto/status" ) var ( @@ -17,23 +16,19 @@ var ( ) // ContainerNotFound describes status of the failure because of the missing container. -// Instances provide [StatusV2] and error interfaces. type ContainerNotFound struct { - v2 status.Status + msg string + dts []*protostatus.Status_Detail } const defaultContainerNotFoundMsg = "container not found" func (x ContainerNotFound) Error() string { - msg := x.v2.Message() - if msg == "" { - msg = defaultContainerNotFoundMsg + if x.msg == "" { + x.msg = defaultContainerNotFoundMsg } - return errMessageStatusV2( - globalizeCodeV2(container.StatusNotFound, container.GlobalizeFail), - msg, - ) + return errMessageStatus(protostatus.ContainerNotFound, x.msg) } // Is implements interface for correct checking current error type with [errors.Is]. @@ -46,42 +41,35 @@ func (x ContainerNotFound) Is(target error) bool { } } -// implements local interface defined in [ErrorFromV2] func. -func (x *ContainerNotFound) fromStatusV2(st *status.Status) { - x.v2 = *st +// implements local interface defined in [ToError] func. +func (x *ContainerNotFound) fromProtoMessage(st *protostatus.Status) { + x.msg = st.Message + x.dts = st.Details } -// ErrorToV2 implements [StatusV2] interface method. -// If the value was returned by [ErrorFromV2], returns the source message. -// Otherwise, returns message with -// - code: CONTAINER_NOT_FOUND; -// - string message: "container not found"; -// - details: empty. -func (x ContainerNotFound) ErrorToV2() *status.Status { - x.v2.SetCode(globalizeCodeV2(container.StatusNotFound, container.GlobalizeFail)) - x.v2.SetMessage(defaultContainerNotFoundMsg) - return &x.v2 +// implements local interface defined in [FromError] func. +func (x ContainerNotFound) protoMessage() *protostatus.Status { + if x.msg == "" { + x.msg = defaultContainerNotFoundMsg + } + return &protostatus.Status{Code: protostatus.ContainerNotFound, Message: x.msg, Details: x.dts} } // EACLNotFound describes status of the failure because of the missing eACL // table. -// Instances provide [StatusV2] and error interfaces. type EACLNotFound struct { - v2 status.Status + msg string + dts []*protostatus.Status_Detail } const defaultEACLNotFoundMsg = "eACL not found" func (x EACLNotFound) Error() string { - msg := x.v2.Message() - if msg == "" { - msg = defaultEACLNotFoundMsg + if x.msg == "" { + x.msg = defaultEACLNotFoundMsg } - return errMessageStatusV2( - globalizeCodeV2(container.StatusEACLNotFound, container.GlobalizeFail), - msg, - ) + return errMessageStatus(protostatus.EACLNotFound, x.msg) } // Is implements interface for correct checking current error type with [errors.Is]. @@ -94,19 +82,16 @@ func (x EACLNotFound) Is(target error) bool { } } -// implements local interface defined in [ErrorFromV2] func. -func (x *EACLNotFound) fromStatusV2(st *status.Status) { - x.v2 = *st +// implements local interface defined in [ToError] func. +func (x *EACLNotFound) fromProtoMessage(st *protostatus.Status) { + x.msg = st.Message + x.dts = st.Details } -// ErrorToV2 implements [StatusV2] interface method. -// If the value was returned by [ErrorFromV2], returns the source message. -// Otherwise, returns message with -// - code: EACL_NOT_FOUND; -// - string message: "eACL not found"; -// - details: empty. -func (x EACLNotFound) ErrorToV2() *status.Status { - x.v2.SetCode(globalizeCodeV2(container.StatusEACLNotFound, container.GlobalizeFail)) - x.v2.SetMessage(defaultEACLNotFoundMsg) - return &x.v2 +// implements local interface defined in [FromError] func. +func (x EACLNotFound) protoMessage() *protostatus.Status { + if x.msg == "" { + x.msg = defaultEACLNotFoundMsg + } + return &protostatus.Status{Code: protostatus.EACLNotFound, Message: x.msg, Details: x.dts} } diff --git a/client/status/object.go b/client/status/object.go index 906cd8f6..da44c44f 100644 --- a/client/status/object.go +++ b/client/status/object.go @@ -3,8 +3,7 @@ package apistatus import ( "errors" - "github.com/nspcc-dev/neofs-api-go/v2/object" - "github.com/nspcc-dev/neofs-api-go/v2/status" + protostatus "github.com/nspcc-dev/neofs-sdk-go/proto/status" ) var ( @@ -29,23 +28,19 @@ var ( ) // ObjectLocked describes status of the failure because of the locked object. -// Instances provide [StatusV2] and error interfaces. type ObjectLocked struct { - v2 status.Status + msg string + dts []*protostatus.Status_Detail } const defaultObjectLockedMsg = "object is locked" func (x ObjectLocked) Error() string { - msg := x.v2.Message() - if msg == "" { - msg = defaultObjectLockedMsg + if x.msg == "" { + x.msg = defaultObjectLockedMsg } - return errMessageStatusV2( - globalizeCodeV2(object.StatusLocked, object.GlobalizeFail), - msg, - ) + return errMessageStatus(protostatus.ObjectLocked, x.msg) } // Is implements interface for correct checking current error type with [errors.Is]. @@ -58,41 +53,34 @@ func (x ObjectLocked) Is(target error) bool { } } -// implements local interface defined in [ErrorFromV2] func. -func (x *ObjectLocked) fromStatusV2(st *status.Status) { - x.v2 = *st +// implements local interface defined in [ToError] func. +func (x *ObjectLocked) fromProtoMessage(st *protostatus.Status) { + x.msg = st.Message + x.dts = st.Details } -// ErrorToV2 implements [StatusV2] interface method. -// If the value was returned by [ErrorFromV2], returns the source message. -// Otherwise, returns message with -// - code: LOCKED; -// - string message: "object is locked"; -// - details: empty. -func (x ObjectLocked) ErrorToV2() *status.Status { - x.v2.SetCode(globalizeCodeV2(object.StatusLocked, object.GlobalizeFail)) - x.v2.SetMessage(defaultObjectLockedMsg) - return &x.v2 +// implements local interface defined in [FromError] func. +func (x ObjectLocked) protoMessage() *protostatus.Status { + if x.msg == "" { + x.msg = defaultObjectLockedMsg + } + return &protostatus.Status{Code: protostatus.ObjectLocked, Message: x.msg, Details: x.dts} } // LockNonRegularObject describes status returned on locking the non-regular object. -// Instances provide [StatusV2] and error interfaces. type LockNonRegularObject struct { - v2 status.Status + msg string + dts []*protostatus.Status_Detail } const defaultLockNonRegularObjectMsg = "locking non-regular object is forbidden" func (x LockNonRegularObject) Error() string { - msg := x.v2.Message() - if msg == "" { - msg = defaultLockNonRegularObjectMsg + if x.msg == "" { + x.msg = defaultLockNonRegularObjectMsg } - return errMessageStatusV2( - globalizeCodeV2(object.StatusLockNonRegularObject, object.GlobalizeFail), - msg, - ) + return errMessageStatus(protostatus.LockIrregularObject, x.msg) } // Is implements interface for correct checking current error type with [errors.Is]. @@ -105,41 +93,34 @@ func (x LockNonRegularObject) Is(target error) bool { } } -// implements local interface defined in [ErrorFromV2] func. -func (x *LockNonRegularObject) fromStatusV2(st *status.Status) { - x.v2 = *st +// implements local interface defined in [ToError] func. +func (x *LockNonRegularObject) fromProtoMessage(st *protostatus.Status) { + x.msg = st.Message + x.dts = st.Details } -// ErrorToV2 implements [StatusV2] interface method. -// If the value was returned by [ErrorFromV2], returns the source message. -// Otherwise, returns message with -// - code: LOCK_NON_REGULAR_OBJECT; -// - string message: "locking non-regular object is forbidden"; -// - details: empty. -func (x LockNonRegularObject) ErrorToV2() *status.Status { - x.v2.SetCode(globalizeCodeV2(object.StatusLockNonRegularObject, object.GlobalizeFail)) - x.v2.SetMessage(defaultLockNonRegularObjectMsg) - return &x.v2 +// implements local interface defined in [FromError] func. +func (x LockNonRegularObject) protoMessage() *protostatus.Status { + if x.msg == "" { + x.msg = defaultLockNonRegularObjectMsg + } + return &protostatus.Status{Code: protostatus.LockIrregularObject, Message: x.msg, Details: x.dts} } // ObjectAccessDenied describes status of the failure because of the access control violation. -// Instances provide [StatusV2] and error interfaces. type ObjectAccessDenied struct { - v2 status.Status + msg string + dts []*protostatus.Status_Detail } const defaultObjectAccessDeniedMsg = "access to object operation denied" func (x ObjectAccessDenied) Error() string { - msg := x.v2.Message() - if msg == "" { - msg = defaultObjectAccessDeniedMsg + if x.msg == "" { + x.msg = defaultObjectAccessDeniedMsg } - return errMessageStatusV2( - globalizeCodeV2(object.StatusAccessDenied, object.GlobalizeFail), - msg, - ) + return errMessageStatus(protostatus.ObjectAccessDenied, x.msg) } // Is implements interface for correct checking current error type with [errors.Is]. @@ -152,52 +133,60 @@ func (x ObjectAccessDenied) Is(target error) bool { } } -// implements local interface defined in [ErrorFromV2] func. -func (x *ObjectAccessDenied) fromStatusV2(st *status.Status) { - x.v2 = *st +// implements local interface defined in [ToError] func. +func (x *ObjectAccessDenied) fromProtoMessage(st *protostatus.Status) { + x.msg = st.Message + x.dts = st.Details } -// ErrorToV2 implements [StatusV2] interface method. -// If the value was returned by [ErrorFromV2], returns the source message. -// Otherwise, returns message with -// - code: ACCESS_DENIED; -// - string message: "access to object operation denied"; -// - details: empty. -func (x ObjectAccessDenied) ErrorToV2() *status.Status { - x.v2.SetCode(globalizeCodeV2(object.StatusAccessDenied, object.GlobalizeFail)) - x.v2.SetMessage(defaultObjectAccessDeniedMsg) - return &x.v2 +// implements local interface defined in [FromError] func. +func (x ObjectAccessDenied) protoMessage() *protostatus.Status { + if x.msg == "" { + x.msg = defaultObjectAccessDeniedMsg + } + return &protostatus.Status{Code: protostatus.ObjectAccessDenied, Message: x.msg, Details: x.dts} } // WriteReason writes human-readable access rejection reason. func (x *ObjectAccessDenied) WriteReason(reason string) { - object.WriteAccessDeniedDesc(&x.v2, reason) + val := []byte(reason) + for i := range x.dts { + if x.dts[i].Id == protostatus.DetailObjectAccessDenialReason { + x.dts[i].Value = val + return + } + } + x.dts = append(x.dts, &protostatus.Status_Detail{ + Id: protostatus.DetailObjectAccessDenialReason, + Value: val, + }) } // Reason returns human-readable access rejection reason returned by the server. // Returns empty value is reason is not presented. func (x ObjectAccessDenied) Reason() string { - return object.ReadAccessDeniedDesc(x.v2) + for i := range x.dts { + if x.dts[i].Id == protostatus.DetailObjectAccessDenialReason { + return string(x.dts[i].Value) + } + } + return "" } // ObjectNotFound describes status of the failure because of the missing object. -// Instances provide [StatusV2] and error interfaces. type ObjectNotFound struct { - v2 status.Status + msg string + dts []*protostatus.Status_Detail } const defaultObjectNotFoundMsg = "object not found" func (x ObjectNotFound) Error() string { - msg := x.v2.Message() - if msg == "" { - msg = defaultObjectNotFoundMsg + if x.msg == "" { + x.msg = defaultObjectNotFoundMsg } - return errMessageStatusV2( - globalizeCodeV2(object.StatusNotFound, object.GlobalizeFail), - msg, - ) + return errMessageStatus(protostatus.ObjectNotFound, x.msg) } // Is implements interface for correct checking current error type with [errors.Is]. @@ -210,41 +199,35 @@ func (x ObjectNotFound) Is(target error) bool { } } -// implements local interface defined in [ErrorFromV2] func. -func (x *ObjectNotFound) fromStatusV2(st *status.Status) { - x.v2 = *st +// implements local interface defined in [ToError] func. +func (x *ObjectNotFound) fromProtoMessage(st *protostatus.Status) { + x.msg = st.Message + x.dts = st.Details } -// ErrorToV2 implements [StatusV2] interface method. -// If the value was returned by [ErrorFromV2], returns the source message. -// Otherwise, returns message with -// - code: OBJECT_NOT_FOUND; -// - string message: "object not found"; -// - details: empty. -func (x ObjectNotFound) ErrorToV2() *status.Status { - x.v2.SetCode(globalizeCodeV2(object.StatusNotFound, object.GlobalizeFail)) - x.v2.SetMessage(defaultObjectNotFoundMsg) - return &x.v2 +// implements local interface defined in [FromError] func. +func (x ObjectNotFound) protoMessage() *protostatus.Status { + if x.msg == "" { + x.msg = defaultObjectNotFoundMsg + } + return &protostatus.Status{Code: protostatus.ObjectNotFound, Message: x.msg, Details: x.dts} } // ObjectAlreadyRemoved describes status of the failure because object has been -// already removed. Instances provide Status and StatusV2 interfaces. +// already removed. type ObjectAlreadyRemoved struct { - v2 status.Status + msg string + dts []*protostatus.Status_Detail } const defaultObjectAlreadyRemovedMsg = "object already removed" func (x ObjectAlreadyRemoved) Error() string { - msg := x.v2.Message() - if msg == "" { - msg = defaultObjectAlreadyRemovedMsg + if x.msg == "" { + x.msg = defaultObjectAlreadyRemovedMsg } - return errMessageStatusV2( - globalizeCodeV2(object.StatusAlreadyRemoved, object.GlobalizeFail), - msg, - ) + return errMessageStatus(protostatus.ObjectAlreadyRemoved, x.msg) } // Is implements interface for correct checking current error type with [errors.Is]. @@ -257,42 +240,35 @@ func (x ObjectAlreadyRemoved) Is(target error) bool { } } -// implements local interface defined in [ErrorFromV2] func. -func (x *ObjectAlreadyRemoved) fromStatusV2(st *status.Status) { - x.v2 = *st +// implements local interface defined in [ToError] func. +func (x *ObjectAlreadyRemoved) fromProtoMessage(st *protostatus.Status) { + x.msg = st.Message + x.dts = st.Details } -// ErrorToV2 implements [StatusV2] interface method. -// If the value was returned by [ErrorFromV2], returns the source message. -// Otherwise, returns message with -// - code: OBJECT_ALREADY_REMOVED; -// - string message: "object already removed"; -// - details: empty. -func (x ObjectAlreadyRemoved) ErrorToV2() *status.Status { - x.v2.SetCode(globalizeCodeV2(object.StatusAlreadyRemoved, object.GlobalizeFail)) - x.v2.SetMessage(defaultObjectAlreadyRemovedMsg) - return &x.v2 +// implements local interface defined in [FromError] func. +func (x ObjectAlreadyRemoved) protoMessage() *protostatus.Status { + if x.msg == "" { + x.msg = defaultObjectAlreadyRemovedMsg + } + return &protostatus.Status{Code: protostatus.ObjectAlreadyRemoved, Message: x.msg, Details: x.dts} } // ObjectOutOfRange describes status of the failure because of the incorrect // provided object ranges. -// Instances provide [StatusV2] and error interfaces. type ObjectOutOfRange struct { - v2 status.Status + msg string + dts []*protostatus.Status_Detail } const defaultObjectOutOfRangeMsg = "out of range" func (x ObjectOutOfRange) Error() string { - msg := x.v2.Message() - if msg == "" { - msg = defaultObjectOutOfRangeMsg + if x.msg == "" { + x.msg = defaultObjectOutOfRangeMsg } - return errMessageStatusV2( - globalizeCodeV2(object.StatusOutOfRange, object.GlobalizeFail), - msg, - ) + return errMessageStatus(protostatus.OutOfRange, x.msg) } // Is implements interface for correct checking current error type with [errors.Is]. @@ -305,19 +281,16 @@ func (x ObjectOutOfRange) Is(target error) bool { } } -// implements local interface defined in [ErrorFromV2] func. -func (x *ObjectOutOfRange) fromStatusV2(st *status.Status) { - x.v2 = *st +// implements local interface defined in [ToError] func. +func (x *ObjectOutOfRange) fromProtoMessage(st *protostatus.Status) { + x.msg = st.Message + x.dts = st.Details } -// ErrorToV2 implements [StatusV2] interface method. -// If the value was returned by [ErrorFromV2], returns the source message. -// Otherwise, returns message with -// - code: OUT_OF_RANGE; -// - string message: "out of range"; -// - details: empty. -func (x ObjectOutOfRange) ErrorToV2() *status.Status { - x.v2.SetCode(globalizeCodeV2(object.StatusOutOfRange, object.GlobalizeFail)) - x.v2.SetMessage(defaultObjectOutOfRangeMsg) - return &x.v2 +// implements local interface defined in [FromError] func. +func (x ObjectOutOfRange) protoMessage() *protostatus.Status { + if x.msg == "" { + x.msg = defaultObjectOutOfRangeMsg + } + return &protostatus.Status{Code: protostatus.OutOfRange, Message: x.msg, Details: x.dts} } diff --git a/client/status/object_test.go b/client/status/object_test.go index 126588a8..faf2ceaf 100644 --- a/client/status/object_test.go +++ b/client/status/object_test.go @@ -14,13 +14,11 @@ func TestObjectAccessDenied_WriteReason(t *testing.T) { res := st.Reason() require.Empty(t, res) - detailNum := apistatus.ErrorToV2(st).NumberOfDetails() - require.Zero(t, detailNum) + require.Empty(t, apistatus.FromError(st).Details) st.WriteReason(reason) res = st.Reason() require.Equal(t, reason, res) - detailNum = apistatus.ErrorToV2(st).NumberOfDetails() - require.EqualValues(t, 1, detailNum) + require.Len(t, apistatus.FromError(st).Details, 1) } diff --git a/client/status/session.go b/client/status/session.go index 6fc470df..39c326ad 100644 --- a/client/status/session.go +++ b/client/status/session.go @@ -3,8 +3,7 @@ package apistatus import ( "errors" - "github.com/nspcc-dev/neofs-api-go/v2/session" - "github.com/nspcc-dev/neofs-api-go/v2/status" + protostatus "github.com/nspcc-dev/neofs-sdk-go/proto/status" ) var ( @@ -17,23 +16,19 @@ var ( ) // SessionTokenNotFound describes status of the failure because of the missing session token. -// Instances provide [StatusV2] and error interfaces. type SessionTokenNotFound struct { - v2 status.Status + msg string + dts []*protostatus.Status_Detail } const defaultSessionTokenNotFoundMsg = "session token not found" func (x SessionTokenNotFound) Error() string { - msg := x.v2.Message() - if msg == "" { - msg = defaultSessionTokenNotFoundMsg + if x.msg == "" { + x.msg = defaultSessionTokenNotFoundMsg } - return errMessageStatusV2( - globalizeCodeV2(session.StatusTokenNotFound, session.GlobalizeFail), - msg, - ) + return errMessageStatus(protostatus.SessionTokenNotFound, x.msg) } // Is implements interface for correct checking current error type with [errors.Is]. @@ -46,41 +41,34 @@ func (x SessionTokenNotFound) Is(target error) bool { } } -// implements local interface defined in [ErrorFromV2] func. -func (x *SessionTokenNotFound) fromStatusV2(st *status.Status) { - x.v2 = *st +// implements local interface defined in [ToError] func. +func (x *SessionTokenNotFound) fromProtoMessage(st *protostatus.Status) { + x.msg = st.Message + x.dts = st.Details } -// ErrorToV2 implements [StatusV2] interface method. -// If the value was returned by [ErrorFromV2], returns the source message. -// Otherwise, returns message with -// - code: TOKEN_NOT_FOUND; -// - string message: "session token not found"; -// - details: empty. -func (x SessionTokenNotFound) ErrorToV2() *status.Status { - x.v2.SetCode(globalizeCodeV2(session.StatusTokenNotFound, session.GlobalizeFail)) - x.v2.SetMessage(defaultSessionTokenNotFoundMsg) - return &x.v2 +// implements local interface defined in [FromError] func. +func (x SessionTokenNotFound) protoMessage() *protostatus.Status { + if x.msg == "" { + x.msg = defaultSessionTokenNotFoundMsg + } + return &protostatus.Status{Code: protostatus.SessionTokenNotFound, Message: x.msg, Details: x.dts} } // SessionTokenExpired describes status of the failure because of the expired session token. -// Instances provide [StatusV2] and error interfaces. type SessionTokenExpired struct { - v2 status.Status + msg string + dts []*protostatus.Status_Detail } const defaultSessionTokenExpiredMsg = "expired session token" func (x SessionTokenExpired) Error() string { - msg := x.v2.Message() - if msg == "" { - msg = defaultSessionTokenExpiredMsg + if x.msg == "" { + x.msg = defaultSessionTokenExpiredMsg } - return errMessageStatusV2( - globalizeCodeV2(session.StatusTokenExpired, session.GlobalizeFail), - msg, - ) + return errMessageStatus(protostatus.SessionTokenExpired, x.msg) } // Is implements interface for correct checking current error type with [errors.Is]. @@ -93,19 +81,16 @@ func (x SessionTokenExpired) Is(target error) bool { } } -// implements local interface defined in [ErrorFromV2] func. -func (x *SessionTokenExpired) fromStatusV2(st *status.Status) { - x.v2 = *st +// implements local interface defined in [ToError] func. +func (x *SessionTokenExpired) fromProtoMessage(st *protostatus.Status) { + x.msg = st.Message + x.dts = st.Details } -// ErrorToV2 implements [StatusV2] interface method. -// If the value was returned by [ErrorFromV2], returns the source message. -// Otherwise, returns message with -// - code: TOKEN_EXPIRED; -// - string message: "expired session token"; -// - details: empty. -func (x SessionTokenExpired) ErrorToV2() *status.Status { - x.v2.SetCode(globalizeCodeV2(session.StatusTokenExpired, session.GlobalizeFail)) - x.v2.SetMessage(defaultSessionTokenExpiredMsg) - return &x.v2 +// implements local interface defined in [FromError] func. +func (x SessionTokenExpired) protoMessage() *protostatus.Status { + if x.msg == "" { + x.msg = defaultSessionTokenExpiredMsg + } + return &protostatus.Status{Code: protostatus.SessionTokenExpired, Message: x.msg, Details: x.dts} } diff --git a/client/status/unrecognized.go b/client/status/unrecognized.go index 15a8e1e8..1a85cf80 100644 --- a/client/status/unrecognized.go +++ b/client/status/unrecognized.go @@ -1,34 +1,47 @@ package apistatus import ( - "github.com/nspcc-dev/neofs-api-go/v2/status" + protostatus "github.com/nspcc-dev/neofs-sdk-go/proto/status" ) +// ErrUnrecognizedStatus allows to check whether some error is a NeoFS status +// unknown to the current lib version. +var ErrUnrecognizedStatus UnrecognizedStatus + +// UnrecognizedStatus describes status unknown to the current lib version. +type UnrecognizedStatus struct { + code uint32 + msg string + dts []*protostatus.Status_Detail +} + // ErrUnrecognizedStatusV2 is an instance of UnrecognizedStatusV2 error status. It's expected to be used for [errors.Is] // and MUST NOT be changed. +// Deprecated: use ErrUnrecognizedStatus instead. var ErrUnrecognizedStatusV2 UnrecognizedStatusV2 // UnrecognizedStatusV2 describes status of the uncertain failure. // Instances provide [StatusV2] and error interfaces. -type UnrecognizedStatusV2 struct { - v2 status.Status -} +// Deprecated: use UnrecognizedStatus instead. +type UnrecognizedStatusV2 = UnrecognizedStatus -func (x UnrecognizedStatusV2) Error() string { - return errMessageStatusV2("unrecognized", x.v2.Message()) +func (x UnrecognizedStatus) Error() string { + return errMessageStatus("unrecognized", x.msg) } // Is implements interface for correct checking current error type with [errors.Is]. -func (x UnrecognizedStatusV2) Is(target error) bool { +func (x UnrecognizedStatus) Is(target error) bool { switch target.(type) { default: return false - case UnrecognizedStatusV2, *UnrecognizedStatusV2: + case UnrecognizedStatus, *UnrecognizedStatus: return true } } -// implements local interface defined in [ErrorFromV2] func. -func (x *UnrecognizedStatusV2) fromStatusV2(st *status.Status) { - x.v2 = *st +// implements local interface defined in [FromError] func. +func (x *UnrecognizedStatus) fromProtoMessage(st *protostatus.Status) { + x.code = st.Code + x.msg = st.Message + x.dts = st.Details } diff --git a/client/status/v2.go b/client/status/v2.go index 8974ea21..d6026914 100644 --- a/client/status/v2.go +++ b/client/status/v2.go @@ -4,24 +4,10 @@ import ( "errors" "fmt" - "github.com/nspcc-dev/neofs-api-go/v2/container" - "github.com/nspcc-dev/neofs-api-go/v2/object" - "github.com/nspcc-dev/neofs-api-go/v2/session" - "github.com/nspcc-dev/neofs-api-go/v2/status" + protostatus "github.com/nspcc-dev/neofs-sdk-go/proto/status" ) -// StatusV2 defines a variety of status instances compatible with NeoFS API V2 protocol. -// -// Note: it is not recommended to use this type directly, it is intended for documentation of the library functionality. -type StatusV2 interface { - // ErrorToV2 returns the status as github.com/nspcc-dev/neofs-api-go/v2/status.Status message structure. - ErrorToV2() *status.Status -} - -// ErrorFromV2 converts [status.Status] message structure to error. Inverse to [ErrorToV2] operation. -// -// If result is not nil, it implements [StatusV2]. This fact should be taken into account only when passing -// the result to the inverse function [ErrorToV2], casts are not compatibility-safe. +// ToError converts [status.Status] message structure to error. Inverse to [FromError] operation. // // Below is the mapping of return codes to status instance types (with a description of parsing details). // Note: notice if the return type is a pointer. @@ -30,114 +16,101 @@ type StatusV2 interface { // - [status.OK]: nil (this also includes nil argument). // // Common failures: -// - [status.Internal]: *[ServerInternal]; -// - [status.SignatureVerificationFail]: *[SignatureVerification]. -// - [status.WrongMagicNumber]: *[WrongMagicNumber]. -// - [status.NodeUnderMaintenance]: *[NodeUnderMaintenance]. +// - [protostatus.InternalServerError]: *[ServerInternal]; +// - [protostatus.SignatureVerificationFail]: *[SignatureVerification]. +// - [protostatus.WrongNetMagic]: *[WrongMagicNumber]. +// - [protostatus.NodeUnderMaintenance]: *[NodeUnderMaintenance]. // // Object failures: -// - [object.StatusLocked]: *[ObjectLocked]; -// - [object.StatusLockNonRegularObject]: *[LockNonRegularObject]. -// - [object.StatusAccessDenied]: *[ObjectAccessDenied]. -// - [object.StatusNotFound]: *[ObjectNotFound]. -// - [object.StatusAlreadyRemoved]: *[ObjectAlreadyRemoved]. -// - [object.StatusOutOfRange]: *[ObjectOutOfRange]. +// - [protostatus.ObjectLocked]: *[ObjectLocked]; +// - [protostatus.LockIrregularObject]: *[LockNonRegularObject]. +// - [protostatus.ObjectAccessDenied]: *[ObjectAccessDenied]. +// - [protostatus.ObjectNotFound]: *[ObjectNotFound]. +// - [protostatus.ObjectAlreadyRemoved]: *[ObjectAlreadyRemoved]. +// - [protostatus.OutOfRange]: *[ObjectOutOfRange]. // // Container failures: -// - [container.StatusNotFound]: *[ContainerNotFound]; -// - [container.StatusEACLNotFound]: *[EACLNotFound]; +// - [protostatus.ContainerNotFound]: *[ContainerNotFound]; +// - [protostatus.EACLNotFound]: *[EACLNotFound]; // // Session failures: -// - [session.StatusTokenNotFound]: *[SessionTokenNotFound]; -// - [session.StatusTokenExpired]: *[SessionTokenExpired]; -func ErrorFromV2(st *status.Status) error { +// - [protostatus.SessionTokenNotFound]: *[SessionTokenNotFound]; +// - [protostatus.SessionTokenExpired]: *[SessionTokenExpired]; +func ToError(st *protostatus.Status) error { + for i, d := range st.GetDetails() { + if d == nil { + return fmt.Errorf("nil detail #%d", i) + } + } + var decoder interface { - fromStatusV2(*status.Status) + fromProtoMessage(*protostatus.Status) Error() string } - switch code := st.Code(); { - case status.IsSuccess(code): - //nolint:exhaustive - switch status.LocalizeSuccess(&code); code { - case status.OK: - return nil - } - case status.IsCommonFail(code): - switch status.LocalizeCommonFail(&code); code { - case status.Internal: - decoder = new(ServerInternal) - case status.WrongMagicNumber: - decoder = new(WrongMagicNumber) - case status.SignatureVerificationFail: - decoder = new(SignatureVerification) - case status.NodeUnderMaintenance: - decoder = new(NodeUnderMaintenance) - } - case object.LocalizeFailStatus(&code): - switch code { - case object.StatusLocked: - decoder = new(ObjectLocked) - case object.StatusLockNonRegularObject: - decoder = new(LockNonRegularObject) - case object.StatusAccessDenied: - decoder = new(ObjectAccessDenied) - case object.StatusNotFound: - decoder = new(ObjectNotFound) - case object.StatusAlreadyRemoved: - decoder = new(ObjectAlreadyRemoved) - case object.StatusOutOfRange: - decoder = new(ObjectOutOfRange) - } - case container.LocalizeFailStatus(&code): - //nolint:exhaustive - switch code { - case container.StatusNotFound: - decoder = new(ContainerNotFound) - case container.StatusEACLNotFound: - decoder = new(EACLNotFound) - } - case session.LocalizeFailStatus(&code): - //nolint:exhaustive - switch code { - case session.StatusTokenNotFound: - decoder = new(SessionTokenNotFound) - case session.StatusTokenExpired: - decoder = new(SessionTokenExpired) - } + switch code := st.GetCode(); code { + case protostatus.OK: + return nil + case protostatus.InternalServerError: + decoder = new(ServerInternal) + case protostatus.WrongNetMagic: + decoder = new(WrongMagicNumber) + case protostatus.SignatureVerificationFail: + decoder = new(SignatureVerification) + case protostatus.NodeUnderMaintenance: + decoder = new(NodeUnderMaintenance) + case protostatus.ObjectLocked: + decoder = new(ObjectLocked) + case protostatus.LockIrregularObject: + decoder = new(LockNonRegularObject) + case protostatus.ObjectAccessDenied: + decoder = new(ObjectAccessDenied) + case protostatus.ObjectNotFound: + decoder = new(ObjectNotFound) + case protostatus.ObjectAlreadyRemoved: + decoder = new(ObjectAlreadyRemoved) + case protostatus.OutOfRange: + decoder = new(ObjectOutOfRange) + case protostatus.ContainerNotFound: + decoder = new(ContainerNotFound) + case protostatus.EACLNotFound: + decoder = new(EACLNotFound) + case protostatus.SessionTokenNotFound: + decoder = new(SessionTokenNotFound) + case protostatus.SessionTokenExpired: + decoder = new(SessionTokenExpired) } if decoder == nil { decoder = new(UnrecognizedStatusV2) } - decoder.fromStatusV2(st) + decoder.fromProtoMessage(st) return decoder } -// ErrorToV2 converts error to status.Status message structure. Inverse to [ErrorFromV2] operation. +// FromError converts error to status.Status message structure. Inverse to [ToError] operation. // -// If argument is the [StatusV2] instance, it is converted directly. -// Otherwise, successes are converted with [status.OK] code w/o details and message, -// failures - with [status.Internal] and error text message w/o details. -func ErrorToV2(err error) *status.Status { +// Nil corresponds to [protostatus.OK] code, any unknown error to +// [protostatus.InternalServerError]. +func FromError(err error) *protostatus.Status { if err == nil { - return newStatusV2WithLocalCode(status.OK, status.GlobalizeSuccess) + return nil } - var instance StatusV2 - if errors.As(err, &instance) { - return instance.ErrorToV2() + var m interface{ protoMessage() *protostatus.Status } + if errors.As(err, &m) { + return m.protoMessage() } - internalErrorStatus := newStatusV2WithLocalCode(status.Internal, status.GlobalizeCommonFail) - internalErrorStatus.SetMessage(err.Error()) - - return internalErrorStatus + return &protostatus.Status{ + Code: protostatus.InternalServerError, + Message: err.Error(), + } } -func errMessageStatusV2(code any, msg string) string { +func errMessageStatus(code any, msg string) string { const ( noMsgFmt = "status: code = %v" msgFmt = noMsgFmt + " message = %s" @@ -149,16 +122,3 @@ func errMessageStatusV2(code any, msg string) string { return fmt.Sprintf(noMsgFmt, code) } - -func newStatusV2WithLocalCode(code status.Code, globalizer func(*status.Code)) *status.Status { - var st status.Status - - st.SetCode(globalizeCodeV2(code, globalizer)) - - return &st -} - -func globalizeCodeV2(code status.Code, globalizer func(*status.Code)) status.Code { - globalizer(&code) - return code -} diff --git a/client/status/v2_test.go b/client/status/v2_test.go index d38beb21..61930b07 100644 --- a/client/status/v2_test.go +++ b/client/status/v2_test.go @@ -8,37 +8,37 @@ import ( "github.com/stretchr/testify/require" ) -func TestFromStatusV2(t *testing.T) { +func TestToError(t *testing.T) { type statusConstructor func() error for _, testItem := range [...]struct { - status any // Status or statusConstructor - codeV2 uint64 - messageV2 string + new statusConstructor + code uint64 + message string compatibleErrs []error checkAsErr func(error) bool }{ { - status: (statusConstructor)(func() error { + new: func() error { return errors.New("some error") - }), - codeV2: 1024, - messageV2: "some error", + }, + code: 1024, + message: "some error", }, { - status: (statusConstructor)(func() error { + new: func() error { return nil - }), - codeV2: 0, + }, + code: 0, }, { - status: (statusConstructor)(func() error { + new: func() error { st := new(apistatus.ServerInternal) st.SetMessage("internal error message") return st - }), - codeV2: 1024, + }, + code: 1024, compatibleErrs: []error{apistatus.ErrServerInternal, apistatus.ServerInternal{}, &apistatus.ServerInternal{}, apistatus.Error}, checkAsErr: func(err error) bool { var target *apistatus.ServerInternal @@ -46,13 +46,13 @@ func TestFromStatusV2(t *testing.T) { }, }, { - status: (statusConstructor)(func() error { + new: func() error { st := new(apistatus.WrongMagicNumber) st.WriteCorrectMagic(322) return st - }), - codeV2: 1025, + }, + code: 1025, compatibleErrs: []error{apistatus.ErrWrongMagicNumber, apistatus.WrongMagicNumber{}, &apistatus.WrongMagicNumber{}, apistatus.Error}, checkAsErr: func(err error) bool { var target *apistatus.WrongMagicNumber @@ -60,10 +60,10 @@ func TestFromStatusV2(t *testing.T) { }, }, { - status: (statusConstructor)(func() error { + new: func() error { return new(apistatus.ObjectLocked) - }), - codeV2: 2050, + }, + code: 2050, compatibleErrs: []error{apistatus.ErrObjectLocked, apistatus.ObjectLocked{}, &apistatus.ObjectLocked{}, apistatus.Error}, checkAsErr: func(err error) bool { var target *apistatus.ObjectLocked @@ -71,10 +71,10 @@ func TestFromStatusV2(t *testing.T) { }, }, { - status: (statusConstructor)(func() error { + new: func() error { return new(apistatus.LockNonRegularObject) - }), - codeV2: 2051, + }, + code: 2051, compatibleErrs: []error{apistatus.ErrLockNonRegularObject, apistatus.LockNonRegularObject{}, &apistatus.LockNonRegularObject{}, apistatus.Error}, checkAsErr: func(err error) bool { var target *apistatus.LockNonRegularObject @@ -82,13 +82,13 @@ func TestFromStatusV2(t *testing.T) { }, }, { - status: (statusConstructor)(func() error { + new: func() error { st := new(apistatus.ObjectAccessDenied) st.WriteReason("any reason") return st - }), - codeV2: 2048, + }, + code: 2048, compatibleErrs: []error{apistatus.ErrObjectAccessDenied, apistatus.ObjectAccessDenied{}, &apistatus.ObjectAccessDenied{}, apistatus.Error}, checkAsErr: func(err error) bool { var target *apistatus.ObjectAccessDenied @@ -96,10 +96,10 @@ func TestFromStatusV2(t *testing.T) { }, }, { - status: (statusConstructor)(func() error { + new: func() error { return new(apistatus.ObjectNotFound) - }), - codeV2: 2049, + }, + code: 2049, compatibleErrs: []error{apistatus.ErrObjectNotFound, apistatus.ObjectNotFound{}, &apistatus.ObjectNotFound{}, apistatus.Error}, checkAsErr: func(err error) bool { var target *apistatus.ObjectNotFound @@ -107,10 +107,10 @@ func TestFromStatusV2(t *testing.T) { }, }, { - status: (statusConstructor)(func() error { + new: func() error { return new(apistatus.ObjectAlreadyRemoved) - }), - codeV2: 2052, + }, + code: 2052, compatibleErrs: []error{apistatus.ErrObjectAlreadyRemoved, apistatus.ObjectAlreadyRemoved{}, &apistatus.ObjectAlreadyRemoved{}, apistatus.Error}, checkAsErr: func(err error) bool { var target *apistatus.ObjectAlreadyRemoved @@ -118,10 +118,10 @@ func TestFromStatusV2(t *testing.T) { }, }, { - status: statusConstructor(func() error { + new: func() error { return new(apistatus.ObjectOutOfRange) - }), - codeV2: 2053, + }, + code: 2053, compatibleErrs: []error{apistatus.ErrObjectOutOfRange, apistatus.ObjectOutOfRange{}, &apistatus.ObjectOutOfRange{}, apistatus.Error}, checkAsErr: func(err error) bool { var target *apistatus.ObjectOutOfRange @@ -129,10 +129,10 @@ func TestFromStatusV2(t *testing.T) { }, }, { - status: (statusConstructor)(func() error { + new: func() error { return new(apistatus.ContainerNotFound) - }), - codeV2: 3072, + }, + code: 3072, compatibleErrs: []error{apistatus.ErrContainerNotFound, apistatus.ContainerNotFound{}, &apistatus.ContainerNotFound{}, apistatus.Error}, checkAsErr: func(err error) bool { var target *apistatus.ContainerNotFound @@ -140,10 +140,10 @@ func TestFromStatusV2(t *testing.T) { }, }, { - status: (statusConstructor)(func() error { + new: func() error { return new(apistatus.EACLNotFound) - }), - codeV2: 3073, + }, + code: 3073, compatibleErrs: []error{apistatus.ErrEACLNotFound, apistatus.EACLNotFound{}, &apistatus.EACLNotFound{}, apistatus.Error}, checkAsErr: func(err error) bool { var target *apistatus.EACLNotFound @@ -151,10 +151,10 @@ func TestFromStatusV2(t *testing.T) { }, }, { - status: (statusConstructor)(func() error { + new: func() error { return new(apistatus.SessionTokenNotFound) - }), - codeV2: 4096, + }, + code: 4096, compatibleErrs: []error{apistatus.ErrSessionTokenNotFound, apistatus.SessionTokenNotFound{}, &apistatus.SessionTokenNotFound{}, apistatus.Error}, checkAsErr: func(err error) bool { var target *apistatus.SessionTokenNotFound @@ -162,10 +162,10 @@ func TestFromStatusV2(t *testing.T) { }, }, { - status: (statusConstructor)(func() error { + new: func() error { return new(apistatus.SessionTokenExpired) - }), - codeV2: 4097, + }, + code: 4097, compatibleErrs: []error{apistatus.ErrSessionTokenExpired, apistatus.SessionTokenExpired{}, &apistatus.SessionTokenExpired{}, apistatus.Error}, checkAsErr: func(err error) bool { var target *apistatus.SessionTokenExpired @@ -173,10 +173,10 @@ func TestFromStatusV2(t *testing.T) { }, }, { - status: (statusConstructor)(func() error { + new: func() error { return new(apistatus.NodeUnderMaintenance) - }), - codeV2: 1027, + }, + code: 1027, compatibleErrs: []error{apistatus.ErrNodeUnderMaintenance, apistatus.NodeUnderMaintenance{}, &apistatus.NodeUnderMaintenance{}, apistatus.Error}, checkAsErr: func(err error) bool { var target *apistatus.NodeUnderMaintenance @@ -184,30 +184,23 @@ func TestFromStatusV2(t *testing.T) { }, }, } { - var st error - cons, ok := testItem.status.(statusConstructor) - require.True(t, ok) - - st = cons() + st := testItem.new() - stv2 := apistatus.ErrorToV2(st) + m := apistatus.FromError(st) // must generate the same status.Status message - require.EqualValues(t, testItem.codeV2, stv2.Code()) - if len(testItem.messageV2) > 0 { - require.Equal(t, testItem.messageV2, stv2.Message()) + require.EqualValues(t, testItem.code, m.GetCode()) + if len(testItem.message) > 0 { + require.Equal(t, testItem.message, m.Message) } - _, ok = st.(apistatus.StatusV2) - if ok { - // restore and convert again - restored := apistatus.ErrorFromV2(stv2) + // restore and convert again + restored := apistatus.ToError(m) - res := apistatus.ErrorToV2(restored) + res := apistatus.FromError(restored) - // must generate the same status.Status message - require.Equal(t, stv2, res) - } + // must generate the same status.Status message + require.Equal(t, m, res) randomError := errors.New("garbage") for _, err := range testItem.compatibleErrs { diff --git a/crypto/proto.go b/crypto/proto.go new file mode 100644 index 00000000..fc305fba --- /dev/null +++ b/crypto/proto.go @@ -0,0 +1,293 @@ +package neofscrypto + +import ( + "errors" + "fmt" + + "github.com/nspcc-dev/neofs-sdk-go/internal/proto" + "github.com/nspcc-dev/neofs-sdk-go/proto/refs" + "github.com/nspcc-dev/neofs-sdk-go/proto/session" +) + +var ( + errSignBody = errors.New("sign body") + errSignMeta = errors.New("sign meta header") + errSignVerifyOrigin = errors.New("sign verification header's origin") + errMissingVerifyHdr = errors.New("missing verification header") + errWrongVerifyHdrNum = errors.New("incorrect number of verification headers") + errMissingVerifyOriginSig = errors.New("missing verification header's origin signature") + errInvalidVerifyOriginSig = errors.New("invalid verification header's origin signature") + errMissingMetaSig = errors.New("missing meta header's signature") + errInvalidMetaSig = errors.New("invalid meta header's signature") + errMissingBodySig = errors.New("missing body signature") + errInvalidBodySig = errors.New("invalid body signature") + errNonOriginBodySig = errors.New("body signature is set in non-origin verification header") +) + +func newErrInvalidVerificationHeader(depth uint, cause error) error { + return fmt.Errorf("invalid verification header at depth %d: %w", depth, cause) +} + +// SignedRequest is a generic interface of a signed NeoFS API request. +type SignedRequest[B proto.Message] interface { + GetBody() B + GetMetaHeader() *session.RequestMetaHeader + GetVerifyHeader() *session.RequestVerificationHeader +} + +// SignedResponse is a generic interface of a signed NeoFS API response. +type SignedResponse[B proto.Message] interface { + GetBody() B + GetMetaHeader() *session.ResponseMetaHeader + GetVerifyHeader() *session.ResponseVerificationHeader +} + +// SignRequestWithBuffer signs request parts using provided [neofscrypto.Signer] +// according to the NeoFS API protocol, and returns resulting verification +// header to attach to this request. +// +// Buffer is optional and free after the call. +func SignRequestWithBuffer[B proto.Message](signer Signer, r SignedRequest[B], buf []byte) (*session.RequestVerificationHeader, error) { + var ln int + var err error + vhOriginal := r.GetVerifyHeader() + + var bs []byte + signBody := vhOriginal == nil + if signBody { // body should be signed by the original sender only + buf, ln = encodeMessage(r.GetBody(), buf) + bs, err = signer.Sign(buf[:ln]) + if err != nil { + return nil, fmt.Errorf("%w: %w", errSignBody, err) + } + } + + buf, ln = encodeMessage(r.GetMetaHeader(), buf) + ms, err := signer.Sign(buf[:ln]) + if err != nil { + return nil, fmt.Errorf("%w: %w", errSignMeta, err) + } + + buf, ln = encodeMessage(vhOriginal, buf) + vs, err := signer.Sign(buf[:ln]) + if err != nil { + return nil, fmt.Errorf("%w: %w", errSignVerifyOrigin, err) + } + + scheme := refs.SignatureScheme(signer.Scheme()) + pub := PublicKeyBytes(signer.Public()) + res := &session.RequestVerificationHeader{ + MetaSignature: &refs.Signature{Key: pub, Sign: ms, Scheme: scheme}, + OriginSignature: &refs.Signature{Key: pub, Sign: vs, Scheme: scheme}, + Origin: vhOriginal, + } + if signBody { + res.BodySignature = &refs.Signature{Key: pub, Sign: bs, Scheme: scheme} + } + return res, nil +} + +// VerifyRequestWithBuffer checks whether verification header of the request is +// formed according to the NeoFS API protocol. +// +// Buffer is optional and free after the call. +func VerifyRequestWithBuffer[B proto.Message](r SignedRequest[B], buf []byte) error { + v := r.GetVerifyHeader() + if v == nil { + return errMissingVerifyHdr + } + + b := r.GetBody() + m := r.GetMetaHeader() + bs := maxEncodedSize(b, m, v) + mo, vo := m.GetOrigin(), v.GetOrigin() + for { + if (mo == nil) != (vo == nil) { + return errWrongVerifyHdrNum + } + if vo == nil { + break + } + if s := maxEncodedSize(mo, vo); s > bs { + bs = s + } + mo, vo = mo.GetOrigin(), vo.GetOrigin() + } + + if len(buf) < bs { + buf = make([]byte, bs) + } + + for i := uint(0); ; m, v, i = m.Origin, v.Origin, i+1 { + if v.MetaSignature == nil { + return newErrInvalidVerificationHeader(i, errMissingMetaSig) + } + if err := verifyMessageSignature(m, v.MetaSignature, buf); err != nil { + return newErrInvalidVerificationHeader(i, fmt.Errorf("%w: %w", errInvalidMetaSig, err)) + } + if v.OriginSignature == nil { + return newErrInvalidVerificationHeader(i, errMissingVerifyOriginSig) + } + if err := verifyMessageSignature(v.Origin, v.OriginSignature, buf); err != nil { + return newErrInvalidVerificationHeader(i, fmt.Errorf("%w: %w", errInvalidVerifyOriginSig, err)) + } + if v.Origin == nil { + if v.BodySignature == nil { + return newErrInvalidVerificationHeader(i, errMissingBodySig) + } + if err := verifyMessageSignature(b, v.BodySignature, buf); err != nil { + return newErrInvalidVerificationHeader(i, fmt.Errorf("%w: %w", errInvalidBodySig, err)) + } + return nil + } + if v.BodySignature != nil { + return newErrInvalidVerificationHeader(i, errNonOriginBodySig) + } + } +} + +// SignResponseWithBuffer signs response parts using provided +// [neofscrypto.Signer] according to the NeoFS API protocol, and returns +// resulting verification header to attach to this response. +// +// Buffer is optional and free after the call. +func SignResponseWithBuffer[B proto.Message](signer Signer, r SignedResponse[B], buf []byte) (*session.ResponseVerificationHeader, error) { + var ln int + var err error + vhOriginal := r.GetVerifyHeader() + + var bs []byte + signBody := vhOriginal == nil + if signBody { // body should be signed by the original sender only + buf, ln = encodeMessage(r.GetBody(), buf) + bs, err = signer.Sign(buf[:ln]) + if err != nil { + return nil, fmt.Errorf("%w: %w", errSignBody, err) + } + } + + buf, ln = encodeMessage(r.GetMetaHeader(), buf) + ms, err := signer.Sign(buf[:ln]) + if err != nil { + return nil, fmt.Errorf("%w: %w", errSignMeta, err) + } + + buf, ln = encodeMessage(vhOriginal, buf) + vs, err := signer.Sign(buf[:ln]) + if err != nil { + return nil, fmt.Errorf("%w: %w", errSignVerifyOrigin, err) + } + + scheme := refs.SignatureScheme(signer.Scheme()) + pub := PublicKeyBytes(signer.Public()) + res := &session.ResponseVerificationHeader{ + MetaSignature: &refs.Signature{Key: pub, Sign: ms, Scheme: scheme}, + OriginSignature: &refs.Signature{Key: pub, Sign: vs, Scheme: scheme}, + Origin: vhOriginal, + } + if signBody { + res.BodySignature = &refs.Signature{Key: pub, Sign: bs, Scheme: scheme} + } + return res, nil +} + +// VerifyResponseWithBuffer checks whether verification header of the response +// is formed according to the NeoFS API protocol. +// +// Buffer is optional and free after the call. +func VerifyResponseWithBuffer[B proto.Message](r SignedResponse[B], buf []byte) error { + v := r.GetVerifyHeader() + if v == nil { + return errMissingVerifyHdr + } + + b := r.GetBody() + m := r.GetMetaHeader() + bs := maxEncodedSize(b, m, v) + mo, vo := m.GetOrigin(), v.GetOrigin() + for { + if (mo == nil) != (vo == nil) { + return errWrongVerifyHdrNum + } + if vo == nil { + break + } + if s := maxEncodedSize(mo, vo); s > bs { + bs = s + } + mo, vo = mo.GetOrigin(), vo.GetOrigin() + } + + if len(buf) < bs { + buf = make([]byte, bs) + } + + for i := uint(0); ; m, v, i = m.Origin, v.Origin, i+1 { + if v.MetaSignature == nil { + return newErrInvalidVerificationHeader(i, errMissingMetaSig) + } + if err := verifyMessageSignature(m, v.MetaSignature, buf); err != nil { + return newErrInvalidVerificationHeader(i, fmt.Errorf("%w: %w", errInvalidMetaSig, err)) + } + if v.OriginSignature == nil { + return newErrInvalidVerificationHeader(i, errMissingVerifyOriginSig) + } + if err := verifyMessageSignature(v.Origin, v.OriginSignature, buf); err != nil { + return newErrInvalidVerificationHeader(i, fmt.Errorf("%w: %w", errInvalidVerifyOriginSig, err)) + } + if v.Origin == nil { + if v.BodySignature == nil { + return newErrInvalidVerificationHeader(i, errMissingBodySig) + } + if err := verifyMessageSignature(b, v.BodySignature, buf); err != nil { + return newErrInvalidVerificationHeader(i, fmt.Errorf("%w: %w", errInvalidBodySig, err)) + } + return nil + } + if v.BodySignature != nil { + return newErrInvalidVerificationHeader(i, errNonOriginBodySig) + } + } +} + +func verifyMessageSignature(m proto.Message, s *refs.Signature, b []byte) error { + if len(s.Key) == 0 { + return errors.New("missing public key") + } + if s.Scheme < 0 { + return fmt.Errorf("negative scheme %d", s.Scheme) + } + pubKey, err := decodePublicKey(Scheme(s.Scheme), s.Key) + if err != nil { + return err + } + + var sz int + b, sz = encodeMessage(m, b) + if !pubKey.Verify(b[:sz], s.Sign) { + return errors.New("signature mismatch") + } + + return nil +} + +// marshals m into buffer and returns it. Second value means buffer len occupied +// for m. +func encodeMessage(m proto.Message, b []byte) ([]byte, int) { + s := m.MarshaledSize() + if len(b) < s { + b = make([]byte, s) + } + m.MarshalStable(b) + return b, s +} + +func maxEncodedSize(ms ...proto.Message) int { + res := ms[0].MarshaledSize() + for _, m := range ms[1:] { + if s := m.MarshaledSize(); s > res { + res = s + } + } + return res +} diff --git a/crypto/proto_test.go b/crypto/proto_test.go new file mode 100644 index 00000000..2179188f --- /dev/null +++ b/crypto/proto_test.go @@ -0,0 +1,878 @@ +package neofscrypto_test + +import ( + "bytes" + "crypto/ecdsa" + "crypto/sha256" + "crypto/sha512" + "math/rand/v2" + "testing" + + neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" + neofscryptotest "github.com/nspcc-dev/neofs-sdk-go/crypto/test" + neofsproto "github.com/nspcc-dev/neofs-sdk-go/internal/proto" + protoacl "github.com/nspcc-dev/neofs-sdk-go/proto/acl" + protoobject "github.com/nspcc-dev/neofs-sdk-go/proto/object" + "github.com/nspcc-dev/neofs-sdk-go/proto/refs" + protosession "github.com/nspcc-dev/neofs-sdk-go/proto/session" + protostatus "github.com/nspcc-dev/neofs-sdk-go/proto/status" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" +) + +var corruptSigTestcases = []struct { + name, msg string + corrupt func(valid *refs.Signature) +}{ + {name: "scheme/negative", msg: "negative scheme -1", corrupt: func(valid *refs.Signature) { valid.Scheme = -1 }}, + {name: "scheme/unsupported ", msg: "unsupported scheme 3", corrupt: func(valid *refs.Signature) { valid.Scheme = 3 }}, + {name: "scheme/other ", msg: "signature mismatch", corrupt: func(valid *refs.Signature) { + if valid.Scheme++; valid.Scheme >= 3 { + valid.Scheme = 0 + } + }}, + {name: "public key/nil", msg: "missing public key", corrupt: func(valid *refs.Signature) { valid.Key = nil }}, + {name: "public key/empty", msg: "missing public key", corrupt: func(valid *refs.Signature) { valid.Key = []byte{} }}, + {name: "public key/undersize", msg: "decode public key from binary: unexpected EOF", corrupt: func(valid *refs.Signature) { + valid.Key = bytes.Clone(requestSignerECDSAPubBin)[:32] + }}, + {name: "public key/oversize", msg: "decode public key from binary: extra data", corrupt: func(valid *refs.Signature) { + valid.Key = append(bytes.Clone(requestSignerECDSAPubBin), 1) + }}, + {name: "public key/prefix/zero", msg: "decode public key from binary: extra data", corrupt: func(valid *refs.Signature) { + valid.Key[0] = 0x00 + }}, + {name: "public key/prefix/unsupported", msg: "decode public key from binary: invalid prefix 5", corrupt: func(valid *refs.Signature) { + valid.Key[0] = 0x05 + }}, + {name: "public key/prefix/uncompressed in compressed form", msg: "decode public key from binary: EOF", corrupt: func(valid *refs.Signature) { + valid.Key[0] = 0x04 + }}, + {name: "public key/prefix/other compressed", msg: "signature mismatch", corrupt: func(valid *refs.Signature) { + if valid.Key[0] == 0x02 { + valid.Key[0] = 0x03 + } else { + valid.Key[0] = 0x02 + } + }}, + {name: "public key/wrong", msg: "signature mismatch", corrupt: func(valid *refs.Signature) { + valid.Key = neofscryptotest.Signer().PublicKeyBytes + }}, + {name: "signature/nil", msg: "signature mismatch", corrupt: func(valid *refs.Signature) { valid.Sign = nil }}, + {name: "signature/empty", msg: "signature mismatch", corrupt: func(valid *refs.Signature) { valid.Sign = []byte{} }}, + {name: "signature/nil", msg: "signature mismatch", corrupt: func(valid *refs.Signature) { valid.Sign = nil }}, + {name: "signature/empty", msg: "signature mismatch", corrupt: func(valid *refs.Signature) { valid.Sign = []byte{} }}, + {name: "signature/undersize", msg: "signature mismatch", corrupt: func(valid *refs.Signature) { + valid.Sign = valid.Sign[:len(valid.Sign)-1] + }}, + {name: "signature/oversize", msg: "signature mismatch", corrupt: func(valid *refs.Signature) { + valid.Sign = append(valid.Sign, 1) + }}, + {name: "signature/one byte change", msg: "signature mismatch", corrupt: func(valid *refs.Signature) { + valid.Sign[rand.IntN(len(valid.Sign))]++ + }}, + // TODO: uncomment after https://github.com/nspcc-dev/neofs-sdk-go/issues/673 + // {name: "public key/infinite", msg: "signature mismatch", corrupt: func(valid *refs.Signature) { + // valid.Key = []byte{0x00} + // }}, +} + +type invalidRequestVerificationHeaderTestcase = struct { + name, msg string + corrupt func(valid *protosession.RequestVerificationHeader) +} + +// finalized in init. +var invalidOriginalRequestVerificationHeaderTestcases = []invalidRequestVerificationHeaderTestcase{ + {name: "body signature/missing", msg: "missing body signature", corrupt: func(valid *protosession.RequestVerificationHeader) { + valid.BodySignature = nil + }}, + {name: "meta header signature/missing", msg: "missing meta header's signature", corrupt: func(valid *protosession.RequestVerificationHeader) { + valid.MetaSignature = nil + }}, + {name: "verification header's origin signature/missing", msg: "missing verification header's origin signature", corrupt: func(valid *protosession.RequestVerificationHeader) { + valid.OriginSignature = nil + }}, +} + +func init() { + for _, tc := range corruptSigTestcases { + invalidOriginalRequestVerificationHeaderTestcases = append(invalidOriginalRequestVerificationHeaderTestcases, invalidRequestVerificationHeaderTestcase{ + name: "body signature/" + tc.name, msg: "invalid body signature: " + tc.msg, + corrupt: func(valid *protosession.RequestVerificationHeader) { tc.corrupt(valid.BodySignature) }, + }, invalidRequestVerificationHeaderTestcase{ + name: "meta header signature/" + tc.name, msg: "invalid meta header's signature: " + tc.msg, + corrupt: func(valid *protosession.RequestVerificationHeader) { tc.corrupt(valid.MetaSignature) }, + }, invalidRequestVerificationHeaderTestcase{ + name: "verification header's origin signature/" + tc.name, msg: "invalid verification header's origin signature: " + tc.msg, + corrupt: func(valid *protosession.RequestVerificationHeader) { tc.corrupt(valid.OriginSignature) }, + }) + } +} + +var ( + reqMetaHdr = &protosession.RequestMetaHeader{ + Version: &refs.Version{Major: 4012726028, Minor: 3480185720}, + Epoch: 18426399493784435637, Ttl: 360369950, + XHeaders: []*protosession.XHeader{ + {Key: "x-header-1-key", Value: "x-header-1-val"}, + {Key: "x-header-2-key", Value: "x-header-2-val"}, + }, + SessionToken: &protosession.SessionToken{ + Body: &protosession.SessionToken_Body{ + Id: []byte("any_ID"), + OwnerId: &refs.OwnerID{Value: []byte("any_session_owner")}, + Lifetime: &protosession.SessionToken_Body_TokenLifetime{ + Exp: 9296388864757340046, Nbf: 7616299382059580946, Iat: 7881369180031591601, + }, + SessionKey: []byte("any_session_key"), + Context: &protosession.SessionToken_Body_Object{ + Object: &protosession.ObjectSessionContext{ + Verb: 598965377, + Target: &protosession.ObjectSessionContext_Target{ + Container: &refs.ContainerID{Value: []byte("any_target_container")}, + Objects: []*refs.ObjectID{ + {Value: []byte("any_target_object_1")}, + {Value: []byte("any_target_object_2")}, + }, + }, + }, + }, + }, + Signature: &refs.Signature{Key: []byte("any_pub"), Sign: []byte("any_sig"), Scheme: 598965377}, + }, + BearerToken: &protoacl.BearerToken{ + Body: &protoacl.BearerToken_Body{ + EaclTable: &protoacl.EACLTable{ + Version: &refs.Version{Major: 318436066, Minor: 2840436841}, + ContainerId: &refs.ContainerID{Value: []byte("any_eACL_container")}, + Records: []*protoacl.EACLRecord{ + {Operation: 1119884853, Action: 62729415, Filters: []*protoacl.EACLRecord_Filter{ + {HeaderType: 623516729, MatchType: 1738829273, Key: "filter-1-1-key", Value: "filter-1-1-val"}, + {HeaderType: 1607116959, MatchType: 1367966035, Key: "filter-1-2-key", Value: "filter-1-2-val"}, + }, Targets: []*protoacl.EACLRecord_Target{ + {Role: 611878932, Keys: [][]byte{[]byte("subj-1-1-1"), []byte("subj-1-1-2")}}, + {Role: 1862775306, Keys: [][]byte{[]byte("subj-1-2-1"), []byte("subj-1-2-2")}}, + }}, + {Operation: 1240073398, Action: 1717003574, Filters: []*protoacl.EACLRecord_Filter{ + {HeaderType: 623516729, MatchType: 1738829273, Key: "filter-2-1-key", Value: "filter-2-1-val"}, + {HeaderType: 1607116959, MatchType: 1367966035, Key: "filter-2-2-key", Value: "filter-2-2-val"}, + }, Targets: []*protoacl.EACLRecord_Target{ + {Role: 611878932, Keys: [][]byte{[]byte("subj-2-1-1"), []byte("subj-2-1-2")}}, + {Role: 1862775306, Keys: [][]byte{[]byte("subj-2-2-1"), []byte("subj-2-2-2")}}, + }}, + }, + }, + OwnerId: &refs.OwnerID{Value: []byte("any_bearer_user")}, + Lifetime: &protoacl.BearerToken_Body_TokenLifetime{ + Exp: 13260042237062625207, Nbf: 8718573876473538197, Iat: 2028326755325539864}, + Issuer: &refs.OwnerID{Value: []byte("any_bearer_issuer")}, + }, + Signature: &refs.Signature{Key: []byte("any_pub"), Sign: []byte("any_sig"), Scheme: 1375722142}, + }, + MagicNumber: 14001122173143970642, + } + reqMetaHdrBin = []byte{10, 12, 8, 140, 174, 181, 249, 14, 16, 248, 214, 189, 251, 12, 16, 181, 247, 213, 227, 229, 150, 238, 219, + 255, 1, 24, 158, 158, 235, 171, 1, 34, 32, 10, 14, 120, 45, 104, 101, 97, 100, 101, 114, 45, 49, 45, 107, 101, 121, 18, 14, 120, + 45, 104, 101, 97, 100, 101, 114, 45, 49, 45, 118, 97, 108, 34, 32, 10, 14, 120, 45, 104, 101, 97, 100, 101, 114, 45, 50, 45, 107, + 101, 121, 18, 14, 120, 45, 104, 101, 97, 100, 101, 114, 45, 50, 45, 118, 97, 108, 42, 188, 1, 10, 159, 1, 10, 6, 97, 110, 121, 95, + 73, 68, 18, 19, 10, 17, 97, 110, 121, 95, 115, 101, 115, 115, 105, 111, 110, 95, 111, 119, 110, 101, 114, 26, 31, 8, 142, 175, 136, 206, + 176, 141, 218, 129, 129, 1, 16, 146, 252, 192, 149, 246, 253, 161, 217, 105, 24, 177, 201, 250, 251, 176, 240, 143, 176, 109, + 34, 15, 97, 110, 121, 95, 115, 101, 115, 115, 105, 111, 110, 95, 107, 101, 121, 42, 78, 8, 129, 249, 205, 157, 2, 18, 70, 10, 22, + 10, 20, 97, 110, 121, 95, 116, 97, 114, 103, 101, 116, 95, 99, 111, 110, 116, 97, 105, 110, 101, 114, 18, 21, 10, 19, 97, 110, 121, 95, + 116, 97, 114, 103, 101, 116, 95, 111, 98, 106, 101, 99, 116, 95, 49, 18, 21, 10, 19, 97, 110, 121, 95, 116, 97, 114, 103, 101, 116, 95, + 111, 98, 106, 101, 99, 116, 95, 50, 18, 24, 10, 7, 97, 110, 121, 95, 112, 117, 98, 18, 7, 97, 110, 121, 95, 115, 105, 103, 24, 129, + 249, 205, 157, 2, 50, 226, 3, 10, 197, 3, 10, 249, 2, 10, 12, 8, 226, 229, 235, 151, 1, 16, 233, 192, 182, 202, 10, 18, + 20, 10, 18, 97, 110, 121, 95, 101, 65, 67, 76, 95, 99, 111, 110, 116, 97, 105, 110, 101, 114, 26, 167, 1, 8, 181, 172, 128, 150, 4, + 16, 199, 217, 244, 29, 26, 44, 8, 185, 184, 168, 169, 2, 16, 217, 219, 145, 189, 6, 26, 14, 102, 105, 108, 116, 101, 114, 45, 49, + 45, 49, 45, 107, 101, 121, 34, 14, 102, 105, 108, 116, 101, 114, 45, 49, 45, 49, 45, 118, 97, 108, 26, 44, 8, 159, 209, 170, 254, + 5, 16, 211, 130, 166, 140, 5, 26, 14, 102, 105, 108, 116, 101, 114, 45, 49, 45, 50, 45, 107, 101, 121, 34, 14, 102, 105, 108, 116, + 101, 114, 45, 49, 45, 50, 45, 118, 97, 108, 34, 30, 8, 148, 144, 226, 163, 2, 18, 10, 115, 117, 98, 106, 45, 49, 45, 49, 45, 49, + 18, 10, 115, 117, 98, 106, 45, 49, 45, 49, 45, 50, 34, 30, 8, 138, 228, 158, 248, 6, 18, 10, 115, 117, 98, 106, 45, 49, 45, 50, + 45, 49, 18, 10, 115, 117, 98, 106, 45, 49, 45, 50, 45, 50, 26, 168, 1, 8, 182, 137, 168, 207, 4, 16, 182, 202, 221, 178, 6, 26, + 44, 8, 185, 184, 168, 169, 2, 16, 217, 219, 145, 189, 6, 26, 14, 102, 105, 108, 116, 101, 114, 45, 50, 45, 49, 45, 107, 101, 121, + 34, 14, 102, 105, 108, 116, 101, 114, 45, 50, 45, 49, 45, 118, 97, 108, 26, 44, 8, 159, 209, 170, 254, 5, 16, 211, 130, 166, 140, + 5, 26, 14, 102, 105, 108, 116, 101, 114, 45, 50, 45, 50, 45, 107, 101, 121, 34, 14, 102, 105, 108, 116, 101, 114, 45, 50, 45, 50, + 45, 118, 97, 108, 34, 30, 8, 148, 144, 226, 163, 2, 18, 10, 115, 117, 98, 106, 45, 50, 45, 49, 45, 49, 18, 10, 115, 117, 98, 106, + 45, 50, 45, 49, 45, 50, 34, 30, 8, 138, 228, 158, 248, 6, 18, 10, 115, 117, 98, 106, 45, 50, 45, 50, 45, 49, 18, 10, 115, 117, + 98, 106, 45, 50, 45, 50, 45, 50, 18, 17, 10, 15, 97, 110, 121, 95, 98, 101, 97, 114, 101, 114, 95, 117, 115, 101, 114, 26, 31, 8, 183, + 239, 172, 246, 142, 197, 200, 130, 184, 1, 16, 149, 205, 210, 185, 246, 151, 166, 255, 120, 24, 152, 236, 229, 220, 255, + 141, 132, 147, 28, 34, 19, 10, 17, 97, 110, 121, 95, 98, 101, 97, 114, 101, 114, 95, 105, 115, 115, 117, 101, 114, 18, 24, 10, 7, 97, + 110, 121, 95, 112, 117, 98, 18, 7, 97, 110, 121, 95, 115, 105, 103, 24, 158, 181, 255, 143, 5, 64, 210, 230, 221, 152, 247, 205, + 254, 166, 194, 1} + + reqMetaHdrL2 = &protosession.RequestMetaHeader{ + Version: &refs.Version{Major: 4012726028, Minor: 3480185720}, + Epoch: 18426399493784435637, Ttl: 360369950, + XHeaders: []*protosession.XHeader{ + {Key: "x-header-1-key", Value: "x-header-1-val"}, + {Key: "x-header-2-key", Value: "x-header-2-val"}, + }, + // tokens unset to reduce the code, they are checked at L1 + Origin: reqMetaHdr, + MagicNumber: 14001122173143970642, + } + reqMetaHdrL2Bin = []byte{10, 12, 8, 140, 174, 181, 249, 14, 16, 248, 214, 189, 251, 12, 16, 181, 247, 213, 227, 229, 150, 238, + 219, 255, 1, 24, 158, 158, 235, 171, 1, 34, 32, 10, 14, 120, 45, 104, 101, 97, 100, 101, 114, 45, 49, 45, 107, 101, 121, 18, 14, + 120, 45, 104, 101, 97, 100, 101, 114, 45, 49, 45, 118, 97, 108, 34, 32, 10, 14, 120, 45, 104, 101, 97, 100, 101, 114, 45, 50, 45, + 107, 101, 121, 18, 14, 120, 45, 104, 101, 97, 100, 101, 114, 45, 50, 45, 118, 97, 108, 58, 146, 6, 10, 12, 8, 140, 174, 181, 249, + 14, 16, 248, 214, 189, 251, 12, 16, 181, 247, 213, 227, 229, 150, 238, 219, 255, 1, 24, 158, 158, 235, 171, 1, 34, 32, 10, + 14, 120, 45, 104, 101, 97, 100, 101, 114, 45, 49, 45, 107, 101, 121, 18, 14, 120, 45, 104, 101, 97, 100, 101, 114, 45, 49, 45, 118, + 97, 108, 34, 32, 10, 14, 120, 45, 104, 101, 97, 100, 101, 114, 45, 50, 45, 107, 101, 121, 18, 14, 120, 45, 104, 101, 97, 100, 101, + 114, 45, 50, 45, 118, 97, 108, 42, 188, 1, 10, 159, 1, 10, 6, 97, 110, 121, 95, 73, 68, 18, 19, 10, 17, 97, 110, 121, 95, 115, 101, + 115, 115, 105, 111, 110, 95, 111, 119, 110, 101, 114, 26, 31, 8, 142, 175, 136, 206, 176, 141, 218, 129, 129, 1, 16, 146, 252, 192, + 149, 246, 253, 161, 217, 105, 24, 177, 201, 250, 251, 176, 240, 143, 176, 109, 34, 15, 97, 110, 121, 95, 115, 101, 115, 115, 105, + 111, 110, 95, 107, 101, 121, 42, 78, 8, 129, 249, 205, 157, 2, 18, 70, 10, 22, 10, 20, 97, 110, 121, 95, 116, 97, 114, 103, 101, + 116, 95, 99, 111, 110, 116, 97, 105, 110, 101, 114, 18, 21, 10, 19, 97, 110, 121, 95, 116, 97, 114, 103, 101, 116, 95, 111, 98, 106, 101, + 99, 116, 95, 49, 18, 21, 10, 19, 97, 110, 121, 95, 116, 97, 114, 103, 101, 116, 95, 111, 98, 106, 101, 99, 116, 95, 50, 18, 24, 10, 7, + 97, 110, 121, 95, 112, 117, 98, 18, 7, 97, 110, 121, 95, 115, 105, 103, 24, 129, 249, 205, 157, 2, 50, 226, 3, 10, 197, 3, 10, + 249, 2, 10, 12, 8, 226, 229, 235, 151, 1, 16, 233, 192, 182, 202, 10, 18, 20, 10, 18, 97, 110, 121, 95, 101, 65, 67, 76, 95, + 99, 111, 110, 116, 97, 105, 110, 101, 114, 26, 167, 1, 8, 181, 172, 128, 150, 4, 16, 199, 217, 244, 29, 26, 44, 8, 185, 184, 168, + 169, 2, 16, 217, 219, 145, 189, 6, 26, 14, 102, 105, 108, 116, 101, 114, 45, 49, 45, 49, 45, 107, 101, 121, 34, 14, 102, 105, 108, + 116, 101, 114, 45, 49, 45, 49, 45, 118, 97, 108, 26, 44, 8, 159, 209, 170, 254, 5, 16, 211, 130, 166, 140, 5, 26, 14, 102, 105, + 108, 116, 101, 114, 45, 49, 45, 50, 45, 107, 101, 121, 34, 14, 102, 105, 108, 116, 101, 114, 45, 49, 45, 50, 45, 118, 97, 108, 34, + 30, 8, 148, 144, 226, 163, 2, 18, 10, 115, 117, 98, 106, 45, 49, 45, 49, 45, 49, 18, 10, 115, 117, 98, 106, 45, 49, 45, 49, 45, + 50, 34, 30, 8, 138, 228, 158, 248, 6, 18, 10, 115, 117, 98, 106, 45, 49, 45, 50, 45, 49, 18, 10, 115, 117, 98, 106, 45, 49, 45, + 50, 45, 50, 26, 168, 1, 8, 182, 137, 168, 207, 4, 16, 182, 202, 221, 178, 6, 26, 44, 8, 185, 184, 168, 169, 2, 16, 217, 219, + 145, 189, 6, 26, 14, 102, 105, 108, 116, 101, 114, 45, 50, 45, 49, 45, 107, 101, 121, 34, 14, 102, 105, 108, 116, 101, 114, 45, 50, + 45, 49, 45, 118, 97, 108, 26, 44, 8, 159, 209, 170, 254, 5, 16, 211, 130, 166, 140, 5, 26, 14, 102, 105, 108, 116, 101, 114, 45, + 50, 45, 50, 45, 107, 101, 121, 34, 14, 102, 105, 108, 116, 101, 114, 45, 50, 45, 50, 45, 118, 97, 108, 34, 30, 8, 148, 144, 226, + 163, 2, 18, 10, 115, 117, 98, 106, 45, 50, 45, 49, 45, 49, 18, 10, 115, 117, 98, 106, 45, 50, 45, 49, 45, 50, 34, 30, 8, 138, + 228, 158, 248, 6, 18, 10, 115, 117, 98, 106, 45, 50, 45, 50, 45, 49, 18, 10, 115, 117, 98, 106, 45, 50, 45, 50, 45, 50, 18, 17, + 10, 15, 97, 110, 121, 95, 98, 101, 97, 114, 101, 114, 95, 117, 115, 101, 114, 26, 31, 8, 183, 239, 172, 246, 142, 197, 200, 130, 184, + 1, 16, 149, 205, 210, 185, 246, 151, 166, 255, 120, 24, 152, 236, 229, 220, 255, 141, 132, 147, 28, 34, 19, 10, 17, 97, 110, + 121, 95, 98, 101, 97, 114, 101, 114, 95, 105, 115, 115, 117, 101, 114, 18, 24, 10, 7, 97, 110, 121, 95, 112, 117, 98, 18, 7, 97, 110, + 121, 95, 115, 105, 103, 24, 158, 181, 255, 143, 5, 64, 210, 230, 221, 152, 247, 205, 254, 166, 194, 1, 64, 210, 230, 221, + 152, 247, 205, 254, 166, 194, 1} +) + +var ( + requestSignerECDSAPubBin = []byte{3, 222, 100, 155, 214, 54, 45, 96, 2, 218, 144, 121, 166, 210, 58, 194, 143, 221, 111, 63, 87, + 254, 66, 2, 236, 94, 45, 93, 30, 39, 191, 127, 80} + requestSignerL2ECDSAPubBin = []byte{3, 95, 195, 112, 130, 26, 227, 140, 73, 208, 191, 208, 134, 199, 189, 139, 238, 55, 22, 49, + 165, 67, 146, 187, 82, 232, 85, 95, 144, 75, 87, 243, 21} + getObjectRequestBody = &protoobject.GetRequest_Body{ + Address: &refs.Address{ + ContainerId: &refs.ContainerID{Value: []byte("any_container")}, + ObjectId: &refs.ObjectID{Value: []byte("any_object")}, + }, + Raw: true, + } + getObjectRequestBodyBin = []byte{10, 31, 10, 15, 10, 13, 97, 110, 121, 95, 99, 111, 110, 116, 97, 105, 110, 101, 114, 18, 12, 10, 10, 97, + 110, 121, 95, 111, 98, 106, 101, 99, 116, 16, 1} + // clone to use. + getObjectUnsignedRequest = &protoobject.GetRequest{ + Body: getObjectRequestBody, + MetaHeader: reqMetaHdr, + } + // clone to use. + getObjectSignedRequest = &protoobject.GetRequest{ + Body: getObjectRequestBody, + MetaHeader: reqMetaHdrL2, + VerifyHeader: &protosession.RequestVerificationHeader{ + BodySignature: nil, + MetaSignature: &refs.Signature{ + Key: bytes.Clone(requestSignerL2ECDSAPubBin), + Sign: []byte{26, 147, 47, 31, 10, 173, 115, 179, 126, 16, 132, 149, 125, 68, 153, 129, 254, 184, 34, 53, 155, 194, 128, 115, 88, 68, 158, 91, 45, 8, 91, 169, 125, 215, 202, 234, 142, 72, 14, 110, 222, 142, 124, 200, 53, 189, 217, 100, 254, 100, 13, 9, 66, 60, 188, 5, 167, 116, 215, 230, 34, 150, 203, 132}, + Scheme: refs.SignatureScheme_ECDSA_RFC6979_SHA256, + }, + OriginSignature: &refs.Signature{ + Key: bytes.Clone(requestSignerL2ECDSAPubBin), + Sign: []byte{175, 192, 13, 37, 185, 173, 75, 11, 49, 178, 102, 150, 37, 208, 1, 158, 69, 252, 242, 121, 204, 220, 170, 117, 103, 250, 194, 218, 212, 144, 245, 177, 56, 67, 189, 182, 12, 122, 241, 4, 187, 154, 253, 56, 24, 138, 16, 103, 143, 203, 29, 228, 136, 33, 49, 245, 30, 165, 111, 23, 117, 149, 149, 228, 242, 157, 202, 93, 66, 215, 69, 103, 197, 232, 107, 147, 246, 192, 177, 158}, + Scheme: refs.SignatureScheme_ECDSA_RFC6979_SHA256_WALLET_CONNECT, + }, + Origin: &protosession.RequestVerificationHeader{ + BodySignature: &refs.Signature{ + Key: bytes.Clone(requestSignerECDSAPubBin), + Sign: []byte{4, 54, 181, 48, 83, 197, 23, 131, 0, 233, 48, 96, 155, 28, 68, 0, 189, 120, 251, 60, 163, 5, 136, 106, 63, + 126, 99, 34, 198, 66, 247, 207, 135, 12, 130, 49, 130, 155, 236, 204, 71, 23, 33, 178, 163, 27, 28, 101, 33, 33, + 91, 229, 217, 170, 250, 226, 62, 93, 22, 3, 181, 81, 69, 9, 97}, + Scheme: refs.SignatureScheme_ECDSA_SHA512, + }, + MetaSignature: &refs.Signature{ + Key: bytes.Clone(requestSignerECDSAPubBin), + Sign: []byte{152, 135, 221, 72, 61, 96, 131, 169, 229, 9, 203, 210, 132, 62, 40, 1, 211, 63, 130, 4, 136, 199, 186, + 219, 104, 2, 50, 101, 89, 252, 144, 184, 28, 125, 230, 39, 128, 238, 210, 223, 69, 128, 164, 112, 218, 133, + 80, 96, 19, 169, 156, 125, 250, 99, 197, 152, 73, 74, 15, 152, 186, 168, 170, 189}, + Scheme: refs.SignatureScheme_ECDSA_RFC6979_SHA256, + }, + OriginSignature: &refs.Signature{ + Key: bytes.Clone(requestSignerECDSAPubBin), + Sign: []byte{232, 128, 107, 75, 64, 63, 81, 149, 215, 6, 170, 132, 68, 181, 142, 100, 169, 242, 40, 227, 12, 103, + 202, 72, 190, 66, 240, 251, 115, 112, 36, 115, 169, 186, 16, 121, 153, 101, 206, 38, 156, 154, 69, 80, 198, 172, 125, + 115, 114, 54, 224, 44, 198, 137, 131, 236, 163, 209, 208, 136, 146, 184, 70, 136, 60, 200, 208, 106, 154, 206, 83, + 44, 222, 202, 169, 116, 157, 3, 5, 181}, + Scheme: refs.SignatureScheme_ECDSA_RFC6979_SHA256_WALLET_CONNECT, + }, + }, + }, + } +) + +func TestSignRequestWithBuffer(t *testing.T) { + anySigner := neofscryptotest.Signer() + pub := &anySigner.ECDSAPrivateKey.PublicKey + checkSignerCreds := func(scheme neofscrypto.Scheme, sigs ...*refs.Signature) { + for i, sig := range sigs { + require.NotNil(t, sig, i) + require.EqualValues(t, scheme, sig.Scheme, i) + require.Equal(t, anySigner.PublicKeyBytes, sig.Key, i) + } + } + + t.Run("signer failure", func(t *testing.T) { + for i, part := range []string{ + "body", + "meta header", + "verification header's origin", + } { + t.Run(part, func(t *testing.T) { + var req protoobject.GetRequest + signer := newNFailedSigner(anySigner, i+1) + _, err := neofscrypto.SignRequestWithBuffer[*protoobject.GetRequest_Body](signer, &req, nil) + require.ErrorContains(t, err, "sign "+part+":") + }) + } + }) + + for _, tc := range []struct { + name string + signer neofscrypto.Signer + hashFunc func([]byte) []byte + verifyFunc func(t testing.TB, pub *ecdsa.PublicKey, hash, sig []byte) + }{ + { + name: "ECDSA_SHA512", + signer: anySigner, + hashFunc: func(b []byte) []byte { h := sha512.Sum512(b); return h[:] }, + verifyFunc: verifyECDSAWithSHA512Signature, + }, + { + name: "ECDSA_SHA256_RFC6979", + signer: anySigner.RFC6979, + hashFunc: func(b []byte) []byte { h := sha256.Sum256(b); return h[:] }, + verifyFunc: verifyECDSAWithSHA256RFC6979Signature, + }, + } { + t.Run(tc.name, func(t *testing.T) { + req := proto.Clone(getObjectUnsignedRequest).(*protoobject.GetRequest) + + vh, err := neofscrypto.SignRequestWithBuffer[*protoobject.GetRequest_Body](tc.signer, req, nil) + require.NoError(t, err) + require.NotNil(t, vh) + require.Nil(t, vh.Origin) + + checkSignerCreds(tc.signer.Scheme(), vh.BodySignature, vh.MetaSignature, vh.OriginSignature) + + tc.verifyFunc(t, pub, tc.hashFunc(getObjectRequestBodyBin), vh.BodySignature.Sign) + tc.verifyFunc(t, pub, tc.hashFunc(reqMetaHdrBin), vh.MetaSignature.Sign) + tc.verifyFunc(t, pub, tc.hashFunc(nil), vh.OriginSignature.Sign) + + req.VerifyHeader = vh + err = neofscrypto.VerifyRequestWithBuffer[*protoobject.GetRequest_Body](req, nil) + require.NoError(t, err) + + t.Run("re-sign", func(t *testing.T) { + req.MetaHeader = reqMetaHdrL2 + + vhL2, err := neofscrypto.SignRequestWithBuffer[*protoobject.GetRequest_Body](tc.signer, req, nil) + require.NoError(t, err) + require.NotNil(t, vhL2) + require.True(t, vhL2.Origin == vh) // as pointers + + checkSignerCreds(tc.signer.Scheme(), vhL2.MetaSignature, vhL2.OriginSignature) + + require.Nil(t, vhL2.BodySignature) + tc.verifyFunc(t, pub, tc.hashFunc(reqMetaHdrL2Bin), vhL2.MetaSignature.Sign) + originHash := tc.hashFunc(neofsproto.MarshalMessage(vh)) + tc.verifyFunc(t, pub, originHash, vhL2.OriginSignature.Sign) + + req.VerifyHeader = vhL2 + err = neofscrypto.VerifyRequestWithBuffer[*protoobject.GetRequest_Body](req, nil) + require.NoError(t, err) + }) + }) + } + t.Run("ECDSA_SHA256_WalletConnect", func(t *testing.T) { + req := proto.Clone(getObjectUnsignedRequest).(*protoobject.GetRequest) + + vh, err := neofscrypto.SignRequestWithBuffer[*protoobject.GetRequest_Body](anySigner.WalletConnect, req, nil) + require.NoError(t, err) + require.NotNil(t, vh) + require.Nil(t, vh.Origin) + + checkSignerCreds(neofscrypto.ECDSA_WALLETCONNECT, vh.BodySignature, vh.MetaSignature, vh.OriginSignature) + + verifyWalletConnectSignature(t, pub, getObjectRequestBodyBin, vh.BodySignature.Sign) + verifyWalletConnectSignature(t, pub, reqMetaHdrBin, vh.MetaSignature.Sign) + verifyWalletConnectSignature(t, pub, nil, vh.OriginSignature.Sign) + + req.VerifyHeader = vh + err = neofscrypto.VerifyRequestWithBuffer[*protoobject.GetRequest_Body](req, nil) + require.NoError(t, err) + + t.Run("re-sign", func(t *testing.T) { + req.MetaHeader = reqMetaHdrL2 + + vhL2, err := neofscrypto.SignRequestWithBuffer[*protoobject.GetRequest_Body](anySigner.WalletConnect, req, nil) + require.NoError(t, err) + require.NotNil(t, vhL2) + require.True(t, vhL2.Origin == vh) // as pointers + + checkSignerCreds(neofscrypto.ECDSA_WALLETCONNECT, vhL2.MetaSignature, vhL2.OriginSignature) + + require.Nil(t, vhL2.BodySignature) + verifyWalletConnectSignature(t, pub, reqMetaHdrL2Bin, vhL2.MetaSignature.Sign) + verifyWalletConnectSignature(t, pub, neofsproto.MarshalMessage(vh.Origin), vh.OriginSignature.Sign) + + req.VerifyHeader = vhL2 + err = neofscrypto.VerifyRequestWithBuffer[*protoobject.GetRequest_Body](req, nil) + require.NoError(t, err) + }) + }) +} + +func TestVerifyRequestWithBuffer(t *testing.T) { + t.Run("correctly signed", func(t *testing.T) { + err := neofscrypto.VerifyRequestWithBuffer[*protoobject.GetRequest_Body](getObjectSignedRequest, nil) + require.NoError(t, err) + }) + t.Run("invalid", func(t *testing.T) { + t.Run("nil", func(t *testing.T) { + t.Run("untyped", func(t *testing.T) { + require.Panics(t, func() { + _ = neofscrypto.VerifyRequestWithBuffer[*protoobject.GetRequest_Body](nil, nil) + }) + }) + t.Run("typed", func(t *testing.T) { + err := neofscrypto.VerifyRequestWithBuffer[*protoobject.GetRequest_Body]((*protoobject.GetRequest)(nil), nil) + require.EqualError(t, err, "missing verification header") + }) + }) + t.Run("without verification header", func(t *testing.T) { + req := proto.Clone(getObjectSignedRequest).(*protoobject.GetRequest) + req.VerifyHeader = nil + err := neofscrypto.VerifyRequestWithBuffer[*protoobject.GetRequest_Body](req, nil) + require.EqualError(t, err, "missing verification header") + }) + for _, tc := range invalidOriginalRequestVerificationHeaderTestcases { + t.Run(tc.name, func(t *testing.T) { + req := proto.Clone(getObjectSignedRequest).(*protoobject.GetRequest) + req.MetaHeader = req.MetaHeader.Origin + req.VerifyHeader = req.VerifyHeader.Origin + tc.corrupt(req.VerifyHeader) + err := neofscrypto.VerifyRequestWithBuffer[*protoobject.GetRequest_Body](req, nil) + require.EqualError(t, err, "invalid verification header at depth 0: "+tc.msg) + + t.Run("resigned", func(t *testing.T) { + req := &protoobject.GetRequest{ + Body: req.Body, + MetaHeader: &protosession.RequestMetaHeader{Origin: req.MetaHeader}, + VerifyHeader: req.VerifyHeader, + } + req.VerifyHeader, err = neofscrypto.SignRequestWithBuffer[*protoobject.GetRequest_Body](neofscryptotest.Signer(), req, nil) + require.NoError(t, err) + + err := neofscrypto.VerifyRequestWithBuffer[*protoobject.GetRequest_Body](req, nil) + require.EqualError(t, err, "invalid verification header at depth 1: "+tc.msg) + }) + }) + } + t.Run("resigned", func(t *testing.T) { + for _, tc := range []struct { + name, msg string + corrupt func(valid *protoobject.GetRequest) + }{ + {name: "redundant verification header", msg: "incorrect number of verification headers", + corrupt: func(valid *protoobject.GetRequest) { + valid.VerifyHeader = &protosession.RequestVerificationHeader{Origin: valid.VerifyHeader} + }, + }, + {name: "lacking verification header", msg: "incorrect number of verification headers", + corrupt: func(valid *protoobject.GetRequest) { + valid.MetaHeader = &protosession.RequestMetaHeader{Origin: valid.MetaHeader} + }, + }, + {name: "with body signature", msg: "invalid verification header at depth 0: body signature is set in non-origin verification header", + corrupt: func(valid *protoobject.GetRequest) { + valid.VerifyHeader.BodySignature = new(refs.Signature) + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + req := proto.Clone(getObjectSignedRequest).(*protoobject.GetRequest) + tc.corrupt(req) + err := neofscrypto.VerifyRequestWithBuffer[*protoobject.GetRequest_Body](req, nil) + require.EqualError(t, err, tc.msg) + }) + } + }) + }) +} + +type invalidResponseVerificationHeaderTestcase = struct { + name, msg string + corrupt func(valid *protosession.ResponseVerificationHeader) +} + +// finalized in init. +var invalidOriginalResponseVerificationHeaderTestcases = []invalidResponseVerificationHeaderTestcase{ + {name: "body signature/missing", msg: "missing body signature", corrupt: func(valid *protosession.ResponseVerificationHeader) { + valid.BodySignature = nil + }}, + {name: "meta header signature/missing", msg: "missing meta header's signature", corrupt: func(valid *protosession.ResponseVerificationHeader) { + valid.MetaSignature = nil + }}, + {name: "verification header's origin signature/missing", msg: "missing verification header's origin signature", corrupt: func(valid *protosession.ResponseVerificationHeader) { + valid.OriginSignature = nil + }}, +} + +func init() { + for _, tc := range corruptSigTestcases { + invalidOriginalResponseVerificationHeaderTestcases = append(invalidOriginalResponseVerificationHeaderTestcases, invalidResponseVerificationHeaderTestcase{ + name: "body signature/" + tc.name, msg: "invalid body signature: " + tc.msg, + corrupt: func(valid *protosession.ResponseVerificationHeader) { tc.corrupt(valid.BodySignature) }, + }, invalidResponseVerificationHeaderTestcase{ + name: "meta header signature/" + tc.name, msg: "invalid meta header's signature: " + tc.msg, + corrupt: func(valid *protosession.ResponseVerificationHeader) { tc.corrupt(valid.MetaSignature) }, + }, invalidResponseVerificationHeaderTestcase{ + name: "verification header's origin signature/" + tc.name, msg: "invalid verification header's origin signature: " + tc.msg, + corrupt: func(valid *protosession.ResponseVerificationHeader) { tc.corrupt(valid.OriginSignature) }, + }) + } +} + +var ( + respMetaHdr = &protosession.ResponseMetaHeader{ + Version: &refs.Version{Major: 4012726028, Minor: 3480185720}, + Epoch: 18426399493784435637, + Ttl: 360369950, + XHeaders: []*protosession.XHeader{ + {Key: "x-header-1-key", Value: "x-header-1-val"}, + {Key: "x-header-2-key", Value: "x-header-2-val"}, + }, + Status: &protostatus.Status{ + Code: 2013711884, + Message: "any status message", + Details: []*protostatus.Status_Detail{ + {Id: 673818269, Value: []byte("detail_1")}, + {Id: 1795152762, Value: []byte("detail_2")}, + }, + }, + } + respMetaHdrBin = []byte{10, 12, 8, 140, 174, 181, 249, 14, 16, 248, 214, 189, 251, 12, 16, 181, 247, 213, 227, 229, 150, 238, + 219, 255, 1, 24, 158, 158, 235, 171, 1, 34, 32, 10, 14, 120, 45, 104, 101, 97, 100, 101, 114, 45, 49, 45, 107, 101, 121, 18, 14, + 120, 45, 104, 101, 97, 100, 101, 114, 45, 49, 45, 118, 97, 108, 34, 32, 10, 14, 120, 45, 104, 101, 97, 100, 101, 114, 45, 50, 45, + 107, 101, 121, 18, 14, 120, 45, 104, 101, 97, 100, 101, 114, 45, 50, 45, 118, 97, 108, 50, 62, 8, 140, 156, 155, 192, 7, 18, 18, 97, + 110, 121, 32, 115, 116, 97, 116, 117, 115, 32, 109, 101, 115, 115, 97, 103, 101, 26, 16, 8, 157, 205, 166, 193, 2, 18, 8, 100, 101, + 116, 97, 105, 108, 95, 49, 26, 16, 8, 250, 182, 255, 215, 6, 18, 8, 100, 101, 116, 97, 105, 108, 95, 50} + + respMetaHdrL2 = &protosession.ResponseMetaHeader{ + Version: &refs.Version{Major: 4012726028, Minor: 3480185720}, + Epoch: 18426399493784435637, + Ttl: 360369950, + XHeaders: []*protosession.XHeader{ + {Key: "x-header-1-key", Value: "x-header-1-val"}, + {Key: "x-header-2-key", Value: "x-header-2-val"}, + }, + Origin: respMetaHdr, + Status: &protostatus.Status{ + Code: 1472978490, + Message: "any status message", + Details: []*protostatus.Status_Detail{ + {Id: 542687564, Value: []byte("detail_1")}, + {Id: 789115882, Value: []byte("detail_2")}, + }, + }, + } + respMetaHdrL2Bin = []byte{10, 12, 8, 140, 174, 181, 249, 14, 16, 248, 214, 189, 251, 12, 16, 181, 247, 213, 227, 229, 150, 238, + 219, 255, 1, 24, 158, 158, 235, 171, 1, 34, 32, 10, 14, 120, 45, 104, 101, 97, 100, 101, 114, 45, 49, 45, 107, 101, 121, 18, 14, + 120, 45, 104, 101, 97, 100, 101, 114, 45, 49, 45, 118, 97, 108, 34, 32, 10, 14, 120, 45, 104, 101, 97, 100, 101, 114, 45, 50, 45, + 107, 101, 121, 18, 14, 120, 45, 104, 101, 97, 100, 101, 114, 45, 50, 45, 118, 97, 108, 42, 163, 1, 10, 12, 8, 140, 174, 181, 249, + 14, 16, 248, 214, 189, 251, 12, 16, 181, 247, 213, 227, 229, 150, 238, 219, 255, 1, 24, 158, 158, 235, 171, 1, 34, 32, 10, + 14, 120, 45, 104, 101, 97, 100, 101, 114, 45, 49, 45, 107, 101, 121, 18, 14, 120, 45, 104, 101, 97, 100, 101, 114, 45, 49, 45, 118, + 97, 108, 34, 32, 10, 14, 120, 45, 104, 101, 97, 100, 101, 114, 45, 50, 45, 107, 101, 121, 18, 14, 120, 45, 104, 101, 97, 100, 101, + 114, 45, 50, 45, 118, 97, 108, 50, 62, 8, 140, 156, 155, 192, 7, 18, 18, 97, 110, 121, 32, 115, 116, 97, 116, 117, 115, 32, 109, 101, + 115, 115, 97, 103, 101, 26, 16, 8, 157, 205, 166, 193, 2, 18, 8, 100, 101, 116, 97, 105, 108, 95, 49, 26, 16, 8, 250, 182, 255, + 215, 6, 18, 8, 100, 101, 116, 97, 105, 108, 95, 50, 50, 62, 8, 186, 188, 175, 190, 5, 18, 18, 97, 110, 121, 32, 115, 116, 97, 116, + 117, 115, 32, 109, 101, 115, 115, 97, 103, 101, 26, 16, 8, 204, 130, 227, 130, 2, 18, 8, 100, 101, 116, 97, 105, 108, 95, 49, 26, + 16, 8, 234, 231, 163, 248, 2, 18, 8, 100, 101, 116, 97, 105, 108, 95, 50} +) + +var ( + responseSignerECDSAPubBin = []byte{2, 233, 67, 160, 254, 231, 98, 137, 171, 220, 101, 138, 15, 186, 53, 234, 17, 18, 38, 245, + 80, 107, 40, 37, 164, 156, 142, 103, 157, 13, 253, 251, 6} + responseSignerL2ECDSAPubBin = []byte{3, 154, 201, 144, 52, 75, 150, 123, 180, 230, 46, 67, 182, 66, 134, 3, 8, 227, 139, 137, 41, + 117, 235, 244, 250, 191, 92, 36, 38, 101, 142, 96, 47} + getObjectResponseBody = &protoobject.GetResponse_Body{ + ObjectPart: &protoobject.GetResponse_Body_Init_{Init: &protoobject.GetResponse_Body_Init{ + ObjectId: &refs.ObjectID{Value: []byte("any_ID")}, + Signature: &refs.Signature{Key: []byte("any_pub"), Sign: []byte("any_sig"), Scheme: 2128773493}, + Header: &protoobject.Header{ + Version: &refs.Version{Major: 1559619596, Minor: 436551331}, + ContainerId: &refs.ContainerID{Value: []byte("any_container")}, + OwnerId: &refs.OwnerID{Value: []byte("any_owner")}, + CreationEpoch: 10561284447300915844, + PayloadLength: 766049361057238504, + }, + }}, + } + getObjectResponseBodyBin = []byte{10, 103, 10, 8, 10, 6, 97, 110, 121, 95, 73, 68, 18, 24, 10, 7, 97, 110, 121, 95, 112, 117, 98, 18, + 7, 97, 110, 121, 95, 115, 105, 103, 24, 245, 130, 138, 247, 7, 26, 65, 10, 12, 8, 140, 208, 215, 231, 5, 16, 163, 253, 148, + 208, 1, 18, 15, 10, 13, 97, 110, 121, 95, 99, 111, 110, 116, 97, 105, 110, 101, 114, 26, 11, 10, 9, 97, 110, 121, 95, 111, 119, 110, 101, + 114, 32, 132, 165, 234, 233, 250, 135, 206, 200, 146, 1, 40, 232, 155, 237, 241, 220, 186, 227, 208, 10} + // clone to use. + getObjectUnsignedResponse = &protoobject.GetResponse{ + Body: getObjectResponseBody, + MetaHeader: respMetaHdr, + } + // clone to use. + getObjectSignedResponse = &protoobject.GetResponse{ + Body: getObjectResponseBody, + MetaHeader: respMetaHdrL2, + VerifyHeader: &protosession.ResponseVerificationHeader{ + BodySignature: nil, + MetaSignature: &refs.Signature{ + Key: bytes.Clone(responseSignerL2ECDSAPubBin), + Sign: []byte{163, 138, 107, 57, 226, 203, 104, 22, 98, 98, 154, 169, 227, 112, 3, 55, 162, 221, 244, 199, 195, + 216, 209, 202, 212, 243, 50, 72, 182, 18, 127, 57, 37, 49, 78, 5, 106, 149, 146, 166, 55, 44, 33, 68, 9, 60, + 65, 169, 33, 187, 65, 162, 142, 150, 252, 118, 125, 74, 248, 34, 78, 7, 173, 240}, + Scheme: refs.SignatureScheme_ECDSA_RFC6979_SHA256, + }, + OriginSignature: &refs.Signature{ + Key: bytes.Clone(responseSignerL2ECDSAPubBin), + Sign: []byte{35, 20, 219, 207, 205, 109, 68, 60, 253, 133, 135, 95, 96, 89, 130, 130, 166, 245, 61, 9, 119, 6, 155, + 185, 203, 202, 213, 19, 81, 248, 139, 17, 95, 180, 242, 115, 169, 254, 213, 162, 235, 166, 147, 69, 207, 221, + 32, 124, 246, 203, 254, 238, 152, 255, 162, 137, 1, 19, 51, 197, 43, 8, 61, 53, 203, 66, 71, 251, 161, 112, 24, + 55, 193, 198, 128, 208, 134, 151, 147, 79}, + Scheme: refs.SignatureScheme_ECDSA_RFC6979_SHA256_WALLET_CONNECT, + }, + Origin: &protosession.ResponseVerificationHeader{ + BodySignature: &refs.Signature{ + Key: bytes.Clone(responseSignerECDSAPubBin), + Sign: []byte{4, 47, 78, 194, 50, 74, 38, 226, 116, 92, 209, 84, 150, 183, 182, 60, 89, 137, 211, 166, 28, 6, + 69, 228, 234, 249, 76, 229, 35, 189, 132, 18, 113, 55, 20, 148, 119, 161, 251, 206, 198, 13, 235, 106, 107, + 55, 61, 181, 42, 253, 212, 180, 57, 102, 139, 79, 194, 182, 148, 182, 8, 90, 153, 62, 21}, + Scheme: refs.SignatureScheme_ECDSA_SHA512, + }, + MetaSignature: &refs.Signature{ + Key: bytes.Clone(responseSignerECDSAPubBin), + Sign: []byte{194, 115, 78, 219, 234, 44, 29, 128, 18, 143, 78, 19, 10, 93, 38, 153, 190, 184, 145, 114, 36, 45, + 60, 89, 106, 245, 247, 129, 125, 156, 102, 143, 200, 55, 66, 203, 106, 47, 145, 53, 40, 161, 152, 35, 23, + 22, 31, 155, 178, 6, 195, 243, 249, 70, 220, 117, 127, 172, 232, 216, 214, 255, 126, 218}, + Scheme: refs.SignatureScheme_ECDSA_RFC6979_SHA256, + }, + OriginSignature: &refs.Signature{ + Key: bytes.Clone(responseSignerECDSAPubBin), + Sign: []byte{64, 177, 241, 85, 198, 123, 114, 71, 253, 169, 228, 142, 139, 152, 102, 62, 51, 51, 124, 38, 184, + 105, 50, 147, 175, 126, 186, 191, 40, 60, 105, 76, 198, 104, 219, 130, 45, 27, 116, 43, 185, 193, 159, 63, 216, + 46, 140, 26, 149, 219, 236, 188, 19, 136, 32, 12, 102, 207, 87, 38, 159, 57, 85, 38, 175, 41, 150, 171, 42, + 233, 67, 111, 218, 149, 90, 74, 159, 142, 26, 211}, + Scheme: refs.SignatureScheme_ECDSA_RFC6979_SHA256_WALLET_CONNECT, + }, + }, + }, + } +) + +func TestSignResponseWithBuffer(t *testing.T) { + anySigner := neofscryptotest.Signer() + pub := &anySigner.ECDSAPrivateKey.PublicKey + checkSignerCreds := func(scheme neofscrypto.Scheme, sigs ...*refs.Signature) { + for i, sig := range sigs { + require.NotNil(t, sig, i) + require.EqualValues(t, scheme, sig.Scheme, i) + require.Equal(t, anySigner.PublicKeyBytes, sig.Key, i) + } + } + + t.Run("signer failure", func(t *testing.T) { + for i, part := range []string{ + "body", + "meta header", + "verification header's origin", + } { + t.Run(part, func(t *testing.T) { + var req protoobject.GetResponse + signer := newNFailedSigner(anySigner, i+1) + _, err := neofscrypto.SignResponseWithBuffer[*protoobject.GetResponse_Body](signer, &req, nil) + require.ErrorContains(t, err, "sign "+part+":") + }) + } + }) + + for _, tc := range []struct { + name string + signer neofscrypto.Signer + hashFunc func([]byte) []byte + verifyFunc func(t testing.TB, pub *ecdsa.PublicKey, hash, sig []byte) + }{ + { + name: "ECDSA_SHA512", + signer: anySigner, + hashFunc: func(b []byte) []byte { h := sha512.Sum512(b); return h[:] }, + verifyFunc: verifyECDSAWithSHA512Signature, + }, + { + name: "ECDSA_SHA256_RFC6979", + signer: anySigner.RFC6979, + hashFunc: func(b []byte) []byte { h := sha256.Sum256(b); return h[:] }, + verifyFunc: verifyECDSAWithSHA256RFC6979Signature, + }, + } { + t.Run(tc.name, func(t *testing.T) { + r := proto.Clone(getObjectUnsignedResponse).(*protoobject.GetResponse) + + vh, err := neofscrypto.SignResponseWithBuffer[*protoobject.GetResponse_Body](tc.signer, r, nil) + require.NoError(t, err) + require.NotNil(t, vh) + require.Nil(t, vh.Origin) + + checkSignerCreds(tc.signer.Scheme(), vh.BodySignature, vh.MetaSignature, vh.OriginSignature) + + tc.verifyFunc(t, pub, tc.hashFunc(getObjectResponseBodyBin), vh.BodySignature.Sign) + tc.verifyFunc(t, pub, tc.hashFunc(respMetaHdrBin), vh.MetaSignature.Sign) + tc.verifyFunc(t, pub, tc.hashFunc(nil), vh.OriginSignature.Sign) + + r.VerifyHeader = vh + err = neofscrypto.VerifyResponseWithBuffer[*protoobject.GetResponse_Body](r, nil) + require.NoError(t, err) + + t.Run("re-sign", func(t *testing.T) { + r.MetaHeader = respMetaHdrL2 + + vhL2, err := neofscrypto.SignResponseWithBuffer[*protoobject.GetResponse_Body](tc.signer, r, nil) + require.NoError(t, err) + require.NotNil(t, vhL2) + require.True(t, vhL2.Origin == vh) // as pointers + + checkSignerCreds(tc.signer.Scheme(), vhL2.MetaSignature, vhL2.OriginSignature) + + require.Nil(t, vhL2.BodySignature) + tc.verifyFunc(t, pub, tc.hashFunc(respMetaHdrL2Bin), vhL2.MetaSignature.Sign) + originHash := tc.hashFunc(neofsproto.MarshalMessage(vh)) + tc.verifyFunc(t, pub, originHash, vhL2.OriginSignature.Sign) + + r.VerifyHeader = vhL2 + err = neofscrypto.VerifyResponseWithBuffer[*protoobject.GetResponse_Body](r, nil) + require.NoError(t, err) + }) + }) + } + t.Run("ECDSA_SHA256_WalletConnect", func(t *testing.T) { + r := proto.Clone(getObjectUnsignedResponse).(*protoobject.GetResponse) + + vh, err := neofscrypto.SignResponseWithBuffer[*protoobject.GetResponse_Body](anySigner.WalletConnect, r, nil) + require.NoError(t, err) + require.NotNil(t, vh) + require.Nil(t, vh.Origin) + + checkSignerCreds(neofscrypto.ECDSA_WALLETCONNECT, vh.BodySignature, vh.MetaSignature, vh.OriginSignature) + + verifyWalletConnectSignature(t, pub, getObjectResponseBodyBin, vh.BodySignature.Sign) + verifyWalletConnectSignature(t, pub, respMetaHdrBin, vh.MetaSignature.Sign) + verifyWalletConnectSignature(t, pub, nil, vh.OriginSignature.Sign) + + r.VerifyHeader = vh + err = neofscrypto.VerifyResponseWithBuffer[*protoobject.GetResponse_Body](r, nil) + require.NoError(t, err) + + t.Run("re-sign", func(t *testing.T) { + r.MetaHeader = respMetaHdrL2 + + vhL2, err := neofscrypto.SignResponseWithBuffer[*protoobject.GetResponse_Body](anySigner.WalletConnect, r, nil) + require.NoError(t, err) + require.NotNil(t, vhL2) + require.True(t, vhL2.Origin == vh) // as pointers + + checkSignerCreds(neofscrypto.ECDSA_WALLETCONNECT, vhL2.MetaSignature, vhL2.OriginSignature) + + require.Nil(t, vhL2.BodySignature) + verifyWalletConnectSignature(t, pub, respMetaHdrL2Bin, vhL2.MetaSignature.Sign) + verifyWalletConnectSignature(t, pub, neofsproto.MarshalMessage(vh.Origin), vh.OriginSignature.Sign) + + r.VerifyHeader = vhL2 + err = neofscrypto.VerifyResponseWithBuffer[*protoobject.GetResponse_Body](r, nil) + require.NoError(t, err) + }) + }) +} + +func TestVerifyResponseWithBuffer(t *testing.T) { + t.Run("correctly signed", func(t *testing.T) { + err := neofscrypto.VerifyResponseWithBuffer[*protoobject.GetResponse_Body](getObjectSignedResponse, nil) + require.NoError(t, err) + }) + t.Run("invalid", func(t *testing.T) { + t.Run("nil", func(t *testing.T) { + t.Run("untyped", func(t *testing.T) { + require.Panics(t, func() { + _ = neofscrypto.VerifyResponseWithBuffer[*protoobject.GetResponse_Body](nil, nil) + }) + }) + t.Run("typed", func(t *testing.T) { + err := neofscrypto.VerifyResponseWithBuffer[*protoobject.GetResponse_Body]((*protoobject.GetResponse)(nil), nil) + require.EqualError(t, err, "missing verification header") + }) + }) + t.Run("without verification header", func(t *testing.T) { + r := proto.Clone(getObjectSignedResponse).(*protoobject.GetResponse) + r.VerifyHeader = nil + err := neofscrypto.VerifyResponseWithBuffer[*protoobject.GetResponse_Body](r, nil) + require.EqualError(t, err, "missing verification header") + }) + for _, tc := range invalidOriginalResponseVerificationHeaderTestcases { + t.Run(tc.name, func(t *testing.T) { + r := proto.Clone(getObjectSignedResponse).(*protoobject.GetResponse) + r.MetaHeader = r.MetaHeader.Origin + r.VerifyHeader = r.VerifyHeader.Origin + tc.corrupt(r.VerifyHeader) + err := neofscrypto.VerifyResponseWithBuffer[*protoobject.GetResponse_Body](r, nil) + require.EqualError(t, err, "invalid verification header at depth 0: "+tc.msg) + + t.Run("resigned", func(t *testing.T) { + resp := &protoobject.GetResponse{ + Body: r.Body, + MetaHeader: &protosession.ResponseMetaHeader{Origin: r.MetaHeader}, + VerifyHeader: r.VerifyHeader, + } + resp.VerifyHeader, err = neofscrypto.SignResponseWithBuffer[*protoobject.GetResponse_Body](neofscryptotest.Signer(), resp, nil) + require.NoError(t, err) + + err := neofscrypto.VerifyResponseWithBuffer[*protoobject.GetResponse_Body](resp, nil) + require.EqualError(t, err, "invalid verification header at depth 1: "+tc.msg) + }) + }) + } + t.Run("resigned", func(t *testing.T) { + for _, tc := range []struct { + name, msg string + corrupt func(valid *protoobject.GetResponse) + }{ + {name: "redundant verification header", msg: "incorrect number of verification headers", + corrupt: func(valid *protoobject.GetResponse) { + valid.VerifyHeader = &protosession.ResponseVerificationHeader{Origin: valid.VerifyHeader} + }, + }, + {name: "lacking verification header", msg: "incorrect number of verification headers", + corrupt: func(valid *protoobject.GetResponse) { + valid.MetaHeader = &protosession.ResponseMetaHeader{Origin: valid.MetaHeader} + }, + }, + {name: "with body signature", msg: "invalid verification header at depth 0: body signature is set in non-origin verification header", + corrupt: func(valid *protoobject.GetResponse) { + valid.VerifyHeader.BodySignature = new(refs.Signature) + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + r := proto.Clone(getObjectSignedResponse).(*protoobject.GetResponse) + tc.corrupt(r) + err := neofscrypto.VerifyResponseWithBuffer[*protoobject.GetResponse_Body](r, nil) + require.EqualError(t, err, tc.msg) + }) + } + }) + }) +} diff --git a/go.mod b/go.mod index cca08ec0..c9f5dd12 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,6 @@ require ( github.com/mr-tron/base58 v1.2.0 github.com/nspcc-dev/hrw/v2 v2.0.2 github.com/nspcc-dev/neo-go v0.106.3 - github.com/nspcc-dev/neofs-api-go/v2 v2.14.1-0.20240827150555-5ce597aa14ea github.com/nspcc-dev/tzhash v1.8.2 github.com/stretchr/testify v1.9.0 github.com/testcontainers/testcontainers-go v0.33.0 diff --git a/go.sum b/go.sum index a5b797ac..b90b0628 100644 --- a/go.sum +++ b/go.sum @@ -85,8 +85,6 @@ github.com/nspcc-dev/hrw/v2 v2.0.2 h1:Vuc2Yu96MCv1YDUjErMuCt5tq+g/43/Y89u/XfyLkR github.com/nspcc-dev/hrw/v2 v2.0.2/go.mod h1:XRsG20axGJfr0Ytcau/UcZ/9NF54RmUIqmoYKuuliSo= github.com/nspcc-dev/neo-go v0.106.3 h1:HEyhgkjQY+HfBzotMJ12xx2VuOUphkngZ4kEkjvXDtE= github.com/nspcc-dev/neo-go v0.106.3/go.mod h1:3vEwJ2ld12N7HRGCaH/l/7EwopplC/+8XdIdPDNmD/M= -github.com/nspcc-dev/neofs-api-go/v2 v2.14.1-0.20240827150555-5ce597aa14ea h1:mK0EMGLvunXcFyq7fBURS/CsN4MH+4nlYiqn6pTwWAU= -github.com/nspcc-dev/neofs-api-go/v2 v2.14.1-0.20240827150555-5ce597aa14ea/go.mod h1:YzhD4EZmC9Z/PNyd7ysC7WXgIgURc9uCG1UWDeV027Y= github.com/nspcc-dev/rfc6979 v0.2.1 h1:8wWxkamHWFmO790GsewSoKUSJjVnL1fmdRpokU/RgRM= github.com/nspcc-dev/rfc6979 v0.2.1/go.mod h1:Tk7h5kyUWkhjyO3zUgFFhy1v2vQv3BvQEntakdtqrWc= github.com/nspcc-dev/tzhash v1.8.2 h1:ebRCbPoEuoqrhC6sSZmrT/jI3h1SzCWakxxV6gp5QAg=