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=