From c0a882404b71e7d8bf4b8005897baedd39423b54 Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Wed, 11 Dec 2024 13:52:21 +0300 Subject: [PATCH] *: Replace `github.com/nspcc-dev/neofs-api-go/v2` module Since 527c964535bfce38575fbea7f13af6caeb8324d8, proto-generated code is located in the current code library. Previously, the neofs-api-go module's code was used for protocol interactions. In essence, it is a super-layer on top of the `google.golang.org/protobuf` module with only one feature: stable marshalling (Protocol Buffers with ascending field order). Since it is now provided by local `proto/*` packages, there is no longer a need for a separate module. In addition to trimming the code base and refactoring, a bonus is the allocation of fewer intermediate Go objects, which will have a beneficial effect on runtime. Although most of the changes are refactorings, the following changes have been applied: 1. All exported elements that depended on neofs-api-go types are permanently removed. They could not be excluded via a temporary deprecation mark as this would cause a conflict with importing ''*.pb.go' files. Such elements were not recommended for use in advance, so most of the updated software will not suffer from breakages. 2. Request/response signing and verification functions have been added to `crypto` package A. They are used by the API client and will also be used by https://github.com/nspcc-dev/neofs-node. 3. `neofs-api-go` a bug with interpreting enums as unsigned numbers, while protobuf lib decodes them into `~int32`. In addition, during encoding/decoding, all unknown enum values were set to zero, which could lead to data loss. This commit fixes both issues. Separating the fix from the refactoring would have required adding artificial buggy code, it was decided not to generate it. 4. Test binaries/JSONs/signatures that followed the erroneous behavior of p.3 are fixed. 5. While replacement of `ReadFromV2` methods called `FromProtoMessage`, `ProtoMessage` method replacing `WriteToV2`, taking into account the proposal from #532 and existing use cases, now dynamically allocates the message structure. Closes #214. Refs #226. Refs #270. Closes #452. Closes #532. Closes #551 (it's almost impossible to do now). Fixes #606. Refs #652. Signed-off-by: Leonard Lyubich --- README.md | 2 +- accounting/decimal.go | 62 +- accounting/decimal_test.go | 23 +- accounting/test/decimal_test.go | 6 +- bearer/bearer.go | 118 ++-- bearer/bearer_test.go | 146 ++--- checksum/checksum.go | 82 +-- checksum/checksum_test.go | 50 +- checksum/test/generate_test.go | 6 +- client/accounting.go | 98 +-- client/accounting_test.go | 11 +- client/client.go | 14 +- client/client_test.go | 225 ++----- client/common.go | 285 +------- client/container.go | 619 ++++++++--------- client/container_test.go | 149 ++--- client/crypto_test.go | 31 +- client/errors.go | 4 + client/example_container_put_test.go | 13 +- client/example_test.go | 80 --- client/messages_test.go | 47 +- client/netmap.go | 234 ++++--- client/netmap_test.go | 30 +- client/object_delete.go | 84 ++- client/object_delete_test.go | 14 +- client/object_get.go | 316 +++++---- client/object_get_test.go | 46 +- client/object_hash.go | 119 ++-- client/object_hash_test.go | 14 +- client/object_put.go | 127 ++-- client/object_put_test.go | 14 +- client/object_replicate.go | 25 +- client/object_replicate_test.go | 11 +- client/object_search.go | 88 +-- client/object_search_test.go | 16 +- client/object_test.go | 38 +- client/reputation.go | 141 ++-- client/reputation_test.go | 19 +- client/response.go | 7 - client/session.go | 107 ++- client/session_container.go | 20 +- client/session_test.go | 11 +- client/sign.go | 390 ----------- client/sign_test.go | 243 ------- client/status/common.go | 186 +++--- client/status/common_test.go | 54 +- client/status/container.go | 77 +-- client/status/object.go | 245 +++---- client/status/object_test.go | 6 +- client/status/session.go | 77 +-- client/status/unrecognized.go | 35 +- client/status/v2.go | 178 ++--- client/status/v2_test.go | 127 ++-- container/container.go | 283 ++++---- container/container_internal_test.go | 42 +- container/container_test.go | 264 +++----- container/example_test.go | 8 +- container/id/id.go | 27 +- container/id/id_test.go | 19 +- container/id/test/id_test.go | 6 +- container/size.go | 69 +- container/size_test.go | 74 +-- crypto/crypto_test.go | 6 +- crypto/ecdsa/wallet_connect.go | 41 +- crypto/proto.go | 287 ++++++++ crypto/signature.go | 100 ++- crypto/signature_test.go | 32 +- crypto/signer.go | 2 +- crypto/test/tests.go | 2 +- crypto/test/tests_test.go | 6 +- eacl/common_test.go | 26 +- eacl/enums.go | 74 +-- eacl/enums_test.go | 189 ++---- eacl/filter.go | 68 +- eacl/filter_test.go | 58 -- eacl/record.go | 88 ++- eacl/record_test.go | 71 +- eacl/table.go | 129 ++-- eacl/table_test.go | 226 +++---- eacl/target.go | 53 +- eacl/target_test.go | 48 -- eacl/test/generate.go | 6 +- eacl/test/generate_test.go | 3 +- eacl/types_test.go | 8 +- go.mod | 1 - go.sum | 2 - internal/proto/encoding.go | 81 ++- netmap/context.go | 9 +- netmap/example_test.go | 8 +- netmap/filter.go | 73 ++- netmap/filter_test.go | 31 +- netmap/helper_test.go | 18 +- netmap/netmap.go | 59 +- netmap/netmap_test.go | 97 +-- netmap/network_info.go | 183 +++--- netmap/network_info_test.go | 146 ++--- netmap/node_info.go | 174 +++-- netmap/node_info_test.go | 136 ++-- netmap/policy.go | 538 ++++++++------- netmap/policy_internal_test.go | 35 +- netmap/policy_test.go | 129 ++-- netmap/selector.go | 34 +- netmap/selector_test.go | 40 +- object/attribute.go | 85 ++- object/attribute_test.go | 93 +-- object/fmt.go | 16 +- object/fmt_test.go | 20 +- object/id/address.go | 55 +- object/id/address_test.go | 79 +-- object/id/id.go | 51 +- object/id/id_test.go | 24 +- object/id/test/generate_test.go | 11 +- object/link.go | 101 ++- object/link_test.go | 6 +- object/lock.go | 72 +- object/object.go | 753 +++++++++------------ object/object_copy.go | 175 ++--- object/object_internal_test.go | 108 ++- object/object_test.go | 949 +++++++-------------------- object/range.go | 33 +- object/range_test.go | 23 - object/search.go | 144 ++-- object/search_test.go | 121 ++-- object/slicer/slicer_test.go | 18 +- object/splitinfo.go | 136 ++-- object/splitinfo_test.go | 122 ++-- object/test/generate.go | 10 +- object/test/generate_test.go | 2 +- object/tombstone.go | 137 ++-- object/tombstone_test.go | 102 ++- object/type.go | 20 +- object/type_test.go | 50 +- object/util_test.go | 14 +- pool/mock_test.go | 16 +- proto/reputation/encoding.go | 30 + proto/reputation/encoding_test.go | 15 + reputation/example_test.go | 20 - reputation/peer.go | 40 +- reputation/peer_test.go | 6 +- reputation/trust.go | 327 ++++----- reputation/trust_test.go | 74 +-- session/common.go | 113 ++-- session/common_internal_test.go | 23 +- session/common_test.go | 86 ++- session/container.go | 82 ++- session/container_internal_test.go | 10 +- session/container_test.go | 217 +++--- session/example_test.go | 8 +- session/object.go | 89 ++- session/object_internal_test.go | 10 +- session/object_test.go | 193 +++--- storagegroup/storagegroup.go | 97 ++- storagegroup/storagegroup_test.go | 132 ++-- storagegroup/test/generate_test.go | 6 +- user/example_test.go | 8 +- user/id.go | 26 +- user/id_test.go | 19 +- user/test/id_test.go | 6 +- version/version.go | 43 +- version/version_test.go | 15 +- 160 files changed, 5859 insertions(+), 8661 deletions(-) delete mode 100644 client/sign.go delete mode 100644 client/sign_test.go create mode 100644 crypto/proto.go delete mode 100644 reputation/example_test.go diff --git a/README.md b/README.md index cb8e1994..4213f0f2 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ # neofs-sdk-go Go implementation of NeoFS SDK. It contains high-level version-independent wrappers -for structures from [neofs-api-go](https://github.com/nspcc-dev/neofs-api-go) as well as +for structures from [proto](https://github.com/nspcc-dev/neofs-sdk-go/proto) packages as well as helper functions for simplifying node/dApp implementations. ## Repository structure diff --git a/accounting/decimal.go b/accounting/decimal.go index 9cea6618..639d521b 100644 --- a/accounting/decimal.go +++ b/accounting/decimal.go @@ -1,36 +1,40 @@ package accounting import ( - "github.com/nspcc-dev/neofs-api-go/v2/accounting" + neofsproto "github.com/nspcc-dev/neofs-sdk-go/internal/proto" + protoaccounting "github.com/nspcc-dev/neofs-sdk-go/proto/accounting" ) // Decimal represents decimal number for accounting operations. // -// Decimal is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/accounting.Decimal -// message. See ReadFromV2 / WriteToV2 methods. +// Decimal is mutually compatible with [protoaccounting.Decimal] message. See +// [Decimal.FromProtoMessage] / [Decimal.FromProtoMessage] methods. // // Instances can be created using built-in var declaration. -// -// Note that direct typecast is not safe and may result in loss of compatibility: -// -// _ = Decimal(accounting.Decimal{}) // not recommended -type Decimal accounting.Decimal +type Decimal struct { + val int64 + prec uint32 +} -// ReadFromV2 reads Decimal from the accounting.Decimal message. Checks if the -// message conforms to NeoFS API V2 protocol. +// FromProtoMessage validates m according to the NeoFS API protocol and restores +// d from it. // -// See also WriteToV2. -func (d *Decimal) ReadFromV2(m accounting.Decimal) error { - *d = Decimal(m) +// See also [Decimal.ProtoMessage]. +func (d *Decimal) FromProtoMessage(m *protoaccounting.Decimal) error { + d.val = m.Value + d.prec = m.Precision return nil } -// WriteToV2 writes Decimal to the accounting.Decimal message. -// The message must not be nil. +// ProtoMessage converts d into message to transmit using the NeoFS API +// protocol. // -// See also ReadFromV2. -func (d Decimal) WriteToV2(m *accounting.Decimal) { - *m = (accounting.Decimal)(d) +// See also [Decimal.FromProtoMessage]. +func (d Decimal) ProtoMessage() *protoaccounting.Decimal { + return &protoaccounting.Decimal{ + Value: d.val, + Precision: d.prec, + } } // Value returns value of the decimal number. @@ -39,14 +43,14 @@ func (d Decimal) WriteToV2(m *accounting.Decimal) { // // See also SetValue. func (d Decimal) Value() int64 { - return (*accounting.Decimal)(&d).GetValue() + return d.val } // SetValue sets value of the decimal number. // // See also Value. func (d *Decimal) SetValue(v int64) { - (*accounting.Decimal)(d).SetValue(v) + d.val = v } // Precision returns precision of the decimal number. @@ -55,14 +59,14 @@ func (d *Decimal) SetValue(v int64) { // // See also SetPrecision. func (d Decimal) Precision() uint32 { - return (*accounting.Decimal)(&d).GetPrecision() + return d.prec } // SetPrecision sets precision of the decimal number. // // See also Precision. func (d *Decimal) SetPrecision(p uint32) { - (*accounting.Decimal)(d).SetPrecision(p) + d.prec = p } // Marshal encodes Decimal into a binary format of the NeoFS API protocol @@ -70,10 +74,7 @@ func (d *Decimal) SetPrecision(p uint32) { // // See also Unmarshal. func (d Decimal) Marshal() []byte { - var m accounting.Decimal - d.WriteToV2(&m) - - return m.StableMarshal(nil) + return neofsproto.Marshal(d) } // Unmarshal decodes NeoFS API protocol binary format into the Decimal @@ -82,12 +83,5 @@ func (d Decimal) Marshal() []byte { // // See also Marshal. func (d *Decimal) Unmarshal(data []byte) error { - var m accounting.Decimal - - err := m.Unmarshal(data) - if err != nil { - return err - } - - return d.ReadFromV2(m) + return neofsproto.Unmarshal(data, d) } diff --git a/accounting/decimal_test.go b/accounting/decimal_test.go index a7a9a9aa..6961a395 100644 --- a/accounting/decimal_test.go +++ b/accounting/decimal_test.go @@ -4,8 +4,8 @@ import ( "math/rand/v2" "testing" - apiaccounting "github.com/nspcc-dev/neofs-api-go/v2/accounting" "github.com/nspcc-dev/neofs-sdk-go/accounting" + protoaccounting "github.com/nspcc-dev/neofs-sdk-go/proto/accounting" "github.com/stretchr/testify/require" ) @@ -48,23 +48,22 @@ func TestDecimal_SetPrecision(t *testing.T) { testDecimalField(t, accounting.Decimal.Precision, (*accounting.Decimal).SetPrecision) } -func TestDecimal_ReadFromV2(t *testing.T) { - var m apiaccounting.Decimal - m.SetValue(anyValidValue) - m.SetPrecision(anyValidPrecision) +func TestDecimal_FromProtoMessage(t *testing.T) { + var m protoaccounting.Decimal + m.Value = anyValidValue + m.Precision = anyValidPrecision var val accounting.Decimal - require.NoError(t, val.ReadFromV2(m)) + require.NoError(t, val.FromProtoMessage(&m)) require.EqualValues(t, anyValidValue, val.Value()) require.EqualValues(t, anyValidPrecision, val.Precision()) } -func TestDecimal_WriteToV2(t *testing.T) { +func TestDecimal_ProtoMessage(t *testing.T) { var val accounting.Decimal - var m apiaccounting.Decimal // zero - val.WriteToV2(&m) + m := val.ProtoMessage() require.Zero(t, m.GetValue()) require.Zero(t, m.GetPrecision()) @@ -72,9 +71,9 @@ func TestDecimal_WriteToV2(t *testing.T) { val.SetValue(anyValidValue) val.SetPrecision(anyValidPrecision) - val.WriteToV2(&m) - require.EqualValues(t, anyValidValue, val.Value()) - require.EqualValues(t, anyValidPrecision, val.Precision()) + m = val.ProtoMessage() + require.EqualValues(t, anyValidValue, m.GetValue()) + require.EqualValues(t, anyValidPrecision, m.GetPrecision()) } func TestToken_Marshal(t *testing.T) { diff --git a/accounting/test/decimal_test.go b/accounting/test/decimal_test.go index 8801f37a..a8f3dcda 100644 --- a/accounting/test/decimal_test.go +++ b/accounting/test/decimal_test.go @@ -3,7 +3,6 @@ package accountingtest_test import ( "testing" - apiaccounting "github.com/nspcc-dev/neofs-api-go/v2/accounting" "github.com/nspcc-dev/neofs-sdk-go/accounting" accountingtest "github.com/nspcc-dev/neofs-sdk-go/accounting/test" "github.com/stretchr/testify/require" @@ -13,10 +12,9 @@ func TestDecimal(t *testing.T) { d := accountingtest.Decimal() require.NotEqual(t, d, accountingtest.Decimal()) - var m apiaccounting.Decimal - d.WriteToV2(&m) + m := d.ProtoMessage() var d2 accounting.Decimal - require.NoError(t, d2.ReadFromV2(m)) + require.NoError(t, d2.FromProtoMessage(m)) require.Equal(t, d, d2) require.NoError(t, new(accounting.Decimal).Unmarshal(d.Marshal())) diff --git a/bearer/bearer.go b/bearer/bearer.go index 92ef1f5c..7e0edc88 100644 --- a/bearer/bearer.go +++ b/bearer/bearer.go @@ -5,18 +5,18 @@ import ( "fmt" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" - "github.com/nspcc-dev/neofs-api-go/v2/acl" - "github.com/nspcc-dev/neofs-api-go/v2/refs" 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" + protoacl "github.com/nspcc-dev/neofs-sdk-go/proto/acl" "github.com/nspcc-dev/neofs-sdk-go/user" ) // Token represents bearer token for object service operations. // -// Token is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/acl.BearerToken -// message. See ReadFromV2 / WriteToV2 methods. +// Token is mutually compatible with [protoacl.BearerToken] message. See +// [Token.FromProtoMessage] / [Token.ProtoMessage] methods. // // Instances can be created using built-in var declaration. type Token struct { @@ -35,26 +35,26 @@ type Token struct { // reads Token from the acl.BearerToken message. If checkFieldPresence is set, // returns an error on absence of any protocol-required field. -func (b *Token) readFromV2(m acl.BearerToken, checkFieldPresence bool) error { +func (b *Token) fromProtoMessage(m *protoacl.BearerToken, checkFieldPresence bool) error { var err error - body := m.GetBody() + body := m.Body if checkFieldPresence && body == nil { return errors.New("missing token body") } - eaclTable := body.GetEACL() + eaclTable := body.GetEaclTable() if b.eaclTableSet = eaclTable != nil; b.eaclTableSet { - if err = b.eaclTable.ReadFromV2(*eaclTable); err != nil { + if err = b.eaclTable.FromProtoMessage(eaclTable); err != nil { return fmt.Errorf("invalid eACL: %w", err) } } else if checkFieldPresence { return errors.New("missing eACL table") } - targetUser := body.GetOwnerID() + targetUser := body.GetOwnerId() if targetUser != nil { - err = b.targetUser.ReadFromV2(*targetUser) + err = b.targetUser.FromProtoMessage(targetUser) if err != nil { return fmt.Errorf("invalid target user: %w", err) } @@ -64,7 +64,7 @@ func (b *Token) readFromV2(m acl.BearerToken, checkFieldPresence bool) error { issuer := body.GetIssuer() if issuer != nil { - err = b.issuer.ReadFromV2(*issuer) + err = b.issuer.FromProtoMessage(issuer) if err != nil { return fmt.Errorf("invalid issuer: %w", err) } @@ -77,9 +77,8 @@ func (b *Token) readFromV2(m acl.BearerToken, checkFieldPresence bool) error { return errors.New("missing token lifetime") } - sig := m.GetSignature() - if b.sigSet = sig != nil; sig != nil { - if err = b.sig.ReadFromV2(*sig); err != nil { + if b.sigSet = m.Signature != nil; b.sigSet { + if err = b.sig.FromProtoMessage(m.Signature); err != nil { return fmt.Errorf("invalid body signature: %w", err) } } else if checkFieldPresence { @@ -93,70 +92,57 @@ func (b *Token) readFromV2(m acl.BearerToken, checkFieldPresence bool) error { return nil } -// ReadFromV2 reads Token from the acl.BearerToken message. +// FromProtoMessage validates m according to the NeoFS API protocol and restores +// b from it. // -// See also WriteToV2. -func (b *Token) ReadFromV2(m acl.BearerToken) error { - return b.readFromV2(m, true) +// See also [Token.ProtoMessage]. +func (b *Token) FromProtoMessage(m *protoacl.BearerToken) error { + return b.fromProtoMessage(m, true) } -func (b Token) fillBody() *acl.BearerTokenBody { +func (b Token) fillBody() *protoacl.BearerToken_Body { lifetimeSet := b.iat != 0 || b.nbf != 0 || b.exp != 0 if !b.eaclTableSet && b.targetUser.IsZero() && !lifetimeSet && b.issuer.IsZero() { return nil } - var body acl.BearerTokenBody + var body protoacl.BearerToken_Body if b.eaclTableSet { - body.SetEACL(b.eaclTable.ToV2()) + body.EaclTable = b.eaclTable.ProtoMessage() } if !b.targetUser.IsZero() { - var targetUser refs.OwnerID - b.targetUser.WriteToV2(&targetUser) - - body.SetOwnerID(&targetUser) + body.OwnerId = b.targetUser.ProtoMessage() } if !b.issuer.IsZero() { - var issuer refs.OwnerID - b.issuer.WriteToV2(&issuer) - - body.SetIssuer(&issuer) + body.Issuer = b.issuer.ProtoMessage() } if lifetimeSet { - var lifetime acl.TokenLifetime - lifetime.SetIat(b.iat) - lifetime.SetNbf(b.nbf) - lifetime.SetExp(b.exp) - - body.SetLifetime(&lifetime) + body.Lifetime = &protoacl.BearerToken_Body_TokenLifetime{Exp: b.exp, Nbf: b.nbf, Iat: b.iat} } return &body } func (b Token) signedData() []byte { - return b.fillBody().StableMarshal(nil) + return neofsproto.MarshalMessage(b.fillBody()) } -// WriteToV2 writes Token to the acl.BearerToken message. -// The message must not be nil. +// ProtoMessage converts sg into message to transmit using the NeoFS API +// protocol. // -// See also ReadFromV2. -func (b Token) WriteToV2(m *acl.BearerToken) { - m.SetBody(b.fillBody()) - - var sig *refs.Signature - +// See also [Token.FromProtoMessage]. +func (b Token) ProtoMessage() *protoacl.BearerToken { + m := &protoacl.BearerToken{ + Body: b.fillBody(), + } if b.sigSet { - sig = new(refs.Signature) - b.sig.WriteToV2(sig) + m.Signature = b.sig.ProtoMessage() } - - m.SetSignature(sig) + return m } // SetExp sets "exp" (expiration time) claim which identifies the @@ -314,15 +300,13 @@ func (b *Token) SignedData() []byte { // UnmarshalSignedData is a reverse op to [Token.SignedData]. func (b *Token) UnmarshalSignedData(data []byte) error { - var body acl.BearerTokenBody - err := body.Unmarshal(data) + var body protoacl.BearerToken_Body + err := neofsproto.UnmarshalMessage(data, &body) if err != nil { return fmt.Errorf("decode body: %w", err) } - var tok acl.BearerToken - tok.SetBody(&body) - return b.readFromV2(tok, false) + return b.fromProtoMessage(&protoacl.BearerToken{Body: &body}, false) } // AttachSignature attaches given signature to the Token. Use [Token.SignedData] @@ -354,10 +338,7 @@ func (b Token) VerifySignature() bool { // // See also Unmarshal. func (b Token) Marshal() []byte { - var m acl.BearerToken - b.WriteToV2(&m) - - return m.StableMarshal(nil) + return neofsproto.Marshal(b) } // Unmarshal decodes NeoFS API protocol binary data into the Token @@ -366,14 +347,7 @@ func (b Token) Marshal() []byte { // // See also Marshal. func (b *Token) Unmarshal(data []byte) error { - var m acl.BearerToken - - err := m.Unmarshal(data) - if err != nil { - return err - } - - return b.readFromV2(m, false) + return neofsproto.UnmarshalOptional(data, b, (*Token).fromProtoMessage) } // MarshalJSON encodes Token into a JSON format of the NeoFS API protocol @@ -381,10 +355,7 @@ func (b *Token) Unmarshal(data []byte) error { // // See also UnmarshalJSON. func (b Token) MarshalJSON() ([]byte, error) { - var m acl.BearerToken - b.WriteToV2(&m) - - return m.MarshalJSON() + return neofsproto.MarshalJSON(b) } // UnmarshalJSON decodes NeoFS API protocol JSON data into the Token @@ -392,14 +363,7 @@ func (b Token) MarshalJSON() ([]byte, error) { // // See also MarshalJSON. func (b *Token) UnmarshalJSON(data []byte) error { - var m acl.BearerToken - - err := m.UnmarshalJSON(data) - if err != nil { - return err - } - - return b.readFromV2(m, false) + return neofsproto.UnmarshalJSONOptional(data, b, (*Token).fromProtoMessage) } // SigningKeyBytes returns issuer's public key in a binary format of diff --git a/bearer/bearer_test.go b/bearer/bearer_test.go index c2502264..de8b4ecf 100644 --- a/bearer/bearer_test.go +++ b/bearer/bearer_test.go @@ -9,13 +9,10 @@ import ( "encoding/base64" "encoding/hex" "encoding/json" - "math" "math/big" "testing" "github.com/nspcc-dev/neo-go/pkg/io" - "github.com/nspcc-dev/neofs-api-go/v2/acl" - "github.com/nspcc-dev/neofs-api-go/v2/refs" "github.com/nspcc-dev/neofs-sdk-go/bearer" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test" @@ -23,6 +20,8 @@ import ( neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa" "github.com/nspcc-dev/neofs-sdk-go/eacl" eacltest "github.com/nspcc-dev/neofs-sdk-go/eacl/test" + protoacl "github.com/nspcc-dev/neofs-sdk-go/proto/acl" + "github.com/nspcc-dev/neofs-sdk-go/proto/refs" "github.com/nspcc-dev/neofs-sdk-go/user" usertest "github.com/nspcc-dev/neofs-sdk-go/user/test" "github.com/stretchr/testify/require" @@ -153,7 +152,8 @@ var validJSONBearerToken = ` ], "targets": [ { - "role": 690857412 + "role": 690857412, + "keys": [] } ] }, @@ -176,9 +176,11 @@ var validJSONBearerToken = ` ], "targets": [ { - "role": 690857412 + "role": 690857412, + "keys": [] }, { + "role": "ROLE_UNSPECIFIED", "keys": [ "NcBrPK1/A0Xs7yVrraePoRRxhceWi7aruA==", "NQ5A3Rf5uoVdi2KDo26GBwakxoh8IMpYaw==", @@ -512,7 +514,6 @@ func TestToken_UnmarshalSignedData(t *testing.T) { err := val.UnmarshalSignedData(validSignedBearerToken) require.NoError(t, err) val.AttachSignature(anyValidSignature) - t.Skip("https://github.com/nspcc-dev/neofs-sdk-go/issues/606") require.Equal(t, validBearerToken, val) } @@ -526,33 +527,19 @@ func TestToken_AttachSignature(t *testing.T) { require.Equal(t, anyValidSignature, sig) } -func TestToken_ReadFromV2(t *testing.T) { - var lt acl.TokenLifetime - lt.SetExp(anyValidExp) - lt.SetIat(anyValidIat) - lt.SetNbf(anyValidNbf) - - var ms refs.OwnerID - ms.SetValue(anyValidSubject[:]) - - var mi refs.OwnerID - mi.SetValue(anyValidIssuer[:]) - - var mb acl.BearerTokenBody - mb.SetEACL(anyValidEACL.ToV2()) - mb.SetOwnerID(&ms) - mb.SetIssuer(&mi) - mb.SetLifetime(<) - - var msig refs.Signature - anyValidSignature.WriteToV2(&msig) - - var m acl.BearerToken - m.SetBody(&mb) - m.SetSignature(&msig) +func TestToken_FromProtoMessage(t *testing.T) { + m := &protoacl.BearerToken{ + Body: &protoacl.BearerToken_Body{ + EaclTable: anyValidEACL.ProtoMessage(), + OwnerId: &refs.OwnerID{Value: anyValidSubject[:]}, + Lifetime: &protoacl.BearerToken_Body_TokenLifetime{Exp: anyValidExp, Nbf: anyValidNbf, Iat: anyValidIat}, + Issuer: &refs.OwnerID{Value: anyValidIssuer[:]}, + }, + Signature: anyValidSignature.ProtoMessage(), + } var val bearer.Token - require.NoError(t, val.ReadFromV2(m)) + require.NoError(t, val.FromProtoMessage(m)) require.Equal(t, anyValidEACL, val.EACLTable()) require.Equal(t, anyValidIssuer, val.Issuer()) @@ -567,93 +554,79 @@ func TestToken_ReadFromV2(t *testing.T) { require.Equal(t, anyValidSignature, sig) // reset optional fields - mb.SetIssuer(nil) - mb.SetOwnerID(nil) + m.Body.Issuer = nil + m.Body.OwnerId = nil val2 := val - require.NoError(t, val2.ReadFromV2(m)) + require.NoError(t, val2.FromProtoMessage(m)) require.Zero(t, val2.Issuer()) require.True(t, val2.AssertUser(usertest.ID())) t.Run("invalid", func(t *testing.T) { for _, tc := range []struct { name, err string - corrupt func(*acl.BearerToken) + corrupt func(*protoacl.BearerToken) }{ {name: "body/missing", err: "missing token body", - corrupt: func(m *acl.BearerToken) { m.SetBody(nil) }}, + corrupt: func(m *protoacl.BearerToken) { m.Body = nil }}, {name: "body/eacl/missing", err: "missing eACL table", - corrupt: func(m *acl.BearerToken) { m.GetBody().SetEACL(nil) }}, + corrupt: func(m *protoacl.BearerToken) { m.Body.EaclTable = nil }}, {name: "body/eacl/invalid container/nil value", err: "invalid eACL: invalid container ID: invalid length 0", - corrupt: func(m *acl.BearerToken) { m.GetBody().GetEACL().SetContainerID(new(refs.ContainerID)) }}, - {name: "body/eacl/invalid container/empty value", err: "invalid eACL: invalid container ID: invalid length 0", corrupt: func(m *acl.BearerToken) { - var mc refs.ContainerID - mc.SetValue([]byte{}) - m.GetBody().GetEACL().SetContainerID(&mc) + corrupt: func(m *protoacl.BearerToken) { m.Body.EaclTable.ContainerId = new(refs.ContainerID) }}, + {name: "body/eacl/invalid container/empty value", err: "invalid eACL: invalid container ID: invalid length 0", corrupt: func(m *protoacl.BearerToken) { + m.Body.EaclTable.ContainerId.Value = []byte{} }}, - {name: "body/eacl/invalid container/undersized value", err: "invalid eACL: invalid container ID: invalid length 31", corrupt: func(m *acl.BearerToken) { - var mc refs.ContainerID - mc.SetValue(make([]byte, 31)) - m.GetBody().GetEACL().SetContainerID(&mc) + {name: "body/eacl/invalid container/undersized value", err: "invalid eACL: invalid container ID: invalid length 31", corrupt: func(m *protoacl.BearerToken) { + m.Body.EaclTable.ContainerId.Value = make([]byte, 31) }}, - {name: "body/eacl/invalid container/oversized value", err: "invalid eACL: invalid container ID: invalid length 33", corrupt: func(m *acl.BearerToken) { - var mc refs.ContainerID - mc.SetValue(make([]byte, 33)) - m.GetBody().GetEACL().SetContainerID(&mc) + {name: "body/eacl/invalid container/oversized value", err: "invalid eACL: invalid container ID: invalid length 33", corrupt: func(m *protoacl.BearerToken) { + m.Body.EaclTable.ContainerId.Value = make([]byte, 33) }}, {name: "body/subject/value/nil", err: "invalid target user: invalid length 0, expected 25", - corrupt: func(m *acl.BearerToken) { m.GetBody().GetOwnerID().SetValue(nil) }}, + corrupt: func(m *protoacl.BearerToken) { m.Body.OwnerId.Value = nil }}, {name: "body/subject/value/empty", err: "invalid target user: invalid length 0, expected 25", - corrupt: func(m *acl.BearerToken) { m.GetBody().GetOwnerID().SetValue([]byte{}) }}, + corrupt: func(m *protoacl.BearerToken) { m.Body.OwnerId.Value = []byte{} }}, {name: "body/subject/value/undersize", err: "invalid target user: invalid length 24, expected 25", - corrupt: func(m *acl.BearerToken) { m.GetBody().GetOwnerID().SetValue(make([]byte, 24)) }}, + corrupt: func(m *protoacl.BearerToken) { m.Body.OwnerId.Value = make([]byte, 24) }}, {name: "body/subject/value/oversize", err: "invalid target user: invalid length 26, expected 25", - corrupt: func(m *acl.BearerToken) { m.GetBody().GetOwnerID().SetValue(make([]byte, 26)) }}, + corrupt: func(m *protoacl.BearerToken) { m.Body.OwnerId.Value = make([]byte, 26) }}, {name: "body/subject/value/wrong prefix", err: "invalid target user: invalid prefix byte 0x42, expected 0x35", - corrupt: func(m *acl.BearerToken) { m.GetBody().GetOwnerID().GetValue()[0] = 0x42 }}, + corrupt: func(m *protoacl.BearerToken) { m.Body.OwnerId.Value[0] = 0x42 }}, {name: "body/subject/value/checksum mismatch", err: "invalid target user: checksum mismatch", - corrupt: func(m *acl.BearerToken) { - v := m.GetBody().GetOwnerID().GetValue() - v[len(v)-1]++ - }}, + corrupt: func(m *protoacl.BearerToken) { m.Body.OwnerId.Value[24]++ }}, {name: "body/lifetime/missing", err: "missing token lifetime", - corrupt: func(m *acl.BearerToken) { m.GetBody().SetLifetime(nil) }}, + corrupt: func(m *protoacl.BearerToken) { m.Body.Lifetime = nil }}, {name: "body/issuer/value/nil", err: "invalid issuer: invalid length 0, expected 25", - corrupt: func(m *acl.BearerToken) { m.GetBody().GetIssuer().SetValue(nil) }}, + corrupt: func(m *protoacl.BearerToken) { m.Body.Issuer.Value = (nil) }}, {name: "body/issuer/value/empty", err: "invalid issuer: invalid length 0, expected 25", - corrupt: func(m *acl.BearerToken) { m.GetBody().GetIssuer().SetValue([]byte{}) }}, + corrupt: func(m *protoacl.BearerToken) { m.Body.Issuer.Value = ([]byte{}) }}, {name: "body/issuer/value/undersize", err: "invalid issuer: invalid length 24, expected 25", - corrupt: func(m *acl.BearerToken) { m.GetBody().GetIssuer().SetValue(make([]byte, 24)) }}, + corrupt: func(m *protoacl.BearerToken) { m.Body.Issuer.Value = (make([]byte, 24)) }}, {name: "body/issuer/value/oversize", err: "invalid issuer: invalid length 26, expected 25", - corrupt: func(m *acl.BearerToken) { m.GetBody().GetIssuer().SetValue(make([]byte, 26)) }}, + corrupt: func(m *protoacl.BearerToken) { m.Body.Issuer.Value = (make([]byte, 26)) }}, {name: "body/issuer/value/wrong prefix", err: "invalid issuer: invalid prefix byte 0x42, expected 0x35", - corrupt: func(m *acl.BearerToken) { m.GetBody().GetIssuer().GetValue()[0] = 0x42 }}, + corrupt: func(m *protoacl.BearerToken) { m.Body.Issuer.Value[0] = 0x42 }}, {name: "body/issuer/value/checksum mismatch", err: "invalid issuer: checksum mismatch", - corrupt: func(m *acl.BearerToken) { - v := m.GetBody().GetIssuer().GetValue() - v[len(v)-1]++ - }}, + corrupt: func(m *protoacl.BearerToken) { m.Body.Issuer.Value[24]++ }}, {name: "signature/missing", err: "missing body signature", - corrupt: func(m *acl.BearerToken) { m.SetSignature(nil) }}, - {name: "signature/invalid scheme", err: "invalid body signature: scheme 2147483648 overflows int32", - corrupt: func(m *acl.BearerToken) { m.GetSignature().SetScheme(math.MaxInt32 + 1) }}, + corrupt: func(m *protoacl.BearerToken) { m.Signature = nil }}, + {name: "signature/scheme/negative", err: "invalid body signature: negative scheme -1", + corrupt: func(m *protoacl.BearerToken) { m.Signature.Scheme = -1 }}, } { t.Run(tc.name, func(t *testing.T) { st := val - var m acl.BearerToken - st.WriteToV2(&m) - tc.corrupt(&m) - require.EqualError(t, new(bearer.Token).ReadFromV2(m), tc.err) + m := st.ProtoMessage() + tc.corrupt(m) + require.EqualError(t, new(bearer.Token).FromProtoMessage(m), tc.err) }) } }) } -func TestToken_WriteToV2(t *testing.T) { +func TestToken_ProtoMessage(t *testing.T) { var val bearer.Token - var m acl.BearerToken // zero - val.WriteToV2(&m) + m := val.ProtoMessage() require.Zero(t, m.GetBody()) require.Zero(t, m.GetSignature()) @@ -666,11 +639,11 @@ func TestToken_WriteToV2(t *testing.T) { val.SetNbf(anyValidNbf) val.AttachSignature(anyValidSignature) - val.WriteToV2(&m) + m = val.ProtoMessage() body := m.GetBody() require.NotNil(t, body) - require.Equal(t, anyValidSubject[:], body.GetOwnerID().GetValue()) + require.Equal(t, anyValidSubject[:], body.GetOwnerId().GetValue()) require.Equal(t, anyValidIssuer[:], body.GetIssuer().GetValue()) lt := body.GetLifetime() @@ -684,11 +657,11 @@ func TestToken_WriteToV2(t *testing.T) { require.Equal(t, anyValidIssuerPublicKeyBytes, sig.GetKey()) require.Equal(t, anyValidSignatureBytes, sig.GetSign()) - e := m.GetBody().GetEACL() + e := m.GetBody().GetEaclTable() require.NotNil(t, e) require.EqualValues(t, 2, e.GetVersion().GetMajor()) require.EqualValues(t, 16, e.GetVersion().GetMinor()) - require.Equal(t, anyValidContainerID[:], e.GetContainerID().GetValue()) + require.Equal(t, anyValidContainerID[:], e.GetContainerId().GetValue()) rs := e.GetRecords() require.Len(t, rs, 2) @@ -786,7 +759,7 @@ func TestToken_Unmarshal(t *testing.T) { {name: "body/issuer/value/checksum mismatch", err: "invalid issuer: checksum mismatch", b: []byte{10, 29, 34, 27, 10, 25, 53, 147, 14, 186, 66, 195, 247, 51, 14, 249, 145, 102, 233, 115, 142, 143, 145, 26, 229, 252, 61, 36, 160, 242, 244}}, - {name: "signature/invalid scheme", err: "invalid body signature: scheme 2147483648 overflows int32", + {name: "signature/invalid scheme", err: "invalid body signature: negative scheme -2147483648", b: []byte{18, 11, 24, 128, 128, 128, 128, 248, 255, 255, 255, 255, 1}}, } { t.Run(tc.name, func(t *testing.T) { @@ -812,7 +785,6 @@ func TestToken_Unmarshal(t *testing.T) { // filled err := val.Unmarshal(validBinBearerToken) require.NoError(t, err) - t.Skip("https://github.com/nspcc-dev/neofs-sdk-go/issues/606") require.Equal(t, validBearerToken, val) } @@ -820,7 +792,6 @@ func TestToken_MarshalJSON(t *testing.T) { //nolint:staticcheck b, err := json.MarshalIndent(validBearerToken, "", " ") require.NoError(t, err) - t.Skip("https://github.com/nspcc-dev/neofs-sdk-go/issues/606") require.JSONEq(t, validJSONBearerToken, string(b)) } @@ -858,7 +829,7 @@ func TestToken_UnmarshalJSON(t *testing.T) { j: `{"body":{"issuer":{"value":"QjMFpm8dFGXApRynOaBSUCnLFP4eisMRXA=="}}}`}, {name: "body/issuer/value/checksum mismatch", err: "invalid issuer: checksum mismatch", j: `{"body":{"issuer":{"value":"NTMFpm8dFGXApRynOaBSUCnLFP4eisMRXQ=="}}}`}, - {name: "signature/invalid scheme", err: "invalid body signature: scheme 2147483648 overflows int32", + {name: "signature/invalid scheme", err: "invalid body signature: negative scheme -2147483648", j: `{"signature":{"scheme":-2147483648}}`}, } { t.Run(tc.name, func(t *testing.T) { @@ -882,7 +853,6 @@ func TestToken_UnmarshalJSON(t *testing.T) { // filled require.NoError(t, val.UnmarshalJSON([]byte(validJSONBearerToken))) - t.Skip("https://github.com/nspcc-dev/neofs-sdk-go/issues/606") require.Equal(t, validBearerToken, val) } diff --git a/checksum/checksum.go b/checksum/checksum.go index cbc32ce2..d6876b57 100644 --- a/checksum/checksum.go +++ b/checksum/checksum.go @@ -7,25 +7,24 @@ import ( "fmt" "hash" - "github.com/nspcc-dev/neofs-api-go/v2/refs" + "github.com/nspcc-dev/neofs-sdk-go/proto/refs" "github.com/nspcc-dev/tzhash/tz" ) // Checksum represents checksum of some digital data. // -// Checksum is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/refs.Checksum -// message. See ReadFromV2 / WriteToV2 methods. +// Checksum is mutually compatible with [refs.Checksum] message. See +// [Checksum.FromProtoMessage] / [Checksum.ProtoMessage] methods. // // Instances must be created using one of the constructors. -// -// Note that direct typecast is not safe and may result in loss of compatibility: -// -// _ = Checksum(refs.Checksum{}) // not recommended -type Checksum refs.Checksum +type Checksum struct { + typ Type + val []byte +} // Type represents the enumeration // of checksum types. -type Type uint32 +type Type int32 const ( Unknown Type = iota // Deprecated: use 0 instead. @@ -42,19 +41,27 @@ func typeToProto(t Type) refs.ChecksumType { default: return refs.ChecksumType(t) case SHA256: - return refs.SHA256 + return refs.ChecksumType_SHA256 case TillichZemor: - return refs.TillichZemor + return refs.ChecksumType_TZ + } +} + +func typeFromProto(t refs.ChecksumType) Type { + switch t { + default: + return Type(t) + case refs.ChecksumType_SHA256: + return SHA256 + case refs.ChecksumType_TZ: + return TillichZemor } } // New constructs new Checksum instance. It is the caller's responsibility to // ensure that the hash matches the type. func New(typ Type, hsh []byte) Checksum { - var res refs.Checksum - res.SetType(typeToProto(typ)) - res.SetSum(hsh) - return Checksum(res) + return Checksum{typ: typ, val: hsh} } // NewSHA256 constructs new Checksum from SHA-256 hash. @@ -87,26 +94,33 @@ func NewFromData(typ Type, data []byte) (Checksum, error) { } } -// ReadFromV2 reads Checksum from the refs.Checksum message. Checks if the -// message conforms to NeoFS API V2 protocol. +// FromProtoMessage validates m according to the NeoFS API protocol and restores +// c from it. // -// See also WriteToV2. -func (c *Checksum) ReadFromV2(m refs.Checksum) error { - if len(m.GetSum()) == 0 { +// See also [Checksum.ProtoMessage]. +func (c *Checksum) FromProtoMessage(m *refs.Checksum) error { + if m.Type < 0 { + return fmt.Errorf("negative type %d", m.Type) + } + if len(m.Sum) == 0 { return errors.New("missing value") } - *c = Checksum(m) + c.typ = typeFromProto(m.Type) + c.val = m.Sum return nil } -// WriteToV2 writes Checksum to the refs.Checksum message. -// The message must not be nil. +// ProtoMessage converts c into message to transmit using the NeoFS API +// protocol. // -// See also ReadFromV2. -func (c Checksum) WriteToV2(m *refs.Checksum) { - *m = (refs.Checksum)(c) +// See also [Checksum.FromProtoMessage]. +func (c Checksum) ProtoMessage() *refs.Checksum { + return &refs.Checksum{ + Type: typeToProto(c.typ), + Sum: c.val, + } } // Type returns checksum type. @@ -115,15 +129,7 @@ func (c Checksum) WriteToV2(m *refs.Checksum) { // // See also [NewTillichZemor], [NewSHA256]. func (c Checksum) Type() Type { - v2 := (refs.Checksum)(c) - switch typ := v2.GetType(); typ { - case refs.SHA256: - return SHA256 - case refs.TillichZemor: - return TillichZemor - default: - return Type(typ) - } + return c.typ } // Value returns checksum bytes. Return value @@ -136,8 +142,7 @@ func (c Checksum) Type() Type { // // See also [NewTillichZemor], [NewSHA256]. func (c Checksum) Value() []byte { - v2 := (refs.Checksum)(c) - return v2.GetSum() + return c.val } // SetSHA256 sets checksum to SHA256 hash. @@ -174,8 +179,7 @@ func (c *Checksum) SetTillichZemor(v [tz.Size]byte) { *c = NewTillichZemor(v) } // String is designed to be human-readable, and its format MAY differ between // SDK versions. func (c Checksum) String() string { - v2 := (refs.Checksum)(c) - return fmt.Sprintf("%s:%s", c.Type(), hex.EncodeToString(v2.GetSum())) + return fmt.Sprintf("%s:%s", c.Type(), hex.EncodeToString(c.Value())) } // String implements fmt.Stringer. diff --git a/checksum/checksum_test.go b/checksum/checksum_test.go index 830952cc..4d7d78d4 100644 --- a/checksum/checksum_test.go +++ b/checksum/checksum_test.go @@ -7,8 +7,8 @@ import ( "math/rand" "testing" - "github.com/nspcc-dev/neofs-api-go/v2/refs" "github.com/nspcc-dev/neofs-sdk-go/checksum" + "github.com/nspcc-dev/neofs-sdk-go/proto/refs" "github.com/nspcc-dev/tzhash/tz" "github.com/stretchr/testify/require" ) @@ -48,23 +48,22 @@ func TestChecksumDecodingFailures(t *testing.T) { name, err string corrupt func(*refs.Checksum) }{ - {name: "value/nil", err: "missing value", corrupt: func(cs *refs.Checksum) { cs.SetSum(nil) }}, - {name: "value/empty", err: "missing value", corrupt: func(cs *refs.Checksum) { cs.SetSum([]byte{}) }}, + {name: "value/nil", err: "missing value", corrupt: func(cs *refs.Checksum) { cs.Sum = nil }}, + {name: "value/empty", err: "missing value", corrupt: func(cs *refs.Checksum) { cs.Sum = []byte{} }}, } { t.Run(tc.name, func(t *testing.T) { var src, dst checksum.Checksum - var m refs.Checksum - src.WriteToV2(&m) - tc.corrupt(&m) - require.ErrorContains(t, dst.ReadFromV2(m), tc.err) + m := src.ProtoMessage() + tc.corrupt(m) + require.ErrorContains(t, dst.FromProtoMessage(m), tc.err) }) } }) } func TestNew(t *testing.T) { - typ := checksum.Type(rand.Uint32()) + typ := checksum.Type(rand.Int31()) val := make([]byte, 128) //nolint:staticcheck rand.Read(val) @@ -72,7 +71,7 @@ func TestNew(t *testing.T) { require.Equal(t, typ, cs.Type()) require.Equal(t, val, cs.Value()) - otherTyp := checksum.Type(rand.Uint32()) + otherTyp := checksum.Type(rand.Int31()) otherVal := make([]byte, 128) //nolint:staticcheck rand.Read(otherVal) @@ -84,19 +83,18 @@ func TestNew(t *testing.T) { t.Run("api", func(t *testing.T) { src := checksum.New(typ, val) var dst checksum.Checksum - var m refs.Checksum - src.WriteToV2(&m) + m := src.ProtoMessage() switch actual := m.GetType(); typ { default: require.EqualValues(t, typ, actual) case checksum.TillichZemor: - require.Equal(t, refs.TillichZemor, actual) + require.Equal(t, refs.ChecksumType_TZ, actual) case checksum.SHA256: - require.Equal(t, refs.SHA256, actual) + require.Equal(t, refs.ChecksumType_SHA256, actual) } require.Equal(t, val, m.GetSum()) - require.NoError(t, dst.ReadFromV2(m)) + require.NoError(t, dst.FromProtoMessage(m)) require.Equal(t, typ, dst.Type()) require.Equal(t, val, dst.Value()) }) @@ -131,13 +129,12 @@ func testTypeConstructor[T [sha256.Size]byte | [tz.Size]byte]( t.Run("api", func(t *testing.T) { src := cons(val) var dst checksum.Checksum - var m refs.Checksum - src.WriteToV2(&m) + m := src.ProtoMessage() require.Equal(t, typAPI, m.GetType()) require.Len(t, m.GetSum(), len(val)) require.Equal(t, val, T(m.GetSum())) - require.NoError(t, dst.ReadFromV2(m)) + require.NoError(t, dst.FromProtoMessage(m)) require.Equal(t, typ, dst.Type()) require.Len(t, dst.Value(), len(val)) require.Equal(t, val, T(dst.Value())) @@ -146,11 +143,11 @@ func testTypeConstructor[T [sha256.Size]byte | [tz.Size]byte]( } func TestNewSHA256(t *testing.T) { - testTypeConstructor(t, checksum.SHA256, refs.SHA256, checksum.NewSHA256) + testTypeConstructor(t, checksum.SHA256, refs.ChecksumType_SHA256, checksum.NewSHA256) } func TestNewTZ(t *testing.T) { - testTypeConstructor(t, checksum.TillichZemor, refs.TillichZemor, checksum.NewTillichZemor) + testTypeConstructor(t, checksum.TillichZemor, refs.ChecksumType_TZ, checksum.NewTillichZemor) } func TestNewFromHash(t *testing.T) { @@ -158,7 +155,7 @@ func TestNewFromHash(t *testing.T) { h.Write([]byte("Hello, world!")) hb := []byte{32, 94, 4, 138} - typ := checksum.Type(rand.Uint32()) + typ := checksum.Type(rand.Int31()) cs := checksum.NewFromHash(typ, h) require.Equal(t, typ, cs.Type()) require.Equal(t, hb, cs.Value()) @@ -167,19 +164,18 @@ func TestNewFromHash(t *testing.T) { t.Run("api", func(t *testing.T) { src := checksum.NewFromHash(typ, h) var dst checksum.Checksum - var m refs.Checksum - src.WriteToV2(&m) + m := src.ProtoMessage() switch actual := m.GetType(); typ { default: require.EqualValues(t, typ, actual) case checksum.TillichZemor: - require.Equal(t, refs.TillichZemor, actual) + require.Equal(t, refs.ChecksumType_TZ, actual) case checksum.SHA256: - require.Equal(t, refs.SHA256, actual) + require.Equal(t, refs.ChecksumType_SHA256, actual) } require.Equal(t, hb, m.GetSum()) - require.NoError(t, dst.ReadFromV2(m)) + require.NoError(t, dst.FromProtoMessage(m)) require.Equal(t, typ, dst.Type()) require.Equal(t, hb, dst.Value()) }) @@ -228,9 +224,9 @@ func TestNewFromData(t *testing.T) { } func TestChecksum_SetSHA256(t *testing.T) { - testTypeConstructor(t, checksum.SHA256, refs.SHA256, func(b [sha256.Size]byte) (c checksum.Checksum) { c.SetSHA256(b); return }) + testTypeConstructor(t, checksum.SHA256, refs.ChecksumType_SHA256, func(b [sha256.Size]byte) (c checksum.Checksum) { c.SetSHA256(b); return }) } func TestChecksum_SetTillichZemor(t *testing.T) { - testTypeConstructor(t, checksum.TillichZemor, refs.TillichZemor, func(b [tz.Size]byte) (c checksum.Checksum) { c.SetTillichZemor(b); return }) + testTypeConstructor(t, checksum.TillichZemor, refs.ChecksumType_TZ, func(b [tz.Size]byte) (c checksum.Checksum) { c.SetTillichZemor(b); return }) } diff --git a/checksum/test/generate_test.go b/checksum/test/generate_test.go index de6dc215..16fb9d52 100644 --- a/checksum/test/generate_test.go +++ b/checksum/test/generate_test.go @@ -3,7 +3,6 @@ package checksumtest_test import ( "testing" - "github.com/nspcc-dev/neofs-api-go/v2/refs" "github.com/nspcc-dev/neofs-sdk-go/checksum" checksumtest "github.com/nspcc-dev/neofs-sdk-go/checksum/test" "github.com/stretchr/testify/require" @@ -13,9 +12,8 @@ func TestChecksum(t *testing.T) { cs := checksumtest.Checksum() require.NotEqual(t, cs, checksumtest.Checksum()) - var m refs.Checksum - cs.WriteToV2(&m) + m := cs.ProtoMessage() var cs2 checksum.Checksum - require.NoError(t, cs2.ReadFromV2(m)) + require.NoError(t, cs2.FromProtoMessage(m)) require.Equal(t, cs, cs2) } diff --git a/client/accounting.go b/client/accounting.go index 8be2ec4e..be3f8e59 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,67 @@ 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, + // // XHeaders: nil, + }, + } + 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/container/container.go b/container/container.go index 0a023bf6..bea1f8dd 100644 --- a/container/container.go +++ b/container/container.go @@ -1,25 +1,32 @@ package container import ( - "bytes" "errors" "fmt" + "slices" "strconv" "strings" "time" "github.com/google/uuid" - "github.com/nspcc-dev/neofs-api-go/v2/container" - v2netmap "github.com/nspcc-dev/neofs-api-go/v2/netmap" - "github.com/nspcc-dev/neofs-api-go/v2/refs" "github.com/nspcc-dev/neofs-sdk-go/container/acl" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" + neofsproto "github.com/nspcc-dev/neofs-sdk-go/internal/proto" "github.com/nspcc-dev/neofs-sdk-go/netmap" + protocontainer "github.com/nspcc-dev/neofs-sdk-go/proto/container" "github.com/nspcc-dev/neofs-sdk-go/user" "github.com/nspcc-dev/neofs-sdk-go/version" ) +// various attributes. +const ( + sysAttrPrefix = "__NEOFS__" + sysAttrDisableHomohash = sysAttrPrefix + "DISABLE_HOMOMORPHIC_HASHING" + sysAttrDomainName = sysAttrPrefix + "NAME" + sysAttrDomainZone = sysAttrPrefix + "ZONE" +) + // Container represents descriptor of the NeoFS container. Container logically // stores NeoFS objects. Container is one of the basic and at the same time // necessary data storage units in the NeoFS. Container includes data about the @@ -35,10 +42,15 @@ import ( // Instances for existing containers can be initialized using decoding methods // (e.g Unmarshal). // -// Container is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/container.Container -// message. See ReadFromV2 / WriteToV2 methods. +// Container is mutually compatible with [protocontainer.Container] +// message. See [Container.FromProtoMessage] / [Container.ProtoMessage] methods. type Container struct { - v2 container.Container + version *version.Version + owner user.ID + nonce uuid.UUID + basicACL acl.Basic + attrs [][2]string + policy *netmap.PlacementPolicy } const ( @@ -50,97 +62,93 @@ const ( func (x Container) CopyTo(dst *Container) { dst.SetBasicACL(x.BasicACL()) - if owner := x.v2.GetOwnerID(); owner != nil { - var newOwner refs.OwnerID - newOwner.SetValue(bytes.Clone(owner.GetValue())) + dst.owner = x.owner - dst.v2.SetOwnerID(&newOwner) + if x.version != nil { + dst.version = new(version.Version) + *dst.version = *x.version } else { - dst.v2.SetOwnerID(nil) - } - - if x.v2.GetVersion() != nil { - ver := x.v2.GetVersion() - newVer := *ver - dst.v2.SetVersion(&newVer) - } else { - dst.v2.SetVersion(nil) + dst.version = nil } // do we need to set the different nonce? - dst.v2.SetNonce(bytes.Clone(x.v2.GetNonce())) - - if len(x.v2.GetAttributes()) > 0 { - dst.v2.SetAttributes([]container.Attribute{}) + dst.nonce = x.nonce - attributeIterator := func(key, val string) { - dst.SetAttribute(key, val) - } - - x.IterateAttributes(attributeIterator) + if len(x.attrs) > 0 { + dst.attrs = slices.Clone(x.attrs) } - if x.v2.GetPlacementPolicy() != nil { - var ppCopy netmap.PlacementPolicy - x.PlacementPolicy().CopyTo(&ppCopy) - dst.SetPlacementPolicy(ppCopy) + if x.policy != nil { + dst.policy = new(netmap.PlacementPolicy) + x.policy.CopyTo(dst.policy) } else { - x.v2.SetPlacementPolicy(nil) + dst.policy = nil } } // reads Container from the container.Container message. If checkFieldPresence is set, // returns an error on absence of any protocol-required field. -func (x *Container) readFromV2(m container.Container, checkFieldPresence bool) error { +func (x *Container) fromProtoMessage(m *protocontainer.Container, checkFieldPresence bool) error { var err error - ownerV2 := m.GetOwnerID() - if ownerV2 != nil { - var owner user.ID - - err = owner.ReadFromV2(*ownerV2) + if m.OwnerId != nil { + err = x.owner.FromProtoMessage(m.OwnerId) if err != nil { return fmt.Errorf("invalid owner: %w", err) } } else if checkFieldPresence { return errors.New("missing owner") + } else { + x.owner = user.ID{} } - binNonce := m.GetNonce() - if len(binNonce) > 0 { - var nonce uuid.UUID - - err = nonce.UnmarshalBinary(binNonce) + if len(m.Nonce) > 0 { + err = x.nonce.UnmarshalBinary(m.Nonce) if err != nil { return fmt.Errorf("invalid nonce: %w", err) - } else if ver := nonce.Version(); ver != 4 { + } else if ver := x.nonce.Version(); ver != 4 { return fmt.Errorf("invalid nonce: wrong UUID version %d, expected 4", ver) } } else if checkFieldPresence { return errors.New("missing nonce") + } else { + x.nonce = uuid.Nil } - ver := m.GetVersion() - if checkFieldPresence && ver == nil { + if m.Version != nil { + if x.version == nil { + x.version = new(version.Version) + } + if err = x.version.FromProtoMessage(m.Version); err != nil { + return fmt.Errorf("invalid version: %w", err) + } + } else if checkFieldPresence { return errors.New("missing version") + } else { + x.version = nil } - policyV2 := m.GetPlacementPolicy() - if policyV2 != nil { - var policy netmap.PlacementPolicy - - err = policy.ReadFromV2(*policyV2) + if m.PlacementPolicy != nil { + if x.policy == nil { + x.policy = new(netmap.PlacementPolicy) + } + err = x.policy.FromProtoMessage(m.PlacementPolicy) if err != nil { return fmt.Errorf("invalid placement policy: %w", err) } } else if checkFieldPresence { return errors.New("missing placement policy") + } else { + x.policy = nil } attrs := m.GetAttributes() mAttr := make(map[string]struct{}, len(attrs)) var key, val string var was bool + if attrs != nil { + x.attrs = make([][2]string, len(attrs)) + } for i := range attrs { key = attrs[i].GetKey() @@ -168,27 +176,49 @@ func (x *Container) readFromV2(m container.Container, checkFieldPresence bool) e } mAttr[key] = struct{}{} + x.attrs[i] = [2]string{key, val} } - x.v2 = m + x.basicACL.FromBits(m.BasicAcl) return nil } -// ReadFromV2 reads Container from the container.Container message. Checks if the -// message conforms to NeoFS API V2 protocol. +// FromProtoMessage validates m according to the NeoFS API protocol and restores +// x from it. // -// See also WriteToV2. -func (x *Container) ReadFromV2(m container.Container) error { - return x.readFromV2(m, true) +// See also [Container.ProtoMessage]. +func (x *Container) FromProtoMessage(m *protocontainer.Container) error { + return x.fromProtoMessage(m, true) } -// WriteToV2 writes Container into the container.Container message. -// The message MUST NOT be nil. +// ProtoMessage converts x into message to transmit using the NeoFS API +// protocol. // -// See also ReadFromV2. -func (x Container) WriteToV2(m *container.Container) { - *m = x.v2 +// See also [Container.FromProtoMessage]. +func (x Container) ProtoMessage() *protocontainer.Container { + m := &protocontainer.Container{ + BasicAcl: x.basicACL.Bits(), + } + if x.version != nil { + m.Version = x.version.ProtoMessage() + } + if !x.owner.IsZero() { + m.OwnerId = x.owner.ProtoMessage() + } + if x.nonce != uuid.Nil { + m.Nonce = x.nonce[:] + } + if x.policy != nil { + m.PlacementPolicy = x.policy.ProtoMessage() + } + if len(x.attrs) > 0 { + m.Attributes = make([]*protocontainer.Container_Attribute, len(x.attrs)) + for i := range x.attrs { + m.Attributes[i] = &protocontainer.Container_Attribute{Key: x.attrs[i][0], Value: x.attrs[i][1]} + } + } + return m } // Marshal encodes Container into a binary format of the NeoFS API protocol @@ -196,7 +226,7 @@ func (x Container) WriteToV2(m *container.Container) { // // See also Unmarshal. func (x Container) Marshal() []byte { - return x.v2.StableMarshal(nil) + return neofsproto.Marshal(x) } // SignedData returns actual payload to sign. @@ -212,14 +242,7 @@ func (x Container) SignedData() []byte { // // See also Marshal. func (x *Container) Unmarshal(data []byte) error { - var m container.Container - - err := m.Unmarshal(data) - if err != nil { - return err - } - - return x.readFromV2(m, false) + return neofsproto.UnmarshalOptional(data, x, (*Container).fromProtoMessage) } // MarshalJSON encodes Container into a JSON format of the NeoFS API protocol @@ -227,7 +250,7 @@ func (x *Container) Unmarshal(data []byte) error { // // See also UnmarshalJSON. func (x Container) MarshalJSON() ([]byte, error) { - return x.v2.MarshalJSON() + return neofsproto.MarshalMessageJSON(x.ProtoMessage()) } // UnmarshalJSON decodes NeoFS API protocol JSON format into the Container @@ -235,11 +258,7 @@ func (x Container) MarshalJSON() ([]byte, error) { // // See also MarshalJSON. func (x *Container) UnmarshalJSON(data []byte) error { - var m container.Container - if err := m.UnmarshalJSON(data); err != nil { - return err - } - return x.readFromV2(m, false) + return neofsproto.UnmarshalJSONOptional(data, x, (*Container).fromProtoMessage) } // Init initializes all internal data of the Container required by NeoFS API @@ -247,17 +266,13 @@ func (x *Container) UnmarshalJSON(data []byte) error { // be called multiple times. Init SHOULD NOT be called if the Container instance // is used for decoding only. func (x *Container) Init() { - var ver refs.Version - version.Current().WriteToV2(&ver) - - x.v2.SetVersion(&ver) - - nonce, err := uuid.New().MarshalBinary() - if err != nil { - panic(fmt.Sprintf("unexpected error from UUID.MarshalBinary: %v", err)) + ver := version.Current() + x.version = &ver + for { + if x.nonce = uuid.New(); x.nonce != uuid.Nil { + break + } } - - x.v2.SetNonce(nonce) } // SetOwner specifies the owner of the Container. Each Container has exactly @@ -266,26 +281,15 @@ func (x *Container) Init() { // // See also Owner. func (x *Container) SetOwner(owner user.ID) { - var m refs.OwnerID - owner.WriteToV2(&m) - - x.v2.SetOwnerID(&m) + x.owner = owner } // Owner returns owner of the Container set using SetOwner. // // Zero Container has no owner which is incorrect according to NeoFS API // protocol. -func (x Container) Owner() (res user.ID) { - m := x.v2.GetOwnerID() - if m != nil { - err := res.ReadFromV2(*m) - if err != nil { - panic(fmt.Sprintf("unexpected error from user.ID.ReadFromV2: %v", err)) - } - } - - return +func (x Container) Owner() user.ID { + return x.owner } // SetBasicACL specifies basic part of the Container ACL. Basic ACL is used @@ -293,16 +297,15 @@ func (x Container) Owner() (res user.ID) { // // See also BasicACL. func (x *Container) SetBasicACL(basicACL acl.Basic) { - x.v2.SetBasicACL(basicACL.Bits()) + x.basicACL = basicACL } // BasicACL returns basic ACL set using SetBasicACL. // // Zero Container has zero basic ACL which structurally correct but doesn't // make sense since it denies any access to any party. -func (x Container) BasicACL() (res acl.Basic) { - res.FromBits(x.v2.GetBasicACL()) - return +func (x Container) BasicACL() acl.Basic { + return x.basicACL } // SetPlacementPolicy sets placement policy for the objects within the Container. @@ -310,26 +313,18 @@ func (x Container) BasicACL() (res acl.Basic) { // // See also PlacementPolicy. func (x *Container) SetPlacementPolicy(policy netmap.PlacementPolicy) { - var m v2netmap.PlacementPolicy - policy.WriteToV2(&m) - - x.v2.SetPlacementPolicy(&m) + x.policy = &policy } // PlacementPolicy returns placement policy set using SetPlacementPolicy. // // Zero Container has no placement policy which is incorrect according to // NeoFS API protocol. -func (x Container) PlacementPolicy() (res netmap.PlacementPolicy) { - m := x.v2.GetPlacementPolicy() - if m != nil { - err := res.ReadFromV2(*m) - if err != nil { - panic(fmt.Sprintf("unexpected error from PlacementPolicy.ReadFromV2: %v", err)) - } +func (x Container) PlacementPolicy() netmap.PlacementPolicy { + if x.policy != nil { + return *x.policy } - - return + return netmap.PlacementPolicy{} } // SetAttribute sets Container attribute value by key. Both key and value @@ -350,21 +345,14 @@ func (x *Container) SetAttribute(key, value string) { panic("empty attribute value") } - attrs := x.v2.GetAttributes() - ln := len(attrs) - - for i := range ln { - if attrs[i].GetKey() == key { - attrs[i].SetValue(value) + for i := range x.attrs { + if x.attrs[i][0] == key { + x.attrs[i][1] = value return } } - attrs = append(attrs, container.Attribute{}) - attrs[ln].SetKey(key) - attrs[ln].SetValue(value) - - x.v2.SetAttributes(attrs) + x.attrs = append(x.attrs, [2]string{key, value}) } // Attribute reads value of the Container attribute by key. Empty result means @@ -372,10 +360,9 @@ func (x *Container) SetAttribute(key, value string) { // // See also SetAttribute, IterateAttributes. func (x Container) Attribute(key string) string { - attrs := x.v2.GetAttributes() - for i := range attrs { - if attrs[i].GetKey() == key { - return attrs[i].GetValue() + for i := range x.attrs { + if x.attrs[i][0] == key { + return x.attrs[i][1] } } @@ -387,9 +374,8 @@ func (x Container) Attribute(key string) string { // // See also [Container.SetAttribute], [Container.Attribute], [Container.IterateUserAttributes]. func (x Container) IterateAttributes(f func(key, val string)) { - attrs := x.v2.GetAttributes() - for i := range attrs { - f(attrs[i].GetKey(), attrs[i].GetValue()) + for i := range x.attrs { + f(x.attrs[i][0], x.attrs[i][1]) } } @@ -399,7 +385,7 @@ func (x Container) IterateAttributes(f func(key, val string)) { // See also [Container.SetAttribute], [Container.Attribute], [Container.IterateAttributes]. func (x Container) IterateUserAttributes(f func(key, val string)) { x.IterateAttributes(func(key, val string) { - if !strings.HasPrefix(key, container.SysAttributePrefix) { + if !strings.HasPrefix(key, sysAttrPrefix) { f(key, val) } }) @@ -452,14 +438,14 @@ const attributeHomoHashEnabled = "true" // // See also IsHomomorphicHashingDisabled. func (x *Container) DisableHomomorphicHashing() { - x.SetAttribute(container.SysAttributeHomomorphicHashing, attributeHomoHashEnabled) + x.SetAttribute(sysAttrDisableHomohash, attributeHomoHashEnabled) } // IsHomomorphicHashingDisabled checks if DisableHomomorphicHashing was called. // // Zero Container has enabled hashing. func (x Container) IsHomomorphicHashingDisabled() bool { - return x.Attribute(container.SysAttributeHomomorphicHashing) == attributeHomoHashEnabled + return x.Attribute(sysAttrDisableHomohash) == attributeHomoHashEnabled } // Domain represents information about container domain registered in the NNS @@ -498,17 +484,17 @@ func (x Domain) Zone() string { // WriteDomain writes Domain into the Container. Name MUST NOT be empty. func (x *Container) WriteDomain(domain Domain) { - x.SetAttribute(container.SysAttributeName, domain.Name()) - x.SetAttribute(container.SysAttributeZone, domain.Zone()) + x.SetAttribute(sysAttrDomainName, domain.Name()) + x.SetAttribute(sysAttrDomainZone, domain.Zone()) } // ReadDomain reads Domain from the Container. Returns value with empty name // if domain is not specified. func (x Container) ReadDomain() (res Domain) { - name := x.Attribute(container.SysAttributeName) + name := x.Attribute(sysAttrDomainName) if name != "" { res.SetName(name) - res.SetZone(x.Attribute(container.SysAttributeZone)) + res.SetZone(x.Attribute(sysAttrDomainZone)) } return @@ -560,9 +546,8 @@ func (x Container) AssertID(id cid.ID) bool { // Version returns the NeoFS API version this container was created with. func (x Container) Version() version.Version { - var v version.Version - if m := x.v2.GetVersion(); m != nil { - _ = v.ReadFromV2(*m) + if x.version != nil { + return *x.version } - return v + return version.Version{} } diff --git a/container/container_internal_test.go b/container/container_internal_test.go index 0404a7c1..b85db235 100644 --- a/container/container_internal_test.go +++ b/container/container_internal_test.go @@ -66,7 +66,7 @@ func TestContainer_CopyTo(t *testing.T) { require.True(t, container.Owner() == dst.Owner()) newOwner := usertest.ID() - dst.v2.GetOwnerID().SetValue(newOwner[:]) + copy(dst.owner[:], newOwner[:]) require.False(t, container.Owner() == dst.Owner()) }) @@ -87,48 +87,42 @@ func TestContainer_CopyTo(t *testing.T) { var dst Container container.CopyTo(&dst) - require.True(t, bytes.Equal(container.v2.GetNonce(), dst.v2.GetNonce())) - dst.v2.SetNonce([]byte{1, 2, 3}) - require.False(t, bytes.Equal(container.v2.GetNonce(), dst.v2.GetNonce())) + require.True(t, container.nonce == dst.nonce) + copy(dst.nonce[:], []byte{1, 2, 3}) + require.False(t, container.nonce == dst.nonce) }) t.Run("overwrite nonce", func(t *testing.T) { var local Container - require.Empty(t, local.v2.GetNonce()) + require.Zero(t, local.nonce) var dst Container - dst.v2.SetNonce([]byte{1, 2, 3}) - require.NotEmpty(t, dst.v2.GetNonce()) + copy(dst.nonce[:], []byte{1, 2, 3}) + require.NotZero(t, dst.nonce) local.CopyTo(&dst) require.True(t, bytes.Equal(local.Marshal(), dst.Marshal())) - require.Empty(t, local.v2.GetNonce()) - require.Empty(t, dst.v2.GetNonce()) + require.Zero(t, local.nonce) + require.Zero(t, dst.nonce) - require.True(t, bytes.Equal(local.v2.GetNonce(), dst.v2.GetNonce())) - dst.v2.SetNonce([]byte{1, 2, 3}) - require.False(t, bytes.Equal(local.v2.GetNonce(), dst.v2.GetNonce())) + require.True(t, local.nonce == dst.nonce) + copy(dst.nonce[:], []byte{1, 2, 3}) + require.False(t, local.nonce == dst.nonce) }) t.Run("change version", func(t *testing.T) { var dst Container container.CopyTo(&dst) - oldVer := container.v2.GetVersion() - require.NotNil(t, oldVer) + require.Equal(t, container.Version(), dst.Version()) - newVer := dst.v2.GetVersion() - require.NotNil(t, newVer) + cp := container.Version() + dst.version.SetMajor(rand.Uint32()) + dst.version.SetMinor(rand.Uint32()) - require.Equal(t, oldVer.GetMajor(), newVer.GetMajor()) - require.Equal(t, oldVer.GetMinor(), newVer.GetMinor()) - - newVer.SetMajor(rand.Uint32()) - newVer.SetMinor(rand.Uint32()) - - require.NotEqual(t, oldVer.GetMajor(), newVer.GetMajor()) - require.NotEqual(t, oldVer.GetMinor(), newVer.GetMinor()) + require.NotEqual(t, cp, dst.Version()) + require.Equal(t, cp, container.Version()) }) t.Run("change attributes", func(t *testing.T) { diff --git a/container/container_test.go b/container/container_test.go index cff927fd..d7011887 100644 --- a/container/container_test.go +++ b/container/container_test.go @@ -12,9 +12,6 @@ import ( "time" "github.com/google/uuid" - v2container "github.com/nspcc-dev/neofs-api-go/v2/container" - apinetmap "github.com/nspcc-dev/neofs-api-go/v2/netmap" - "github.com/nspcc-dev/neofs-api-go/v2/refs" "github.com/nspcc-dev/neofs-sdk-go/container" "github.com/nspcc-dev/neofs-sdk-go/container/acl" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" @@ -23,6 +20,9 @@ import ( 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/netmap" + protocontainer "github.com/nspcc-dev/neofs-sdk-go/proto/container" + protonetmap "github.com/nspcc-dev/neofs-sdk-go/proto/netmap" + "github.com/nspcc-dev/neofs-sdk-go/proto/refs" "github.com/nspcc-dev/neofs-sdk-go/user" usertest "github.com/nspcc-dev/neofs-sdk-go/user/test" "github.com/nspcc-dev/neofs-sdk-go/version" @@ -107,10 +107,9 @@ func init() { validContainer.DisableHomomorphicHashing() // init sets random nonce, we need a fixed one. There is no setter for it - var m v2container.Container - validContainer.WriteToV2(&m) - m.SetNonce(anyValidNonce[:]) - if err := validContainer.ReadFromV2(m); err != nil { + m := validContainer.ProtoMessage() + m.Nonce = anyValidNonce[:] + if err := validContainer.FromProtoMessage(m); err != nil { panic(fmt.Errorf("unexpected encode-decode failure: %w", err)) } } @@ -301,8 +300,7 @@ var validJSONContainer = ` // type does not provide getter, this is a helper. func extractNonce(t testing.TB, c container.Container) uuid.UUID { - var m v2container.Container - c.WriteToV2(&m) + m := c.ProtoMessage() var nonce uuid.UUID if b := m.GetNonce(); len(b) > 0 { require.NoError(t, nonce.UnmarshalBinary(b)) @@ -310,103 +308,58 @@ func extractNonce(t testing.TB, c container.Container) uuid.UUID { return nonce } -func protoUserFromBytes(b []byte) *refs.OwnerID { - var m refs.OwnerID - m.SetValue(b) - return &m -} - -func setContainerAttributes(m *v2container.Container, els ...string) { +func setContainerAttributes(m *protocontainer.Container, els ...string) { if len(els)%2 != 0 { panic("must be even") } - mas := make([]v2container.Attribute, len(els)/2) + m.Attributes = make([]*protocontainer.Container_Attribute, len(els)/2) for i := range len(els) / 2 { - mas[i].SetKey(els[2*i]) - mas[i].SetValue(els[2*i+1]) + m.Attributes[i] = &protocontainer.Container_Attribute{Key: els[2*i], Value: els[2*i+1]} } - m.SetAttributes(mas) } -func TestContainer_ReadFromV2(t *testing.T) { - var mv refs.Version - mv.SetMajor(2526956385) - mv.SetMinor(95168785) - - var mas []v2container.Attribute - newAttr := func(k, v string) v2container.Attribute { - var a v2container.Attribute - a.SetKey(k) - a.SetValue(v) - return a - } - addAttr := func(k, v string) { mas = append(mas, newAttr(k, v)) } - addAttr("k1", "v1") - addAttr("k2", "v2") - addAttr("Name", anyValidName) - addAttr("Timestamp", "1727681164") - addAttr("__NEOFS__NAME", anyValidDomainName) - addAttr("__NEOFS__ZONE", anyValidDomainZone) - addAttr("__NEOFS__DISABLE_HOMOMORPHIC_HASHING", "true") - - mrs := make([]apinetmap.Replica, 2) - mrs[0].SetSelector("selector_0") - mrs[0].SetCount(2583748530) - mrs[1].SetSelector("selector_1") - mrs[1].SetCount(358755354) - - mss := make([]apinetmap.Selector, 2) - mss[0].SetName("selector_0") - mss[0].SetCount(1814781076) - mss[0].SetClause(apinetmap.Same) - mss[0].SetFilter("filter_0") - mss[0].SetAttribute("attribute_0") - mss[1].SetName("selector_1") - mss[1].SetCount(1814781076) - mss[1].SetClause(apinetmap.Distinct) - mss[1].SetFilter("filter_1") - mss[1].SetAttribute("attribute_1") - - msubs := make([]apinetmap.Filter, 0, 2) - addSub := func(name, key string, op apinetmap.Operation, val string) { - var f apinetmap.Filter - f.SetName(name) - f.SetKey(key) - f.SetOp(op) - f.SetValue(val) - msubs = append(msubs, f) +func TestContainer_FromProtoMessage(t *testing.T) { + m := &protocontainer.Container{ + Version: &refs.Version{Major: 2526956385, Minor: 95168785}, + OwnerId: &refs.OwnerID{Value: anyValidOwner[:]}, + Nonce: anyValidNonce[:], + BasicAcl: anyValidBasicACL.Bits(), + Attributes: []*protocontainer.Container_Attribute{ + {Key: "k1", Value: "v1"}, + {Key: "k2", Value: "v2"}, + {Key: "Name", Value: anyValidName}, + {Key: "Timestamp", Value: "1727681164"}, + {Key: "__NEOFS__NAME", Value: anyValidDomainName}, + {Key: "__NEOFS__ZONE", Value: anyValidDomainZone}, + {Key: "__NEOFS__DISABLE_HOMOMORPHIC_HASHING", Value: "true"}, + }, + PlacementPolicy: &protonetmap.PlacementPolicy{ + Replicas: []*protonetmap.Replica{ + {Count: 2583748530, Selector: "selector_0"}, + {Count: 358755354, Selector: "selector_1"}, + }, + ContainerBackupFactor: anyValidBackupFactor, + Selectors: []*protonetmap.Selector{ + {Name: "selector_0", Count: 1814781076, Clause: protonetmap.Clause_SAME, Attribute: "attribute_0", Filter: "filter_0"}, + {Name: "selector_1", Count: 1814781076, Clause: protonetmap.Clause_DISTINCT, Attribute: "attribute_1", Filter: "filter_1"}, + }, + Filters: []*protonetmap.Filter{ + {Name: "filter_0", Op: protonetmap.Operation_AND, Filters: []*protonetmap.Filter{ + {Name: "filter_0_0", Key: "key_0_0", Op: protonetmap.Operation_EQ, Value: "val_0_0"}, + {Name: "filter_0_1", Key: "key_0_1", Op: protonetmap.Operation_NE, Value: "val_0_1"}, + }}, + {Name: "filter_1", Op: protonetmap.Operation_OR, Filters: []*protonetmap.Filter{ + {Name: "filter_1_0", Key: "key_1_0", Op: protonetmap.Operation_GT, Value: "1889407708985023116"}, + {Name: "filter_1_1", Key: "key_1_1", Op: protonetmap.Operation_GE, Value: "1429243097315344888"}, + {Name: "filter_1_2", Key: "key_1_2", Op: protonetmap.Operation_LT, Value: "3722656060317482335"}, + {Name: "filter_1_3", Key: "key_1_3", Op: protonetmap.Operation_LE, Value: "1950504987705284805"}, + }}, + }, + }, } - addSub("filter_0_0", "key_0_0", apinetmap.EQ, "val_0_0") - addSub("filter_0_1", "key_0_1", apinetmap.NE, "val_0_1") - mfs := make([]apinetmap.Filter, 2) - mfs[0].SetName("filter_0") - mfs[0].SetOp(apinetmap.AND) - mfs[0].SetFilters(msubs) - msubs = make([]apinetmap.Filter, 0, 4) - addSub("filter_1_0", "key_1_0", apinetmap.GT, "1889407708985023116") - addSub("filter_1_1", "key_1_1", apinetmap.GE, "1429243097315344888") - addSub("filter_1_2", "key_1_2", apinetmap.LT, "3722656060317482335") - addSub("filter_1_3", "key_1_3", apinetmap.LE, "1950504987705284805") - mfs[1].SetName("filter_1") - mfs[1].SetOp(apinetmap.OR) - mfs[1].SetFilters(msubs) - - var mp apinetmap.PlacementPolicy - mp.SetContainerBackupFactor(anyValidBackupFactor) - mp.SetReplicas(mrs) - mp.SetSelectors(mss) - mp.SetFilters(mfs) - - var m v2container.Container - m.SetVersion(&mv) - m.SetOwnerID(protoUserFromBytes(anyValidOwner[:])) - m.SetNonce(anyValidNonce[:]) - m.SetBasicACL(anyValidBasicACL.Bits()) - m.SetPlacementPolicy(&mp) - m.SetAttributes(mas) var val container.Container - require.NoError(t, val.ReadFromV2(m)) + require.NoError(t, val.FromProtoMessage(m)) ver := val.Version() require.EqualValues(t, 2526956385, ver.Major()) require.EqualValues(t, 95168785, ver.Minor()) @@ -484,108 +437,95 @@ func TestContainer_ReadFromV2(t *testing.T) { require.Empty(t, subs[3].SubFilters()) // reset optional fields - m.SetBasicACL(0) - m.SetAttributes([]v2container.Attribute{newAttr("__NEOFS__DISABLE_HOMOMORPHIC_HASHING", "anything not true")}) + m.BasicAcl = 0 + m.Attributes = []*protocontainer.Container_Attribute{{Key: "__NEOFS__DISABLE_HOMOMORPHIC_HASHING", Value: "anything not true"}} val2 := val - require.NoError(t, val2.ReadFromV2(m)) + require.NoError(t, val2.FromProtoMessage(m)) require.Zero(t, val2.BasicACL()) require.False(t, val2.IsHomomorphicHashingDisabled()) t.Run("invalid", func(t *testing.T) { for _, tc := range []struct { name, err string - corrupt func(*v2container.Container) + corrupt func(*protocontainer.Container) }{ {name: "version/missing", err: "missing version", - corrupt: func(m *v2container.Container) { m.SetVersion(nil) }}, + corrupt: func(m *protocontainer.Container) { m.Version = nil }}, {name: "owner/missing", err: "missing owner", - corrupt: func(m *v2container.Container) { m.SetOwnerID(nil) }}, + corrupt: func(m *protocontainer.Container) { m.OwnerId = nil }}, {name: "owner/value/nil", err: "invalid owner: invalid length 0, expected 25", - corrupt: func(m *v2container.Container) { m.SetOwnerID(protoUserFromBytes(nil)) }}, + corrupt: func(m *protocontainer.Container) { m.OwnerId.Value = nil }}, {name: "owner/value/empty", err: "invalid owner: invalid length 0, expected 25", - corrupt: func(m *v2container.Container) { m.SetOwnerID(protoUserFromBytes([]byte{})) }}, + corrupt: func(m *protocontainer.Container) { m.OwnerId.Value = []byte{} }}, {name: "owner/value/undersize", err: "invalid owner: invalid length 24, expected 25", - corrupt: func(m *v2container.Container) { m.SetOwnerID(protoUserFromBytes(make([]byte, 24))) }}, + corrupt: func(m *protocontainer.Container) { m.OwnerId.Value = make([]byte, 24) }}, {name: "owner/value/oversize", err: "invalid owner: invalid length 26, expected 25", - corrupt: func(m *v2container.Container) { m.SetOwnerID(protoUserFromBytes(make([]byte, 26))) }}, + corrupt: func(m *protocontainer.Container) { m.OwnerId.Value = make([]byte, 26) }}, {name: "owner/value/wrong prefix", err: "invalid owner: invalid prefix byte 0x42, expected 0x35", - corrupt: func(m *v2container.Container) { - b := bytes.Clone(anyValidOwner[:]) - b[0] = 0x42 - m.SetOwnerID(protoUserFromBytes(b)) + corrupt: func(m *protocontainer.Container) { + m.OwnerId.Value = bytes.Clone(anyValidOwner[:]) + m.OwnerId.Value[0] = 0x42 }}, {name: "owner/value/checksum mismatch", err: "invalid owner: checksum mismatch", - corrupt: func(m *v2container.Container) { - b := bytes.Clone(anyValidOwner[:]) - b[len(b)-1]++ - m.SetOwnerID(protoUserFromBytes(b)) + corrupt: func(m *protocontainer.Container) { + m.OwnerId.Value = bytes.Clone(anyValidOwner[:]) + m.OwnerId.Value[24]++ }}, {name: "nonce/nil", err: "missing nonce", - corrupt: func(m *v2container.Container) { m.SetNonce(nil) }}, + corrupt: func(m *protocontainer.Container) { m.Nonce = nil }}, {name: "nonce/empty", err: "missing nonce", - corrupt: func(m *v2container.Container) { m.SetNonce([]byte{}) }}, + corrupt: func(m *protocontainer.Container) { m.Nonce = []byte{} }}, {name: "nonce/undersize", err: "invalid nonce: invalid UUID (got 15 bytes)", - corrupt: func(m *v2container.Container) { m.SetNonce(anyValidNonce[:15]) }}, + corrupt: func(m *protocontainer.Container) { m.Nonce = anyValidNonce[:15] }}, {name: "nonce/oversize", err: "invalid nonce: invalid UUID (got 17 bytes)", - corrupt: func(m *v2container.Container) { m.SetNonce(append(anyValidNonce[:], 1)) }}, + corrupt: func(m *protocontainer.Container) { m.Nonce = append(anyValidNonce[:], 1) }}, {name: "nonce/wrong version", err: "invalid nonce: wrong UUID version 3, expected 4", - corrupt: func(m *v2container.Container) { - b := bytes.Clone(anyValidNonce[:]) - b[6] = 3 << 4 - m.SetNonce(b) + corrupt: func(m *protocontainer.Container) { + m.Nonce = bytes.Clone(anyValidNonce[:]) + m.Nonce[6] = 3 << 4 }}, {name: "policy/replicas/nil", err: "invalid placement policy: missing replicas", - corrupt: func(m *v2container.Container) { - var mp apinetmap.PlacementPolicy - mp.SetReplicas(nil) - m.SetPlacementPolicy(&mp) - }}, + corrupt: func(m *protocontainer.Container) { m.PlacementPolicy.Replicas = nil }}, {name: "policy/replicas/empty", err: "invalid placement policy: missing replicas", - corrupt: func(m *v2container.Container) { - var mp apinetmap.PlacementPolicy - mp.SetReplicas([]apinetmap.Replica{}) - m.SetPlacementPolicy(&mp) - }}, + corrupt: func(m *protocontainer.Container) { m.PlacementPolicy.Replicas = []*protonetmap.Replica{} }}, {name: "attributes/no key", err: "empty attribute key", - corrupt: func(m *v2container.Container) { setContainerAttributes(m, "k1", "v1", "", "v2") }}, + corrupt: func(m *protocontainer.Container) { setContainerAttributes(m, "k1", "v1", "", "v2") }}, {name: "attributes/no value", err: `empty "k2" attribute value`, - corrupt: func(m *v2container.Container) { setContainerAttributes(m, "k1", "v1", "k2", "") }}, + corrupt: func(m *protocontainer.Container) { setContainerAttributes(m, "k1", "v1", "k2", "") }}, {name: "attributes/duplicated", err: "duplicated attribute k1", - corrupt: func(m *v2container.Container) { setContainerAttributes(m, "k1", "v1", "k2", "v2", "k1", "v3") }}, + corrupt: func(m *protocontainer.Container) { setContainerAttributes(m, "k1", "v1", "k2", "v2", "k1", "v3") }}, {name: "attributes/timestamp", err: "invalid attribute value Timestamp: foo (strconv.ParseInt: parsing \"foo\": invalid syntax)", - corrupt: func(m *v2container.Container) { setContainerAttributes(m, "Timestamp", "foo") }}, + corrupt: func(m *protocontainer.Container) { setContainerAttributes(m, "Timestamp", "foo") }}, } { t.Run(tc.name, func(t *testing.T) { val2 := val - var m v2container.Container - val2.WriteToV2(&m) - tc.corrupt(&m) - require.EqualError(t, new(container.Container).ReadFromV2(m), tc.err) + m := val2.ProtoMessage() + tc.corrupt(m) + require.EqualError(t, new(container.Container).FromProtoMessage(m), tc.err) }) } }) } -func TestContainer_WriteToV2(t *testing.T) { +func TestContainer_ProtoMessage(t *testing.T) { var val container.Container - var m v2container.Container // zero - val.WriteToV2(&m) + m := val.ProtoMessage() require.Zero(t, m.GetVersion()) - require.Zero(t, m.GetOwnerID()) + require.Zero(t, m.GetOwnerId()) require.Zero(t, m.GetNonce()) - require.Zero(t, m.GetBasicACL()) + require.Zero(t, m.GetBasicAcl()) require.Zero(t, m.GetPlacementPolicy()) require.Zero(t, m.GetAttributes()) // filled - validContainer.WriteToV2(&m) + m = validContainer.ProtoMessage() require.EqualValues(t, 2, m.GetVersion().GetMajor()) require.EqualValues(t, 16, m.GetVersion().GetMinor()) require.Len(t, m.GetNonce(), 16) require.EqualValues(t, 4, m.GetNonce()[6]>>4) - require.EqualValues(t, 1043832770, m.GetBasicACL()) + require.EqualValues(t, 1043832770, m.GetBasicAcl()) mas := m.GetAttributes() require.Len(t, mas, 7) for i, pair := range [][2]string{ @@ -615,12 +555,12 @@ func TestContainer_WriteToV2(t *testing.T) { require.Len(t, mss, 2) require.Equal(t, "selector_0", mss[0].GetName()) require.EqualValues(t, 1814781076, mss[0].GetCount()) - require.Equal(t, apinetmap.Same, mss[0].GetClause()) + require.Equal(t, protonetmap.Clause_SAME, mss[0].GetClause()) require.Equal(t, "filter_0", mss[0].GetFilter()) require.Equal(t, "attribute_0", mss[0].GetAttribute()) require.Equal(t, "selector_1", mss[1].GetName()) require.EqualValues(t, 1505136737, mss[1].GetCount()) - require.Equal(t, apinetmap.Distinct, mss[1].GetClause()) + require.Equal(t, protonetmap.Clause_DISTINCT, mss[1].GetClause()) require.Equal(t, "filter_1", mss[1].GetFilter()) require.Equal(t, "attribute_1", mss[1].GetAttribute()) @@ -629,51 +569,51 @@ func TestContainer_WriteToV2(t *testing.T) { // filter#0 require.Equal(t, "filter_0", mfs[0].GetName()) require.Zero(t, mfs[0].GetKey()) - require.Equal(t, apinetmap.AND, mfs[0].GetOp()) + require.Equal(t, protonetmap.Operation_AND, mfs[0].GetOp()) require.Zero(t, mfs[0].GetValue()) msubs := mfs[0].GetFilters() require.Len(t, msubs, 2) // sub#0 require.Equal(t, "filter_0_0", msubs[0].GetName()) require.Equal(t, "key_0_0", msubs[0].GetKey()) - require.Equal(t, apinetmap.EQ, msubs[0].GetOp()) + require.Equal(t, protonetmap.Operation_EQ, msubs[0].GetOp()) require.Equal(t, "val_0_0", msubs[0].GetValue()) require.Zero(t, msubs[0].GetFilters()) // sub#1 require.Equal(t, "filter_0_1", msubs[1].GetName()) require.Equal(t, "key_0_1", msubs[1].GetKey()) - require.Equal(t, apinetmap.NE, msubs[1].GetOp()) + require.Equal(t, protonetmap.Operation_NE, msubs[1].GetOp()) require.Equal(t, "val_0_1", msubs[1].GetValue()) require.Zero(t, msubs[1].GetFilters()) // filter#1 require.Equal(t, "filter_1", mfs[1].GetName()) require.Zero(t, mfs[1].GetKey()) - require.Equal(t, apinetmap.OR, mfs[1].GetOp()) + require.Equal(t, protonetmap.Operation_OR, mfs[1].GetOp()) require.Zero(t, mfs[1].GetValue()) msubs = mfs[1].GetFilters() require.Len(t, msubs, 4) // sub#0 require.Equal(t, "filter_1_0", msubs[0].GetName()) require.Equal(t, "key_1_0", msubs[0].GetKey()) - require.Equal(t, apinetmap.GT, msubs[0].GetOp()) + require.Equal(t, protonetmap.Operation_GT, msubs[0].GetOp()) require.Equal(t, "1889407708985023116", msubs[0].GetValue()) require.Zero(t, msubs[0].GetFilters()) // sub#1 require.Equal(t, "filter_1_1", msubs[1].GetName()) require.Equal(t, "key_1_1", msubs[1].GetKey()) - require.Equal(t, apinetmap.GE, msubs[1].GetOp()) + require.Equal(t, protonetmap.Operation_GE, msubs[1].GetOp()) require.Equal(t, "1429243097315344888", msubs[1].GetValue()) require.Zero(t, msubs[1].GetFilters()) // sub#2 require.Equal(t, "filter_1_2", msubs[2].GetName()) require.Equal(t, "key_1_2", msubs[2].GetKey()) - require.Equal(t, apinetmap.LT, msubs[2].GetOp()) + require.Equal(t, protonetmap.Operation_LT, msubs[2].GetOp()) require.Equal(t, "3722656060317482335", msubs[2].GetValue()) require.Zero(t, msubs[2].GetFilters()) // sub#3 require.Equal(t, "filter_1_3", msubs[3].GetName()) require.Equal(t, "key_1_3", msubs[3].GetKey()) - require.Equal(t, apinetmap.LE, msubs[3].GetOp()) + require.Equal(t, protonetmap.Operation_LE, msubs[3].GetOp()) require.Equal(t, "1950504987705284805", msubs[3].GetValue()) require.Zero(t, msubs[3].GetFilters()) } @@ -956,16 +896,8 @@ func TestCalculateID(t *testing.T) { var id cid.ID val.CalculateID(&id) - var msg refs.ContainerID - id.WriteToV2(&msg) - h := sha256.Sum256(val.Marshal()) - require.Equal(t, h[:], msg.GetValue()) - - var id2 cid.ID - require.NoError(t, id2.ReadFromV2(msg)) - - require.True(t, val.AssertID(id2)) + require.EqualValues(t, h, id) } func TestContainer_CalculateSignature(t *testing.T) { diff --git a/container/example_test.go b/container/example_test.go index 0046bb3b..54fdc792 100644 --- a/container/example_test.go +++ b/container/example_test.go @@ -3,7 +3,6 @@ package container_test import ( "time" - apiGoContainer "github.com/nspcc-dev/neofs-api-go/v2/container" "github.com/nspcc-dev/neofs-sdk-go/container" "github.com/nspcc-dev/neofs-sdk-go/container/acl" "github.com/nspcc-dev/neofs-sdk-go/netmap" @@ -43,16 +42,13 @@ func ExampleContainer_Init() { // Instances can be also used to process NeoFS API V2 protocol messages with [https://github.com/nspcc-dev/neofs-api] package. func ExampleContainer_marshalling() { - // import apiGoContainer "github.com/nspcc-dev/neofs-api-go/v2/container" - // On the client side. var cnr container.Container - var msg apiGoContainer.Container - cnr.WriteToV2(&msg) + msg := cnr.ProtoMessage() // *send message* // On the server side. - _ = cnr.ReadFromV2(msg) + _ = cnr.FromProtoMessage(msg) } diff --git a/container/id/id.go b/container/id/id.go index 963dd520..70ff4a1c 100644 --- a/container/id/id.go +++ b/container/id/id.go @@ -6,7 +6,7 @@ import ( "fmt" "github.com/mr-tron/base58" - "github.com/nspcc-dev/neofs-api-go/v2/refs" + "github.com/nspcc-dev/neofs-sdk-go/proto/refs" ) // Size is the size of an [ID] in bytes. @@ -17,8 +17,8 @@ const Size = sha256.Size // // ID implements built-in comparable interface. // -// ID is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/refs.ContainerID -// message. See ReadFromV2 / WriteToV2 methods. +// ID is mutually compatible with [refs.ContainerID] message. See +// [ID.FromProtoMessage] / [ID.ProtoMessage] methods. type ID [Size]byte // ErrZero is an error returned on zero [ID] encounter. @@ -42,25 +42,24 @@ func DecodeString(s string) (ID, error) { return id, id.DecodeString(s) } -// ReadFromV2 reads ID from the refs.ContainerID message. -// Returns an error if the message is malformed according -// to the NeoFS API V2 protocol. +// FromProtoMessage validates m according to the NeoFS API protocol and restores +// id from it. // -// See also WriteToV2. -func (id *ID) ReadFromV2(m refs.ContainerID) error { - err := id.Decode(m.GetValue()) +// See also [ID.ProtoMessage]. +func (id *ID) FromProtoMessage(m *refs.ContainerID) error { + err := id.Decode(m.Value) if err == nil && id.IsZero() { err = ErrZero } return err } -// WriteToV2 writes ID to the refs.ContainerID message. -// The message must not be nil. +// ProtoMessage converts id into message to transmit using the NeoFS API +// protocol. // -// See also ReadFromV2. -func (id ID) WriteToV2(m *refs.ContainerID) { - m.SetValue(id[:]) +// See also [ID.FromProtoMessage]. +func (id ID) ProtoMessage() *refs.ContainerID { + return &refs.ContainerID{Value: id[:]} } // Encode encodes ID into [Size] bytes of dst. Panics if diff --git a/container/id/id_test.go b/container/id/id_test.go index 67c01a47..218eb17d 100644 --- a/container/id/id_test.go +++ b/container/id/id_test.go @@ -6,9 +6,9 @@ import ( "math/rand" "testing" - "github.com/nspcc-dev/neofs-api-go/v2/refs" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test" + "github.com/nspcc-dev/neofs-sdk-go/proto/refs" "github.com/stretchr/testify/require" ) @@ -31,11 +31,10 @@ var invalidValueTestcases = []invalidValueTestCase{ {name: "oversized value", err: "invalid length 33", val: make([]byte, 33)}, } -func TestID_ReadFromV2(t *testing.T) { - var m refs.ContainerID - m.SetValue(validBytes[:]) +func TestID_FromProtoMessage(t *testing.T) { + m := &refs.ContainerID{Value: validBytes[:]} var id cid.ID - require.NoError(t, id.ReadFromV2(m)) + require.NoError(t, id.FromProtoMessage(m)) require.EqualValues(t, validBytes, id) t.Run("invalid", func(t *testing.T) { @@ -43,9 +42,8 @@ func TestID_ReadFromV2(t *testing.T) { name: "zero value", err: "zero container ID", val: make([]byte, cid.Size), }) { t.Run(tc.name, func(t *testing.T) { - var m refs.ContainerID - m.SetValue(tc.val) - require.EqualError(t, new(cid.ID).ReadFromV2(m), tc.err) + m := &refs.ContainerID{Value: tc.val} + require.EqualError(t, new(cid.ID).FromProtoMessage(m), tc.err) }) } }) @@ -102,10 +100,9 @@ func TestID_DecodeString(t *testing.T) { }) } -func TestID_WriteToV2(t *testing.T) { +func TestID_ProtoMessage(t *testing.T) { id := cidtest.ID() - var m refs.ContainerID - id.WriteToV2(&m) + m := id.ProtoMessage() require.Equal(t, id[:], m.GetValue()) } diff --git a/container/id/test/id_test.go b/container/id/test/id_test.go index 4ec59669..7a4bc51f 100644 --- a/container/id/test/id_test.go +++ b/container/id/test/id_test.go @@ -4,7 +4,6 @@ import ( "math/rand/v2" "testing" - "github.com/nspcc-dev/neofs-api-go/v2/refs" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test" "github.com/stretchr/testify/require" @@ -14,10 +13,9 @@ func TestID(t *testing.T) { id := cidtest.ID() require.NotEqual(t, id, cidtest.ID()) - var m refs.ContainerID - id.WriteToV2(&m) + m := id.ProtoMessage() var id2 cid.ID - require.NoError(t, id2.ReadFromV2(m)) + require.NoError(t, id2.FromProtoMessage(m)) } func TestNIDs(t *testing.T) { diff --git a/container/size.go b/container/size.go index 2402a5f9..31223141 100644 --- a/container/size.go +++ b/container/size.go @@ -4,62 +4,68 @@ import ( "errors" "fmt" - "github.com/nspcc-dev/neofs-api-go/v2/container" - "github.com/nspcc-dev/neofs-api-go/v2/refs" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" + protocontainer "github.com/nspcc-dev/neofs-sdk-go/proto/container" ) // SizeEstimation groups information about estimation of the size of the data // stored in the NeoFS container. // -// SizeEstimation is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/container.UsedSpaceAnnouncement -// message. See ReadFromV2 / WriteToV2 methods. +// SizeEstimation is mutually compatible with [container.UsedSpaceAnnouncement] +// message. See [Container.FromProtoMessage] / [Container.ProtoMessage] methods. type SizeEstimation struct { - m container.UsedSpaceAnnouncement + epoch uint64 + cnr cid.ID + val uint64 } -// ReadFromV2 reads SizeEstimation from the container.UsedSpaceAnnouncement message. -// Checks if the message conforms to NeoFS API V2 protocol. +// FromProtoMessage validates m according to the NeoFS API protocol and restores +// x from it. // -// See also WriteToV2. -func (x *SizeEstimation) ReadFromV2(m container.UsedSpaceAnnouncement) error { - cnrV2 := m.GetContainerID() - if cnrV2 == nil { +// See also [SizeEstimation.ProtoMessage]. +func (x *SizeEstimation) FromProtoMessage(m *protocontainer.AnnounceUsedSpaceRequest_Body_Announcement) error { + if m.ContainerId == nil { return errors.New("missing container") } - var cnr cid.ID - - err := cnr.ReadFromV2(*cnrV2) + err := x.cnr.FromProtoMessage(m.ContainerId) if err != nil { return fmt.Errorf("invalid container: %w", err) } - x.m = m + x.epoch = m.Epoch + x.val = m.UsedSpace return nil } -// WriteToV2 writes SizeEstimation into the container.UsedSpaceAnnouncement message. -// The message MUST NOT be nil. +// ProtoMessage converts x into message to transmit using the NeoFS API +// protocol. // -// See also ReadFromV2. -func (x SizeEstimation) WriteToV2(m *container.UsedSpaceAnnouncement) { - *m = x.m +// See also [Container.FromProtoMessage]. +func (x SizeEstimation) ProtoMessage() *protocontainer.AnnounceUsedSpaceRequest_Body_Announcement { + m := &protocontainer.AnnounceUsedSpaceRequest_Body_Announcement{ + Epoch: x.epoch, + UsedSpace: x.val, + } + if !x.cnr.IsZero() { + m.ContainerId = x.cnr.ProtoMessage() + } + return m } // SetEpoch sets epoch when estimation of the container data size was calculated. // // See also Epoch. func (x *SizeEstimation) SetEpoch(epoch uint64) { - x.m.SetEpoch(epoch) + x.epoch = epoch } // Epoch return epoch set using SetEpoch. // // Zero SizeEstimation represents estimation in zero epoch. func (x SizeEstimation) Epoch() uint64 { - return x.m.GetEpoch() + return x.epoch } // SetContainer specifies the container for which the amount of data is estimated. @@ -67,10 +73,7 @@ func (x SizeEstimation) Epoch() uint64 { // // See also Container. func (x *SizeEstimation) SetContainer(cnr cid.ID) { - var cidV2 refs.ContainerID - cnr.WriteToV2(&cidV2) - - x.m.SetContainerID(&cidV2) + x.cnr = cnr } // Container returns container set using SetContainer. @@ -78,27 +81,19 @@ func (x *SizeEstimation) SetContainer(cnr cid.ID) { // Zero SizeEstimation is not bound to any container (returns zero) which is // incorrect according to NeoFS API protocol. func (x SizeEstimation) Container() (res cid.ID) { - m := x.m.GetContainerID() - if m != nil { - err := res.ReadFromV2(*m) - if err != nil { - panic(fmt.Errorf("unexpected error from cid.ID.ReadFromV2: %w", err)) - } - } - - return + return x.cnr } // SetValue sets estimated amount of data (in bytes) in the specified container. // // See also Value. func (x *SizeEstimation) SetValue(value uint64) { - x.m.SetUsedSpace(value) + x.val = value } // Value returns data size estimation set using SetValue. // // Zero SizeEstimation has zero value. func (x SizeEstimation) Value() uint64 { - return x.m.GetUsedSpace() + return x.val } diff --git a/container/size_test.go b/container/size_test.go index f6572cc3..5e6e6a61 100644 --- a/container/size_test.go +++ b/container/size_test.go @@ -3,11 +3,11 @@ package container_test import ( "testing" - v2container "github.com/nspcc-dev/neofs-api-go/v2/container" - "github.com/nspcc-dev/neofs-api-go/v2/refs" "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" + protocontainer "github.com/nspcc-dev/neofs-sdk-go/proto/container" + "github.com/nspcc-dev/neofs-sdk-go/proto/refs" "github.com/stretchr/testify/require" ) @@ -63,79 +63,75 @@ func TestSizeEstimation_Value(t *testing.T) { require.EqualValues(t, anyValidVolume+1, val.Value()) } -func protoIDFromBytes(b []byte) *refs.ContainerID { - var m refs.ContainerID - m.SetValue(b) - return &m -} - -func TestSizeEstimation_ReadFromV2(t *testing.T) { - var m v2container.UsedSpaceAnnouncement - m.SetEpoch(anyValidEpoch) - m.SetContainerID(protoIDFromBytes(anyValidID[:])) - m.SetUsedSpace(anyValidVolume) +func TestSizeEstimation_FromProtoMessage(t *testing.T) { + m := &protocontainer.AnnounceUsedSpaceRequest_Body_Announcement{ + Epoch: anyValidEpoch, + ContainerId: &refs.ContainerID{Value: anyValidID[:]}, + UsedSpace: anyValidVolume, + } var val container.SizeEstimation - require.NoError(t, val.ReadFromV2(m)) + require.NoError(t, val.FromProtoMessage(m)) require.EqualValues(t, anyValidEpoch, val.Epoch()) require.Equal(t, anyValidID, val.Container()) require.EqualValues(t, anyValidVolume, val.Value()) // reset optional fields - m.SetEpoch(0) - m.SetUsedSpace(0) + m.Epoch = 0 + m.UsedSpace = 0 val2 := val - require.NoError(t, val2.ReadFromV2(m)) + require.NoError(t, val2.FromProtoMessage(m)) require.Zero(t, val2.Epoch()) require.Zero(t, val2.Value()) t.Run("invalid", func(t *testing.T) { for _, tc := range []struct { name, err string - corrupt func(announcement *v2container.UsedSpaceAnnouncement) + corrupt func(announcement *protocontainer.AnnounceUsedSpaceRequest_Body_Announcement) }{ {name: "container/missing", err: "missing container", - corrupt: func(m *v2container.UsedSpaceAnnouncement) { m.SetContainerID(nil) }}, + corrupt: func(m *protocontainer.AnnounceUsedSpaceRequest_Body_Announcement) { m.ContainerId = nil }}, {name: "container/nil", err: "invalid container: invalid length 0", - corrupt: func(m *v2container.UsedSpaceAnnouncement) { m.SetContainerID(protoIDFromBytes(nil)) }}, + corrupt: func(m *protocontainer.AnnounceUsedSpaceRequest_Body_Announcement) { m.ContainerId.Value = nil }}, {name: "container/empty", err: "invalid container: invalid length 0", - corrupt: func(m *v2container.UsedSpaceAnnouncement) { m.SetContainerID(protoIDFromBytes([]byte{})) }}, + corrupt: func(m *protocontainer.AnnounceUsedSpaceRequest_Body_Announcement) { m.ContainerId.Value = []byte{} }}, {name: "container/undersize", err: "invalid container: invalid length 31", - corrupt: func(m *v2container.UsedSpaceAnnouncement) { m.SetContainerID(protoIDFromBytes(anyValidID[:31])) }}, + corrupt: func(m *protocontainer.AnnounceUsedSpaceRequest_Body_Announcement) { + m.ContainerId.Value = anyValidID[:31] + }}, {name: "container/oversize", err: "invalid container: invalid length 33", - corrupt: func(m *v2container.UsedSpaceAnnouncement) { - m.SetContainerID(protoIDFromBytes(append(anyValidID[:], 1))) + corrupt: func(m *protocontainer.AnnounceUsedSpaceRequest_Body_Announcement) { + m.ContainerId.Value = append(anyValidID[:], 1) }}, } { t.Run(tc.name, func(t *testing.T) { val2 := val - var m v2container.UsedSpaceAnnouncement - val2.WriteToV2(&m) - tc.corrupt(&m) - require.EqualError(t, new(container.SizeEstimation).ReadFromV2(m), tc.err) + m := val2.ProtoMessage() + tc.corrupt(m) + require.EqualError(t, new(container.SizeEstimation).FromProtoMessage(m), tc.err) }) } t.Run("container/zero", func(t *testing.T) { - var m v2container.UsedSpaceAnnouncement - m.SetContainerID(protoIDFromBytes(make([]byte, cid.Size))) - require.ErrorIs(t, val2.ReadFromV2(m), cid.ErrZero) + m := &protocontainer.AnnounceUsedSpaceRequest_Body_Announcement{ + ContainerId: &refs.ContainerID{Value: make([]byte, cid.Size)}, + } + require.ErrorIs(t, val2.FromProtoMessage(m), cid.ErrZero) }) }) } -func TestSizeEstimation_WriteToV2(t *testing.T) { +func TestSizeEstimation_ProtoMessage(t *testing.T) { var val container.SizeEstimation - var m v2container.UsedSpaceAnnouncement // zero - val.WriteToV2(&m) - require.Zero(t, val.Epoch()) - require.Zero(t, val.Container()) - require.Zero(t, val.Value()) + m := val.ProtoMessage() + require.Zero(t, m.GetEpoch()) + require.Zero(t, m.GetContainerId()) + require.Zero(t, m.GetUsedSpace()) // filled - validSizeEstimation.WriteToV2(&m) + m = validSizeEstimation.ProtoMessage() require.EqualValues(t, anyValidEpoch, m.GetEpoch()) - require.Equal(t, anyValidID[:], m.GetContainerID().GetValue()) + require.Equal(t, anyValidID[:], m.GetContainerId().GetValue()) require.EqualValues(t, anyValidVolume, m.GetUsedSpace()) } diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go index 4549cc55..8b9dcd2f 100644 --- a/crypto/crypto_test.go +++ b/crypto/crypto_test.go @@ -5,7 +5,6 @@ import ( "testing" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" - "github.com/nspcc-dev/neofs-api-go/v2/refs" neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa" "github.com/stretchr/testify/require" @@ -20,7 +19,6 @@ func TestSignature(t *testing.T) { require.NoError(t, err) var s neofscrypto.Signature - var m refs.Signature for _, f := range []func() neofscrypto.Signer{ func() neofscrypto.Signer { @@ -38,9 +36,9 @@ func TestSignature(t *testing.T) { err := s.Calculate(signer, data) require.NoError(t, err) - s.WriteToV2(&m) + m := s.ProtoMessage() - require.NoError(t, s.ReadFromV2(m)) + require.NoError(t, s.FromProtoMessage(m)) valid := s.Verify(data) require.True(t, valid, "type %T", signer) diff --git a/crypto/ecdsa/wallet_connect.go b/crypto/ecdsa/wallet_connect.go index 5c46f69a..2eb739fa 100644 --- a/crypto/ecdsa/wallet_connect.go +++ b/crypto/ecdsa/wallet_connect.go @@ -3,14 +3,20 @@ package neofsecdsa import ( "crypto/ecdsa" "crypto/elliptic" + "crypto/rand" + "crypto/sha256" "encoding/base64" + "encoding/hex" "fmt" + "math/big" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" - "github.com/nspcc-dev/neofs-api-go/v2/util/signature/walletconnect" + "github.com/nspcc-dev/neo-go/pkg/io" neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" ) +const saltLen = 16 + // SignerWalletConnect is similar to SignerRFC6979 with 2 changes: // 1. The data is base64 encoded before signing/verifying. // 2. The signature is a concatenation of the signature itself and 16-byte salt. @@ -29,7 +35,16 @@ func (x SignerWalletConnect) Scheme() neofscrypto.Scheme { func (x SignerWalletConnect) Sign(data []byte) ([]byte, error) { b64 := make([]byte, base64.StdEncoding.EncodedLen(len(data))) base64.StdEncoding.Encode(b64, data) - return walletconnect.Sign((*ecdsa.PrivateKey)(&x), b64) + var salt [saltLen]byte + _, err := rand.Read(salt[:]) + if err != nil { + return nil, fmt.Errorf("randomize salt: %w", err) + } + sig, err := SignerRFC6979(x).Sign(saltMessageWalletConnect(b64, salt[:])) + if err != nil { + return nil, err + } + return append(sig, salt[:]...), nil } // Public initializes PublicKey and returns it as neofscrypto.PublicKey. @@ -79,7 +94,27 @@ func (x *PublicKeyWalletConnect) Decode(data []byte) error { // Verify verifies data signature calculated by ECDSA algorithm with SHA-512 hashing. func (x PublicKeyWalletConnect) Verify(data, signature []byte) bool { + if len(signature) != keys.SignatureLen+saltLen { + return false + } b64 := make([]byte, base64.StdEncoding.EncodedLen(len(data))) base64.StdEncoding.Encode(b64, data) - return walletconnect.Verify((*ecdsa.PublicKey)(&x), b64, signature) + sig, salt := signature[:keys.SignatureLen], signature[keys.SignatureLen:] + h := sha256.Sum256(saltMessageWalletConnect(b64, salt)) + r := new(big.Int).SetBytes(sig[:keys.SignatureLen/2]) + s := new(big.Int).SetBytes(sig[keys.SignatureLen/2:]) + return ecdsa.Verify((*ecdsa.PublicKey)(&x), h[:], r, s) +} + +// saltMessageWalletConnect calculates signed message for given data and salt +// according to WalletConnect. +func saltMessageWalletConnect(data, salt []byte) []byte { + saltedLen := hex.EncodedLen(len(salt)) + len(data) + b := make([]byte, 4+io.GetVarSize(saltedLen)+saltedLen+2) + b[0], b[1], b[2], b[3] = 0x01, 0x00, 0x01, 0xf0 + n := 4 + io.PutVarUint(b[4:], uint64(saltedLen)) + n += hex.Encode(b[n:], salt) + n += copy(b[n:], data) + b[n], b[n+1] = 0x00, 0x00 + return b } diff --git a/crypto/proto.go b/crypto/proto.go new file mode 100644 index 00000000..470e154b --- /dev/null +++ b/crypto/proto.go @@ -0,0 +1,287 @@ +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 request 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") +) + +// SignedRequest is a generic interface of a signed NeoFS API request. +type SignedRequest[BODY proto.Message] interface { + GetBody() BODY + GetMetaHeader() *session.RequestMetaHeader + GetVerifyHeader() *session.RequestVerificationHeader +} + +// SignedResponse is a generic interface of a signed NeoFS API response. +type SignedResponse[BODY proto.Message] interface { + GetBody() BODY + 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[BODY proto.Message](signer Signer, r SignedRequest[BODY], 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[BODY proto.Message](r SignedRequest[BODY], buf []byte) error { + v := r.GetVerifyHeader() + if v == nil { + return errMissingVerifyHdr + } + + b := r.GetBody() + m := r.GetMetaHeader() + bs := maxEncodedSize(b, m, v) + for { + mo, vo := m.GetOrigin(), v.GetOrigin() + if (mo == nil) != (vo == nil) { + return errWrongVerifyHdrNum + } + if vo == nil { + break + } + if s := maxEncodedSize(mo, vo); s > bs { + bs = s + } + } + + if len(buf) < bs { + buf = make([]byte, bs) + } + + for ; ; m, v = m.Origin, v.Origin { + if v.MetaSignature == nil { + return errMissingMetaSig + } + if err := verifyMessageSignature(m, v.MetaSignature, buf); err != nil { + return fmt.Errorf("%w: %w", errInvalidMetaSig, err) + } + if v.OriginSignature == nil { + return errMissingVerifyOriginSig + } + if err := verifyMessageSignature(v.Origin, v.OriginSignature, buf); err != nil { + return fmt.Errorf("%w: %w", errInvalidVerifyOriginSig, err) + } + if v.Origin == nil { + if v.BodySignature == nil { + return errMissingBodySig + } + if err := verifyMessageSignature(b, v.BodySignature, buf); err != nil { + return fmt.Errorf("%w: %w", errInvalidBodySig, err) + } + return nil + } + if v.BodySignature != nil { + return 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[BODY proto.Message](signer Signer, r SignedResponse[BODY], 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[BODY proto.Message](r SignedResponse[BODY], buf []byte) error { + v := r.GetVerifyHeader() + if v == nil { + return errMissingVerifyHdr + } + + b := r.GetBody() + m := r.GetMetaHeader() + bs := maxEncodedSize(b, m, v) + for { + mo, vo := m.GetOrigin(), v.GetOrigin() + if (mo == nil) != (vo == nil) { + return errWrongVerifyHdrNum + } + if vo == nil { + break + } + if s := maxEncodedSize(mo, vo); s > bs { + bs = s + } + } + + if len(buf) < bs { + buf = make([]byte, bs) + } + + for ; ; m, v = m.Origin, v.Origin { + if v.MetaSignature == nil { + return errMissingMetaSig + } + if err := verifyMessageSignature(m, v.MetaSignature, buf); err != nil { + return fmt.Errorf("%w: %w", errInvalidMetaSig, err) + } + if v.OriginSignature == nil { + return errMissingVerifyOriginSig + } + if err := verifyMessageSignature(v.Origin, v.OriginSignature, buf); err != nil { + return fmt.Errorf("%w: %w", errInvalidVerifyOriginSig, err) + } + if v.Origin == nil { + if v.BodySignature == nil { + return errMissingBodySig + } + if err := verifyMessageSignature(b, v.BodySignature, buf); err != nil { + return fmt.Errorf("%w: %w", errInvalidBodySig, err) + } + return nil + } + if v.BodySignature != nil { + return 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/signature.go b/crypto/signature.go index b19ae083..06fad235 100644 --- a/crypto/signature.go +++ b/crypto/signature.go @@ -2,22 +2,19 @@ package neofscrypto import ( "fmt" - "math" - "github.com/nspcc-dev/neofs-api-go/v2/refs" + neofsproto "github.com/nspcc-dev/neofs-sdk-go/internal/proto" + "github.com/nspcc-dev/neofs-sdk-go/proto/refs" ) // StablyMarshallable describes structs which can be marshalled transparently. -type StablyMarshallable interface { - StableMarshal([]byte) []byte - StableSize() int -} +type StablyMarshallable = neofsproto.Message // Signature represents a confirmation of data integrity received by the // digital signature mechanism. // -// Signature is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/refs.Signature -// message. See ReadFromV2 / WriteToV2 methods. +// Signature is mutually compatible with [refs.Signature] message. See +// [Signature.FromProtoMessage] / [Signature.ProtoMessage] methods. // // Instances should be constructed using one of the constructors. type Signature struct { @@ -36,29 +33,30 @@ func NewSignature(scheme Scheme, publicKey PublicKey, value []byte) Signature { return NewSignatureFromRawKey(scheme, PublicKeyBytes(publicKey), value) } -// ReadFromV2 reads Signature from the refs.Signature message. Checks if the -// message conforms to NeoFS API V2 protocol. +// FromProtoMessage validates m according to the NeoFS API protocol and restores +// x from it. // -// See also WriteToV2. -func (x *Signature) ReadFromV2(m refs.Signature) error { - scheme := m.GetScheme() - if scheme > math.MaxInt32 { // max value of Scheme type - return fmt.Errorf("scheme %d overflows int32", scheme) +// See also [Signature.ProtoMessage]. +func (x *Signature) FromProtoMessage(m *refs.Signature) error { + if m.Scheme < 0 { + return fmt.Errorf("negative scheme %d", m.Scheme) } - x.scheme = Scheme(scheme) - x.pub = m.GetKey() - x.val = m.GetSign() + x.scheme = Scheme(m.Scheme) + x.pub = m.Key + x.val = m.Sign return nil } -// WriteToV2 writes Signature to the refs.Signature message. -// The message must not be nil. +// ProtoMessage converts x into message to transmit using the NeoFS API +// protocol. // -// See also ReadFromV2. -func (x Signature) WriteToV2(m *refs.Signature) { - m.SetScheme(refs.SignatureScheme(x.scheme)) - m.SetKey(x.pub) - m.SetSign(x.val) +// See also [Signature.FromProtoMessage]. +func (x Signature) ProtoMessage() *refs.Signature { + return &refs.Signature{ + Key: x.pub, + Sign: x.val, + Scheme: refs.SignatureScheme(x.scheme), + } } // Calculate signs data using Signer and encodes public key for subsequent @@ -78,33 +76,6 @@ func (x *Signature) Calculate(signer Signer, data []byte) error { return nil } -// CalculateMarshalled signs data using Signer and encodes public key for subsequent verification. -// If signer is a StaticSigner, just sets prepared signature. -// -// Pre-allocated byte slice can be passed in buf parameter to avoid new allocations. In ideal case buf length should be -// StableSize length. If buffer length shorter than StableSize or nil, new slice will be allocated. -// -// Signer MUST NOT be nil. -// -// See also Verify. -func (x *Signature) CalculateMarshalled(signer Signer, obj StablyMarshallable, buf []byte) error { - if static, ok := signer.(*StaticSigner); ok { - *x = NewSignature(static.scheme, static.pubKey, static.sig) - return nil - } - - var data []byte - if obj != nil { - if len(buf) >= obj.StableSize() { - data = obj.StableMarshal(buf[0:obj.StableSize()]) - } else { - data = obj.StableMarshal(nil) - } - } - - return x.Calculate(signer, data) -} - // Verify verifies data signature using encoded public key. True means valid // signature. // @@ -119,8 +90,8 @@ func (x Signature) Verify(data []byte) bool { // Scheme returns signature scheme used by signer to calculate the signature. // -// Scheme MUST NOT be called before [NewSignature], [Signature.ReadFromV2] or -// [Signature.Calculate] methods. +// Scheme MUST NOT be called before [NewSignature], [Signature.FromProtoMessage] +// or [Signature.Calculate] methods. func (x Signature) Scheme() Scheme { return x.scheme } @@ -132,8 +103,8 @@ func (x *Signature) SetScheme(s Scheme) { // PublicKey returns public key of the signer which calculated the signature. // -// PublicKey MUST NOT be called before [NewSignature], [Signature.ReadFromV2] or -// [Signature.Calculate] methods. +// PublicKey MUST NOT be called before [NewSignature], +// [Signature.FromProtoMessage] or [Signature.Calculate] methods. // // See also [Signature.PublicKeyBytes]. func (x Signature) PublicKey() PublicKey { @@ -151,7 +122,7 @@ func (x *Signature) SetPublicKeyBytes(pub []byte) { // calculated the signature. // // PublicKeyBytes MUST NOT be called before [NewSignature], -// [Signature.ReadFromV2] or [Signature.Calculate] methods. +// [Signature.FromProtoMessage] or [Signature.Calculate] methods. // // The value returned shares memory with the structure itself, so changing it can lead to data corruption. // Make a copy if you need to change it. @@ -171,8 +142,8 @@ func (x *Signature) SetValue(v []byte) { // The value returned shares memory with the structure itself, so changing it can lead to data corruption. // Make a copy if you need to change it. // -// Value MUST NOT be called before [NewSignature], [Signature.ReadFromV2] or -// [Signature.Calculate] methods. +// Value MUST NOT be called before [NewSignature], [Signature.FromProtoMessage] +// or [Signature.Calculate] methods. func (x Signature) Value() []byte { return x.val } @@ -192,3 +163,14 @@ func decodePublicKey(scheme Scheme, b []byte) (PublicKey, error) { return pubKey, nil } + +// Marshal encodes x transmitted via NeoFS API protocol into a dynamically +// allocated buffer. +func (x Signature) Marshal() []byte { + return neofsproto.Marshal(x) +} + +// Unmarshal decodes x transmitted via NeoFS API protocol from data. +func (x *Signature) Unmarshal(b []byte) error { + return neofsproto.Unmarshal(b, x) +} diff --git a/crypto/signature_test.go b/crypto/signature_test.go index 78c8e461..08baecba 100644 --- a/crypto/signature_test.go +++ b/crypto/signature_test.go @@ -1,16 +1,16 @@ package neofscrypto_test import ( - "math" "testing" - "github.com/nspcc-dev/neofs-api-go/v2/refs" 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/proto/refs" "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" ) -const anyUnsupportedScheme = math.MaxInt32 + 1 +const anyUnsupportedScheme = -1 func TestSignatureLifecycle(t *testing.T) { data := []byte("Hello, world!") @@ -34,30 +34,29 @@ func TestSignatureLifecycle(t *testing.T) { testSig(clientSig) - var sigV2 refs.Signature - clientSig.WriteToV2(&sigV2) + m := clientSig.ProtoMessage() - require.Equal(t, refs.SignatureScheme(scheme), sigV2.GetScheme()) - require.Equal(t, bPubKey, sigV2.GetKey()) - require.Equal(t, clientSig.Value(), sigV2.GetSign()) + require.Equal(t, refs.SignatureScheme(scheme), m.GetScheme()) + require.Equal(t, bPubKey, m.GetKey()) + require.Equal(t, clientSig.Value(), m.GetSign()) - // sigV2 transmitted to server over the network + // m transmitted to server over the network var serverSig neofscrypto.Signature - err = serverSig.ReadFromV2(sigV2) + err = serverSig.FromProtoMessage(m) require.NoError(t, err) testSig(serverSig) // break the message in different ways for i, breakSig := range []func(*refs.Signature){ - func(sigV2 *refs.Signature) { sigV2.SetScheme(refs.SignatureScheme(anyUnsupportedScheme)) }, + func(sigV2 *refs.Signature) { sigV2.Scheme = refs.SignatureScheme(anyUnsupportedScheme) }, } { - sigV2Cp := sigV2 - breakSig(&sigV2Cp) + m := proto.Clone(m).(*refs.Signature) + breakSig(m) - err = serverSig.ReadFromV2(sigV2Cp) + err = serverSig.FromProtoMessage(m) require.Errorf(t, err, "break func #%d", i) } } @@ -79,12 +78,11 @@ func TestNewSignature(t *testing.T) { checkFields(sig) - var sigMsg refs.Signature - sig.WriteToV2(&sigMsg) + m := sig.ProtoMessage() var sig2 neofscrypto.Signature - err := sig2.ReadFromV2(sigMsg) + err := sig2.FromProtoMessage(m) require.NoError(t, err) checkFields(sig2) diff --git a/crypto/signer.go b/crypto/signer.go index 2d671c67..aeb0c62f 100644 --- a/crypto/signer.go +++ b/crypto/signer.go @@ -4,7 +4,7 @@ import ( "errors" "fmt" - "github.com/nspcc-dev/neofs-api-go/v2/refs" + "github.com/nspcc-dev/neofs-sdk-go/proto/refs" ) // ErrIncorrectSigner is returned from function when the signer passed to it diff --git a/crypto/test/tests.go b/crypto/test/tests.go index db226cd3..fedfaafa 100644 --- a/crypto/test/tests.go +++ b/crypto/test/tests.go @@ -55,7 +55,7 @@ func Signature() neofscrypto.Signature { sig := make([]byte, 1+rand.Intn(128)) //nolint:staticcheck // cryptorandom is not required for testing rand.Read(sig) - return neofscrypto.NewSignature(neofscrypto.Scheme(rand.Uint32()%3), Signer().Public(), sig) + return neofscrypto.NewSignature(neofscrypto.Scheme(rand.Int31()%3), Signer().Public(), sig) } // ECDSAPrivateKey returns random ECDSA private key. diff --git a/crypto/test/tests_test.go b/crypto/test/tests_test.go index 04bcacdb..f2be9e43 100644 --- a/crypto/test/tests_test.go +++ b/crypto/test/tests_test.go @@ -3,7 +3,6 @@ package neofscryptotest_test import ( "testing" - "github.com/nspcc-dev/neofs-api-go/v2/refs" neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa" neofscryptotest "github.com/nspcc-dev/neofs-sdk-go/crypto/test" @@ -14,10 +13,9 @@ func TestSignature(t *testing.T) { s := neofscryptotest.Signature() require.NotEqual(t, s, neofscryptotest.Signature()) - var m refs.Signature - s.WriteToV2(&m) + m := s.ProtoMessage() var s2 neofscrypto.Signature - require.NoError(t, s2.ReadFromV2(m)) + require.NoError(t, s2.FromProtoMessage(m)) require.Equal(t, s, s2) } diff --git a/eacl/common_test.go b/eacl/common_test.go index 3fd1c096..334825da 100644 --- a/eacl/common_test.go +++ b/eacl/common_test.go @@ -7,12 +7,12 @@ import ( "math/big" "testing" - protoacl "github.com/nspcc-dev/neofs-api-go/v2/acl" "github.com/nspcc-dev/neofs-sdk-go/checksum" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" "github.com/nspcc-dev/neofs-sdk-go/eacl" "github.com/nspcc-dev/neofs-sdk-go/object" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" + protoacl "github.com/nspcc-dev/neofs-sdk-go/proto/acl" "github.com/nspcc-dev/neofs-sdk-go/user" "github.com/nspcc-dev/neofs-sdk-go/version" "github.com/nspcc-dev/tzhash/tz" @@ -211,8 +211,8 @@ var ( // corresponds to anyValidRecords. anyValidJSONRecords = []string{` { - "operation": 5692342, - "action": 12943052, + "action": 5692342, + "operation": 12943052, "filters": [ { "headerType": 4509681, @@ -230,8 +230,8 @@ var ( } `, ` { - "operation": 43658603, - "action": 12943052, + "action": 43658603, + "operation": 12943052, "filters": [ { "headerType": 4509681, @@ -275,8 +275,8 @@ var ( }, "records": [ { - "operation": 5692342, - "action": 12943052, + "action": 5692342, + "operation": 12943052, "filters": [ { "headerType": 4509681, @@ -293,11 +293,11 @@ var ( ] }, { - "operation": 43658603, - "action": 12943052, + "action": 43658603, + "operation": 12943052, "filters": [ { - "headerType": 43658603, + "headerType": 4509681, "matchType": 949385, "key": "key_54093643", "value": "val_34811040" @@ -337,7 +337,7 @@ func init() { } } -func assertProtoTargetsEqual(t testing.TB, ts []eacl.Target, ms []protoacl.Target) { +func assertProtoTargetsEqual(t testing.TB, ts []eacl.Target, ms []*protoacl.EACLRecord_Target) { require.Len(t, ms, len(ts)) for i := range ts { require.EqualValues(t, ts[i].Role(), ms[i].GetRole(), i) @@ -345,7 +345,7 @@ func assertProtoTargetsEqual(t testing.TB, ts []eacl.Target, ms []protoacl.Targe } } -func assertProtoFiltersEqual(t testing.TB, fs []eacl.Filter, ms []protoacl.HeaderFilter) { +func assertProtoFiltersEqual(t testing.TB, fs []eacl.Filter, ms []*protoacl.EACLRecord_Filter) { require.Len(t, ms, len(fs)) for i := range fs { require.EqualValues(t, fs[i].From(), ms[i].GetHeaderType(), i) @@ -355,7 +355,7 @@ func assertProtoFiltersEqual(t testing.TB, fs []eacl.Filter, ms []protoacl.Heade } } -func assertProtoRecordsEqual(t testing.TB, rs []eacl.Record, ms []protoacl.Record) { +func assertProtoRecordsEqual(t testing.TB, rs []eacl.Record, ms []*protoacl.EACLRecord) { require.Len(t, ms, len(rs)) for i := range rs { require.EqualValues(t, rs[i].Action(), ms[i].GetAction(), i) diff --git a/eacl/enums.go b/eacl/enums.go index c9083092..4cc9d4bd 100644 --- a/eacl/enums.go +++ b/eacl/enums.go @@ -2,13 +2,11 @@ package eacl import ( "strconv" - - v2acl "github.com/nspcc-dev/neofs-api-go/v2/acl" ) // Action enumerates actions that may be applied within NeoFS access management. // What and how specific Action affects depends on the specific context. -type Action uint32 +type Action int32 const ( ActionUnspecified Action = iota // undefined (zero) @@ -21,7 +19,7 @@ const ( const ActionUnknown = ActionUnspecified // Operation enumerates operations on NeoFS resources under access control. -type Operation uint32 +type Operation int32 const ( OperationUnspecified Operation = iota // undefined (zero) @@ -39,7 +37,7 @@ const ( const OperationUnknown = OperationUnspecified // Role enumerates groups of subjects requesting access to NeoFS resources. -type Role uint32 +type Role int32 const ( RoleUnspecified Role = iota // undefined (zero) @@ -54,7 +52,7 @@ const RoleUnknown = RoleUnspecified // Match enumerates operators to check attribute value compliance. What and how // specific Match affects depends on the specific context. -type Match uint32 +type Match int32 const ( MatchUnspecified Match = iota // undefined (zero) @@ -73,7 +71,7 @@ const MatchUnknown = MatchUnspecified // FilterHeaderType enumerates the classes of resource attributes processed // within NeoFS access management. -type FilterHeaderType uint32 +type FilterHeaderType int32 const ( HeaderTypeUnspecified FilterHeaderType = iota // undefined (zero) @@ -86,14 +84,6 @@ const ( // Deprecated: use HeaderTypeUnspecified instead. const HeaderTypeUnknown = HeaderTypeUnspecified -// ToV2 converts Action to v2 Action enum value. -// Deprecated: do not use it. -func (a Action) ToV2() v2acl.Action { return v2acl.Action(a) } - -// ActionFromV2 converts v2 Action enum value to Action. -// Deprecated: do not use it. -func ActionFromV2(action v2acl.Action) Action { return Action(action) } - const ( actionStringZero = "ACTION_UNSPECIFIED" actionStringAllow = "ALLOW" @@ -124,7 +114,7 @@ func (a Action) EncodeToString() string { return a.String() } func (a Action) String() string { switch a { default: - return strconv.FormatUint(uint64(a), 10) + return strconv.FormatInt(int64(a), 10) case 0: return actionStringZero case ActionAllow: @@ -141,7 +131,7 @@ func (a Action) String() string { func (a *Action) DecodeString(s string) bool { switch s { default: - n, err := strconv.ParseUint(s, 10, 32) + n, err := strconv.ParseInt(s, 10, 32) if err != nil { return false } @@ -156,14 +146,6 @@ func (a *Action) DecodeString(s string) bool { return true } -// ToV2 converts Operation to v2 Operation enum value. -// Deprecated: do not use it. -func (o Operation) ToV2() v2acl.Operation { return v2acl.Operation(o) } - -// OperationFromV2 converts v2 Operation enum value to Operation. -// Deprecated: do not use it. -func OperationFromV2(operation v2acl.Operation) Operation { return Operation(operation) } - const ( opStringZero = "OPERATION_UNSPECIFIED" opStringGet = "GET" @@ -209,7 +191,7 @@ func (o Operation) EncodeToString() string { return o.String() } func (o Operation) String() string { switch o { default: - return strconv.FormatUint(uint64(o), 10) + return strconv.FormatInt(int64(o), 10) case 0: return opStringZero case OperationGet: @@ -236,7 +218,7 @@ func (o Operation) String() string { func (o *Operation) DecodeString(s string) bool { switch s { default: - n, err := strconv.ParseUint(s, 10, 32) + n, err := strconv.ParseInt(s, 10, 32) if err != nil { return false } @@ -261,14 +243,6 @@ func (o *Operation) DecodeString(s string) bool { return true } -// ToV2 converts Role to v2 Role enum value. -// Deprecated: do not use it. -func (r Role) ToV2() v2acl.Role { return v2acl.Role(r) } - -// RoleFromV2 converts v2 Role enum value to Role. -// Deprecated: do not use it. -func RoleFromV2(role v2acl.Role) Role { return Role(role) } - const ( roleStringZero = "ROLE_UNSPECIFIED" roleStringUser = "USER" @@ -302,7 +276,7 @@ func (r Role) EncodeToString() string { return r.String() } func (r Role) String() string { switch r { default: - return strconv.FormatUint(uint64(r), 10) + return strconv.FormatInt(int64(r), 10) case 0: return roleStringZero case RoleUser: @@ -321,7 +295,7 @@ func (r Role) String() string { func (r *Role) DecodeString(s string) bool { switch s { default: - n, err := strconv.ParseUint(s, 10, 32) + n, err := strconv.ParseInt(s, 10, 32) if err != nil { return false } @@ -338,14 +312,6 @@ func (r *Role) DecodeString(s string) bool { return true } -// ToV2 converts Match to v2 MatchType enum value. -// Deprecated: do not use it. -func (m Match) ToV2() v2acl.MatchType { return v2acl.MatchType(m) } - -// MatchFromV2 converts v2 MatchType enum value to Match. -// Deprecated: do not use it. -func MatchFromV2(match v2acl.MatchType) Match { return Match(match) } - const ( matcherStringZero = "MATCH_TYPE_UNSPECIFIED" matcherStringEqual = "STRING_EQUAL" @@ -391,7 +357,7 @@ func (m Match) EncodeToString() string { return m.String() } func (m Match) String() string { switch m { default: - return strconv.FormatUint(uint64(m), 10) + return strconv.FormatInt(int64(m), 10) case 0: return matcherStringZero case MatchStringEqual: @@ -418,7 +384,7 @@ func (m Match) String() string { func (m *Match) DecodeString(s string) bool { switch s { default: - n, err := strconv.ParseUint(s, 10, 32) + n, err := strconv.ParseInt(s, 10, 32) if err != nil { return false } @@ -443,16 +409,6 @@ func (m *Match) DecodeString(s string) bool { return true } -// ToV2 converts FilterHeaderType to v2 HeaderType enum value. -// Deprecated: do not use it. -func (h FilterHeaderType) ToV2() v2acl.HeaderType { return v2acl.HeaderType(h) } - -// FilterHeaderTypeFromV2 converts v2 HeaderType enum value to FilterHeaderType. -// Deprecated: do not use it. -func FilterHeaderTypeFromV2(header v2acl.HeaderType) FilterHeaderType { - return FilterHeaderType(header) -} - const ( headerTypeStringZero = "HEADER_UNSPECIFIED" headerTypeStringRequest = "REQUEST" @@ -485,7 +441,7 @@ func (h FilterHeaderType) EncodeToString() string { return h.String() } func (h FilterHeaderType) String() string { switch h { default: - return strconv.FormatUint(uint64(h), 10) + return strconv.FormatInt(int64(h), 10) case 0: return headerTypeStringZero case HeaderFromRequest: @@ -504,7 +460,7 @@ func (h FilterHeaderType) String() string { func (h *FilterHeaderType) DecodeString(s string) bool { switch s { default: - n, err := strconv.ParseUint(s, 10, 32) + n, err := strconv.ParseInt(s, 10, 32) if err != nil { return false } diff --git a/eacl/enums_test.go b/eacl/enums_test.go index 6e47e023..6f5b54e1 100644 --- a/eacl/enums_test.go +++ b/eacl/enums_test.go @@ -4,61 +4,63 @@ import ( "fmt" "testing" - v2acl "github.com/nspcc-dev/neofs-api-go/v2/acl" "github.com/nspcc-dev/neofs-sdk-go/eacl" + protoacl "github.com/nspcc-dev/neofs-sdk-go/proto/acl" "github.com/stretchr/testify/require" ) var ( - eqV2Actions = map[eacl.Action]v2acl.Action{ - eacl.ActionUnspecified: v2acl.ActionUnknown, - eacl.ActionAllow: v2acl.ActionAllow, - eacl.ActionDeny: v2acl.ActionDeny, + protoActions = map[eacl.Action]protoacl.Action{ + eacl.ActionUnspecified: protoacl.Action_ACTION_UNSPECIFIED, + eacl.ActionAllow: protoacl.Action_ALLOW, + eacl.ActionDeny: protoacl.Action_DENY, } - eqV2Operations = map[eacl.Operation]v2acl.Operation{ - eacl.OperationUnspecified: v2acl.OperationUnknown, - eacl.OperationGet: v2acl.OperationGet, - eacl.OperationHead: v2acl.OperationHead, - eacl.OperationPut: v2acl.OperationPut, - eacl.OperationDelete: v2acl.OperationDelete, - eacl.OperationSearch: v2acl.OperationSearch, - eacl.OperationRange: v2acl.OperationRange, - eacl.OperationRangeHash: v2acl.OperationRangeHash, + protoOperations = map[eacl.Operation]protoacl.Operation{ + eacl.OperationUnspecified: protoacl.Operation_OPERATION_UNSPECIFIED, + eacl.OperationGet: protoacl.Operation_GET, + eacl.OperationHead: protoacl.Operation_HEAD, + eacl.OperationPut: protoacl.Operation_PUT, + eacl.OperationDelete: protoacl.Operation_DELETE, + eacl.OperationSearch: protoacl.Operation_SEARCH, + eacl.OperationRange: protoacl.Operation_GETRANGE, + eacl.OperationRangeHash: protoacl.Operation_GETRANGEHASH, } - eqV2Roles = map[eacl.Role]v2acl.Role{ - eacl.RoleUnspecified: v2acl.RoleUnknown, - eacl.RoleUser: v2acl.RoleUser, - eacl.RoleSystem: v2acl.RoleSystem, - eacl.RoleOthers: v2acl.RoleOthers, + protoRoles = map[eacl.Role]protoacl.Role{ + eacl.RoleUnspecified: protoacl.Role_ROLE_UNSPECIFIED, + eacl.RoleUser: protoacl.Role_USER, + eacl.RoleSystem: protoacl.Role_SYSTEM, + eacl.RoleOthers: protoacl.Role_OTHERS, } - eqV2Matches = map[eacl.Match]v2acl.MatchType{ - eacl.MatchUnspecified: v2acl.MatchTypeUnknown, - eacl.MatchStringEqual: v2acl.MatchTypeStringEqual, - eacl.MatchStringNotEqual: v2acl.MatchTypeStringNotEqual, - eacl.MatchNotPresent: v2acl.MatchTypeNotPresent, - eacl.MatchNumGT: v2acl.MatchTypeNumGT, - eacl.MatchNumGE: v2acl.MatchTypeNumGE, - eacl.MatchNumLT: v2acl.MatchTypeNumLT, - eacl.MatchNumLE: v2acl.MatchTypeNumLE, + protoMatches = map[eacl.Match]protoacl.MatchType{ + eacl.MatchUnspecified: protoacl.MatchType_MATCH_TYPE_UNSPECIFIED, + eacl.MatchStringEqual: protoacl.MatchType_STRING_EQUAL, + eacl.MatchStringNotEqual: protoacl.MatchType_STRING_NOT_EQUAL, + eacl.MatchNotPresent: protoacl.MatchType_NOT_PRESENT, + eacl.MatchNumGT: protoacl.MatchType_NUM_GT, + eacl.MatchNumGE: protoacl.MatchType_NUM_GE, + eacl.MatchNumLT: protoacl.MatchType_NUM_LT, + eacl.MatchNumLE: protoacl.MatchType_NUM_LE, } - eqV2HeaderTypes = map[eacl.FilterHeaderType]v2acl.HeaderType{ - eacl.HeaderTypeUnspecified: v2acl.HeaderTypeUnknown, - eacl.HeaderFromRequest: v2acl.HeaderTypeRequest, - eacl.HeaderFromObject: v2acl.HeaderTypeObject, - eacl.HeaderFromService: v2acl.HeaderTypeService, + protoHeaderTypes = map[eacl.FilterHeaderType]protoacl.HeaderType{ + eacl.HeaderTypeUnspecified: protoacl.HeaderType_HEADER_UNSPECIFIED, + eacl.HeaderFromRequest: protoacl.HeaderType_REQUEST, + eacl.HeaderFromObject: protoacl.HeaderType_OBJECT, + eacl.HeaderFromService: protoacl.HeaderType_SERVICE, } actionStrings = map[eacl.Action]string{ + -1: "-1", 0: "ACTION_UNSPECIFIED", eacl.ActionAllow: "ALLOW", eacl.ActionDeny: "DENY", 3: "3", } roleStrings = map[eacl.Role]string{ + -1: "-1", 0: "ROLE_UNSPECIFIED", eacl.RoleUser: "USER", eacl.RoleSystem: "SYSTEM", @@ -66,6 +68,7 @@ var ( 4: "4", } opStrings = map[eacl.Operation]string{ + -1: "-1", 0: "OPERATION_UNSPECIFIED", eacl.OperationGet: "GET", eacl.OperationHead: "HEAD", @@ -77,6 +80,7 @@ var ( 8: "8", } matcherStrings = map[eacl.Match]string{ + -1: "-1", 0: "MATCH_TYPE_UNSPECIFIED", eacl.MatchStringEqual: "STRING_EQUAL", eacl.MatchStringNotEqual: "STRING_NOT_EQUAL", @@ -88,6 +92,7 @@ var ( 8: "8", } headerTypeStrings = map[eacl.FilterHeaderType]string{ + -1: "-1", 0: "HEADER_UNSPECIFIED", eacl.HeaderFromRequest: "REQUEST", eacl.HeaderFromObject: "OBJECT", @@ -96,80 +101,6 @@ var ( } ) -func TestAction(t *testing.T) { - require.Equal(t, eacl.ActionUnspecified, eacl.ActionUnknown) - t.Run("known actions", func(t *testing.T) { - for i := eacl.ActionUnspecified; i <= eacl.ActionDeny; i++ { - require.Equal(t, eqV2Actions[i], i.ToV2()) - require.Equal(t, eacl.ActionFromV2(i.ToV2()), i) - } - }) - - t.Run("unknown actions", func(t *testing.T) { - require.EqualValues(t, 1000, eacl.Action(1000).ToV2()) - require.EqualValues(t, 1000, eacl.ActionFromV2(1000)) - }) -} - -func TestOperation(t *testing.T) { - require.Equal(t, eacl.OperationUnspecified, eacl.OperationUnknown) - t.Run("known operations", func(t *testing.T) { - for i := eacl.OperationUnspecified; i <= eacl.OperationRangeHash; i++ { - require.Equal(t, eqV2Operations[i], i.ToV2()) - require.Equal(t, eacl.OperationFromV2(i.ToV2()), i) - } - }) - - t.Run("unknown operations", func(t *testing.T) { - require.EqualValues(t, 1000, eacl.Operation(1000).ToV2()) - require.EqualValues(t, 1000, eacl.OperationFromV2(1000)) - }) -} - -func TestRole(t *testing.T) { - t.Run("known roles", func(t *testing.T) { - for i := eacl.RoleUnspecified; i <= eacl.RoleOthers; i++ { - require.Equal(t, eqV2Roles[i], i.ToV2()) - require.Equal(t, eacl.RoleFromV2(i.ToV2()), i) - } - }) - - t.Run("unknown roles", func(t *testing.T) { - require.EqualValues(t, 1000, eacl.Operation(1000).ToV2()) - require.EqualValues(t, 1000, eacl.RoleFromV2(1000)) - }) -} - -func TestMatch(t *testing.T) { - require.Equal(t, eacl.MatchUnspecified, eacl.MatchUnknown) - t.Run("known matches", func(t *testing.T) { - for i := eacl.MatchUnspecified; i <= eacl.MatchStringNotEqual; i++ { - require.Equal(t, eqV2Matches[i], i.ToV2()) - require.Equal(t, eacl.MatchFromV2(i.ToV2()), i) - } - }) - - t.Run("unknown matches", func(t *testing.T) { - require.EqualValues(t, 1000, eacl.Match(1000).ToV2()) - require.EqualValues(t, 1000, eacl.MatchFromV2(1000)) - }) -} - -func TestFilterHeaderType(t *testing.T) { - require.Equal(t, eacl.HeaderTypeUnspecified, eacl.HeaderTypeUnknown) - t.Run("known header types", func(t *testing.T) { - for i := eacl.HeaderTypeUnspecified; i <= eacl.HeaderFromService; i++ { - require.Equal(t, eqV2HeaderTypes[i], i.ToV2()) - require.Equal(t, eacl.FilterHeaderTypeFromV2(i.ToV2()), i) - } - }) - - t.Run("unknown header types", func(t *testing.T) { - require.EqualValues(t, 1000, eacl.FilterHeaderType(1000).ToV2()) - require.EqualValues(t, 1000, eacl.FilterHeaderTypeFromV2(1000)) - }) -} - type enumIface interface { DecodeString(string) bool EncodeToString() string @@ -201,11 +132,7 @@ func testEnumStrings(t *testing.T, e enumIface, items []enumStringItem) { } func TestActionProto(t *testing.T) { - for x, y := range map[v2acl.Action]eacl.Action{ - v2acl.ActionUnknown: eacl.ActionUnspecified, - v2acl.ActionAllow: eacl.ActionAllow, - v2acl.ActionDeny: eacl.ActionDeny, - } { + for x, y := range protoActions { require.EqualValues(t, x, y) } } @@ -227,7 +154,7 @@ func TestAction_String(t *testing.T) { } type enum interface { - ~uint32 + ~int32 fmt.Stringer } @@ -260,12 +187,7 @@ func TestActionFromString(t *testing.T) { } func TestRoleProto(t *testing.T) { - for x, y := range map[v2acl.Role]eacl.Role{ - v2acl.RoleUnknown: eacl.RoleUnspecified, - v2acl.RoleUser: eacl.RoleUser, - v2acl.RoleSystem: eacl.RoleSystem, - v2acl.RoleOthers: eacl.RoleOthers, - } { + for x, y := range protoRoles { require.EqualValues(t, x, y) } } @@ -296,16 +218,7 @@ func TestRoleFromString(t *testing.T) { } func TestOperationProto(t *testing.T) { - for x, y := range map[v2acl.Operation]eacl.Operation{ - v2acl.OperationUnknown: eacl.OperationUnspecified, - v2acl.OperationGet: eacl.OperationGet, - v2acl.OperationHead: eacl.OperationHead, - v2acl.OperationPut: eacl.OperationPut, - v2acl.OperationDelete: eacl.OperationDelete, - v2acl.OperationSearch: eacl.OperationSearch, - v2acl.OperationRange: eacl.OperationRange, - v2acl.OperationRangeHash: eacl.OperationRangeHash, - } { + for x, y := range protoOperations { require.EqualValues(t, x, y) } } @@ -340,16 +253,7 @@ func TestOperationFromString(t *testing.T) { } func TestMatchProto(t *testing.T) { - for x, y := range map[v2acl.MatchType]eacl.Match{ - v2acl.MatchTypeUnknown: eacl.MatchUnspecified, - v2acl.MatchTypeStringEqual: eacl.MatchStringEqual, - v2acl.MatchTypeStringNotEqual: eacl.MatchStringNotEqual, - v2acl.MatchTypeNotPresent: eacl.MatchNotPresent, - v2acl.MatchTypeNumGT: eacl.MatchNumGT, - v2acl.MatchTypeNumGE: eacl.MatchNumGE, - v2acl.MatchTypeNumLT: eacl.MatchNumLT, - v2acl.MatchTypeNumLE: eacl.MatchNumLE, - } { + for x, y := range protoMatches { require.EqualValues(t, x, y) } } @@ -384,12 +288,7 @@ func TestMatcherFromString(t *testing.T) { } func TestFilterHeaderTypeProto(t *testing.T) { - for x, y := range map[v2acl.HeaderType]eacl.FilterHeaderType{ - v2acl.HeaderTypeUnknown: eacl.FilterHeaderType(0), - v2acl.HeaderTypeRequest: eacl.HeaderFromRequest, - v2acl.HeaderTypeObject: eacl.HeaderFromObject, - v2acl.HeaderTypeService: eacl.HeaderFromService, - } { + for x, y := range protoHeaderTypes { require.EqualValues(t, x, y) } } diff --git a/eacl/filter.go b/eacl/filter.go index 8816b84a..ed7a4042 100644 --- a/eacl/filter.go +++ b/eacl/filter.go @@ -1,11 +1,13 @@ package eacl import ( + "fmt" "strconv" - v2acl "github.com/nspcc-dev/neofs-api-go/v2/acl" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" + neofsproto "github.com/nspcc-dev/neofs-sdk-go/internal/proto" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" + protoacl "github.com/nspcc-dev/neofs-sdk-go/proto/acl" "github.com/nspcc-dev/neofs-sdk-go/user" ) @@ -121,31 +123,26 @@ func (f Filter) From() FilterHeaderType { return f.from } -// ToV2 converts Filter to v2 acl.EACLRecord.Filter message. -// -// Nil Filter converts to nil. -// Deprecated: do not use it. -func (f *Filter) ToV2() *v2acl.HeaderFilter { - if f != nil { - return f.toProtoMessage() +func (f Filter) protoMessage() *protoacl.EACLRecord_Filter { + return &protoacl.EACLRecord_Filter{ + HeaderType: protoacl.HeaderType(f.from), + MatchType: protoacl.MatchType(f.matcher), + Key: f.key, + Value: f.value.EncodeToString(), } - return nil -} - -func (f Filter) toProtoMessage() *v2acl.HeaderFilter { - filter := new(v2acl.HeaderFilter) - filter.SetValue(f.value.EncodeToString()) - filter.SetKey(f.key) - filter.SetMatchType(v2acl.MatchType(f.matcher)) - filter.SetHeaderType(v2acl.HeaderType(f.from)) - return filter } -func (f *Filter) fromProtoMessage(m *v2acl.HeaderFilter) error { - f.from = FilterHeaderType(m.GetHeaderType()) - f.matcher = Match(m.GetMatchType()) - f.key = m.GetKey() - f.value = staticStringer(m.GetValue()) +func (f *Filter) fromProtoMessage(m *protoacl.EACLRecord_Filter) error { + if m.HeaderType < 0 { + return fmt.Errorf("negative header type %d", m.HeaderType) + } + if m.MatchType < 0 { + return fmt.Errorf("negative match type %d", m.MatchType) + } + f.from = FilterHeaderType(m.HeaderType) + f.matcher = Match(m.MatchType) + f.key = m.Key + f.value = staticStringer(m.Value) return nil } @@ -163,28 +160,15 @@ func NewFilter() *Filter { return &f } -// NewFilterFromV2 converts v2 acl.EACLRecord.Filter message to Filter. -// Deprecated: do not use it. -func NewFilterFromV2(filter *v2acl.HeaderFilter) *Filter { - f := new(Filter) - - if filter == nil { - return f - } - - _ = f.fromProtoMessage(filter) - return f -} - // Marshal marshals Filter into a protobuf binary form. func (f Filter) Marshal() []byte { - return f.toProtoMessage().StableMarshal(nil) + return neofsproto.MarshalMessage(f.protoMessage()) } // Unmarshal unmarshals protobuf binary representation of Filter. func (f *Filter) Unmarshal(data []byte) error { - m := new(v2acl.HeaderFilter) - if err := m.Unmarshal(data); err != nil { + m := new(protoacl.EACLRecord_Filter) + if err := neofsproto.UnmarshalMessage(data, m); err != nil { return err } return f.fromProtoMessage(m) @@ -192,13 +176,13 @@ func (f *Filter) Unmarshal(data []byte) error { // MarshalJSON encodes Filter to protobuf JSON format. func (f Filter) MarshalJSON() ([]byte, error) { - return f.toProtoMessage().MarshalJSON() + return neofsproto.MarshalMessageJSON(f.protoMessage()) } // UnmarshalJSON decodes Filter from protobuf JSON format. func (f *Filter) UnmarshalJSON(data []byte) error { - m := new(v2acl.HeaderFilter) - if err := m.UnmarshalJSON(data); err != nil { + m := new(protoacl.EACLRecord_Filter) + if err := neofsproto.UnmarshalMessageJSON(data, m); err != nil { return err } return f.fromProtoMessage(m) diff --git a/eacl/filter_test.go b/eacl/filter_test.go index 7ef745cb..05a4090b 100644 --- a/eacl/filter_test.go +++ b/eacl/filter_test.go @@ -2,11 +2,8 @@ package eacl_test import ( "encoding/json" - "math/rand/v2" - "strconv" "testing" - protoacl "github.com/nspcc-dev/neofs-api-go/v2/acl" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" "github.com/nspcc-dev/neofs-sdk-go/eacl" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" @@ -14,58 +11,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestFilter_ToV2(t *testing.T) { - require.Nil(t, (*eacl.Filter)(nil).ToV2()) - key := "key_" + strconv.Itoa(rand.Int()) - val := "val_" + strconv.Itoa(rand.Int()) - r := eacl.ConstructFilter(anyValidHeaderType, key, anyValidMatcher, val) - m := r.ToV2() - require.EqualValues(t, anyValidHeaderType, m.GetHeaderType()) - require.Equal(t, key, m.GetKey()) - require.EqualValues(t, anyValidMatcher, m.GetMatchType()) - require.Equal(t, val, m.GetValue()) - - t.Run("default values", func(t *testing.T) { - filter := eacl.NewFilter() - - // check initial values - require.Empty(t, filter.Key()) - require.Empty(t, filter.Value()) - require.Zero(t, filter.From()) - require.Zero(t, filter.Matcher()) - - // convert to v2 message - filterV2 := filter.ToV2() - - require.Empty(t, filterV2.GetKey()) - require.Empty(t, filterV2.GetValue()) - require.Zero(t, filterV2.GetHeaderType()) - require.Zero(t, filterV2.GetMatchType()) - }) -} - -func TestNewFilterFromV2(t *testing.T) { - typ := protoacl.HeaderType(rand.Uint32()) - key := "key_" + strconv.Itoa(rand.Int()) - op := protoacl.MatchType(rand.Uint32()) - val := "val_" + strconv.Itoa(rand.Int()) - var m protoacl.HeaderFilter - m.SetHeaderType(typ) - m.SetKey(key) - m.SetMatchType(op) - m.SetValue(val) - - f := eacl.NewFilterFromV2(&m) - require.EqualValues(t, typ, f.From()) - require.Equal(t, key, f.Key()) - require.EqualValues(t, op, f.Matcher()) - require.Equal(t, val, f.Value()) - - t.Run("nil", func(t *testing.T) { - require.Equal(t, new(eacl.Filter), eacl.NewFilterFromV2(nil)) - }) -} - func TestFilter_Marshal(t *testing.T) { for i := range anyValidFilters { require.Equal(t, anyValidBinFilters[i], anyValidFilters[i].Marshal(), i) @@ -82,7 +27,6 @@ func TestFilter_Unmarshal(t *testing.T) { var f eacl.Filter for i := range anyValidBinFilters { require.NoError(t, f.Unmarshal(anyValidBinFilters[i]), i) - t.Skip("https://github.com/nspcc-dev/neofs-sdk-go/issues/606") require.EqualValues(t, anyValidFilters[i], f, i) } } @@ -99,7 +43,6 @@ func TestFilter_MarshalJSON(t *testing.T) { b, err := anyValidFilters[i].MarshalJSON() require.NoError(t, err, i) require.NoError(t, f1.UnmarshalJSON(b), i) - t.Skip("https://github.com/nspcc-dev/neofs-sdk-go/issues/606") require.Equal(t, anyValidFilters[i], f1, i) b, err = json.Marshal(anyValidFilters[i]) @@ -113,7 +56,6 @@ func TestFilter_UnmarshalJSON(t *testing.T) { var f1, f2 eacl.Filter for i := range anyValidJSONFilters { require.NoError(t, f1.UnmarshalJSON([]byte(anyValidJSONFilters[i])), i) - t.Skip("https://github.com/nspcc-dev/neofs-sdk-go/issues/606") require.Equal(t, anyValidFilters[i], f1, i) require.NoError(t, json.Unmarshal([]byte(anyValidJSONFilters[i]), &f2), i) diff --git a/eacl/record.go b/eacl/record.go index a0de31bc..9c04b366 100644 --- a/eacl/record.go +++ b/eacl/record.go @@ -5,11 +5,12 @@ import ( "fmt" "slices" - v2acl "github.com/nspcc-dev/neofs-api-go/v2/acl" "github.com/nspcc-dev/neofs-sdk-go/checksum" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" + neofsproto "github.com/nspcc-dev/neofs-sdk-go/internal/proto" "github.com/nspcc-dev/neofs-sdk-go/object" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" + protoacl "github.com/nspcc-dev/neofs-sdk-go/proto/acl" "github.com/nspcc-dev/neofs-sdk-go/user" "github.com/nspcc-dev/neofs-sdk-go/version" ) @@ -213,63 +214,61 @@ func (r *Record) AddObjectHomomorphicHashFilter(m Match, h checksum.Checksum) { r.SetFilters(append(r.Filters(), NewObjectPropertyFilter(FilterObjectPayloadHomomorphicChecksum, m, h.String()))) } -// ToV2 converts Record to v2 acl.EACLRecord message. -// -// Nil Record converts to nil. -// Deprecated: do not use it. -func (r *Record) ToV2() *v2acl.Record { - if r != nil { - return r.toProtoMessage() +func (r Record) toProtoMessage() *protoacl.EACLRecord { + m := &protoacl.EACLRecord{ + Operation: protoacl.Operation(r.operation), + Action: protoacl.Action(r.action), } - return nil -} - -func (r Record) toProtoMessage() *v2acl.Record { - v2 := new(v2acl.Record) if r.targets != nil { - targets := make([]v2acl.Target, len(r.targets)) + m.Targets = make([]*protoacl.EACLRecord_Target, len(r.targets)) for i := range r.targets { - targets[i] = *r.targets[i].toProtoMessage() + m.Targets[i] = r.targets[i].protoMessage() } - - v2.SetTargets(targets) } if r.filters != nil { - filters := make([]v2acl.HeaderFilter, len(r.filters)) + m.Filters = make([]*protoacl.EACLRecord_Filter, len(r.filters)) for i := range r.filters { - filters[i] = *r.filters[i].toProtoMessage() + m.Filters[i] = r.filters[i].protoMessage() } - - v2.SetFilters(filters) } - v2.SetAction(v2acl.Action(r.action)) - v2.SetOperation(v2acl.Operation(r.operation)) - - return v2 + return m } -func (r *Record) fromProtoMessage(m *v2acl.Record) error { - mt := m.GetTargets() +func (r *Record) fromProtoMessage(m *protoacl.EACLRecord) error { + if m.Action < 0 { + return fmt.Errorf("negative action %d", m.Action) + } + if m.Operation < 0 { + return fmt.Errorf("negative op %d", m.Operation) + } + + mt := m.Targets r.targets = make([]Target, len(mt)) for i := range mt { - if err := r.targets[i].fromProtoMessage(&mt[i]); err != nil { + if mt[i] == nil { + return fmt.Errorf("nil target #%d", i) + } + if err := r.targets[i].fromProtoMessage(mt[i]); err != nil { return fmt.Errorf("invalid subject descriptor #%d: %w", i, err) } } - mf := m.GetFilters() + mf := m.Filters r.filters = make([]Filter, len(mf)) for i := range mf { - if err := r.filters[i].fromProtoMessage(&mf[i]); err != nil { + if mf[i] == nil { + return fmt.Errorf("nil filter #%d", i) + } + if err := r.filters[i].fromProtoMessage(mf[i]); err != nil { return fmt.Errorf("invalid filter #%d: %w", i, err) } } - r.action = Action(m.GetAction()) - r.operation = Operation(m.GetOperation()) + r.action = Action(m.Action) + r.operation = Operation(m.Operation) return nil } @@ -299,28 +298,15 @@ func CreateRecord(action Action, operation Operation) *Record { return r } -// NewRecordFromV2 converts v2 acl.EACLRecord message to Record. -// Deprecated: do not use it. -func NewRecordFromV2(record *v2acl.Record) *Record { - r := NewRecord() - - if record == nil { - return r - } - - _ = r.fromProtoMessage(record) - return r -} - // Marshal marshals Record into a protobuf binary form. func (r Record) Marshal() []byte { - return r.toProtoMessage().StableMarshal(nil) + return neofsproto.MarshalMessage(r.toProtoMessage()) } // Unmarshal unmarshals protobuf binary representation of Record. func (r *Record) Unmarshal(data []byte) error { - m := new(v2acl.Record) - if err := m.Unmarshal(data); err != nil { + m := new(protoacl.EACLRecord) + if err := neofsproto.UnmarshalMessage(data, m); err != nil { return err } return r.fromProtoMessage(m) @@ -328,13 +314,13 @@ func (r *Record) Unmarshal(data []byte) error { // MarshalJSON encodes Record to protobuf JSON format. func (r Record) MarshalJSON() ([]byte, error) { - return r.toProtoMessage().MarshalJSON() + return neofsproto.MarshalMessageJSON(r.toProtoMessage()) } // UnmarshalJSON decodes Record from protobuf JSON format. func (r *Record) UnmarshalJSON(data []byte) error { - m := new(v2acl.Record) - if err := m.UnmarshalJSON(data); err != nil { + m := new(protoacl.EACLRecord) + if err := neofsproto.UnmarshalMessageJSON(data, m); err != nil { return err } return r.fromProtoMessage(m) diff --git a/eacl/record_test.go b/eacl/record_test.go index 748092e4..fbcb95b5 100644 --- a/eacl/record_test.go +++ b/eacl/record_test.go @@ -3,10 +3,8 @@ package eacl_test import ( "encoding/json" "math/rand/v2" - "strconv" "testing" - protoacl "github.com/nspcc-dev/neofs-api-go/v2/acl" "github.com/nspcc-dev/neofs-sdk-go/eacl" "github.com/stretchr/testify/require" ) @@ -20,7 +18,7 @@ func TestAddFormedTarget(t *testing.T) { require.Zero(t, r.Targets()[0].Role()) require.Equal(t, anyValidECDSABinPublicKeys, r.Targets()[0].BinaryKeys()) - role := eacl.Role(rand.Uint32()) + role := eacl.Role(rand.Int32()) eacl.AddFormedTarget(&r, role) require.Len(t, r.Targets(), 2) require.Equal(t, role, r.Targets()[1].Role()) @@ -36,66 +34,6 @@ func TestRecord_AddFilter(t *testing.T) { require.Equal(t, anyValidFilters, r.Filters()) } -func TestRecord_ToV2(t *testing.T) { - require.Nil(t, (*eacl.Record)(nil).ToV2()) - r := eacl.ConstructRecord(anyValidAction, anyValidOp, anyValidTargets, anyValidFilters...) - m := r.ToV2() - require.EqualValues(t, anyValidAction, m.GetAction()) - require.EqualValues(t, anyValidOp, m.GetOperation()) - assertProtoTargetsEqual(t, anyValidTargets, m.GetTargets()) - assertProtoFiltersEqual(t, anyValidFilters, m.GetFilters()) - - t.Run("default values", func(t *testing.T) { - record := eacl.NewRecord() - - // check initial values - require.Zero(t, record.Operation()) - require.Zero(t, record.Action()) - require.Nil(t, record.Targets()) - require.Nil(t, record.Filters()) - - // convert to v2 message - recordV2 := record.ToV2() - - require.Zero(t, recordV2.GetOperation()) - require.Zero(t, recordV2.GetAction()) - require.Nil(t, recordV2.GetTargets()) - require.Nil(t, recordV2.GetFilters()) - }) -} - -func TestNewRecordFromV2(t *testing.T) { - a := protoacl.Action(rand.Uint32()) - op := protoacl.Operation(rand.Uint32()) - ts := make([]protoacl.Target, 2) - for i := range ts { - ts[i].SetRole(protoacl.Role(rand.Uint32())) - ts[i].SetKeys(anyValidBinPublicKeys) - } - fs := make([]protoacl.HeaderFilter, 2) - for i := range fs { - fs[i].SetHeaderType(protoacl.HeaderType(rand.Uint32())) - fs[i].SetKey("key_" + strconv.Itoa(rand.Int())) - fs[i].SetMatchType(protoacl.MatchType(rand.Uint32())) - fs[i].SetValue("val_" + strconv.Itoa(rand.Int())) - } - var m protoacl.Record - m.SetAction(a) - m.SetOperation(op) - m.SetTargets(ts) - m.SetFilters(fs) - - r := eacl.NewRecordFromV2(&m) - require.EqualValues(t, a, r.Action()) - require.EqualValues(t, op, r.Operation()) - assertProtoTargetsEqual(t, r.Targets(), ts) - assertProtoFiltersEqual(t, r.Filters(), fs) - - t.Run("nil", func(t *testing.T) { - require.Equal(t, new(eacl.Record), eacl.NewRecordFromV2(nil)) - }) -} - func TestRecord_Marshal(t *testing.T) { for i := range anyValidRecords { require.Equal(t, anyValidBinRecords[i], anyValidRecords[i].Marshal(), i) @@ -112,7 +50,6 @@ func TestRecord_Unmarshal(t *testing.T) { var r eacl.Record for i := range anyValidBinRecords { require.NoError(t, r.Unmarshal(anyValidBinRecords[i]), i) - t.Skip("https://github.com/nspcc-dev/neofs-sdk-go/issues/606") require.EqualValues(t, anyValidRecords[i], r, i) } } @@ -129,7 +66,6 @@ func TestRecord_MarshalJSON(t *testing.T) { b, err := anyValidRecords[i].MarshalJSON() require.NoError(t, err, i) require.NoError(t, r1.UnmarshalJSON(b), i) - t.Skip("https://github.com/nspcc-dev/neofs-sdk-go/issues/606") require.Equal(t, anyValidRecords[i], r1, i) b, err = json.Marshal(anyValidRecords[i]) @@ -143,11 +79,10 @@ func TestRecord_UnmarshalJSON(t *testing.T) { var r1, r2 eacl.Record for i := range anyValidJSONRecords { require.NoError(t, r1.UnmarshalJSON([]byte(anyValidJSONRecords[i])), i) - t.Skip("https://github.com/nspcc-dev/neofs-sdk-go/issues/606") - require.Equal(t, anyValidFilters[i], r1, i) + require.Equal(t, anyValidRecords[i], r1, i) require.NoError(t, json.Unmarshal([]byte(anyValidJSONRecords[i]), &r2), i) - require.Equal(t, anyValidJSONRecords[i], r2, i) + require.Equal(t, r1, r2, i) } } diff --git a/eacl/table.go b/eacl/table.go index 8cc8f737..da4a08d4 100644 --- a/eacl/table.go +++ b/eacl/table.go @@ -4,15 +4,17 @@ import ( "bytes" "fmt" - v2acl "github.com/nspcc-dev/neofs-api-go/v2/acl" - "github.com/nspcc-dev/neofs-api-go/v2/refs" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" + neofsproto "github.com/nspcc-dev/neofs-sdk-go/internal/proto" + protoacl "github.com/nspcc-dev/neofs-sdk-go/proto/acl" "github.com/nspcc-dev/neofs-sdk-go/version" ) +var zeroVersion version.Version + // Table is a group of ContainerEACL records for single container. // -// Table is compatible with v2 acl.EACLTable message. +// Table is compatible with v2 [protoacl.EACLTable] message. // // Table should be created using one of the constructors. type Table struct { @@ -108,18 +110,14 @@ func (t *Table) AddRecord(r *Record) { } } -// ReadFromV2 reads Table from the [v2acl.Table] message. Returns an error if -// the message is malformed according to the NeoFS API V2 protocol. The message -// must not be nil. -// -// ReadFromV2 is intended to be used by the NeoFS API V2 client/server -// implementation only and is not expected to be directly used by applications. +// FromProtoMessage validates m according to the NeoFS API protocol and restores +// t from it. // -// See also [Table.ToV2]. -func (t *Table) ReadFromV2(m v2acl.Table) error { +// See also [Table.ProtoMessage]. +func (t *Table) FromProtoMessage(m *protoacl.EACLTable) error { // set container id - if id := m.GetContainerID(); id != nil { - if err := t.cid.ReadFromV2(*id); err != nil { + if m.ContainerId != nil { + if err := t.cid.FromProtoMessage(m.ContainerId); err != nil { return fmt.Errorf("invalid container ID: %w", err) } } else { @@ -127,20 +125,21 @@ func (t *Table) ReadFromV2(m v2acl.Table) error { } // set version - if v := m.GetVersion(); v != nil { - ver := version.Version{} - ver.SetMajor(v.GetMajor()) - ver.SetMinor(v.GetMinor()) - - t.SetVersion(ver) + if m.Version != nil { + if err := t.version.FromProtoMessage(m.Version); err != nil { + return fmt.Errorf("invalid version: %w", err) + } } // set eacl records - v2records := m.GetRecords() - t.records = make([]Record, len(v2records)) + rs := m.Records + t.records = make([]Record, len(rs)) - for i := range v2records { - if err := t.records[i].fromProtoMessage(&v2records[i]); err != nil { + for i := range rs { + if rs[i] == nil { + return fmt.Errorf("nil record #%d", i) + } + if err := t.records[i].fromProtoMessage(rs[i]); err != nil { return fmt.Errorf("invalid record #%d: %w", i, err) } } @@ -148,34 +147,28 @@ func (t *Table) ReadFromV2(m v2acl.Table) error { return nil } -// ToV2 converts Table to v2 acl.EACLTable message. -// -// Nil Table converts to nil. +// ProtoMessage converts t into message to transmit using the NeoFS API +// protocol. // -// See also [Table.ReadFromV2]. -func (t Table) ToV2() *v2acl.Table { - v2 := new(v2acl.Table) - var cidV2 refs.ContainerID - +// See also [Table.FromProtoMessage]. +func (t Table) ProtoMessage() *protoacl.EACLTable { + m := new(protoacl.EACLTable) if !t.cid.IsZero() { - t.cid.WriteToV2(&cidV2) - v2.SetContainerID(&cidV2) + m.ContainerId = t.cid.ProtoMessage() } if t.records != nil { - records := make([]v2acl.Record, len(t.records)) + m.Records = make([]*protoacl.EACLRecord, len(t.records)) for i := range t.records { - records[i] = *t.records[i].toProtoMessage() + m.Records[i] = t.records[i].toProtoMessage() } - - v2.SetRecords(records) } - var verV2 refs.Version - t.version.WriteToV2(&verV2) - v2.SetVersion(&verV2) + if t.version != zeroVersion { + m.Version = t.version.ProtoMessage() + } - return v2 + return m } // NewTable creates, initializes and returns blank Table instance. @@ -198,45 +191,9 @@ func CreateTable(cid cid.ID) *Table { return &t } -// NewTableFromV2 converts v2 acl.EACLTable message to Table. -// -// Deprecated: BUG: container ID length is not checked. Use [Table.ReadFromV2] -// instead. -func NewTableFromV2(table *v2acl.Table) *Table { - t := new(Table) - - if table == nil { - return t - } - - // set version - if v := table.GetVersion(); v != nil { - ver := version.Version{} - ver.SetMajor(v.GetMajor()) - ver.SetMinor(v.GetMinor()) - - t.SetVersion(ver) - } - - // set container id - if id := table.GetContainerID(); id != nil { - copy(t.cid[:], id.GetValue()) - } - - // set eacl records - v2records := table.GetRecords() - t.records = make([]Record, len(v2records)) - - for i := range v2records { - _ = t.records[i].fromProtoMessage(&v2records[i]) - } - - return t -} - // Marshal marshals Table into a protobuf binary form. func (t Table) Marshal() []byte { - return t.ToV2().StableMarshal(nil) + return neofsproto.Marshal(t) } // SignedData returns actual payload to sign. @@ -249,26 +206,18 @@ func (t Table) SignedData() []byte { // Unmarshal unmarshals protobuf binary representation of Table. Use [Unmarshal] // to decode data into a new Table. func (t *Table) Unmarshal(data []byte) error { - var m v2acl.Table - if err := m.Unmarshal(data); err != nil { - return err - } - return t.ReadFromV2(m) + return neofsproto.Unmarshal(data, t) } // MarshalJSON encodes Table to protobuf JSON format. func (t Table) MarshalJSON() ([]byte, error) { - return t.ToV2().MarshalJSON() + return neofsproto.MarshalJSON(t) } // UnmarshalJSON decodes Table from protobuf JSON format. Use [UnmarshalJSON] to // decode data into a new Table. func (t *Table) UnmarshalJSON(data []byte) error { - var m v2acl.Table - if err := m.UnmarshalJSON(data); err != nil { - return err - } - return t.ReadFromV2(m) + return neofsproto.UnmarshalJSON(data, t) } // EqualTables compares Table with each other. @@ -278,5 +227,5 @@ func EqualTables(t1, t2 Table) bool { return bytes.Equal(t1.Marshal(), t2.Marsha // IsZero checks whether all fields of the table are zero/empty. The property // can be used as a marker of unset eACL. func (t Table) IsZero() bool { - return t.cid.IsZero() && len(t.records) == 0 && t.version == version.Version{} + return t.cid.IsZero() && len(t.records) == 0 && t.version == zeroVersion } diff --git a/eacl/table_test.go b/eacl/table_test.go index 63f56683..993dccaf 100644 --- a/eacl/table_test.go +++ b/eacl/table_test.go @@ -6,36 +6,59 @@ import ( "strconv" "testing" - protoacl "github.com/nspcc-dev/neofs-api-go/v2/acl" - "github.com/nspcc-dev/neofs-api-go/v2/refs" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test" "github.com/nspcc-dev/neofs-sdk-go/eacl" - "github.com/nspcc-dev/neofs-sdk-go/version" + protoacl "github.com/nspcc-dev/neofs-sdk-go/proto/acl" + "github.com/nspcc-dev/neofs-sdk-go/proto/refs" "github.com/stretchr/testify/require" ) var invalidProtoEACLTestcases = []struct { name string err string - corrupt func(*protoacl.Table) + corrupt func(*protoacl.EACLTable) }{ - {name: "invalid container/nil value", err: "invalid container ID: invalid length 0", - corrupt: func(m *protoacl.Table) { m.SetContainerID(new(refs.ContainerID)) }}, - {name: "invalid container/empty value", err: "invalid container ID: invalid length 0", corrupt: func(m *protoacl.Table) { - var mc refs.ContainerID - mc.SetValue([]byte{}) - m.SetContainerID(&mc) + {name: "container/nil value", err: "invalid container ID: invalid length 0", + corrupt: func(m *protoacl.EACLTable) { m.ContainerId = new(refs.ContainerID) }}, + {name: "container/value/empty", err: "invalid container ID: invalid length 0", corrupt: func(m *protoacl.EACLTable) { + m.ContainerId = &refs.ContainerID{Value: []byte{}} }}, - {name: "invalid container/undersized value", err: "invalid container ID: invalid length 31", corrupt: func(m *protoacl.Table) { - var mc refs.ContainerID - mc.SetValue(make([]byte, 31)) - m.SetContainerID(&mc) + {name: "container/undersized value", err: "invalid container ID: invalid length 31", corrupt: func(m *protoacl.EACLTable) { + m.ContainerId = &refs.ContainerID{Value: make([]byte, 31)} }}, - {name: "invalid container/oversized value", err: "invalid container ID: invalid length 33", corrupt: func(m *protoacl.Table) { - var mc refs.ContainerID - mc.SetValue(make([]byte, 33)) - m.SetContainerID(&mc) + {name: "container/oversized value", err: "invalid container ID: invalid length 33", corrupt: func(m *protoacl.EACLTable) { + m.ContainerId = &refs.ContainerID{Value: make([]byte, 33)} + }}, + {name: "container/zero", err: "invalid container ID: zero container ID", corrupt: func(m *protoacl.EACLTable) { + m.ContainerId = &refs.ContainerID{Value: make([]byte, 32)} + }}, + {name: "record/zero", err: "invalid container ID: zero container ID", corrupt: func(m *protoacl.EACLTable) { + m.ContainerId = &refs.ContainerID{Value: make([]byte, 32)} + }}, + {name: "records/nil element", err: "nil record #1", corrupt: func(m *protoacl.EACLTable) { + m.Records[1] = nil + }}, + {name: "records/negative action", err: "invalid record #1: negative action -1", corrupt: func(m *protoacl.EACLTable) { + m.Records[1].Action = -1 + }}, + {name: "records/negative op", err: "invalid record #1: negative op -1", corrupt: func(m *protoacl.EACLTable) { + m.Records[1].Operation = -1 + }}, + {name: "records/filters/nil element", err: "invalid record #1: nil filter #1", corrupt: func(m *protoacl.EACLTable) { + m.Records[1].Filters[1] = nil + }}, + {name: "records/filters/negative header type", err: "invalid record #1: invalid filter #1: negative header type -1", corrupt: func(m *protoacl.EACLTable) { + m.Records[1].Filters[1].HeaderType = -1 + }}, + {name: "records/filters/negative match type", err: "invalid record #1: invalid filter #1: negative match type -1", corrupt: func(m *protoacl.EACLTable) { + m.Records[1].Filters[1].MatchType = -1 + }}, + {name: "records/targets/nil element", err: "invalid record #1: nil target #1", corrupt: func(m *protoacl.EACLTable) { + m.Records[1].Targets[1] = nil + }}, + {name: "records/targets/negative role", err: "invalid record #1: invalid subject descriptor #1: negative role -1", corrupt: func(m *protoacl.EACLTable) { + m.Records[1].Targets[1].Role = -1 }}, } @@ -48,44 +71,6 @@ func TestTable_AddRecord(t *testing.T) { } } -func TestTable_ToV2(t *testing.T) { - assert := func(t testing.TB, tbl eacl.Table, m *protoacl.Table) { - require.EqualValues(t, 2, tbl.Version().Major()) - require.EqualValues(t, 16, tbl.Version().Minor()) - require.Len(t, m.GetRecords(), len(tbl.Records())) - assertProtoRecordsEqual(t, tbl.Records(), m.GetRecords()) - } - - tbl := eacl.ConstructTable(anyValidRecords) - assert(t, tbl, tbl.ToV2()) - - t.Run("with container", func(t *testing.T) { - cnr := cidtest.ID() - tbl = eacl.NewTableForContainer(cnr, anyValidRecords) - m := tbl.ToV2() - assert(t, tbl, m) - require.Equal(t, cnr[:], m.GetContainerID().GetValue()) - }) - t.Run("default values", func(t *testing.T) { - table := eacl.NewTable() - - // check initial values - require.Equal(t, version.Current(), table.Version()) - require.Nil(t, table.Records()) - _, set := table.CID() - require.False(t, set) - - // convert to v2 message - tableV2 := table.ToV2() - - var verV2 refs.Version - version.Current().WriteToV2(&verV2) - require.Equal(t, verV2, *tableV2.GetVersion()) - require.Nil(t, tableV2.GetRecords()) - require.Nil(t, tableV2.GetContainerID()) - }) -} - func TestTable_LimitToContainer(t *testing.T) { cnr := cidtest.ID() var tbl eacl.Table @@ -112,109 +97,57 @@ func TestTable_SetCID(t *testing.T) { require.Equal(t, cnr, tbl.GetCID()) } -func TestNewTableFromV2(t *testing.T) { - var ver refs.Version - ver.SetMajor(rand.Uint32()) - ver.SetMinor(rand.Uint32()) - - bCnr := make([]byte, cid.Size) - //nolint:staticcheck - rand.Read(bCnr) - var cnr refs.ContainerID - cnr.SetValue(bCnr) - - rs := make([]protoacl.Record, 2) - for i := range rs { - ts := make([]protoacl.Target, 2) - for i := range ts { - ts[i].SetRole(protoacl.Role(rand.Uint32())) - ts[i].SetKeys(anyValidBinPublicKeys) - } - fs := make([]protoacl.HeaderFilter, 2) - for i := range fs { - fs[i].SetHeaderType(protoacl.HeaderType(rand.Uint32())) - fs[i].SetKey("key_" + strconv.Itoa(rand.Int())) - fs[i].SetMatchType(protoacl.MatchType(rand.Uint32())) - fs[i].SetValue("val_" + strconv.Itoa(rand.Int())) - } - rs[i].SetAction(protoacl.Action(rand.Uint32())) - rs[i].SetOperation(protoacl.Operation(rand.Uint32())) - rs[i].SetTargets(ts) - rs[i].SetFilters(fs) +func TestTable_FromProtoMessage(t *testing.T) { + m := &protoacl.EACLTable{ + Version: &refs.Version{ + Major: rand.Uint32(), + Minor: rand.Uint32(), + }, + ContainerId: &refs.ContainerID{Value: make([]byte, cid.Size)}, + Records: []*protoacl.EACLRecord{{}, {}}, } - - var m protoacl.Table - m.SetVersion(&ver) - m.SetContainerID(&cnr) - m.SetRecords(rs) - - tbl := eacl.NewTableFromV2(&m) - require.EqualValues(t, ver.GetMajor(), tbl.Version().Major()) - require.EqualValues(t, ver.GetMinor(), tbl.Version().Minor()) - resCnr, ok := tbl.CID() - require.True(t, ok) - require.Equal(t, bCnr, resCnr[:]) - assertProtoRecordsEqual(t, tbl.Records(), rs) - - t.Run("nil", func(t *testing.T) { - require.Equal(t, new(eacl.Table), eacl.NewTableFromV2(nil)) - }) -} - -func TestTable_ReadFromV2(t *testing.T) { - var ver refs.Version - ver.SetMajor(rand.Uint32()) - ver.SetMinor(rand.Uint32()) - - bCnr := make([]byte, cid.Size) //nolint:staticcheck - rand.Read(bCnr) - var cnr refs.ContainerID - cnr.SetValue(bCnr) - - rs := make([]protoacl.Record, 2) - for i := range rs { - ts := make([]protoacl.Target, 2) - for i := range ts { - ts[i].SetRole(protoacl.Role(rand.Uint32())) - ts[i].SetKeys(anyValidBinPublicKeys) + rand.Read(m.ContainerId.Value) + + for _, r := range m.Records { + r.Targets = make([]*protoacl.EACLRecord_Target, 2) + for i := range r.Targets { + r.Targets[i] = &protoacl.EACLRecord_Target{ + Role: protoacl.Role(rand.Int31()), + Keys: anyValidBinPublicKeys, + } } - fs := make([]protoacl.HeaderFilter, 2) - for i := range fs { - fs[i].SetHeaderType(protoacl.HeaderType(rand.Uint32())) - fs[i].SetKey("key_" + strconv.Itoa(rand.Int())) - fs[i].SetMatchType(protoacl.MatchType(rand.Uint32())) - fs[i].SetValue("val_" + strconv.Itoa(rand.Int())) + r.Filters = make([]*protoacl.EACLRecord_Filter, 2) + for i := range r.Filters { + r.Filters[i] = &protoacl.EACLRecord_Filter{ + HeaderType: protoacl.HeaderType(rand.Int31()), + MatchType: protoacl.MatchType(rand.Int31()), + Key: "key_" + strconv.Itoa(rand.Int()), + Value: "val_" + strconv.Itoa(rand.Int()), + } } - rs[i].SetAction(protoacl.Action(rand.Uint32())) - rs[i].SetOperation(protoacl.Operation(rand.Uint32())) - rs[i].SetTargets(ts) - rs[i].SetFilters(fs) + r.Action = protoacl.Action(rand.Int31()) + r.Operation = protoacl.Operation(rand.Int31()) } - var m protoacl.Table - m.SetVersion(&ver) - m.SetContainerID(&cnr) - m.SetRecords(rs) - var tbl eacl.Table - require.NoError(t, tbl.ReadFromV2(m)) - require.EqualValues(t, ver.GetMajor(), tbl.Version().Major()) - require.EqualValues(t, ver.GetMinor(), tbl.Version().Minor()) - require.EqualValues(t, bCnr, tbl.GetCID()) - assertProtoRecordsEqual(t, tbl.Records(), rs) - - m.SetContainerID(nil) - require.NoError(t, tbl.ReadFromV2(m)) + require.NoError(t, tbl.FromProtoMessage(m)) + require.EqualValues(t, m.Version.GetMajor(), tbl.Version().Major()) + require.EqualValues(t, m.Version.GetMinor(), tbl.Version().Minor()) + require.EqualValues(t, m.ContainerId.Value, tbl.GetCID()) + assertProtoRecordsEqual(t, tbl.Records(), m.Records) + + m.ContainerId = nil + require.NoError(t, tbl.FromProtoMessage(m)) require.Zero(t, tbl.GetCID()) t.Run("invalid", func(t *testing.T) { for _, tc := range invalidProtoEACLTestcases { t.Run(tc.name, func(t *testing.T) { - m := anyValidEACL.ToV2() + m := anyValidEACL.ProtoMessage() require.NotNil(t, m) tc.corrupt(m) - require.EqualError(t, new(eacl.Table).ReadFromV2(*m), tc.err) + require.EqualError(t, new(eacl.Table).FromProtoMessage(m), tc.err) }) } }) @@ -237,7 +170,6 @@ func testUnmarshalTableFunc(t *testing.T, f func(*eacl.Table, []byte) error) { var tbl eacl.Table require.NoError(t, f(&tbl, anyValidEACLBytes)) - t.Skip("https://github.com/nspcc-dev/neofs-sdk-go/issues/606") require.Equal(t, anyValidEACL, tbl) } @@ -260,7 +192,6 @@ func TestTable_MarshalJSON(t *testing.T) { b, err := anyValidEACL.MarshalJSON() require.NoError(t, err) require.NoError(t, tbl1.UnmarshalJSON(b)) - t.Skip("https://github.com/nspcc-dev/neofs-sdk-go/issues/606") require.Equal(t, anyValidEACL, tbl1) var tbl2 eacl.Table @@ -279,7 +210,6 @@ func testUnmarshalTableJSONFunc(t *testing.T, f func(*eacl.Table, []byte) error) var tbl eacl.Table require.NoError(t, f(&tbl, []byte(anyValidEACLJSON))) - t.Skip("https://github.com/nspcc-dev/neofs-sdk-go/issues/606") require.Equal(t, anyValidEACL, tbl) } diff --git a/eacl/target.go b/eacl/target.go index a029e935..52ca27f0 100644 --- a/eacl/target.go +++ b/eacl/target.go @@ -3,10 +3,12 @@ package eacl import ( "bytes" "crypto/ecdsa" + "fmt" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/util" - v2acl "github.com/nspcc-dev/neofs-api-go/v2/acl" + neofsproto "github.com/nspcc-dev/neofs-sdk-go/internal/proto" + protoacl "github.com/nspcc-dev/neofs-sdk-go/proto/acl" "github.com/nspcc-dev/neofs-sdk-go/user" ) @@ -200,28 +202,19 @@ func (t Target) Role() Role { return t.role } -// ToV2 converts Target to v2 acl.EACLRecord.Target message. -// -// Nil Target converts to nil. -// Deprecated: do not use it. -func (t *Target) ToV2() *v2acl.Target { - if t != nil { - return t.toProtoMessage() +func (t Target) protoMessage() *protoacl.EACLRecord_Target { + return &protoacl.EACLRecord_Target{ + Role: protoacl.Role(t.role), + Keys: t.subjs, } - return nil -} - -func (t Target) toProtoMessage() *v2acl.Target { - target := new(v2acl.Target) - target.SetRole(v2acl.Role(t.role)) - target.SetKeys(t.subjs) - - return target } -func (t *Target) fromProtoMessage(m *v2acl.Target) error { - t.role = Role(m.GetRole()) - t.subjs = m.GetKeys() +func (t *Target) fromProtoMessage(m *protoacl.EACLRecord_Target) error { + if m.Role < 0 { + return fmt.Errorf("negative role %d", m.Role) + } + t.role = Role(m.Role) + t.subjs = m.Keys return nil } @@ -234,23 +227,15 @@ func (t *Target) fromProtoMessage(m *v2acl.Target) error { // Deprecated: use [NewTargetByRole] or [TargetByPublicKeys] instead. func NewTarget() *Target { return new(Target) } -// NewTargetFromV2 converts v2 acl.EACLRecord.Target message to Target. -// Deprecated: do not use it. -func NewTargetFromV2(target *v2acl.Target) *Target { - t := new(Target) - _ = t.fromProtoMessage(target) - return t -} - // Marshal marshals Target into a protobuf binary form. func (t Target) Marshal() []byte { - return t.toProtoMessage().StableMarshal(nil) + return neofsproto.MarshalMessage(t.protoMessage()) } // Unmarshal unmarshals protobuf binary representation of Target. func (t *Target) Unmarshal(data []byte) error { - m := new(v2acl.Target) - if err := m.Unmarshal(data); err != nil { + m := new(protoacl.EACLRecord_Target) + if err := neofsproto.UnmarshalMessage(data, m); err != nil { return err } return t.fromProtoMessage(m) @@ -258,13 +243,13 @@ func (t *Target) Unmarshal(data []byte) error { // MarshalJSON encodes Target to protobuf JSON format. func (t Target) MarshalJSON() ([]byte, error) { - return t.toProtoMessage().MarshalJSON() + return neofsproto.MarshalMessageJSON(t.protoMessage()) } // UnmarshalJSON decodes Target from protobuf JSON format. func (t *Target) UnmarshalJSON(data []byte) error { - m := new(v2acl.Target) - if err := m.UnmarshalJSON(data); err != nil { + m := new(protoacl.EACLRecord_Target) + if err := neofsproto.UnmarshalMessageJSON(data, m); err != nil { return err } return t.fromProtoMessage(m) diff --git a/eacl/target_test.go b/eacl/target_test.go index e6632d19..8946a257 100644 --- a/eacl/target_test.go +++ b/eacl/target_test.go @@ -7,57 +7,12 @@ import ( "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/util" - protoacl "github.com/nspcc-dev/neofs-api-go/v2/acl" "github.com/nspcc-dev/neofs-sdk-go/eacl" "github.com/nspcc-dev/neofs-sdk-go/user" usertest "github.com/nspcc-dev/neofs-sdk-go/user/test" "github.com/stretchr/testify/require" ) -func TestTarget_ToV2(t *testing.T) { - r := eacl.NewTargetByRole(anyValidRole) - subjs := [][]byte{ - anyValidECDSABinPublicKeys[0], - anyUserSet[0][:], - anyValidECDSABinPublicKeys[1], - anyUserSet[1][:], - anyUserSet[2][:], - } - r.SetRawSubjects(subjs) - m := r.ToV2() - require.EqualValues(t, anyValidRole, m.GetRole()) - require.Equal(t, subjs, m.GetKeys()) - - t.Run("default values", func(t *testing.T) { - target := eacl.NewTarget() - - // check initial values - require.Zero(t, target.Role()) - require.Nil(t, target.BinaryKeys()) - - // convert to v2 message - targetV2 := target.ToV2() - - require.Equal(t, protoacl.RoleUnknown, targetV2.GetRole()) - require.Nil(t, targetV2.GetKeys()) - }) -} - -func TestNewTargetFromV2(t *testing.T) { - role := protoacl.Role(rand.Uint32()) - var m protoacl.Target - m.SetRole(role) - m.SetKeys(anyValidBinPublicKeys) - - r := eacl.NewTargetFromV2(&m) - require.EqualValues(t, role, r.Role()) - require.Equal(t, anyValidBinPublicKeys, m.GetKeys()) - - t.Run("nil", func(t *testing.T) { - require.Equal(t, new(eacl.Target), eacl.NewTargetFromV2(nil)) - }) -} - func TestTarget_Marshal(t *testing.T) { for i := range anyValidTargets { require.Equal(t, anyValidBinTargets[i], anyValidTargets[i].Marshal()) @@ -75,7 +30,6 @@ func TestTarget_Unmarshal(t *testing.T) { for i := range anyValidBinTargets { err := tgt.Unmarshal(anyValidBinTargets[i]) require.NoError(t, err) - t.Skip("https://github.com/nspcc-dev/neofs-sdk-go/issues/606") require.Equal(t, anyValidTargets[i], tgt) } } @@ -92,7 +46,6 @@ func TestTarget_MarshalJSON(t *testing.T) { b, err := anyValidTargets[i].MarshalJSON() require.NoError(t, err, i) require.NoError(t, tgt1.UnmarshalJSON(b), i) - t.Skip("https://github.com/nspcc-dev/neofs-sdk-go/issues/606") require.Equal(t, anyValidTargets[i], tgt1, i) b, err = json.Marshal(anyValidTargets[i]) @@ -106,7 +59,6 @@ func TestTarget_UnmarshalJSON(t *testing.T) { var tgt1, tgt2 eacl.Target for i := range anyValidJSONTargets { require.NoError(t, tgt1.UnmarshalJSON([]byte(anyValidJSONTargets[i])), i) - t.Skip("https://github.com/nspcc-dev/neofs-sdk-go/issues/606") require.Equal(t, anyValidTargets[i], tgt1, i) require.NoError(t, json.Unmarshal([]byte(anyValidJSONTargets[i]), &tgt2), i) diff --git a/eacl/test/generate.go b/eacl/test/generate.go index 93cbc8f0..a012a051 100644 --- a/eacl/test/generate.go +++ b/eacl/test/generate.go @@ -12,7 +12,7 @@ import ( // Target returns random eacl.Target. func Target() eacl.Target { if rand.Int()%2 == 0 { - return eacl.NewTargetByRole(eacl.Role(rand.Uint32())) + return eacl.NewTargetByRole(eacl.Role(rand.Int32())) } return eacl.NewTargetByAccounts(usertest.IDs(1 + rand.N(10))) } @@ -29,7 +29,7 @@ func Targets(n int) []eacl.Target { // Filter returns random eacl.Filter. func Filter() eacl.Filter { si := strconv.Itoa(rand.Int()) - return eacl.ConstructFilter(eacl.FilterHeaderType(rand.Uint32()), "key_"+si, eacl.Match(rand.Uint32()), "val_"+si) + return eacl.ConstructFilter(eacl.FilterHeaderType(rand.Int32()), "key_"+si, eacl.Match(rand.Int32()), "val_"+si) } // Filters returns n random eacl.Filter instances. @@ -45,7 +45,7 @@ func Filters(n int) []eacl.Filter { func Record() eacl.Record { tn := 1 + rand.N(10) fn := 1 + rand.N(10) - return eacl.ConstructRecord(eacl.Action(rand.Uint32()), eacl.Operation(rand.Uint32()), Targets(tn), Filters(fn)...) + return eacl.ConstructRecord(eacl.Action(rand.Int32()), eacl.Operation(rand.Int32()), Targets(tn), Filters(fn)...) } // Records returns n random eacl.Record instances. diff --git a/eacl/test/generate_test.go b/eacl/test/generate_test.go index 990781fa..683926fa 100644 --- a/eacl/test/generate_test.go +++ b/eacl/test/generate_test.go @@ -41,12 +41,11 @@ func TestTable(t *testing.T) { require.NotEqual(t, eACL, eacltest.Table()) var eACL2 eacl.Table - require.NoError(t, eACL2.ReadFromV2(*eACL.ToV2())) + require.NoError(t, eACL2.FromProtoMessage(eACL.ProtoMessage())) require.Equal(t, eACL, eACL2) var eACL3 eacl.Table require.NoError(t, eACL3.Unmarshal(eACL.Marshal())) - t.Skip("https://github.com/nspcc-dev/neofs-sdk-go/issues/606") require.Equal(t, eACL, eACL3) j, err := eACL.MarshalJSON() diff --git a/eacl/types_test.go b/eacl/types_test.go index 53d9a5de..538e5ed1 100644 --- a/eacl/types_test.go +++ b/eacl/types_test.go @@ -15,13 +15,13 @@ func TestValidationUnit_WithContainerID(t *testing.T) { } func TestValidationUnit_WithRole(t *testing.T) { - role := Role(rand.Uint32()) + role := Role(rand.Int32()) u := new(ValidationUnit).WithRole(role) require.Equal(t, role, u.role) } func TestValidationUnit_WithOperation(t *testing.T) { - op := Operation(rand.Uint32()) + op := Operation(rand.Int32()) u := new(ValidationUnit).WithOperation(op) require.Equal(t, op, u.op) } @@ -40,8 +40,8 @@ func TestValidationUnit_WithSenderKey(t *testing.T) { func TestValidationUnit_WithEACLTable(t *testing.T) { eACL := NewTableForContainer(cidtest.ID(), []Record{ - ConstructRecord(Action(rand.Uint32()), Operation(rand.Uint32()), []Target{ - NewTargetByRole(Role(rand.Uint32())), + ConstructRecord(Action(rand.Int32()), Operation(rand.Int32()), []Target{ + NewTargetByRole(Role(rand.Int32())), }), }) u := new(ValidationUnit).WithEACLTable(&eACL) 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= diff --git a/internal/proto/encoding.go b/internal/proto/encoding.go index cb933b9c..05508652 100644 --- a/internal/proto/encoding.go +++ b/internal/proto/encoding.go @@ -7,7 +7,9 @@ import ( "encoding/binary" "math" + "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/encoding/protowire" + "google.golang.org/protobuf/proto" ) // Message is provided by protobuf 'message' types used in NeoFS for so-called @@ -21,13 +23,90 @@ type Message interface { MarshalStable(b []byte) } -// MarshalMessage returns m encoded to dynamically allocated buffer. +// MarshalMessage encodes m into a dynamically allocated buffer. func MarshalMessage(m Message) []byte { b := make([]byte, m.MarshaledSize()) m.MarshalStable(b) return b } +// Marshal encodes v transmitted via NeoFS API protocol into a dynamically +// allocated buffer. +func Marshal[M Message, T interface{ ProtoMessage() M }](v T) []byte { + return MarshalMessage(v.ProtoMessage()) +} + +// UnmarshalMessage decodes m from b. +func UnmarshalMessage(b []byte, m proto.Message) error { return proto.Unmarshal(b, m) } + +// Unmarshal decodes v transmitted via NeoFS API protocol from b. +func Unmarshal[M_ any, M interface { + *M_ + proto.Message + Message +}, T interface{ FromProtoMessage(M) error }](b []byte, v T) error { + m := M(new(M_)) + if err := UnmarshalMessage(b, m); err != nil { + return err + } + return v.FromProtoMessage(m) +} + +// UnmarshalOptional decodes v from [protojson] with ignored missing required +// fields. +func UnmarshalOptional[M_ any, M interface { + *M_ + proto.Message + Message +}, T interface{ FromProtoMessage(M) error }](b []byte, v T, dec func(T, M, bool) error) error { + m := M(new(M_)) + if err := UnmarshalMessage(b, m); err != nil { + return err + } + return dec(v, m, false) +} + +var jOpts = protojson.MarshalOptions{EmitUnpopulated: true} + +// MarshalMessageJSON encodes m into [protojson] with unpopulated fields' +// emission. +func MarshalMessageJSON(m proto.Message) ([]byte, error) { return jOpts.Marshal(m) } + +// MarshalJSON encodes v into [protojson] with unpopulated fields' emission. +func MarshalJSON[M proto.Message, T interface{ ProtoMessage() M }](v T) ([]byte, error) { + return MarshalMessageJSON(v.ProtoMessage()) +} + +// UnmarshalMessageJSON decodes m from [protojson]. +func UnmarshalMessageJSON(b []byte, m proto.Message) error { return protojson.Unmarshal(b, m) } + +// UnmarshalJSON decodes v from [protojson]. +func UnmarshalJSON[M_ any, M interface { + *M_ + proto.Message + Message +}, T interface{ FromProtoMessage(M) error }](b []byte, v T) error { + m := M(new(M_)) + if err := UnmarshalMessageJSON(b, m); err != nil { + return err + } + return v.FromProtoMessage(m) +} + +// UnmarshalJSONOptional decodes v from [protojson] with ignored missing +// required fields. +func UnmarshalJSONOptional[M_ any, M interface { + *M_ + proto.Message + Message +}, T interface{ FromProtoMessage(M) error }](b []byte, v T, dec func(T, M, bool) error) error { + m := M(new(M_)) + if err := UnmarshalMessageJSON(b, m); err != nil { + return err + } + return dec(v, m, false) +} + // Bytes is a type parameter constraint for any byte arrays. type Bytes interface{ ~[]byte | ~string } diff --git a/netmap/context.go b/netmap/context.go index bb280b1f..7a0e04d0 100644 --- a/netmap/context.go +++ b/netmap/context.go @@ -4,7 +4,6 @@ import ( "errors" "github.com/nspcc-dev/hrw/v2" - "github.com/nspcc-dev/neofs-api-go/v2/netmap" ) // context of a placement build process. @@ -13,10 +12,10 @@ type context struct { netMap NetMap // cache of processed filters - processedFilters map[string]*netmap.Filter + processedFilters map[string]*Filter // cache of processed selectors - processedSelectors map[string]*netmap.Selector + processedSelectors map[string]*Selector // stores results of selector processing selections map[string][]nodes @@ -55,8 +54,8 @@ var ( func newContext(nm NetMap) *context { return &context{ netMap: nm, - processedFilters: make(map[string]*netmap.Filter), - processedSelectors: make(map[string]*netmap.Selector), + processedFilters: make(map[string]*Filter), + processedSelectors: make(map[string]*Selector), selections: make(map[string][]nodes), numCache: make(map[string]uint64), diff --git a/netmap/example_test.go b/netmap/example_test.go index bb87378f..b6bef4b2 100644 --- a/netmap/example_test.go +++ b/netmap/example_test.go @@ -3,24 +3,20 @@ package netmap_test import ( "fmt" - apiGoNetmap "github.com/nspcc-dev/neofs-api-go/v2/netmap" "github.com/nspcc-dev/neofs-sdk-go/netmap" ) // Instances can be also used to process NeoFS API V2 protocol messages with [https://github.com/nspcc-dev/neofs-api] package. func ExampleNodeInfo_marshalling() { - // import apiGoNetmap "github.com/nspcc-dev/neofs-api-go/v2/netmap" - // On the client side. var info netmap.NodeInfo - var msg apiGoNetmap.NodeInfo - info.WriteToV2(&msg) + msg := info.ProtoMessage() // *send message* // On the server side. - _ = info.ReadFromV2(msg) + _ = info.FromProtoMessage(msg) } // When forming information about storage node to be registered the NeoFS diff --git a/netmap/filter.go b/netmap/filter.go index bd229cad..126d743f 100644 --- a/netmap/filter.go +++ b/netmap/filter.go @@ -3,8 +3,6 @@ package netmap import ( "fmt" "strconv" - - "github.com/nspcc-dev/neofs-api-go/v2/netmap" ) // mainFilterName is a name of the filter @@ -15,15 +13,15 @@ const mainFilterName = "*" func (c *context) processFilters(p PlacementPolicy) error { for i := range p.filters { if err := c.processFilter(p.filters[i], true); err != nil { - return fmt.Errorf("process filter #%d (%s): %w", i, p.filters[i].GetName(), err) + return fmt.Errorf("process filter #%d (%s): %w", i, p.filters[i].Name(), err) } } return nil } -func (c *context) processFilter(f netmap.Filter, top bool) error { - fName := f.GetName() +func (c *context) processFilter(f Filter, top bool) error { + fName := f.Name() if fName == mainFilterName { return fmt.Errorf("%w: '%s' is reserved", errInvalidFilterName, mainFilterName) } @@ -36,10 +34,10 @@ func (c *context) processFilter(f netmap.Filter, top bool) error { return errFilterNotFound } - inner := f.GetFilters() + inner := f.SubFilters() - switch op := f.GetOp(); op { - case netmap.AND, netmap.OR: + switch op := f.Op(); op { + case FilterOpAND, FilterOpOR: for i := range inner { if err := c.processFilter(inner[i], false); err != nil { return fmt.Errorf("process inner filter #%d: %w", i, err) @@ -53,12 +51,12 @@ func (c *context) processFilter(f netmap.Filter, top bool) error { } switch op { - case netmap.EQ, netmap.NE: - case netmap.GT, netmap.GE, netmap.LT, netmap.LE: - val := f.GetValue() + case FilterOpEQ, FilterOpNE: + case FilterOpGT, FilterOpGE, FilterOpLT, FilterOpLE: + val := f.Value() n, err := strconv.ParseUint(val, 10, 64) if err != nil { - return fmt.Errorf("%w: '%s'", errInvalidNumber, f.GetValue()) + return fmt.Errorf("%w: '%s'", errInvalidNumber, val) } c.numCache[val] = n @@ -77,38 +75,41 @@ func (c *context) processFilter(f netmap.Filter, top bool) error { // match matches f against b. It returns no errors because // filter should have been parsed during context creation // and missing node properties are considered as a regular fail. -func (c *context) match(f *netmap.Filter, b NodeInfo) bool { - switch f.GetOp() { - case netmap.AND, netmap.OR: - inner := f.GetFilters() +func (c *context) match(f *Filter, b NodeInfo) bool { + if f == nil { + return false + } + switch f.Op() { + case FilterOpAND, FilterOpOR: + inner := f.SubFilters() for i := range inner { fSub := &inner[i] - if name := inner[i].GetName(); name != "" { + if name := inner[i].Name(); name != "" { fSub = c.processedFilters[name] } ok := c.match(fSub, b) - if ok == (f.GetOp() == netmap.OR) { + if ok == (f.Op() == FilterOpOR) { return ok } } - return f.GetOp() == netmap.AND + return f.Op() == FilterOpAND default: - return c.matchKeyValue(f, b) + return c.matchKeyValue(*f, b) } } -func (c *context) matchKeyValue(f *netmap.Filter, b NodeInfo) bool { - switch op := f.GetOp(); op { - case netmap.EQ: - return b.Attribute(f.GetKey()) == f.GetValue() - case netmap.NE: - return b.Attribute(f.GetKey()) != f.GetValue() +func (c *context) matchKeyValue(f Filter, b NodeInfo) bool { + switch op := f.Op(); op { + case FilterOpEQ: + return b.Attribute(f.Key()) == f.Value() + case FilterOpNE: + return b.Attribute(f.Key()) != f.Value() default: var attr uint64 - switch f.GetKey() { + switch f.Key() { case attrPrice: attr = b.Price() case attrCapacity: @@ -116,7 +117,7 @@ func (c *context) matchKeyValue(f *netmap.Filter, b NodeInfo) bool { default: var err error - attr, err = strconv.ParseUint(b.Attribute(f.GetKey()), 10, 64) + attr, err = strconv.ParseUint(b.Attribute(f.Key()), 10, 64) if err != nil { // Note: because filters are somewhat independent from nodes attributes, // We don't report an error here, and fail filter instead. @@ -125,14 +126,14 @@ func (c *context) matchKeyValue(f *netmap.Filter, b NodeInfo) bool { } switch op { - case netmap.GT: - return attr > c.numCache[f.GetValue()] - case netmap.GE: - return attr >= c.numCache[f.GetValue()] - case netmap.LT: - return attr < c.numCache[f.GetValue()] - case netmap.LE: - return attr <= c.numCache[f.GetValue()] + case FilterOpGT: + return attr > c.numCache[f.Value()] + case FilterOpGE: + return attr >= c.numCache[f.Value()] + case FilterOpLT: + return attr < c.numCache[f.Value()] + case FilterOpLE: + return attr <= c.numCache[f.Value()] default: // do nothing and return false } diff --git a/netmap/filter_test.go b/netmap/filter_test.go index fc300737..bb057d08 100644 --- a/netmap/filter_test.go +++ b/netmap/filter_test.go @@ -4,17 +4,16 @@ import ( "errors" "testing" - "github.com/nspcc-dev/neofs-api-go/v2/netmap" "github.com/stretchr/testify/require" ) func TestContext_ProcessFilters(t *testing.T) { fs := []Filter{ - newFilter("StorageSSD", "Storage", "SSD", netmap.EQ), - newFilter("GoodRating", "Rating", "4", netmap.GE), - newFilter("Main", "", "", netmap.AND, + newFilter("StorageSSD", "Storage", "SSD", FilterOpEQ), + newFilter("GoodRating", "Rating", "4", FilterOpGE), + newFilter("Main", "", "", FilterOpAND, newFilter("StorageSSD", "", "", 0), - newFilter("", "IntField", "123", netmap.LT), + newFilter("", "IntField", "123", FilterOpLT), newFilter("GoodRating", "", "", 0)), } @@ -23,11 +22,11 @@ func TestContext_ProcessFilters(t *testing.T) { require.NoError(t, c.processFilters(p)) require.Equal(t, 3, len(c.processedFilters)) for _, f := range fs { - require.Equal(t, f.m, *c.processedFilters[f.m.GetName()]) + require.Equal(t, f, *c.processedFilters[f.Name()]) } - require.Equal(t, uint64(4), c.numCache[fs[1].m.GetValue()]) - require.Equal(t, uint64(123), c.numCache[fs[2].m.GetFilters()[1].GetValue()]) + require.Equal(t, uint64(4), c.numCache[fs[1].Value()]) + require.Equal(t, uint64(123), c.numCache[fs[2].SubFilters()[1].Value()]) } func TestContext_ProcessFiltersInvalid(t *testing.T) { @@ -38,24 +37,24 @@ func TestContext_ProcessFiltersInvalid(t *testing.T) { }{ { "UnnamedTop", - newFilter("", "Storage", "SSD", netmap.EQ), + newFilter("", "Storage", "SSD", FilterOpEQ), errUnnamedTopFilter, }, { "InvalidReference", - newFilter("Main", "", "", netmap.AND, + newFilter("Main", "", "", FilterOpAND, newFilter("StorageSSD", "", "", 0)), errFilterNotFound, }, { "NonEmptyKeyed", - newFilter("Main", "Storage", "SSD", netmap.EQ, + newFilter("Main", "Storage", "SSD", FilterOpEQ, newFilter("StorageSSD", "", "", 0)), errNonEmptyFilters, }, { "InvalidNumber", - newFilter("Main", "Rating", "three", netmap.GE), + newFilter("Main", "Rating", "three", FilterOpGE), errInvalidNumber, }, { @@ -65,7 +64,7 @@ func TestContext_ProcessFiltersInvalid(t *testing.T) { }, { "InvalidName", - newFilter("*", "Rating", "3", netmap.GE), + newFilter("*", "Rating", "3", FilterOpGE), errInvalidFilterName, }, } @@ -84,12 +83,12 @@ func TestFilter_MatchSimple_InvalidOp(t *testing.T) { b.SetAttribute("Rating", "4") b.SetAttribute("Country", "Germany") - f := newFilter("Main", "Rating", "5", netmap.EQ) + f := newFilter("Main", "Rating", "5", FilterOpEQ) c := newContext(NetMap{}) p := newPlacementPolicy(1, nil, nil, []Filter{f}) require.NoError(t, c.processFilters(p)) // just for the coverage - f.m.SetOp(0) - require.False(t, c.match(&f.m, b)) + f.op = 0 + require.False(t, c.match(&f, b)) } diff --git a/netmap/helper_test.go b/netmap/helper_test.go index 0eff22af..e84ff3db 100644 --- a/netmap/helper_test.go +++ b/netmap/helper_test.go @@ -1,19 +1,11 @@ package netmap -import ( - "github.com/nspcc-dev/neofs-api-go/v2/netmap" -) - -func newFilter(name string, k, v string, op netmap.Operation, fs ...Filter) (f Filter) { +func newFilter(name string, k, v string, op FilterOp, fs ...Filter) (f Filter) { f.SetName(name) - f.m.SetKey(k) - f.m.SetOp(op) - f.m.SetValue(v) - inner := make([]netmap.Filter, len(fs)) - for i := range fs { - inner[i] = fs[i].m - } - f.m.SetFilters(inner) + f.key = k + f.op = op + f.val = v + f.subs = fs return f } diff --git a/netmap/netmap.go b/netmap/netmap.go index 0f3249a2..b69080a7 100644 --- a/netmap/netmap.go +++ b/netmap/netmap.go @@ -5,16 +5,16 @@ import ( "slices" "github.com/nspcc-dev/hrw/v2" - "github.com/nspcc-dev/neofs-api-go/v2/netmap" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" + protonetmap "github.com/nspcc-dev/neofs-sdk-go/proto/netmap" ) // NetMap represents NeoFS network map. It includes information about all // storage nodes registered in NeoFS the network. // -// NetMap is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/netmap.NetMap -// message. See ReadFromV2 / WriteToV2 methods. +// NetMap is mutually compatible with [protonetmap.Netmap] message. See +// [NetMap.FromProtoMessage] / [NetMap.ProtoMessage] methods. // // Instances can be created using built-in var declaration. type NetMap struct { @@ -23,50 +23,51 @@ type NetMap struct { nodes []NodeInfo } -// ReadFromV2 reads NetMap from the netmap.NetMap message. Checks if the -// message conforms to NeoFS API V2 protocol. +// FromProtoMessage validates msg according to the NeoFS API protocol and +// restores m from it. // -// See also WriteToV2. -func (m *NetMap) ReadFromV2(msg netmap.NetMap) error { +// See also [NetMap.ProtoMessage]. +func (m *NetMap) FromProtoMessage(msg *protonetmap.Netmap) error { var err error - nodes := msg.Nodes() - if nodes == nil { + if msg.Nodes == nil { m.nodes = nil } else { - m.nodes = make([]NodeInfo, len(nodes)) + m.nodes = make([]NodeInfo, len(msg.Nodes)) - for i := range nodes { - err = m.nodes[i].ReadFromV2(nodes[i]) + for i := range msg.Nodes { + if msg.Nodes[i] == nil { + return fmt.Errorf("nil node info #%d", i) + } + err = m.nodes[i].FromProtoMessage(msg.Nodes[i]) if err != nil { return fmt.Errorf("invalid node info: %w", err) } } } - m.epoch = msg.Epoch() + m.epoch = msg.Epoch return nil } -// WriteToV2 writes NetMap to the netmap.NetMap message. The message -// MUST NOT be nil. +// ProtoMessage converts m into message to transmit using the NeoFS API +// protocol. // -// See also ReadFromV2. -func (m NetMap) WriteToV2(msg *netmap.NetMap) { - var nodes []netmap.NodeInfo - +// See also [NetMap.FromProtoMessage]. +func (m NetMap) ProtoMessage() *protonetmap.Netmap { + msg := &protonetmap.Netmap{ + Epoch: m.epoch, + } if m.nodes != nil { - nodes = make([]netmap.NodeInfo, len(m.nodes)) + msg.Nodes = make([]*protonetmap.NodeInfo, len(m.nodes)) for i := range m.nodes { - m.nodes[i].WriteToV2(&nodes[i]) + msg.Nodes[i] = m.nodes[i].ProtoMessage() } - - msg.SetNodes(nodes) } - msg.SetEpoch(m.epoch) + return msg } // SetNodes sets information list about all storage nodes from the NeoFS network. @@ -196,12 +197,12 @@ func (m NetMap) ContainerNodes(p PlacementPolicy, containerID cid.ID) ([][]NodeI result := make([][]NodeInfo, len(p.replicas)) for i := range p.replicas { - sName := p.replicas[i].GetSelector() + sName := p.replicas[i].SelectorName() if sName == "" { if len(p.selectors) == 0 { - var s netmap.Selector - s.SetCount(p.replicas[i].GetCount()) - s.SetFilter(mainFilterName) + var s Selector + s.SetNumberOfNodes(p.replicas[i].NumberOfObjects()) + s.SetFilterName(mainFilterName) nodes, err := c.getSelection(p, s) if err != nil { @@ -212,7 +213,7 @@ func (m NetMap) ContainerNodes(p PlacementPolicy, containerID cid.ID) ([][]NodeI } for i := range p.selectors { - result[i] = append(result[i], flattenNodes(c.selections[p.selectors[i].GetName()])...) + result[i] = append(result[i], flattenNodes(c.selections[p.selectors[i].Name()])...) } continue diff --git a/netmap/netmap_test.go b/netmap/netmap_test.go index 4827a210..96678db7 100644 --- a/netmap/netmap_test.go +++ b/netmap/netmap_test.go @@ -3,8 +3,8 @@ package netmap_test import ( "testing" - apinetmap "github.com/nspcc-dev/neofs-api-go/v2/netmap" "github.com/nspcc-dev/neofs-sdk-go/netmap" + protonetmap "github.com/nspcc-dev/neofs-sdk-go/proto/netmap" "github.com/stretchr/testify/require" ) @@ -56,32 +56,33 @@ func init() { validNetmap.SetNodes(anyValidNodes) } -func TestNetMap_ReadFromV2(t *testing.T) { - mns := make([]apinetmap.NodeInfo, 2) - mns[0].SetPublicKey([]byte("public_key_0")) - mns[1].SetPublicKey([]byte("public_key_1")) - mns[0].SetAddresses("endpoint_0_0", "endpoint_0_1") - mns[1].SetAddresses("endpoint_1_0", "endpoint_1_1") - mns[0].SetState(apinetmap.Offline) - mns[1].SetState(apinetmap.Maintenance) - - addAttr := func(m *apinetmap.NodeInfo, k, v string) { - var a apinetmap.Attribute - a.SetKey(k) - a.SetValue(v) - m.SetAttributes(append(m.GetAttributes(), a)) +func TestNetMap_FromProtoMessage(t *testing.T) { + m := &protonetmap.Netmap{ + Epoch: anyValidCurrentEpoch, + Nodes: []*protonetmap.NodeInfo{ + { + PublicKey: []byte("public_key_0"), + Addresses: []string{"endpoint_0_0", "endpoint_0_1"}, + Attributes: []*protonetmap.NodeInfo_Attribute{ + {Key: "k_0_0", Value: "v_0_0"}, + {Key: "k_0_1", Value: "v_0_1"}, + }, + State: protonetmap.NodeInfo_OFFLINE, + }, + { + PublicKey: []byte("public_key_1"), + Addresses: []string{"endpoint_1_0", "endpoint_1_1"}, + Attributes: []*protonetmap.NodeInfo_Attribute{ + {Key: "k_1_0", Value: "v_1_0"}, + {Key: "k_1_1", Value: "v_1_1"}, + }, + State: protonetmap.NodeInfo_MAINTENANCE, + }, + }, } - addAttr(&mns[0], "k_0_0", "v_0_0") - addAttr(&mns[0], "k_0_1", "v_0_1") - addAttr(&mns[1], "k_1_0", "v_1_0") - addAttr(&mns[1], "k_1_1", "v_1_1") - - var m apinetmap.NetMap - m.SetEpoch(anyValidCurrentEpoch) - m.SetNodes(mns) var val netmap.NetMap - require.NoError(t, val.ReadFromV2(m)) + require.NoError(t, val.FromProtoMessage(m)) require.EqualValues(t, anyValidCurrentEpoch, val.Epoch()) ns := val.Nodes() @@ -123,59 +124,59 @@ func TestNetMap_ReadFromV2(t *testing.T) { }, collectedAttrs) // reset optional fields - m.SetEpoch(0) - m.SetNodes(nil) + m.Epoch = 0 + m.Nodes = nil val2 := val - require.NoError(t, val2.ReadFromV2(m)) + require.NoError(t, val2.FromProtoMessage(m)) require.Zero(t, val2.Epoch()) require.Zero(t, val2.Nodes()) t.Run("invalid", func(t *testing.T) { for _, tc := range []struct { name, err string - corrupt func(netMap *apinetmap.NetMap) + corrupt func(netMap *protonetmap.Netmap) }{ + {name: "nodes/nil", err: "nil node info #1", + corrupt: func(m *protonetmap.Netmap) { m.Nodes[1] = nil }}, {name: "nodes/public key/nil", err: "invalid node info: missing public key", - corrupt: func(m *apinetmap.NetMap) { m.Nodes()[1].SetPublicKey(nil) }}, + corrupt: func(m *protonetmap.Netmap) { m.Nodes[1].PublicKey = nil }}, {name: "nodes/public key/empty", err: "invalid node info: missing public key", - corrupt: func(m *apinetmap.NetMap) { m.Nodes()[1].SetPublicKey([]byte{}) }}, + corrupt: func(m *protonetmap.Netmap) { m.Nodes[1].PublicKey = []byte{} }}, {name: "nodes/endpoints/empty", err: "invalid node info: missing network endpoints", - corrupt: func(m *apinetmap.NetMap) { m.Nodes()[1].SetAddresses() }}, + corrupt: func(m *protonetmap.Netmap) { m.Nodes[1].Addresses = nil }}, {name: "nodes/attributes/no key", err: "invalid node info: empty key of the attribute #1", - corrupt: func(m *apinetmap.NetMap) { setNodeAttributes(&m.Nodes()[1], "k1", "v1", "", "v2") }}, + corrupt: func(m *protonetmap.Netmap) { setNodeAttributes(m.Nodes[1], "k1", "v1", "", "v2") }}, {name: "nodes/attributes/no value", err: `invalid node info: empty "k2" attribute value`, - corrupt: func(m *apinetmap.NetMap) { setNodeAttributes(&m.Nodes()[1], "k1", "v1", "k2", "") }}, + corrupt: func(m *protonetmap.Netmap) { setNodeAttributes(m.Nodes[1], "k1", "v1", "k2", "") }}, {name: "nodes/attributes/duplicated", err: "invalid node info: duplicated attribute k1", - corrupt: func(m *apinetmap.NetMap) { setNodeAttributes(&m.Nodes()[1], "k1", "v1", "k2", "v2", "k1", "v3") }}, + corrupt: func(m *protonetmap.Netmap) { setNodeAttributes(m.Nodes[1], "k1", "v1", "k2", "v2", "k1", "v3") }}, {name: "nodes/attributes/capacity", err: "invalid node info: invalid Capacity attribute: strconv.ParseUint: parsing \"foo\": invalid syntax", - corrupt: func(m *apinetmap.NetMap) { setNodeAttributes(&m.Nodes()[1], "Capacity", "foo") }}, + corrupt: func(m *protonetmap.Netmap) { setNodeAttributes(m.Nodes[1], "Capacity", "foo") }}, {name: "nodes/attributes/price", err: "invalid node info: invalid Price attribute: strconv.ParseUint: parsing \"foo\": invalid syntax", - corrupt: func(m *apinetmap.NetMap) { setNodeAttributes(&m.Nodes()[1], "Price", "foo") }}, + corrupt: func(m *protonetmap.Netmap) { setNodeAttributes(m.Nodes[1], "Price", "foo") }}, } { t.Run(tc.name, func(t *testing.T) { st := val - var m apinetmap.NetMap - st.WriteToV2(&m) - tc.corrupt(&m) - require.EqualError(t, new(netmap.NetMap).ReadFromV2(m), tc.err) + m := st.ProtoMessage() + tc.corrupt(m) + require.EqualError(t, new(netmap.NetMap).FromProtoMessage(m), tc.err) }) } }) } -func TestNetMap_WriteToV2(t *testing.T) { +func TestNetMap_ProtoMessage(t *testing.T) { var val netmap.NetMap - var m apinetmap.NetMap // zero - val.WriteToV2(&m) - require.Zero(t, m.Epoch()) - require.Zero(t, m.Nodes()) + m := val.ProtoMessage() + require.Zero(t, m.GetEpoch()) + require.Zero(t, m.GetNodes()) // filled - validNetmap.WriteToV2(&m) - require.EqualValues(t, anyValidCurrentEpoch, m.Epoch()) - ns := m.Nodes() + m = validNetmap.ProtoMessage() + require.EqualValues(t, anyValidCurrentEpoch, m.GetEpoch()) + ns := m.GetNodes() require.Len(t, ns, 2) require.EqualValues(t, "public_key_0", ns[0].GetPublicKey()) require.EqualValues(t, "public_key_1", ns[1].GetPublicKey()) diff --git a/netmap/network_info.go b/netmap/network_info.go index 668466a6..9c6d6fbc 100644 --- a/netmap/network_info.go +++ b/netmap/network_info.go @@ -1,50 +1,63 @@ package netmap import ( - "bytes" "encoding/binary" "errors" "fmt" "math" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" - "github.com/nspcc-dev/neofs-api-go/v2/netmap" + neofsproto "github.com/nspcc-dev/neofs-sdk-go/internal/proto" + protonetmap "github.com/nspcc-dev/neofs-sdk-go/proto/netmap" ) // NetworkInfo groups information about the NeoFS network state. Mainly used to // describe the current state of the network. // -// NetworkInfo is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/netmap.NetworkInfo -// message. See ReadFromV2 / WriteToV2 methods. +// NetworkInfo is mutually compatible with [protonetmap.NetworkInfo] +// message. See [NetworkInfo.FromProtoMessage] / [NetworkInfo.ProtoMessage] methods. // // Instances can be created using built-in var declaration. type NetworkInfo struct { - m netmap.NetworkInfo + curEpoch uint64 + netMagic uint64 + msPerBlock int64 + prms [][2][]byte } // reads NetworkInfo from netmap.NetworkInfo message. If checkFieldPresence is set, // returns an error on absence of any protocol-required field. Verifies format of any // presented field according to NeoFS API V2 protocol. -func (x *NetworkInfo) readFromV2(m netmap.NetworkInfo, checkFieldPresence bool) error { +func (x *NetworkInfo) fromProtoMessage(m *protonetmap.NetworkInfo, checkFieldPresence bool) error { c := m.GetNetworkConfig() if checkFieldPresence && c == nil { return errors.New("missing network config") } - if checkFieldPresence && c.NumberOfParameters() <= 0 { + if checkFieldPresence && len(c.Parameters) == 0 { return errors.New("missing network parameters") } + if c == nil { + x.curEpoch = m.CurrentEpoch + x.netMagic = m.MagicNumber + x.msPerBlock = m.MsPerBlock + return nil + } + var err error - mNames := make(map[string]struct{}, c.NumberOfParameters()) + mNames := make(map[string]struct{}, len(c.Parameters)) + prms := make([][2][]byte, len(c.Parameters)) - c.IterateParameters(func(prm *netmap.NetworkParameter) bool { + for i, prm := range c.Parameters { + if prm == nil { + return fmt.Errorf("nil parameter #%d", i) + } name := string(prm.GetKey()) _, was := mNames[name] if was { - err = fmt.Errorf("duplicated parameter name: %s", name) - return true + return fmt.Errorf("duplicated parameter name: %s", name) } mNames[name] = struct{}{} @@ -52,8 +65,7 @@ func (x *NetworkInfo) readFromV2(m netmap.NetworkInfo, checkFieldPresence bool) switch name { default: if len(prm.GetValue()) == 0 { - err = fmt.Errorf("empty %q parameter value", name) - return true + return fmt.Errorf("empty %q parameter value", name) } case configEigenTrustAlpha: var num uint64 @@ -81,35 +93,47 @@ func (x *NetworkInfo) readFromV2(m netmap.NetworkInfo, checkFieldPresence bool) } if err != nil { - err = fmt.Errorf("invalid %s parameter: %w", name, err) + return fmt.Errorf("invalid %s parameter: %w", name, err) } - return err != nil - }) - - if err != nil { - return err + prms[i] = [2][]byte{prm.Key, prm.Value} } - x.m = m + x.curEpoch = m.CurrentEpoch + x.netMagic = m.MagicNumber + x.msPerBlock = m.MsPerBlock + x.prms = prms return nil } -// ReadFromV2 reads NetworkInfo from the netmap.NetworkInfo message. Checks if the -// message conforms to NeoFS API V2 protocol. +// FromProtoMessage validates m according to the NeoFS API protocol and restores +// x from it. // -// See also WriteToV2. -func (x *NetworkInfo) ReadFromV2(m netmap.NetworkInfo) error { - return x.readFromV2(m, true) +// See also [NetworkInfo.ProtoMessage]. +func (x *NetworkInfo) FromProtoMessage(m *protonetmap.NetworkInfo) error { + return x.fromProtoMessage(m, true) } -// WriteToV2 writes NetworkInfo to the netmap.NetworkInfo message. The message -// MUST NOT be nil. +// ProtoMessage converts x into message to transmit using the NeoFS API +// protocol. // -// See also ReadFromV2. -func (x NetworkInfo) WriteToV2(m *netmap.NetworkInfo) { - *m = x.m +// See also [NetworkInfo.FromProtoMessage]. +func (x NetworkInfo) ProtoMessage() *protonetmap.NetworkInfo { + m := &protonetmap.NetworkInfo{ + CurrentEpoch: x.curEpoch, + MagicNumber: x.netMagic, + MsPerBlock: x.msPerBlock, + } + if len(x.prms) > 0 { + m.NetworkConfig = &protonetmap.NetworkConfig{ + Parameters: make([]*protonetmap.NetworkConfig_Parameter, len(x.prms)), + } + for i := range x.prms { + m.NetworkConfig.Parameters[i] = &protonetmap.NetworkConfig_Parameter{Key: x.prms[i][0], Value: x.prms[i][1]} + } + } + return m } // Marshal encodes NetworkInfo into a binary format of the NeoFS API protocol @@ -117,10 +141,7 @@ func (x NetworkInfo) WriteToV2(m *netmap.NetworkInfo) { // // See also Unmarshal. func (x NetworkInfo) Marshal() []byte { - var m netmap.NetworkInfo - x.WriteToV2(&m) - - return m.StableMarshal(nil) + return neofsproto.Marshal(x) } // Unmarshal decodes NeoFS API protocol binary format into the NetworkInfo @@ -129,105 +150,65 @@ func (x NetworkInfo) Marshal() []byte { // // See also Marshal. func (x *NetworkInfo) Unmarshal(data []byte) error { - var m netmap.NetworkInfo - - err := m.Unmarshal(data) - if err != nil { - return err - } - - return x.readFromV2(m, false) + return neofsproto.UnmarshalOptional(data, x, (*NetworkInfo).fromProtoMessage) } // CurrentEpoch returns epoch set using SetCurrentEpoch. // // Zero NetworkInfo has zero current epoch. func (x NetworkInfo) CurrentEpoch() uint64 { - return x.m.GetCurrentEpoch() + return x.curEpoch } // SetCurrentEpoch sets current epoch of the NeoFS network. func (x *NetworkInfo) SetCurrentEpoch(epoch uint64) { - x.m.SetCurrentEpoch(epoch) + x.curEpoch = epoch } // MagicNumber returns magic number set using SetMagicNumber. // // Zero NetworkInfo has zero magic. func (x NetworkInfo) MagicNumber() uint64 { - return x.m.GetMagicNumber() + return x.netMagic } // SetMagicNumber sets magic number of the NeoFS Sidechain. // // See also MagicNumber. func (x *NetworkInfo) SetMagicNumber(epoch uint64) { - x.m.SetMagicNumber(epoch) + x.netMagic = epoch } // MsPerBlock returns network parameter set using SetMsPerBlock. func (x NetworkInfo) MsPerBlock() int64 { - return x.m.GetMsPerBlock() + return x.msPerBlock } // SetMsPerBlock sets MillisecondsPerBlock network parameter of the NeoFS Sidechain. // // See also MsPerBlock. func (x *NetworkInfo) SetMsPerBlock(v int64) { - x.m.SetMsPerBlock(v) + x.msPerBlock = v } func (x *NetworkInfo) setConfig(name string, val []byte) { - c := x.m.GetNetworkConfig() - if c == nil { - c = new(netmap.NetworkConfig) - - var prm netmap.NetworkParameter - prm.SetKey([]byte(name)) - prm.SetValue(val) - - c.SetParameters(prm) - - x.m.SetNetworkConfig(c) - - return - } - - found := false - prms := make([]netmap.NetworkParameter, 0, c.NumberOfParameters()) - - c.IterateParameters(func(prm *netmap.NetworkParameter) bool { - found = bytes.Equal(prm.GetKey(), []byte(name)) - if found { - prm.SetValue(val) - } else { - prms = append(prms, *prm) + for i := range x.prms { + if string(x.prms[i][0]) == name { + x.prms[i][1] = val + return } - - return found - }) - - if !found { - prms = append(prms, netmap.NetworkParameter{}) - prms[len(prms)-1].SetKey([]byte(name)) - prms[len(prms)-1].SetValue(val) - - c.SetParameters(prms...) } -} -func (x NetworkInfo) configValue(name string) (res []byte) { - x.m.GetNetworkConfig().IterateParameters(func(prm *netmap.NetworkParameter) bool { - if string(prm.GetKey()) == name { - res = prm.GetValue() + x.prms = append(x.prms, [2][]byte{[]byte(name), val}) +} - return true +func (x NetworkInfo) configValue(name string) []byte { + for i := range x.prms { + if string(x.prms[i][0]) == name { + return x.prms[i][1] } - - return false - }) - - return + } + return nil } // SetRawNetworkParameter sets named NeoFS network parameter whose value is @@ -258,13 +239,11 @@ func (x *NetworkInfo) RawNetworkParameter(name string) []byte { // // Zero NetworkInfo has no network parameters. func (x *NetworkInfo) IterateRawNetworkParameters(f func(name string, value []byte)) { - c := x.m.GetNetworkConfig() - - c.IterateParameters(func(prm *netmap.NetworkParameter) bool { - name := string(prm.GetKey()) + for i := range x.prms { + name := string(x.prms[i][0]) switch name { default: - f(name, prm.GetValue()) + f(name, x.prms[i][1]) case configEigenTrustAlpha, configAuditFee, @@ -279,9 +258,7 @@ func (x *NetworkInfo) IterateRawNetworkParameters(f func(name string, value []by configHomomorphicHashingDisabled, configMaintenanceModeAllowed: } - - return false - }) + } } func (x *NetworkInfo) setConfigUint64(name string, num uint64) { @@ -332,7 +309,7 @@ func (x NetworkInfo) configUint64(name string) uint64 { res, err := decodeConfigValueUint64(val) if err != nil { // potential panic is OK since value MUST be correct since it is - // verified in ReadFromV2 or set by provided method. + // verified in FromProtoMessage or set by provided method. panic(err) } @@ -348,7 +325,7 @@ func (x NetworkInfo) configBool(name string) bool { res, err := decodeConfigValueBool(val) if err != nil { // potential panic is OK since value MUST be correct since it is - // verified in ReadFromV2 or set by provided method. + // verified in FromProtoMessage or set by provided method. panic(err) } diff --git a/netmap/network_info_test.go b/netmap/network_info_test.go index 019f4bef..754df068 100644 --- a/netmap/network_info_test.go +++ b/netmap/network_info_test.go @@ -3,8 +3,8 @@ package netmap_test import ( "testing" - apinetmap "github.com/nspcc-dev/neofs-api-go/v2/netmap" "github.com/nspcc-dev/neofs-sdk-go/netmap" + protonetmap "github.com/nspcc-dev/neofs-sdk-go/proto/netmap" "github.com/stretchr/testify/require" ) @@ -213,52 +213,46 @@ func TestNetworkInfo_AllowMaintenanceMode(t *testing.T) { require.True(t, x.MaintenanceModeAllowed()) } -func setNetworkPrms[T string | []byte](ni *apinetmap.NetworkInfo, els ...T) { +func setNetworkPrms[T string | []byte](ni *protonetmap.NetworkInfo, els ...T) { if len(els)%2 != 0 { panic("must be even") } - mps := make([]apinetmap.NetworkParameter, len(els)/2) + ni.NetworkConfig.Parameters = make([]*protonetmap.NetworkConfig_Parameter, len(els)/2) for i := range len(els) / 2 { - mps[i].SetKey([]byte(els[2*i])) - mps[i].SetValue([]byte(els[2*i+1])) + ni.NetworkConfig.Parameters[i] = &protonetmap.NetworkConfig_Parameter{ + Key: []byte(els[2*i]), + Value: []byte(els[2*i+1]), + } } - var mc apinetmap.NetworkConfig - mc.SetParameters(mps...) - ni.SetNetworkConfig(&mc) } -func TestNetworkInfo_ReadFromV2(t *testing.T) { - var mps []apinetmap.NetworkParameter - addP := func(k string, v []byte) { - var m apinetmap.NetworkParameter - m.SetKey([]byte(k)) - m.SetValue(v) - mps = append(mps, m) +func TestNetworkInfo_FromProtoMessage(t *testing.T) { + m := &protonetmap.NetworkInfo{ + CurrentEpoch: anyValidCurrentEpoch, + MagicNumber: anyValidMagicNumber, + MsPerBlock: anyValidMSPerBlock, + NetworkConfig: &protonetmap.NetworkConfig{ + Parameters: []*protonetmap.NetworkConfig_Parameter{ + {Key: []byte("k1"), Value: []byte("v1")}, + {Key: []byte("k2"), Value: []byte("v2")}, + {Key: []byte("AuditFee"), Value: anyValidBinAuditFee}, + {Key: []byte("BasicIncomeRate"), Value: anyValidBinStoragePrice}, + {Key: []byte("ContainerAliasFee"), Value: anyValidBinNamedContainerFee}, + {Key: []byte("ContainerFee"), Value: anyValidBinContainerFee}, + {Key: []byte("EigenTrustAlpha"), Value: anyValidBinEigenTrustAlpha}, + {Key: []byte("EigenTrustIterations"), Value: anyValidBinEigenTrustIterations}, + {Key: []byte("EpochDuration"), Value: anyValidBinEpochDuration}, + {Key: []byte("HomomorphicHashingDisabled"), Value: anyValidBinHomoHashDisabled}, + {Key: []byte("InnerRingCandidateFee"), Value: anyValidBinIRCandidateFee}, + {Key: []byte("MaintenanceModeAllowed"), Value: anyValidBinMaintenanceModeAllowed}, + {Key: []byte("MaxObjectSize"), Value: anyValidBinMaxObjectSize}, + {Key: []byte("WithdrawFee"), Value: anyValidBinWithdrawalFee}, + }, + }, } - addP("k1", []byte("v1")) - addP("k2", []byte("v2")) - addP("AuditFee", anyValidBinAuditFee) - addP("BasicIncomeRate", anyValidBinStoragePrice) - addP("ContainerAliasFee", anyValidBinNamedContainerFee) - addP("ContainerFee", anyValidBinContainerFee) - addP("EigenTrustAlpha", anyValidBinEigenTrustAlpha) - addP("EigenTrustIterations", anyValidBinEigenTrustIterations) - addP("EpochDuration", anyValidBinEpochDuration) - addP("HomomorphicHashingDisabled", anyValidBinHomoHashDisabled) - addP("InnerRingCandidateFee", anyValidBinIRCandidateFee) - addP("MaintenanceModeAllowed", anyValidBinMaintenanceModeAllowed) - addP("MaxObjectSize", anyValidBinMaxObjectSize) - addP("WithdrawFee", anyValidBinWithdrawalFee) - var mc apinetmap.NetworkConfig - mc.SetParameters(mps...) - var m apinetmap.NetworkInfo - m.SetCurrentEpoch(anyValidCurrentEpoch) - m.SetMagicNumber(anyValidMagicNumber) - m.SetMsPerBlock(anyValidMSPerBlock) - m.SetNetworkConfig(&mc) var val netmap.NetworkInfo - require.NoError(t, val.ReadFromV2(m)) + require.NoError(t, val.FromProtoMessage(m)) require.EqualValues(t, "v1", val.RawNetworkParameter("k1")) require.EqualValues(t, "v2", val.RawNetworkParameter("k2")) require.Equal(t, anyValidCurrentEpoch, val.CurrentEpoch()) @@ -278,12 +272,12 @@ func TestNetworkInfo_ReadFromV2(t *testing.T) { require.Equal(t, anyValidWithdrawalFee, val.WithdrawalFee()) // reset optional fields - mc.SetParameters(mps[0]) - m.SetCurrentEpoch(0) - m.SetMagicNumber(0) - m.SetMsPerBlock(0) + m.NetworkConfig.Parameters = m.NetworkConfig.Parameters[:1] + m.CurrentEpoch = 0 + m.MagicNumber = 0 + m.MsPerBlock = 0 val2 := val - require.NoError(t, val2.ReadFromV2(m)) + require.NoError(t, val2.FromProtoMessage(m)) require.EqualValues(t, "v1", val.RawNetworkParameter("k1")) require.Zero(t, val2.RawNetworkParameter("k2")) require.Zero(t, val2.CurrentEpoch()) @@ -305,89 +299,85 @@ func TestNetworkInfo_ReadFromV2(t *testing.T) { t.Run("invalid", func(t *testing.T) { for _, tc := range []struct { name, err string - corrupt func(*apinetmap.NetworkInfo) + corrupt func(*protonetmap.NetworkInfo) }{ {name: "netconfig/missing", err: "missing network config", - corrupt: func(m *apinetmap.NetworkInfo) { m.SetNetworkConfig(nil) }}, + corrupt: func(m *protonetmap.NetworkInfo) { m.NetworkConfig = nil }}, {name: "netconfig/prms/missing", err: "missing network parameters", - corrupt: func(m *apinetmap.NetworkInfo) { m.SetNetworkConfig(new(apinetmap.NetworkConfig)) }}, + corrupt: func(m *protonetmap.NetworkInfo) { m.NetworkConfig = new(protonetmap.NetworkConfig) }}, + {name: "netconfig/prms/nil", err: "nil parameter #1", + corrupt: func(m *protonetmap.NetworkInfo) { + m.NetworkConfig.Parameters[1] = nil + }}, {name: "netconfig/prms/no value", err: `empty "k1" parameter value`, - corrupt: func(m *apinetmap.NetworkInfo) { setNetworkPrms(m, "k1", "") }}, + corrupt: func(m *protonetmap.NetworkInfo) { setNetworkPrms(m, "k1", "") }}, {name: "netconfig/prms/duplicated", err: "duplicated parameter name: k1", - corrupt: func(m *apinetmap.NetworkInfo) { setNetworkPrms(m, "k1", "v1", "k2", "v2", "k1", "v3") }}, + corrupt: func(m *protonetmap.NetworkInfo) { setNetworkPrms(m, "k1", "v1", "k2", "v2", "k1", "v3") }}, {name: "netconfig/prms/eigen trust alpha/overflow", err: "invalid EigenTrustAlpha parameter: invalid uint64 parameter length 9", - corrupt: func(m *apinetmap.NetworkInfo) { setNetworkPrms(m, "EigenTrustAlpha", "123456789") }}, + corrupt: func(m *protonetmap.NetworkInfo) { setNetworkPrms(m, "EigenTrustAlpha", "123456789") }}, {name: "netconfig/prms/eigen trust alpha/negative", err: "invalid EigenTrustAlpha parameter: EigenTrust alpha value -0.50 is out of range [0, 1]", - corrupt: func(m *apinetmap.NetworkInfo) { + corrupt: func(m *protonetmap.NetworkInfo) { setNetworkPrms(m, []byte("EigenTrustAlpha"), []byte{0, 0, 0, 0, 0, 0, 224, 191}) }}, {name: "netconfig/prms/eigen trust alpha/too big", err: "invalid EigenTrustAlpha parameter: EigenTrust alpha value 1.50 is out of range [0, 1]", - corrupt: func(m *apinetmap.NetworkInfo) { + corrupt: func(m *protonetmap.NetworkInfo) { setNetworkPrms(m, []byte("EigenTrustAlpha"), []byte{0, 0, 0, 0, 0, 0, 248, 63}) }}, {name: "netconfig/prms/homo hash disabled/overflow", err: "invalid HomomorphicHashingDisabled parameter: invalid bool parameter contract format too big: integer", - corrupt: func(m *apinetmap.NetworkInfo) { + corrupt: func(m *protonetmap.NetworkInfo) { setNetworkPrms(m, []byte("HomomorphicHashingDisabled"), make([]byte, 33)) }}, {name: "netconfig/prms/maintenance allowed/overflow", err: "invalid MaintenanceModeAllowed parameter: invalid bool parameter contract format too big: integer", - corrupt: func(m *apinetmap.NetworkInfo) { + corrupt: func(m *protonetmap.NetworkInfo) { setNetworkPrms(m, []byte("MaintenanceModeAllowed"), make([]byte, 33)) }}, {name: "netconfig/prms/audit fee/overflow", err: "invalid AuditFee parameter: invalid uint64 parameter length 9", - corrupt: func(m *apinetmap.NetworkInfo) { setNetworkPrms(m, []byte("AuditFee"), make([]byte, 9)) }}, + corrupt: func(m *protonetmap.NetworkInfo) { setNetworkPrms(m, []byte("AuditFee"), make([]byte, 9)) }}, {name: "netconfig/prms/storage price/overflow", err: "invalid BasicIncomeRate parameter: invalid uint64 parameter length 9", - corrupt: func(m *apinetmap.NetworkInfo) { setNetworkPrms(m, []byte("BasicIncomeRate"), make([]byte, 9)) }}, + corrupt: func(m *protonetmap.NetworkInfo) { setNetworkPrms(m, []byte("BasicIncomeRate"), make([]byte, 9)) }}, {name: "netconfig/prms/container fee/overflow", err: "invalid ContainerFee parameter: invalid uint64 parameter length 9", - corrupt: func(m *apinetmap.NetworkInfo) { setNetworkPrms(m, []byte("ContainerFee"), make([]byte, 9)) }}, + corrupt: func(m *protonetmap.NetworkInfo) { setNetworkPrms(m, []byte("ContainerFee"), make([]byte, 9)) }}, {name: "netconfig/prms/named container fee/overflow", err: "invalid ContainerAliasFee parameter: invalid uint64 parameter length 9", - corrupt: func(m *apinetmap.NetworkInfo) { setNetworkPrms(m, []byte("ContainerAliasFee"), make([]byte, 9)) }}, + corrupt: func(m *protonetmap.NetworkInfo) { setNetworkPrms(m, []byte("ContainerAliasFee"), make([]byte, 9)) }}, {name: "netconfig/prms/eigen trust iterations/overflow", err: "invalid EigenTrustIterations parameter: invalid uint64 parameter length 9", - corrupt: func(m *apinetmap.NetworkInfo) { setNetworkPrms(m, []byte("EigenTrustIterations"), make([]byte, 9)) }}, + corrupt: func(m *protonetmap.NetworkInfo) { setNetworkPrms(m, []byte("EigenTrustIterations"), make([]byte, 9)) }}, {name: "netconfig/prms/epoch duration/overflow", err: "invalid EpochDuration parameter: invalid uint64 parameter length 9", - corrupt: func(m *apinetmap.NetworkInfo) { setNetworkPrms(m, []byte("EpochDuration"), make([]byte, 9)) }}, + corrupt: func(m *protonetmap.NetworkInfo) { setNetworkPrms(m, []byte("EpochDuration"), make([]byte, 9)) }}, {name: "netconfig/prms/ir candidate fee/overflow", err: "invalid InnerRingCandidateFee parameter: invalid uint64 parameter length 9", - corrupt: func(m *apinetmap.NetworkInfo) { setNetworkPrms(m, []byte("InnerRingCandidateFee"), make([]byte, 9)) }}, + corrupt: func(m *protonetmap.NetworkInfo) { setNetworkPrms(m, []byte("InnerRingCandidateFee"), make([]byte, 9)) }}, {name: "netconfig/prms/max object size/overflow", err: "invalid MaxObjectSize parameter: invalid uint64 parameter length 9", - corrupt: func(m *apinetmap.NetworkInfo) { setNetworkPrms(m, []byte("MaxObjectSize"), make([]byte, 9)) }}, + corrupt: func(m *protonetmap.NetworkInfo) { setNetworkPrms(m, []byte("MaxObjectSize"), make([]byte, 9)) }}, {name: "netconfig/prms/withdrawal fee/overflow", err: "invalid WithdrawFee parameter: invalid uint64 parameter length 9", - corrupt: func(m *apinetmap.NetworkInfo) { setNetworkPrms(m, []byte("WithdrawFee"), make([]byte, 9)) }}, + corrupt: func(m *protonetmap.NetworkInfo) { setNetworkPrms(m, []byte("WithdrawFee"), make([]byte, 9)) }}, } { t.Run(tc.name, func(t *testing.T) { st := val - var m apinetmap.NetworkInfo - st.WriteToV2(&m) - tc.corrupt(&m) - require.EqualError(t, new(netmap.NetworkInfo).ReadFromV2(m), tc.err) + m := st.ProtoMessage() + tc.corrupt(m) + require.EqualError(t, new(netmap.NetworkInfo).FromProtoMessage(m), tc.err) }) } }) } -func TestNetworkInfo_WriteToV2(t *testing.T) { +func TestNetworkInfo_ProtoMessage(t *testing.T) { var val netmap.NetworkInfo - var m apinetmap.NetworkInfo // zero - val.WriteToV2(&m) + m := val.ProtoMessage() require.Zero(t, m.GetCurrentEpoch()) require.Zero(t, m.GetMagicNumber()) require.Zero(t, m.GetMsPerBlock()) require.Zero(t, m.GetNetworkConfig()) // filled - validNetworkInfo.WriteToV2(&m) + m = validNetworkInfo.ProtoMessage() require.Equal(t, anyValidCurrentEpoch, m.GetCurrentEpoch()) require.Equal(t, anyValidMagicNumber, m.GetMagicNumber()) require.Equal(t, anyValidMSPerBlock, m.GetMsPerBlock()) mc := m.GetNetworkConfig() require.NotNil(t, mc) - require.EqualValues(t, 14, mc.NumberOfParameters()) - var collected [][2][]byte - mc.IterateParameters(func(p *apinetmap.NetworkParameter) bool { - collected = append(collected, [2][]byte{p.GetKey(), p.GetValue()}) - return false - }) - require.Len(t, collected, 14) + require.Len(t, mc.Parameters, 14) for i, pair := range [][2]any{ {"k1", "v1"}, {"k2", "v2"}, @@ -404,8 +394,8 @@ func TestNetworkInfo_WriteToV2(t *testing.T) { {"MaxObjectSize", anyValidBinMaxObjectSize}, {"WithdrawFee", anyValidBinWithdrawalFee}, } { - require.EqualValues(t, pair[0], collected[i][0]) - require.EqualValues(t, pair[1], collected[i][1]) + require.EqualValues(t, pair[0], mc.Parameters[i].Key) + require.EqualValues(t, pair[1], mc.Parameters[i].Value) } } diff --git a/netmap/node_info.go b/netmap/node_info.go index 001d4764..23f7c900 100644 --- a/netmap/node_info.go +++ b/netmap/node_info.go @@ -8,8 +8,9 @@ import ( "strings" "github.com/nspcc-dev/hrw/v2" - "github.com/nspcc-dev/neofs-api-go/v2/netmap" neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" + neofsproto "github.com/nspcc-dev/neofs-sdk-go/internal/proto" + protonetmap "github.com/nspcc-dev/neofs-sdk-go/proto/netmap" ) // NodeInfo groups information about NeoFS storage node which is reflected @@ -18,18 +19,24 @@ import ( // about the nodes is available to all network participants to work with the network // map (mainly to comply with container storage policies). // -// NodeInfo is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/netmap.NodeInfo -// message. See ReadFromV2 / WriteToV2 methods. +// NodeInfo is mutually compatible with [protonetmap.NodeInfo] message. See +// [NodeInfo.FromProtoMessage] / [NodeInfo.ProtoMessage] methods. // // Instances can be created using built-in var declaration. type NodeInfo struct { - m netmap.NodeInfo + state protonetmap.NodeInfo_State + pub []byte + addrs []string + attrs [][2]string } // reads NodeInfo from netmap.NodeInfo message. If checkFieldPresence is set, // returns an error on absence of any protocol-required field. Verifies format of any // presented field according to NeoFS API V2 protocol. -func (x *NodeInfo) readFromV2(m netmap.NodeInfo, checkFieldPresence bool) error { +func (x *NodeInfo) fromProtoMessage(m *protonetmap.NodeInfo, checkFieldPresence bool) error { + if m.State < 0 { + return fmt.Errorf("negative state %d", m.State) + } var err error binPublicKey := m.GetPublicKey() @@ -37,13 +44,17 @@ func (x *NodeInfo) readFromV2(m netmap.NodeInfo, checkFieldPresence bool) error return errors.New("missing public key") } - if checkFieldPresence && m.NumberOfAddresses() <= 0 { + if checkFieldPresence && len(m.Addresses) == 0 { return errors.New("missing network endpoints") } attributes := m.GetAttributes() mAttr := make(map[string]struct{}, len(attributes)) + attrs := make([][2]string, len(attributes)) for i := range attributes { + if attributes[i] == nil { + return fmt.Errorf("nil attribute #%d", i) + } key := attributes[i].GetKey() if key == "" { return fmt.Errorf("empty key of the attribute #%d", i) @@ -51,46 +62,62 @@ func (x *NodeInfo) readFromV2(m netmap.NodeInfo, checkFieldPresence bool) error return fmt.Errorf("duplicated attribute %s", key) } + val := attributes[i].GetValue() switch { case key == attrCapacity: - _, err = strconv.ParseUint(attributes[i].GetValue(), 10, 64) + _, err = strconv.ParseUint(val, 10, 64) if err != nil { return fmt.Errorf("invalid %s attribute: %w", attrCapacity, err) } case key == attrPrice: var err error - _, err = strconv.ParseUint(attributes[i].GetValue(), 10, 64) + _, err = strconv.ParseUint(val, 10, 64) if err != nil { return fmt.Errorf("invalid %s attribute: %w", attrPrice, err) } default: - if attributes[i].GetValue() == "" { + if val == "" { return fmt.Errorf("empty %q attribute value", key) } } mAttr[key] = struct{}{} + attrs[i][0], attrs[i][1] = key, val } - x.m = m + x.state = m.State + x.pub = m.PublicKey + x.addrs = m.Addresses + x.attrs = attrs return nil } -// ReadFromV2 reads NodeInfo from the netmap.NodeInfo message. Checks if the -// message conforms to NeoFS API V2 protocol. +// FromProtoMessage validates m according to the NeoFS API protocol and restores +// x from it. // -// See also WriteToV2. -func (x *NodeInfo) ReadFromV2(m netmap.NodeInfo) error { - return x.readFromV2(m, true) +// See also [NodeInfo.ProtoMessage]. +func (x *NodeInfo) FromProtoMessage(m *protonetmap.NodeInfo) error { + return x.fromProtoMessage(m, true) } -// WriteToV2 writes NodeInfo to the netmap.NodeInfo message. The message MUST NOT -// be nil. +// ProtoMessage converts x into message to transmit using the NeoFS API +// protocol. // -// See also ReadFromV2. -func (x NodeInfo) WriteToV2(m *netmap.NodeInfo) { - *m = x.m +// See also [NodeInfo.FromProtoMessage]. +func (x NodeInfo) ProtoMessage() *protonetmap.NodeInfo { + m := &protonetmap.NodeInfo{ + PublicKey: x.pub, + Addresses: x.addrs, + State: x.state, + } + if len(x.attrs) > 0 { + m.Attributes = make([]*protonetmap.NodeInfo_Attribute, len(x.attrs)) + for i := range x.attrs { + m.Attributes[i] = &protonetmap.NodeInfo_Attribute{Key: x.attrs[i][0], Value: x.attrs[i][1]} + } + } + return m } // Marshal encodes NodeInfo into a binary format of the NeoFS API protocol @@ -98,10 +125,7 @@ func (x NodeInfo) WriteToV2(m *netmap.NodeInfo) { // // See also Unmarshal. func (x NodeInfo) Marshal() []byte { - var m netmap.NodeInfo - x.WriteToV2(&m) - - return m.StableMarshal(nil) + return neofsproto.Marshal(x) } // Unmarshal decodes NeoFS API protocol binary format into the NodeInfo @@ -110,14 +134,7 @@ func (x NodeInfo) Marshal() []byte { // // See also Marshal. func (x *NodeInfo) Unmarshal(data []byte) error { - var m netmap.NodeInfo - - err := m.Unmarshal(data) - if err != nil { - return err - } - - return x.readFromV2(m, false) + return neofsproto.UnmarshalOptional(data, x, (*NodeInfo).fromProtoMessage) } // MarshalJSON encodes NodeInfo into a JSON format of the NeoFS API protocol @@ -125,10 +142,7 @@ func (x *NodeInfo) Unmarshal(data []byte) error { // // See also UnmarshalJSON. func (x NodeInfo) MarshalJSON() ([]byte, error) { - var m netmap.NodeInfo - x.WriteToV2(&m) - - return m.MarshalJSON() + return neofsproto.MarshalJSON(x) } // UnmarshalJSON decodes NeoFS API protocol JSON format into the NodeInfo @@ -136,14 +150,7 @@ func (x NodeInfo) MarshalJSON() ([]byte, error) { // // See also MarshalJSON. func (x *NodeInfo) UnmarshalJSON(data []byte) error { - var m netmap.NodeInfo - - err := m.UnmarshalJSON(data) - if err != nil { - return err - } - - return x.readFromV2(m, false) + return neofsproto.UnmarshalJSONOptional(data, x, (*NodeInfo).fromProtoMessage) } // SetPublicKey sets binary-encoded public key bound to the node. The key @@ -155,7 +162,7 @@ func (x *NodeInfo) UnmarshalJSON(data []byte) error { // // See also [NodeInfo.PublicKey]. func (x *NodeInfo) SetPublicKey(key []byte) { - x.m.SetPublicKey(key) + x.pub = key } // PublicKey returns value set using [NodeInfo.SetPublicKey]. @@ -169,7 +176,7 @@ func (x *NodeInfo) SetPublicKey(key []byte) { // The value returned shares memory with the structure itself, so changing it can lead to data corruption. // Make a copy if you need to change it. func (x NodeInfo) PublicKey() []byte { - return x.m.GetPublicKey() + return x.pub } // StringifyPublicKey returns HEX representation of PublicKey. @@ -187,14 +194,14 @@ func StringifyPublicKey(node NodeInfo) string { // // See also IterateNetworkEndpoints. func (x *NodeInfo) SetNetworkEndpoints(v ...string) { - x.m.SetAddresses(v...) + x.addrs = v } // NumberOfNetworkEndpoints returns number of network endpoints announced by the node. // // See also SetNetworkEndpoints. func (x NodeInfo) NumberOfNetworkEndpoints() int { - return x.m.NumberOfAddresses() + return len(x.addrs) } // IterateNetworkEndpoints iterates over network endpoints announced by the @@ -206,7 +213,11 @@ func (x NodeInfo) NumberOfNetworkEndpoints() int { // // See also SetNetworkEndpoints. func (x NodeInfo) IterateNetworkEndpoints(f func(string) bool) { - x.m.IterateAddresses(f) + for i := range x.addrs { + if f(x.addrs[i]) { + return + } + } } // IterateNetworkEndpoints is an extra-sugared function over IterateNetworkEndpoints @@ -226,7 +237,7 @@ var _ hrw.Hashable = NodeInfo{} // Hash is needed to support weighted HRW therefore sort function sorts nodes // based on their public key. Hash isn't expected to be used directly. func (x NodeInfo) Hash() uint64 { - return hrw.Hash(x.m.GetPublicKey()) + return hrw.Hash(x.PublicKey()) } // less declares "less than" comparison between two NodeInfo instances: @@ -448,15 +459,14 @@ func (x NodeInfo) ExternalAddresses() []string { // // See also SetAttribute. func (x NodeInfo) NumberOfAttributes() int { - return len(x.m.GetAttributes()) + return len(x.attrs) } // IterateAttributes iterates over all node attributes and passes the into f. // Handler MUST NOT be nil. func (x NodeInfo) IterateAttributes(f func(key, value string)) { - a := x.m.GetAttributes() - for i := range a { - f(a[i].GetKey(), a[i].GetValue()) + for i := range x.attrs { + f(x.attrs[i][0], x.attrs[i][1]) } } @@ -465,11 +475,7 @@ func (x NodeInfo) IterateAttributes(f func(key, value string)) { // // See also Attribute, IterateAttributes. func (x NodeInfo) GetAttributes() [][2]string { - attrs := make([][2]string, len(x.m.GetAttributes())) - for i, attr := range x.m.GetAttributes() { - attrs[i] = [2]string{attr.GetKey(), attr.GetValue()} - } - return attrs + return x.attrs } // SetAttributes sets list of node attributes. @@ -478,7 +484,6 @@ func (x NodeInfo) GetAttributes() [][2]string { // // See also SetAttribute. func (x *NodeInfo) SetAttributes(attrs [][2]string) { - netmapAttrs := make([]netmap.Attribute, 0, len(attrs)) for _, attr := range attrs { if attr[0] == "" { panic("empty key in SetAttributes") @@ -486,13 +491,9 @@ func (x *NodeInfo) SetAttributes(attrs [][2]string) { if attr[1] == "" { panic(fmt.Errorf("empty value in SetAttributes for key: %s", attr[0])) } - - netmapAttrs = append(netmapAttrs, netmap.Attribute{}) - netmapAttrs[len(netmapAttrs)-1].SetKey(attr[0]) - netmapAttrs[len(netmapAttrs)-1].SetValue(attr[1]) } - x.m.SetAttributes(netmapAttrs) + x.attrs = attrs } // SetAttribute sets value of the node attribute value by the given key. @@ -504,28 +505,22 @@ func (x *NodeInfo) SetAttribute(key, value string) { panic("empty value in SetAttribute") } - a := x.m.GetAttributes() - for i := range a { - if a[i].GetKey() == key { - a[i].SetValue(value) + for i := range x.attrs { + if x.attrs[i][0] == key { + x.attrs[i][1] = value return } } - a = append(a, netmap.Attribute{}) - a[len(a)-1].SetKey(key) - a[len(a)-1].SetValue(value) - - x.m.SetAttributes(a) + x.attrs = append(x.attrs, [2]string{key, value}) } // Attribute returns value of the node attribute set using SetAttribute by the // given key. Returns empty string if attribute is missing. func (x NodeInfo) Attribute(key string) string { - a := x.m.GetAttributes() - for i := range a { - if a[i].GetKey() == key { - return a[i].GetValue() + for i := range x.attrs { + if x.attrs[i][0] == key { + return x.attrs[i][1] } } @@ -535,30 +530,27 @@ func (x NodeInfo) Attribute(key string) string { // SortAttributes sorts node attributes set using SetAttribute lexicographically. // The method is only needed to make NodeInfo consistent, e.g. for signing. func (x *NodeInfo) SortAttributes() { - as := x.m.GetAttributes() - if len(as) == 0 { + if len(x.attrs) == 0 { return } - sort.Slice(as, func(i, j int) bool { - switch strings.Compare(as[i].GetKey(), as[j].GetKey()) { + sort.Slice(x.attrs, func(i, j int) bool { + switch strings.Compare(x.attrs[i][0], x.attrs[j][0]) { case -1: return true case 1: return false default: - return as[i].GetValue() < as[j].GetValue() + return x.attrs[i][1] < x.attrs[j][1] } }) - - x.m.SetAttributes(as) } // SetOffline sets the state of the node to "offline". When a node updates // information about itself in the network map, this action is interpreted as // an intention to leave the network. func (x *NodeInfo) SetOffline() { - x.m.SetState(netmap.Offline) + x.state = protonetmap.NodeInfo_OFFLINE } // IsOffline checks if the node is in the "offline" state. @@ -568,7 +560,7 @@ func (x *NodeInfo) SetOffline() { // // See also SetOffline. func (x NodeInfo) IsOffline() bool { - return x.m.GetState() == netmap.Offline + return x.state == protonetmap.NodeInfo_OFFLINE } // SetOnline sets the state of the node to "online". When a node updates @@ -577,7 +569,7 @@ func (x NodeInfo) IsOffline() bool { // // See also IsOnline. func (x *NodeInfo) SetOnline() { - x.m.SetState(netmap.Online) + x.state = protonetmap.NodeInfo_ONLINE } // IsOnline checks if the node is in the "online" state. @@ -587,7 +579,7 @@ func (x *NodeInfo) SetOnline() { // // See also SetOnline. func (x NodeInfo) IsOnline() bool { - return x.m.GetState() == netmap.Online + return x.state == protonetmap.NodeInfo_ONLINE } // SetMaintenance sets the state of the node to "maintenance". When a node updates @@ -596,7 +588,7 @@ func (x NodeInfo) IsOnline() bool { // // See also IsMaintenance. func (x *NodeInfo) SetMaintenance() { - x.m.SetState(netmap.Maintenance) + x.state = protonetmap.NodeInfo_MAINTENANCE } // IsMaintenance checks if the node is in the "maintenance" state. @@ -605,7 +597,7 @@ func (x *NodeInfo) SetMaintenance() { // // See also SetMaintenance. func (x NodeInfo) IsMaintenance() bool { - return x.m.GetState() == netmap.Maintenance + return x.state == protonetmap.NodeInfo_MAINTENANCE } const attrVerifiedNodesDomain = "VerifiedNodesDomain" diff --git a/netmap/node_info_test.go b/netmap/node_info_test.go index d97f0dba..378d8376 100644 --- a/netmap/node_info_test.go +++ b/netmap/node_info_test.go @@ -5,8 +5,8 @@ import ( "strings" "testing" - apinetmap "github.com/nspcc-dev/neofs-api-go/v2/netmap" "github.com/nspcc-dev/neofs-sdk-go/netmap" + protonetmap "github.com/nspcc-dev/neofs-sdk-go/proto/netmap" "github.com/stretchr/testify/require" ) @@ -488,48 +488,41 @@ func TestNodeInfo_SetVerifiedNodesDomain(t *testing.T) { require.Equal(t, anyValidVerifiedNodesDomain, n.VerifiedNodesDomain()) } -func setNodeAttributes(ni *apinetmap.NodeInfo, els ...string) { +func setNodeAttributes(ni *protonetmap.NodeInfo, els ...string) { if len(els)%2 != 0 { panic("must be even") } - mas := make([]apinetmap.Attribute, len(els)/2) + ni.Attributes = make([]*protonetmap.NodeInfo_Attribute, len(els)/2) for i := range len(els) / 2 { - mas[i].SetKey(els[2*i]) - mas[i].SetValue(els[2*i+1]) + ni.Attributes[i] = &protonetmap.NodeInfo_Attribute{Key: els[2*i], Value: els[2*i+1]} } - ni.SetAttributes(mas) } -func TestNodeInfo_ReadFromV2(t *testing.T) { - var mas []apinetmap.Attribute - addAttr := func(k, v string) { - var a apinetmap.Attribute - a.SetKey(k) - a.SetValue(v) - mas = append(mas, a) +func TestNodeInfo_FromProtoMessage(t *testing.T) { + m := &protonetmap.NodeInfo{ + PublicKey: anyValidPublicKey, + Addresses: anyValidNetworkEndpoints, + Attributes: []*protonetmap.NodeInfo_Attribute{ + {Key: "k1", Value: "v1"}, + {Key: "k2", Value: "v2"}, + {Key: "Capacity", Value: "9010937245406684209"}, + {Key: "Price", Value: "10993309018040354285"}, + {Key: "UN-LOCODE", Value: anyValidLOCODE}, + {Key: "CountryCode", Value: anyValidCountryCode}, + {Key: "Country", Value: anyValidCountryName}, + {Key: "Location", Value: anyValidLocationName}, + {Key: "SubDivCode", Value: anyValidSubdivCode}, + {Key: "SubDiv", Value: anyValidSubdivName}, + {Key: "SubDivName", Value: anyValidSubdivName}, + {Key: "Continent", Value: anyValidContinentName}, + {Key: "ExternalAddr", Value: strings.Join(anyValidExternalNetworkEndpoints, ",")}, + {Key: "Version", Value: anyValidNodeVersion}, + {Key: "VerifiedNodesDomain", Value: anyValidVerifiedNodesDomain}, + }, } - addAttr("k1", "v1") - addAttr("k2", "v2") - addAttr("Capacity", "9010937245406684209") - addAttr("Price", "10993309018040354285") - addAttr("UN-LOCODE", anyValidLOCODE) - addAttr("CountryCode", anyValidCountryCode) - addAttr("Country", anyValidCountryName) - addAttr("Location", anyValidLocationName) - addAttr("SubDivCode", anyValidSubdivCode) - addAttr("SubDiv", anyValidSubdivName) - addAttr("SubDivName", anyValidSubdivName) - addAttr("Continent", anyValidContinentName) - addAttr("ExternalAddr", strings.Join(anyValidExternalNetworkEndpoints, ",")) - addAttr("Version", anyValidNodeVersion) - addAttr("VerifiedNodesDomain", anyValidVerifiedNodesDomain) - var m apinetmap.NodeInfo - m.SetPublicKey(anyValidPublicKey) - m.SetAddresses(anyValidNetworkEndpoints...) - m.SetAttributes(mas) var val netmap.NodeInfo - require.NoError(t, val.ReadFromV2(m)) + require.NoError(t, val.FromProtoMessage(m)) require.Equal(t, anyValidPublicKey, val.PublicKey()) var i int val.IterateNetworkEndpoints(func(el string) bool { @@ -562,23 +555,23 @@ func TestNodeInfo_ReadFromV2(t *testing.T) { require.Equal(t, anyValidVerifiedNodesDomain, val.VerifiedNodesDomain()) for _, tc := range []struct { - st apinetmap.NodeState + st protonetmap.NodeInfo_State check func(netmap.NodeInfo) bool }{ - {st: apinetmap.Online, check: netmap.NodeInfo.IsOnline}, - {st: apinetmap.Offline, check: netmap.NodeInfo.IsOffline}, - {st: apinetmap.Maintenance, check: netmap.NodeInfo.IsMaintenance}, + {st: protonetmap.NodeInfo_ONLINE, check: netmap.NodeInfo.IsOnline}, + {st: protonetmap.NodeInfo_OFFLINE, check: netmap.NodeInfo.IsOffline}, + {st: protonetmap.NodeInfo_MAINTENANCE, check: netmap.NodeInfo.IsMaintenance}, } { - m.SetState(tc.st) - require.NoError(t, val.ReadFromV2(m), tc.st) + m.State = tc.st + require.NoError(t, val.FromProtoMessage(m), tc.st) require.True(t, tc.check(val)) } // reset optional fields - m.SetAttributes(nil) - m.SetState(0) + m.Attributes = nil + m.State = 0 val2 := val - require.NoError(t, val2.ReadFromV2(m)) + require.NoError(t, val2.FromProtoMessage(m)) require.Zero(t, val2.NumberOfAttributes()) val2.IterateAttributes(func(string, string) { t.Fatal("handler must not be called") @@ -594,57 +587,56 @@ func TestNodeInfo_ReadFromV2(t *testing.T) { t.Run("invalid", func(t *testing.T) { for _, tc := range []struct { name, err string - corrupt func(info *apinetmap.NodeInfo) + corrupt func(info *protonetmap.NodeInfo) }{ {name: "public key/nil", err: "missing public key", - corrupt: func(m *apinetmap.NodeInfo) { m.SetPublicKey(nil) }}, + corrupt: func(m *protonetmap.NodeInfo) { m.PublicKey = nil }}, {name: "public key/empty", err: "missing public key", - corrupt: func(m *apinetmap.NodeInfo) { m.SetPublicKey([]byte{}) }}, + corrupt: func(m *protonetmap.NodeInfo) { m.PublicKey = []byte{} }}, + {name: "endpoints/nil", err: "missing network endpoints", + corrupt: func(m *protonetmap.NodeInfo) { m.Addresses = nil }}, {name: "endpoints/empty", err: "missing network endpoints", - corrupt: func(m *apinetmap.NodeInfo) { m.SetAddresses() }}, + corrupt: func(m *protonetmap.NodeInfo) { m.Addresses = []string{} }}, + {name: "attributes/nil", err: "nil attribute #1", + corrupt: func(m *protonetmap.NodeInfo) { m.Attributes[1] = nil }}, {name: "attributes/no key", err: "empty key of the attribute #1", - corrupt: func(m *apinetmap.NodeInfo) { setNodeAttributes(m, "k1", "v1", "", "v2") }}, + corrupt: func(m *protonetmap.NodeInfo) { setNodeAttributes(m, "k1", "v1", "", "v2") }}, {name: "attributes/no value", err: `empty "k2" attribute value`, - corrupt: func(m *apinetmap.NodeInfo) { setNodeAttributes(m, "k1", "v1", "k2", "") }}, + corrupt: func(m *protonetmap.NodeInfo) { setNodeAttributes(m, "k1", "v1", "k2", "") }}, {name: "attributes/duplicated", err: "duplicated attribute k1", - corrupt: func(m *apinetmap.NodeInfo) { setNodeAttributes(m, "k1", "v1", "k2", "v2", "k1", "v3") }}, + corrupt: func(m *protonetmap.NodeInfo) { setNodeAttributes(m, "k1", "v1", "k2", "v2", "k1", "v3") }}, {name: "attributes/capacity", err: "invalid Capacity attribute: strconv.ParseUint: parsing \"foo\": invalid syntax", - corrupt: func(m *apinetmap.NodeInfo) { setNodeAttributes(m, "Capacity", "foo") }}, + corrupt: func(m *protonetmap.NodeInfo) { setNodeAttributes(m, "Capacity", "foo") }}, {name: "attributes/price", err: "invalid Price attribute: strconv.ParseUint: parsing \"foo\": invalid syntax", - corrupt: func(m *apinetmap.NodeInfo) { setNodeAttributes(m, "Price", "foo") }}, + corrupt: func(m *protonetmap.NodeInfo) { setNodeAttributes(m, "Price", "foo") }}, + {name: "state/negative", err: "negative state -1", + corrupt: func(m *protonetmap.NodeInfo) { m.State = -1 }}, } { t.Run(tc.name, func(t *testing.T) { st := val - var m apinetmap.NodeInfo - st.WriteToV2(&m) - tc.corrupt(&m) - require.EqualError(t, new(netmap.NodeInfo).ReadFromV2(m), tc.err) + m := st.ProtoMessage() + tc.corrupt(m) + require.EqualError(t, new(netmap.NodeInfo).FromProtoMessage(m), tc.err) }) } }) } -func TestNodeInfo_WriteToV2(t *testing.T) { +func TestNodeInfo_ProtoMessage(t *testing.T) { var val netmap.NodeInfo - var m apinetmap.NodeInfo // zero - val.WriteToV2(&m) + m := val.ProtoMessage() require.Zero(t, m.GetPublicKey()) - require.Zero(t, m.NumberOfAddresses()) - m.IterateAddresses(func(string) bool { t.Fatal("handler must not be called"); return false }) + require.Zero(t, m.GetAddresses()) require.Zero(t, m.GetAttributes()) require.Zero(t, m.GetState()) // filled - validNodeInfo.WriteToV2(&m) + m = validNodeInfo.ProtoMessage() require.Equal(t, anyValidPublicKey, m.GetPublicKey()) - require.EqualValues(t, 3, m.NumberOfAddresses()) - var collected []string - m.IterateAddresses(func(el string) bool { - collected = append(collected, el) - return false - }) + require.Equal(t, anyValidNetworkEndpoints, m.GetAddresses()) + mas := m.GetAttributes() require.Len(t, mas, 14) for i, pair := range [][2]string{ @@ -670,15 +662,15 @@ func TestNodeInfo_WriteToV2(t *testing.T) { for _, tc := range []struct { setState func(*netmap.NodeInfo) - exp apinetmap.NodeState + exp protonetmap.NodeInfo_State }{ - {setState: (*netmap.NodeInfo).SetOnline, exp: apinetmap.Online}, - {setState: (*netmap.NodeInfo).SetOffline, exp: apinetmap.Offline}, - {setState: (*netmap.NodeInfo).SetMaintenance, exp: apinetmap.Maintenance}, + {setState: (*netmap.NodeInfo).SetOnline, exp: protonetmap.NodeInfo_ONLINE}, + {setState: (*netmap.NodeInfo).SetOffline, exp: protonetmap.NodeInfo_OFFLINE}, + {setState: (*netmap.NodeInfo).SetMaintenance, exp: protonetmap.NodeInfo_MAINTENANCE}, } { val2 := validNodeInfo tc.setState(&val2) - val2.WriteToV2(&m) + m := val2.ProtoMessage() require.Equal(t, tc.exp, m.GetState(), tc.exp) } } diff --git a/netmap/policy.go b/netmap/policy.go index a2e6b773..d39e8fe5 100644 --- a/netmap/policy.go +++ b/netmap/policy.go @@ -6,33 +6,33 @@ import ( "io" "slices" "strconv" - "strings" "github.com/antlr4-go/antlr/v4" - "github.com/nspcc-dev/neofs-api-go/v2/netmap" + neofsproto "github.com/nspcc-dev/neofs-sdk-go/internal/proto" "github.com/nspcc-dev/neofs-sdk-go/netmap/parser" + protonetmap "github.com/nspcc-dev/neofs-sdk-go/proto/netmap" ) // PlacementPolicy declares policy to store objects in the NeoFS container. // Within itself, PlacementPolicy represents a set of rules to select a subset // of nodes from NeoFS network map - node-candidates for object storage. // -// PlacementPolicy is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/netmap.PlacementPolicy -// message. See ReadFromV2 / WriteToV2 methods. +// PlacementPolicy is mutually compatible with [protonetmap.PlacementPolicy] +// message. See [PlacementPolicy.FromProtoMessage] / [PlacementPolicy.ProtoMessage] methods. // // Instances can be created using built-in var declaration. type PlacementPolicy struct { backupFactor uint32 - filters []netmap.Filter + filters []Filter - selectors []netmap.Selector + selectors []Selector - replicas []netmap.Replica + replicas []ReplicaDescriptor } // FilterOp defines the matching property. -type FilterOp uint32 +type FilterOp int32 // Supported FilterOp values. const ( @@ -71,24 +71,15 @@ func (x FilterOp) String() string { } } -func copyFilter(f netmap.Filter) netmap.Filter { - var filter netmap.Filter +func copyFilter(f Filter) Filter { + filter := f - filter.SetName(f.GetName()) - filter.SetKey(f.GetKey()) - filter.SetOp(f.GetOp()) - filter.SetValue(f.GetValue()) + if len(f.subs) > 0 { + filter.subs = make([]Filter, len(f.subs)) - if f.GetFilters() != nil { - filters := make([]netmap.Filter, len(f.GetFilters())) - - for i, internalFilter := range f.GetFilters() { - filters[i] = copyFilter(internalFilter) + for i := range f.subs { + filter.subs[i] = copyFilter(f.subs[i]) } - - filter.SetFilters(filters) - } else { - filter.SetFilters(nil) } return filter @@ -98,27 +89,52 @@ func copyFilter(f netmap.Filter) netmap.Filter { func (p PlacementPolicy) CopyTo(dst *PlacementPolicy) { dst.SetContainerBackupFactor(p.backupFactor) - dst.filters = make([]netmap.Filter, len(p.filters)) - for i, f := range p.filters { - dst.filters[i] = copyFilter(f) + dst.filters = make([]Filter, len(p.filters)) + for i := range p.filters { + dst.filters[i] = copyFilter(p.filters[i]) } - // netmap.Selector is a struct with simple types, no links inside. Just create a new slice and copy all items inside. + // protonetmap.Selector is a struct with simple types, no links inside. Just create a new slice and copy all items inside. dst.selectors = slices.Clone(p.selectors) - // netmap.Replica is a struct with simple types, no links inside. Just create a new slice and copy all items inside. + // protonetmap.Replica is a struct with simple types, no links inside. Just create a new slice and copy all items inside. dst.replicas = slices.Clone(p.replicas) } -func (p *PlacementPolicy) readFromV2(m netmap.PlacementPolicy, checkFieldPresence bool) error { - p.replicas = m.GetReplicas() - if checkFieldPresence && len(p.replicas) == 0 { +func (p *PlacementPolicy) fromProtoMessage(m *protonetmap.PlacementPolicy, checkFieldPresence bool) error { + if checkFieldPresence && len(m.Replicas) == 0 { return errors.New("missing replicas") } + p.replicas = make([]ReplicaDescriptor, len(m.Replicas)) + for i, r := range m.Replicas { + if r == nil { + return fmt.Errorf("nil replica #%d", i) + } + p.replicas[i].fromProtoMessage(r) + } + + p.selectors = make([]Selector, len(m.Selectors)) + for i, s := range m.Selectors { + if s == nil { + return fmt.Errorf("nil selector #%d", i) + } + if err := p.selectors[i].fromProtoMessage(s); err != nil { + return fmt.Errorf("invalid selector #%d: %w", i, err) + } + } + + p.filters = make([]Filter, len(m.Filters)) + for i, f := range m.Filters { + if f == nil { + return fmt.Errorf("nil filter #%d", i) + } + if err := p.filters[i].fromProtoMessage(f); err != nil { + return fmt.Errorf("invalid filter #%d: %w", i, err) + } + } + p.backupFactor = m.GetContainerBackupFactor() - p.selectors = m.GetSelectors() - p.filters = m.GetFilters() return nil } @@ -128,10 +144,7 @@ func (p *PlacementPolicy) readFromV2(m netmap.PlacementPolicy, checkFieldPresenc // // See also Unmarshal. func (p PlacementPolicy) Marshal() []byte { - var m netmap.PlacementPolicy - p.WriteToV2(&m) - - return m.StableMarshal(nil) + return neofsproto.Marshal(p) } // Unmarshal decodes NeoFS API protocol binary format into the PlacementPolicy @@ -140,14 +153,7 @@ func (p PlacementPolicy) Marshal() []byte { // // See also Marshal. func (p *PlacementPolicy) Unmarshal(data []byte) error { - var m netmap.PlacementPolicy - - err := m.Unmarshal(data) - if err != nil { - return err - } - - return p.readFromV2(m, false) + return neofsproto.UnmarshalOptional(data, p, (*PlacementPolicy).fromProtoMessage) } // MarshalJSON encodes PlacementPolicy into a JSON format of the NeoFS API @@ -155,10 +161,7 @@ func (p *PlacementPolicy) Unmarshal(data []byte) error { // // See also UnmarshalJSON. func (p PlacementPolicy) MarshalJSON() ([]byte, error) { - var m netmap.PlacementPolicy - p.WriteToV2(&m) - - return m.MarshalJSON() + return neofsproto.MarshalMessageJSON(p.ProtoMessage()) } // UnmarshalJSON decodes NeoFS API protocol JSON format into the PlacementPolicy @@ -166,51 +169,79 @@ func (p PlacementPolicy) MarshalJSON() ([]byte, error) { // // See also MarshalJSON. func (p *PlacementPolicy) UnmarshalJSON(data []byte) error { - var m netmap.PlacementPolicy - - err := m.UnmarshalJSON(data) - if err != nil { - return err - } - - return p.readFromV2(m, false) + return neofsproto.UnmarshalJSONOptional(data, p, (*PlacementPolicy).fromProtoMessage) } -// ReadFromV2 reads PlacementPolicy from the netmap.PlacementPolicy message. -// Checks if the message conforms to NeoFS API V2 protocol. +// FromProtoMessage validates m according to the NeoFS API protocol and restores +// p from it. // -// See also WriteToV2. -func (p *PlacementPolicy) ReadFromV2(m netmap.PlacementPolicy) error { - return p.readFromV2(m, true) +// See also [PlacementPolicy.ProtoMessage]. +func (p *PlacementPolicy) FromProtoMessage(m *protonetmap.PlacementPolicy) error { + return p.fromProtoMessage(m, true) } -// WriteToV2 writes PlacementPolicy to the netmap.PlacementPolicy message. -// The message must not be nil. +// ProtoMessage converts p into message to transmit using the NeoFS API +// protocol. // -// See also ReadFromV2. -func (p PlacementPolicy) WriteToV2(m *netmap.PlacementPolicy) { - m.SetContainerBackupFactor(p.backupFactor) - m.SetFilters(p.filters) - m.SetSelectors(p.selectors) - m.SetReplicas(p.replicas) +// See also [PlacementPolicy.FromProtoMessage]. +func (p PlacementPolicy) ProtoMessage() *protonetmap.PlacementPolicy { + m := &protonetmap.PlacementPolicy{ + ContainerBackupFactor: p.backupFactor, + } + if len(p.replicas) > 0 { + m.Replicas = make([]*protonetmap.Replica, len(p.replicas)) + for i := range p.replicas { + m.Replicas[i] = p.replicas[i].protoMessage() + } + } + if len(p.selectors) > 0 { + m.Selectors = make([]*protonetmap.Selector, len(p.selectors)) + for i := range p.selectors { + m.Selectors[i] = p.selectors[i].protoMessage() + } + } + if len(p.filters) > 0 { + m.Filters = make([]*protonetmap.Filter, len(p.filters)) + for i := range p.filters { + m.Filters[i] = p.filters[i].protoMessage() + } + } + return m } // ReplicaDescriptor replica descriptor characterizes replicas of objects from // the subset selected by a particular Selector. type ReplicaDescriptor struct { - m netmap.Replica + count uint32 + selector string +} + +// fromProtoMessage validates m according to the NeoFS API protocol and restores +// r from it. +func (r *ReplicaDescriptor) fromProtoMessage(m *protonetmap.Replica) { + r.count = m.Count + r.selector = m.Selector +} + +// protoMessage converts r into message to transmit using the NeoFS API +// protocol. +func (r ReplicaDescriptor) protoMessage() *protonetmap.Replica { + return &protonetmap.Replica{ + Count: r.count, + Selector: r.selector, + } } // SetNumberOfObjects sets number of object replicas. func (r *ReplicaDescriptor) SetNumberOfObjects(c uint32) { - r.m.SetCount(c) + r.count = c } // NumberOfObjects returns number set using SetNumberOfObjects. // // Zero ReplicaDescriptor has zero number of objects. func (r ReplicaDescriptor) NumberOfObjects() uint32 { - return r.m.GetCount() + return r.count } // SetSelectorName sets name of the related Selector. @@ -220,7 +251,7 @@ func (r ReplicaDescriptor) NumberOfObjects() uint32 { // // See also [ReplicaDescriptor.SelectorName]. func (r *ReplicaDescriptor) SetSelectorName(s string) { - r.m.SetSelector(s) + r.selector = s } // SelectorName returns name of the related Selector. @@ -230,7 +261,7 @@ func (r *ReplicaDescriptor) SetSelectorName(s string) { // // See also [ReplicaDescriptor.SetSelectorName]. func (r ReplicaDescriptor) SelectorName() string { - return r.m.GetSelector() + return r.selector } // SetReplicas sets list of object replica's characteristics. @@ -238,11 +269,7 @@ func (r ReplicaDescriptor) SelectorName() string { // See also [PlacementPolicy.Replicas], [PlacementPolicy.NumberOfReplicas], // [PlacementPolicy.ReplicaNumberByIndex]. func (p *PlacementPolicy) SetReplicas(rs []ReplicaDescriptor) { - p.replicas = make([]netmap.Replica, len(rs)) - - for i := range rs { - p.replicas[i] = rs[i].m - } + p.replicas = rs } // Replicas returns list of object replica characteristics. @@ -250,11 +277,7 @@ func (p *PlacementPolicy) SetReplicas(rs []ReplicaDescriptor) { // See also [PlacementPolicy.SetReplicas], [PlacementPolicy.NumberOfReplicas], // [PlacementPolicy.ReplicaNumberByIndex]. func (p PlacementPolicy) Replicas() []ReplicaDescriptor { - rs := make([]ReplicaDescriptor, len(p.replicas)) - for i := range p.replicas { - rs[i].m = p.replicas[i] - } - return rs + return p.replicas } // NumberOfReplicas returns number of replica descriptors set using SetReplicas. @@ -270,7 +293,7 @@ func (p PlacementPolicy) NumberOfReplicas() int { // // Zero PlacementPolicy has no replicas. func (p PlacementPolicy) ReplicaNumberByIndex(i int) uint32 { - return p.replicas[i].GetCount() + return p.replicas[i].NumberOfObjects() } // SetContainerBackupFactor sets container backup factor: it controls how deep @@ -296,14 +319,44 @@ func (p *PlacementPolicy) ContainerBackupFactor() uint32 { // Selector describes the bucket selection operator: choose a number of nodes // from the bucket taking the nearest nodes to the related container by hash distance. type Selector struct { - m netmap.Selector + name string + count uint32 + clause protonetmap.Clause + attr string + filter string +} + +// fromProtoMessage validates m according to the NeoFS API protocol and restores +// s from it. +func (s *Selector) fromProtoMessage(m *protonetmap.Selector) error { + if m.Clause < 0 { + return fmt.Errorf("negative clause %d", m.Clause) + } + s.name = m.Name + s.count = m.Count + s.clause = m.Clause + s.attr = m.Attribute + s.filter = m.Filter + return nil +} + +// protoMessage converts s into message to transmit using the NeoFS API +// protocol. +func (s Selector) protoMessage() *protonetmap.Selector { + return &protonetmap.Selector{ + Name: s.name, + Count: s.count, + Clause: s.clause, + Attribute: s.attr, + Filter: s.filter, + } } // SetName sets name with which the Selector can be referenced. // // Zero Selector is unnamed. func (s *Selector) SetName(name string) { - s.m.SetName(name) + s.name = name } // Name returns name with which the Selector can be referenced. @@ -312,7 +365,7 @@ func (s *Selector) SetName(name string) { // // See also [Selector.Name]. func (s Selector) Name() string { - return s.m.GetName() + return s.name } // SetNumberOfNodes sets number of nodes to select from the bucket. @@ -321,7 +374,7 @@ func (s Selector) Name() string { // // See also [Selector.NumberOfNodes]. func (s *Selector) SetNumberOfNodes(num uint32) { - s.m.SetCount(num) + s.count = num } // NumberOfNodes returns number of nodes to select from the bucket. @@ -330,7 +383,7 @@ func (s *Selector) SetNumberOfNodes(num uint32) { // // See also [Selector.SetNumberOfNodes]. func (s Selector) NumberOfNodes() uint32 { - return s.m.GetCount() + return s.count } // SelectByBucketAttribute sets attribute of the bucket to select nodes from. @@ -339,7 +392,7 @@ func (s Selector) NumberOfNodes() uint32 { // // See also [Selector.BucketAttribute]. func (s *Selector) SelectByBucketAttribute(bucket string) { - s.m.SetAttribute(bucket) + s.attr = bucket } // BucketAttribute returns attribute of the bucket to select nodes from. @@ -348,7 +401,7 @@ func (s *Selector) SelectByBucketAttribute(bucket string) { // // See also [Selector.SelectByBucketAttribute]. func (s *Selector) BucketAttribute() string { - return s.m.GetAttribute() + return s.attr } // SelectSame makes selection algorithm to select only nodes having the same values @@ -358,7 +411,7 @@ func (s *Selector) BucketAttribute() string { // // See also [Selector.SelectByBucketAttribute], [Selector.IsSame]. func (s *Selector) SelectSame() { - s.m.SetClause(netmap.Same) + s.clause = protonetmap.Clause_SAME } // IsSame checks whether selection algorithm is set to select only nodes having @@ -366,7 +419,7 @@ func (s *Selector) SelectSame() { // // See also [Selector.SelectSame]. func (s *Selector) IsSame() bool { - return s.m.GetClause() == netmap.Same + return s.clause == protonetmap.Clause_SAME } // SelectDistinct makes selection algorithm to select only nodes having the different values @@ -376,7 +429,7 @@ func (s *Selector) IsSame() bool { // // See also [Selector.SelectByBucketAttribute], [Selector.IsDistinct]. func (s *Selector) SelectDistinct() { - s.m.SetClause(netmap.Distinct) + s.clause = protonetmap.Clause_DISTINCT } // IsDistinct checks whether selection algorithm is set to select only nodes @@ -384,7 +437,7 @@ func (s *Selector) SelectDistinct() { // // See also [Selector.SelectByBucketAttribute], [Selector.SelectDistinct]. func (s *Selector) IsDistinct() bool { - return s.m.GetClause() == netmap.Distinct + return s.clause == protonetmap.Clause_DISTINCT } // SetFilterName sets reference to pre-filtering nodes for selection. @@ -393,7 +446,7 @@ func (s *Selector) IsDistinct() bool { // // See also Filter.SetName. func (s *Selector) SetFilterName(f string) { - s.m.SetFilter(f) + s.filter = f } // FilterName returns reference to pre-filtering nodes for selection. @@ -402,7 +455,7 @@ func (s *Selector) SetFilterName(f string) { // // See also [Filter.SetName], [Selector.SetFilterName]. func (s *Selector) FilterName() string { - return s.m.GetFilter() + return s.filter } // SetSelectors sets list of Selector to form the subset of the nodes to store @@ -412,11 +465,7 @@ func (s *Selector) FilterName() string { // // See also [PlacementPolicy.Selectors]. func (p *PlacementPolicy) SetSelectors(ss []Selector) { - p.selectors = make([]netmap.Selector, len(ss)) - - for i := range ss { - p.selectors[i] = ss[i].m - } + p.selectors = ss } // Selectors returns list of Selector to form the subset of the nodes to store @@ -426,16 +475,55 @@ func (p *PlacementPolicy) SetSelectors(ss []Selector) { // // See also [PlacementPolicy.SetSelectors]. func (p PlacementPolicy) Selectors() []Selector { - ss := make([]Selector, len(p.selectors)) - for i := range p.selectors { - ss[i].m = p.selectors[i] - } - return ss + return p.selectors } // Filter contains rules for filtering the node sets. type Filter struct { - m netmap.Filter + name string + key string + op FilterOp + val string + subs []Filter +} + +// fromProtoMessage validates m according to the NeoFS API protocol and restores +// x from it. +func (x *Filter) fromProtoMessage(m *protonetmap.Filter) error { + if m.Op < 0 { + return fmt.Errorf("negative op %d", m.Op) + } + var subs []Filter + if len(m.Filters) > 0 { + subs = make([]Filter, len(m.Filters)) + for i := range m.Filters { + if err := subs[i].fromProtoMessage(m.Filters[i]); err != nil { + return fmt.Errorf("invalid sub-filter #%d: %w", i, err) + } + } + } + x.name = m.Name + x.setAttribute(m.Key, FilterOp(m.Op), m.Value) + x.subs = subs + return nil +} + +// protoMessage converts x into message to transmit using the NeoFS API +// protocol. +func (x Filter) protoMessage() *protonetmap.Filter { + m := &protonetmap.Filter{ + Name: x.name, + Key: x.key, + Op: protonetmap.Operation(x.op), + Value: x.val, + } + if len(x.subs) > 0 { + m.Filters = make([]*protonetmap.Filter, len(x.subs)) + for i := range x.subs { + m.Filters[i] = x.subs[i].protoMessage() + } + } + return m } // SetName sets name with which the Filter can be referenced or, for inner filters, @@ -446,7 +534,7 @@ type Filter struct { // // See also [Filter.Name]. func (x *Filter) SetName(name string) { - x.m.SetName(name) + x.name = name } // Name returns name with which the Filter can be referenced or, for inner @@ -457,57 +545,47 @@ func (x *Filter) SetName(name string) { // // See also [Filter.SetName]. func (x Filter) Name() string { - return x.m.GetName() + return x.name } // Key returns key to the property. func (x Filter) Key() string { - return x.m.GetKey() + return x.key } // Op returns operator to match the property. func (x Filter) Op() FilterOp { - return FilterOp(x.m.GetOp()) + return x.op } // Value returns value to check the property against. func (x Filter) Value() string { - return x.m.GetValue() + return x.val } // SubFilters returns list of sub-filters when Filter is complex. func (x Filter) SubFilters() []Filter { - fsm := x.m.GetFilters() - if len(fsm) == 0 { - return nil - } - - fs := make([]Filter, len(fsm)) - for i := range fsm { - fs[i] = Filter{m: fsm[i]} - } - - return fs + return x.subs } -func (x *Filter) setAttribute(key string, op netmap.Operation, val string) { - x.m.SetKey(key) - x.m.SetOp(op) - x.m.SetValue(val) +func (x *Filter) setAttribute(key string, op FilterOp, val string) { + x.key = key + x.op = op + x.val = val } // Equal applies the rule to accept only nodes with the same attribute value. // // Method SHOULD NOT be called along with other similar methods. func (x *Filter) Equal(key, value string) { - x.setAttribute(key, netmap.EQ, value) + x.setAttribute(key, FilterOpEQ, value) } // NotEqual applies the rule to accept only nodes with the distinct attribute value. // // Method SHOULD NOT be called along with other similar methods. func (x *Filter) NotEqual(key, value string) { - x.setAttribute(key, netmap.NE, value) + x.setAttribute(key, FilterOpNE, value) } // NumericGT applies the rule to accept only nodes with the numeric attribute @@ -515,7 +593,7 @@ func (x *Filter) NotEqual(key, value string) { // // Method SHOULD NOT be called along with other similar methods. func (x *Filter) NumericGT(key string, num int64) { - x.setAttribute(key, netmap.GT, strconv.FormatInt(num, 10)) + x.setAttribute(key, FilterOpGT, strconv.FormatInt(num, 10)) } // NumericGE applies the rule to accept only nodes with the numeric attribute @@ -523,7 +601,7 @@ func (x *Filter) NumericGT(key string, num int64) { // // Method SHOULD NOT be called along with other similar methods. func (x *Filter) NumericGE(key string, num int64) { - x.setAttribute(key, netmap.GE, strconv.FormatInt(num, 10)) + x.setAttribute(key, FilterOpGE, strconv.FormatInt(num, 10)) } // NumericLT applies the rule to accept only nodes with the numeric attribute @@ -531,7 +609,7 @@ func (x *Filter) NumericGE(key string, num int64) { // // Method SHOULD NOT be called along with other similar methods. func (x *Filter) NumericLT(key string, num int64) { - x.setAttribute(key, netmap.LT, strconv.FormatInt(num, 10)) + x.setAttribute(key, FilterOpLT, strconv.FormatInt(num, 10)) } // NumericLE applies the rule to accept only nodes with the numeric attribute @@ -539,22 +617,12 @@ func (x *Filter) NumericLT(key string, num int64) { // // Method SHOULD NOT be called along with other similar methods. func (x *Filter) NumericLE(key string, num int64) { - x.setAttribute(key, netmap.LE, strconv.FormatInt(num, 10)) + x.setAttribute(key, FilterOpLE, strconv.FormatInt(num, 10)) } -func (x *Filter) setInnerFilters(op netmap.Operation, filters []Filter) { +func (x *Filter) setInnerFilters(op FilterOp, filters []Filter) { x.setAttribute("", op, "") - - inner := x.m.GetFilters() - if rem := len(filters) - len(inner); rem > 0 { - inner = append(inner, make([]netmap.Filter, rem)...) - } - - for i := range filters { - inner[i] = filters[i].m - } - - x.m.SetFilters(inner) + x.subs = filters } // LogicalOR applies the rule to accept only nodes which satisfy at least one @@ -562,7 +630,7 @@ func (x *Filter) setInnerFilters(op netmap.Operation, filters []Filter) { // // Method SHOULD NOT be called along with other similar methods. func (x *Filter) LogicalOR(filters ...Filter) { - x.setInnerFilters(netmap.OR, filters) + x.setInnerFilters(FilterOpOR, filters) } // LogicalAND applies the rule to accept only nodes which satisfy all the given @@ -570,7 +638,7 @@ func (x *Filter) LogicalOR(filters ...Filter) { // // Method SHOULD NOT be called along with other similar methods. func (x *Filter) LogicalAND(filters ...Filter) { - x.setInnerFilters(netmap.AND, filters) + x.setInnerFilters(FilterOpAND, filters) } // Filters returns list of Filter that will be applied when selecting nodes. @@ -579,11 +647,7 @@ func (x *Filter) LogicalAND(filters ...Filter) { // // See also [PlacementPolicy.SetFilters]. func (p PlacementPolicy) Filters() []Filter { - fs := make([]Filter, len(p.filters)) - for i := range p.filters { - fs[i] = Filter{m: p.filters[i]} - } - return fs + return p.filters } // SetFilters sets list of Filter that will be applied when selecting nodes. @@ -592,11 +656,7 @@ func (p PlacementPolicy) Filters() []Filter { // // See also [PlacementPolicy.Filters]. func (p *PlacementPolicy) SetFilters(fs []Filter) { - p.filters = make([]netmap.Filter, len(fs)) - - for i := range fs { - p.filters[i] = fs[i].m - } + p.filters = fs } // WriteStringTo encodes PlacementPolicy into human-readably query and writes @@ -623,8 +683,8 @@ func (p PlacementPolicy) WriteStringTo(w io.StringWriter) (err error) { return err } - c := p.replicas[i].GetCount() - s := p.replicas[i].GetSelector() + c := p.replicas[i].NumberOfObjects() + s := p.replicas[i].SelectorName() if s != "" { _, err = w.WriteString(fmt.Sprintf("REP %d IN %s", c, s)) @@ -657,18 +717,18 @@ func (p PlacementPolicy) WriteStringTo(w io.StringWriter) (err error) { return err } - _, err = w.WriteString(fmt.Sprintf("SELECT %d", p.selectors[i].GetCount())) + _, err = w.WriteString(fmt.Sprintf("SELECT %d", p.selectors[i].NumberOfNodes())) if err != nil { return err } - if s = p.selectors[i].GetAttribute(); s != "" { + if s = p.selectors[i].BucketAttribute(); s != "" { var clause string - switch p.selectors[i].GetClause() { - case netmap.Same: + switch p.selectors[i].clause { + case protonetmap.Clause_SAME: clause = "SAME " - case netmap.Distinct: + case protonetmap.Clause_DISTINCT: clause = "DISTINCT " default: clause = "" @@ -680,14 +740,14 @@ func (p PlacementPolicy) WriteStringTo(w io.StringWriter) (err error) { } } - if s = p.selectors[i].GetFilter(); s != "" { + if s = p.selectors[i].FilterName(); s != "" { _, err = w.WriteString(" FROM " + s) if err != nil { return err } } - if s = p.selectors[i].GetName(); s != "" { + if s = p.selectors[i].Name(); s != "" { _, err = w.WriteString(" AS " + s) if err != nil { return err @@ -715,25 +775,25 @@ func (p PlacementPolicy) WriteStringTo(w io.StringWriter) (err error) { return nil } -func writeFilterStringTo(w io.StringWriter, f netmap.Filter) error { +func writeFilterStringTo(w io.StringWriter, f Filter) error { var err error var s string - op := f.GetOp() + op := f.Op() unspecified := op == 0 - if s = f.GetKey(); s != "" { - _, err = w.WriteString(fmt.Sprintf("%s %s %s", s, op, f.GetValue())) + if s = f.Key(); s != "" { + _, err = w.WriteString(fmt.Sprintf("%s %s %s", s, op, f.Value())) if err != nil { return err } - } else if s = f.GetName(); unspecified && s != "" { + } else if s = f.Name(); unspecified && s != "" { _, err = w.WriteString(fmt.Sprintf("@%s", s)) if err != nil { return err } } - inner := f.GetFilters() + inner := f.SubFilters() for i := range inner { if i != 0 { _, err = w.WriteString(" " + op.String() + " ") @@ -748,7 +808,7 @@ func writeFilterStringTo(w io.StringWriter, f netmap.Filter) error { } } - if s = f.GetName(); s != "" && !unspecified { + if s = f.Name(); s != "" && !unspecified { _, err = w.WriteString(" AS " + s) if err != nil { return err @@ -828,15 +888,14 @@ func (p *policyVisitor) VisitPolicy(ctx *parser.PolicyContext) any { pl := new(PlacementPolicy) repStmts := ctx.AllRepStmt() - pl.replicas = make([]netmap.Replica, 0, len(repStmts)) + pl.replicas = make([]ReplicaDescriptor, len(repStmts)) - for _, r := range repStmts { - res, ok := r.Accept(p).(*netmap.Replica) + for i, r := range repStmts { + res, ok := r.Accept(p).(*protonetmap.Replica) if !ok { return nil } - - pl.replicas = append(pl.replicas, *res) + pl.replicas[i].fromProtoMessage(res) } if cbfStmt := ctx.CbfStmt(); cbfStmt != nil { @@ -848,22 +907,29 @@ func (p *policyVisitor) VisitPolicy(ctx *parser.PolicyContext) any { } selStmts := ctx.AllSelectStmt() - pl.selectors = make([]netmap.Selector, 0, len(selStmts)) + pl.selectors = make([]Selector, len(selStmts)) - for _, s := range selStmts { - res, ok := s.Accept(p).(*netmap.Selector) + for i, s := range selStmts { + res, ok := s.Accept(p).(*protonetmap.Selector) if !ok { return nil } - - pl.selectors = append(pl.selectors, *res) + if err := pl.selectors[i].fromProtoMessage(res); err != nil { + return fmt.Errorf("invalid selector #%d: %w", i, err) + } } filtStmts := ctx.AllFilterStmt() - pl.filters = make([]netmap.Filter, 0, len(filtStmts)) + pl.filters = make([]Filter, len(filtStmts)) - for _, f := range filtStmts { - pl.filters = append(pl.filters, *f.Accept(p).(*netmap.Filter)) + for i, f := range filtStmts { + res, ok := f.Accept(p).(*protonetmap.Filter) + if !ok { + return nil + } + if err := pl.filters[i].fromProtoMessage(res); err != nil { + return fmt.Errorf("invalid filter #%d: %w", i, err) + } } return pl @@ -885,11 +951,11 @@ func (p *policyVisitor) VisitRepStmt(ctx *parser.RepStmtContext) any { return p.reportError(errInvalidNumber) } - rs := new(netmap.Replica) - rs.SetCount(uint32(num)) + rs := new(protonetmap.Replica) + rs.Count = uint32(num) if sel := ctx.GetSelector(); sel != nil { - rs.SetSelector(sel.GetText()) + rs.Selector = sel.GetText() } return rs @@ -902,29 +968,29 @@ func (p *policyVisitor) VisitSelectStmt(ctx *parser.SelectStmtContext) any { return p.reportError(errInvalidNumber) } - s := new(netmap.Selector) - s.SetCount(uint32(res)) + s := new(protonetmap.Selector) + s.Count = uint32(res) if clStmt := ctx.Clause(); clStmt != nil { - s.SetClause(clauseFromString(clStmt.GetText())) + s.Clause = clauseFromString(clStmt.GetText()) } if bStmt := ctx.GetBucket(); bStmt != nil { - s.SetAttribute(ctx.GetBucket().GetText()) + s.Attribute = ctx.GetBucket().GetText() } - s.SetFilter(ctx.GetFilter().GetText()) // either ident or wildcard + s.Filter = ctx.GetFilter().GetText() // either ident or wildcard if ctx.AS() != nil { - s.SetName(ctx.GetName().GetText()) + s.Name = ctx.GetName().GetText() } return s } // VisitFilterStmt implements parser.QueryVisitor interface. func (p *policyVisitor) VisitFilterStmt(ctx *parser.FilterStmtContext) any { - f := p.VisitFilterExpr(ctx.GetExpr().(*parser.FilterExprContext)).(*netmap.Filter) - f.SetName(ctx.GetName().GetText()) + f := p.VisitFilterExpr(ctx.GetExpr().(*parser.FilterExprContext)).(*protonetmap.Filter) + f.Name = ctx.GetName().GetText() return f } @@ -937,21 +1003,21 @@ func (p *policyVisitor) VisitFilterExpr(ctx *parser.FilterExprContext) any { return inner.Accept(p) } - f := new(netmap.Filter) + f := new(protonetmap.Filter) op := operationFromString(ctx.GetOp().GetText()) - f.SetOp(op) + f.Op = op - f1 := *ctx.GetF1().Accept(p).(*netmap.Filter) - f2 := *ctx.GetF2().Accept(p).(*netmap.Filter) + f1 := ctx.GetF1().Accept(p).(*protonetmap.Filter) + f2 := ctx.GetF2().Accept(p).(*protonetmap.Filter) // Consider f1=(.. AND ..) AND f2. This can be merged because our AND operation // is of arbitrary arity. ANTLR generates left-associative parse-tree by default. if f1.GetOp() == op { - f.SetFilters(append(f1.GetFilters(), f2)) + f.Filters = append(f1.GetFilters(), f2) return f } - f.SetFilters([]netmap.Filter{f1, f2}) + f.Filters = []*protonetmap.Filter{f1, f2} return f } @@ -981,9 +1047,9 @@ func (p *policyVisitor) VisitFilterValue(ctx *parser.FilterValueContext) any { // VisitExpr implements parser.QueryVisitor interface. func (p *policyVisitor) VisitExpr(ctx *parser.ExprContext) any { - f := new(netmap.Filter) + f := new(protonetmap.Filter) if flt := ctx.GetFilter(); flt != nil { - f.SetName(flt.GetText()) + f.Name = flt.GetText() return f } @@ -991,9 +1057,9 @@ func (p *policyVisitor) VisitExpr(ctx *parser.ExprContext) any { opStr := ctx.SIMPLE_OP().GetText() value := ctx.GetValue().Accept(p) - f.SetKey(key.(string)) - f.SetOp(operationFromString(opStr)) - f.SetValue(value.(string)) + f.Key = key.(string) + f.Op = operationFromString(opStr) + f.Value = value.(string) return f } @@ -1004,21 +1070,21 @@ func validatePolicy(p PlacementPolicy) error { seenFilters := map[string]bool{} for i := range p.filters { - seenFilters[p.filters[i].GetName()] = true + seenFilters[p.filters[i].Name()] = true } seenSelectors := map[string]bool{} for i := range p.selectors { - if flt := p.selectors[i].GetFilter(); flt != mainFilterName && !seenFilters[flt] { + if flt := p.selectors[i].FilterName(); flt != mainFilterName && !seenFilters[flt] { return fmt.Errorf("%w: '%s'", errUnknownFilter, flt) } - seenSelectors[p.selectors[i].GetName()] = true + seenSelectors[p.selectors[i].Name()] = true } for i := range p.replicas { - if sel := p.replicas[i].GetSelector(); sel != "" && !seenSelectors[sel] { + if sel := p.replicas[i].SelectorName(); sel != "" && !seenSelectors[sel] { return fmt.Errorf("%w: '%s'", errUnknownSelector, sel) } } @@ -1026,20 +1092,42 @@ func validatePolicy(p PlacementPolicy) error { return nil } -func clauseFromString(s string) (c netmap.Clause) { - if !c.FromString(strings.ToUpper(s)) { +func clauseFromString(s string) protonetmap.Clause { + switch s { + default: // Such errors should be handled by ANTLR code thus this panic. - panic(fmt.Errorf("BUG: invalid clause: %s", c)) + panic(fmt.Errorf("BUG: invalid clause: %s", s)) + case "CLAUSE_UNSPECIFIED": + return protonetmap.Clause_CLAUSE_UNSPECIFIED + case "SAME": + return protonetmap.Clause_SAME + case "DISTINCT": + return protonetmap.Clause_DISTINCT } - - return } -func operationFromString(s string) (op netmap.Operation) { - if !op.FromString(strings.ToUpper(s)) { +func operationFromString(s string) protonetmap.Operation { + switch s { + default: // Such errors should be handled by ANTLR code thus this panic. - panic(fmt.Errorf("BUG: invalid operation: %s", op)) + panic(fmt.Errorf("BUG: invalid operation: %s", s)) + case "OPERATION_UNSPECIFIED": + return protonetmap.Operation_OPERATION_UNSPECIFIED + case "EQ": + return protonetmap.Operation_EQ + case "NE": + return protonetmap.Operation_NE + case "GT": + return protonetmap.Operation_GT + case "GE": + return protonetmap.Operation_GE + case "LT": + return protonetmap.Operation_LT + case "LE": + return protonetmap.Operation_LE + case "OR": + return protonetmap.Operation_OR + case "AND": + return protonetmap.Operation_AND } - - return } diff --git a/netmap/policy_internal_test.go b/netmap/policy_internal_test.go index 9c48fad9..97aa622a 100644 --- a/netmap/policy_internal_test.go +++ b/netmap/policy_internal_test.go @@ -4,7 +4,6 @@ import ( "bytes" "testing" - "github.com/nspcc-dev/neofs-api-go/v2/netmap" "github.com/stretchr/testify/require" ) @@ -37,15 +36,15 @@ func TestPlacementPolicy_CopyTo(t *testing.T) { var dst PlacementPolicy pp.CopyTo(&dst) - var f2 netmap.Filter + var f2 Filter f2.SetName("filter2") - require.Equal(t, pp.filters[0].GetName(), dst.filters[0].GetName()) + require.Equal(t, pp.filters[0].Name(), dst.filters[0].Name()) dst.filters[0].SetName("f2") - require.NotEqual(t, pp.filters[0].GetName(), dst.filters[0].GetName()) + require.NotEqual(t, pp.filters[0].Name(), dst.filters[0].Name()) dst.filters[0] = f2 - require.NotEqual(t, pp.filters[0].GetName(), dst.filters[0].GetName()) + require.NotEqual(t, pp.filters[0].Name(), dst.filters[0].Name()) }) t.Run("internal filters", func(t *testing.T) { @@ -54,7 +53,7 @@ func TestPlacementPolicy_CopyTo(t *testing.T) { var topFilter Filter topFilter.SetName("topFilter") - topFilter.setInnerFilters(netmap.EQ, []Filter{includedFilter}) + topFilter.setInnerFilters(FilterOpEQ, []Filter{includedFilter}) var policy PlacementPolicy policy.SetFilters([]Filter{topFilter}) @@ -64,13 +63,13 @@ func TestPlacementPolicy_CopyTo(t *testing.T) { require.True(t, bytes.Equal(policy.Marshal(), dst.Marshal())) t.Run("change extra filter", func(t *testing.T) { - require.Equal(t, topFilter.m.GetName(), dst.filters[0].GetName()) - require.Equal(t, topFilter.m.GetFilters()[0].GetName(), dst.filters[0].GetFilters()[0].GetName()) + require.Equal(t, topFilter.Name(), dst.filters[0].Name()) + require.Equal(t, topFilter.SubFilters()[0].Name(), dst.filters[0].SubFilters()[0].Name()) - dst.filters[0].GetFilters()[0].SetName("someInternalFilterName") + dst.filters[0].SubFilters()[0].SetName("someInternalFilterName") - require.Equal(t, topFilter.m.GetName(), dst.filters[0].GetName()) - require.NotEqual(t, topFilter.m.GetFilters()[0].GetName(), dst.filters[0].GetFilters()[0].GetName()) + require.Equal(t, topFilter.Name(), dst.filters[0].Name()) + require.NotEqual(t, topFilter.SubFilters()[0].Name(), dst.filters[0].SubFilters()[0].Name()) }) }) @@ -88,23 +87,23 @@ func TestPlacementPolicy_CopyTo(t *testing.T) { var dst PlacementPolicy pp.CopyTo(&dst) - require.Equal(t, pp.selectors[0].GetName(), dst.selectors[0].GetName()) + require.Equal(t, pp.selectors[0].Name(), dst.selectors[0].Name()) dst.selectors[0].SetName("s2") - require.NotEqual(t, pp.selectors[0].GetName(), dst.selectors[0].GetName()) + require.NotEqual(t, pp.selectors[0].Name(), dst.selectors[0].Name()) - var s2 netmap.Selector + var s2 Selector s2.SetName("selector2") dst.selectors[0] = s2 - require.NotEqual(t, pp.selectors[0].GetName(), dst.selectors[0].GetName()) + require.NotEqual(t, pp.selectors[0].Name(), dst.selectors[0].Name()) }) t.Run("change replica", func(t *testing.T) { var dst PlacementPolicy pp.CopyTo(&dst) - require.Equal(t, pp.replicas[0].GetSelector(), dst.replicas[0].GetSelector()) - dst.replicas[0].SetSelector("s2") - require.NotEqual(t, pp.replicas[0].GetSelector(), dst.replicas[0].GetSelector()) + require.Equal(t, pp.replicas[0].SelectorName(), dst.replicas[0].SelectorName()) + dst.replicas[0].SetSelectorName("s2") + require.NotEqual(t, pp.replicas[0].SelectorName(), dst.replicas[0].SelectorName()) }) } diff --git a/netmap/policy_test.go b/netmap/policy_test.go index 8b67306d..1729647b 100644 --- a/netmap/policy_test.go +++ b/netmap/policy_test.go @@ -5,8 +5,8 @@ import ( "strings" "testing" - apinetmap "github.com/nspcc-dev/neofs-api-go/v2/netmap" "github.com/nspcc-dev/neofs-sdk-go/netmap" + protonetmap "github.com/nspcc-dev/neofs-sdk-go/proto/netmap" "github.com/stretchr/testify/require" ) @@ -254,54 +254,33 @@ func TestPlacementPolicy_SetFilters(t *testing.T) { require.Equal(t, anyValidFilters, p.Filters()) } -func TestPlacementPolicy_ReadFromV2(t *testing.T) { - var m apinetmap.PlacementPolicy - m.SetContainerBackupFactor(anyValidBackupFactor) - mrs := make([]apinetmap.Replica, 2) - mrs[0].SetSelector("selector_0") - mrs[0].SetCount(2583748530) - mrs[1].SetSelector("selector_1") - mrs[1].SetCount(358755354) - m.SetReplicas(mrs) - mss := make([]apinetmap.Selector, 2) - mss[0].SetName("selector_0") - mss[0].SetCount(1814781076) - mss[0].SetClause(apinetmap.Same) - mss[0].SetFilter("filter_0") - mss[0].SetAttribute("attribute_0") - mss[1].SetName("selector_1") - mss[1].SetCount(1814781076) - mss[1].SetClause(apinetmap.Distinct) - mss[1].SetFilter("filter_1") - mss[1].SetAttribute("attribute_1") - m.SetSelectors(mss) - msubs := make([]apinetmap.Filter, 0, 2) - addSub := func(name, key string, op apinetmap.Operation, val string) { - var f apinetmap.Filter - f.SetName(name) - f.SetKey(key) - f.SetOp(op) - f.SetValue(val) - msubs = append(msubs, f) +func TestPlacementPolicy_FromProtoMessage(t *testing.T) { + m := &protonetmap.PlacementPolicy{ + Replicas: []*protonetmap.Replica{ + {Count: 2583748530, Selector: "selector_0"}, + {Count: 358755354, Selector: "selector_1"}, + }, + ContainerBackupFactor: anyValidBackupFactor, + Selectors: []*protonetmap.Selector{ + {Name: "selector_0", Count: 1814781076, Clause: protonetmap.Clause_SAME, Attribute: "attribute_0", Filter: "filter_0"}, + {Name: "selector_1", Count: 1814781076, Clause: protonetmap.Clause_DISTINCT, Attribute: "attribute_1", Filter: "filter_1"}, + }, + Filters: []*protonetmap.Filter{ + {Name: "filter_0", Op: protonetmap.Operation_AND, Filters: []*protonetmap.Filter{ + {Name: "filter_0_0", Key: "key_0_0", Op: protonetmap.Operation_EQ, Value: "val_0_0"}, + {Name: "filter_0_1", Key: "key_0_1", Op: protonetmap.Operation_NE, Value: "val_0_1"}, + }}, + {Name: "filter_1", Key: "", Op: protonetmap.Operation_OR, Value: "", Filters: []*protonetmap.Filter{ + {Name: "filter_1_0", Key: "key_1_0", Op: protonetmap.Operation_GT, Value: "1889407708985023116"}, + {Name: "filter_1_1", Key: "key_1_1", Op: protonetmap.Operation_GE, Value: "1429243097315344888"}, + {Name: "filter_1_2", Key: "key_1_2", Op: protonetmap.Operation_LT, Value: "3722656060317482335"}, + {Name: "filter_1_3", Key: "key_1_3", Op: protonetmap.Operation_LE, Value: "1950504987705284805"}, + }}, + }, } - addSub("filter_0_0", "key_0_0", apinetmap.EQ, "val_0_0") - addSub("filter_0_1", "key_0_1", apinetmap.NE, "val_0_1") - mfs := make([]apinetmap.Filter, 2) - mfs[0].SetName("filter_0") - mfs[0].SetOp(apinetmap.AND) - mfs[0].SetFilters(msubs) - msubs = make([]apinetmap.Filter, 0, 4) - addSub("filter_1_0", "key_1_0", apinetmap.GT, "1889407708985023116") - addSub("filter_1_1", "key_1_1", apinetmap.GE, "1429243097315344888") - addSub("filter_1_2", "key_1_2", apinetmap.LT, "3722656060317482335") - addSub("filter_1_3", "key_1_3", apinetmap.LE, "1950504987705284805") - mfs[1].SetName("filter_1") - mfs[1].SetOp(apinetmap.OR) - mfs[1].SetFilters(msubs) - m.SetFilters(mfs) var val netmap.PlacementPolicy - require.NoError(t, val.ReadFromV2(m)) + require.NoError(t, val.FromProtoMessage(m)) require.EqualValues(t, anyValidBackupFactor, val.ContainerBackupFactor()) rs := val.Replicas() require.Len(t, rs, 2) @@ -365,48 +344,56 @@ func TestPlacementPolicy_ReadFromV2(t *testing.T) { require.Empty(t, subs[3].SubFilters()) // reset optional fields - m.SetSelectors(nil) - m.SetFilters(nil) + m.Selectors = nil + m.Filters = nil val2 := val - require.NoError(t, val2.ReadFromV2(m)) + require.NoError(t, val2.FromProtoMessage(m)) require.Empty(t, val2.Selectors()) require.Empty(t, val2.Filters()) t.Run("invalid", func(t *testing.T) { for _, tc := range []struct { name, err string - corrupt func(*apinetmap.PlacementPolicy) + corrupt func(*protonetmap.PlacementPolicy) }{ {name: "replicas/nil", err: "missing replicas", - corrupt: func(m *apinetmap.PlacementPolicy) { m.SetReplicas(nil) }}, + corrupt: func(m *protonetmap.PlacementPolicy) { m.Replicas = nil }}, + {name: "replicas/nil element", err: "nil replica #1", + corrupt: func(m *protonetmap.PlacementPolicy) { m.Replicas[1] = nil }}, {name: "replicas/empty", err: "missing replicas", - corrupt: func(m *apinetmap.PlacementPolicy) { m.SetReplicas([]apinetmap.Replica{}) }}, + corrupt: func(m *protonetmap.PlacementPolicy) { m.Replicas = []*protonetmap.Replica{} }}, + {name: "selectors/nil element", err: "nil selector #1", + corrupt: func(m *protonetmap.PlacementPolicy) { m.Selectors[1] = nil }}, + {name: "selectors/negative clause", err: "invalid selector #1: negative clause -1", + corrupt: func(m *protonetmap.PlacementPolicy) { m.Selectors[1].Clause = -1 }}, + {name: "filters/nil element", err: "nil filter #1", + corrupt: func(m *protonetmap.PlacementPolicy) { m.Filters[1] = nil }}, + {name: "filters/negative op", err: "invalid filter #1: negative op -1", + corrupt: func(m *protonetmap.PlacementPolicy) { m.Filters[1].Op = -1 }}, } { t.Run(tc.name, func(t *testing.T) { st := val - var m apinetmap.PlacementPolicy - st.WriteToV2(&m) - tc.corrupt(&m) - require.EqualError(t, new(netmap.PlacementPolicy).ReadFromV2(m), tc.err) + m := st.ProtoMessage() + tc.corrupt(m) + require.EqualError(t, new(netmap.PlacementPolicy).FromProtoMessage(m), tc.err) }) } }) } -func TestPlacementPolicy_WriteToV2(t *testing.T) { +func TestPlacementPolicy_ProtoMessage(t *testing.T) { var val netmap.PlacementPolicy - var m apinetmap.PlacementPolicy // zero - val.WriteToV2(&m) + m := val.ProtoMessage() require.Zero(t, m.GetContainerBackupFactor()) require.Zero(t, m.GetReplicas()) require.Zero(t, m.GetSelectors()) require.Zero(t, m.GetFilters()) - require.Zero(t, m.GetSubnetID()) + require.Zero(t, m.GetSubnetId()) //nolint: staticcheck // must be supported still // filled - validPlacementPolicy.WriteToV2(&m) + m = validPlacementPolicy.ProtoMessage() require.EqualValues(t, anyValidBackupFactor, m.GetContainerBackupFactor()) mrs := m.GetReplicas() @@ -420,12 +407,12 @@ func TestPlacementPolicy_WriteToV2(t *testing.T) { require.Len(t, mss, 2) require.Equal(t, "selector_0", mss[0].GetName()) require.EqualValues(t, 1814781076, mss[0].GetCount()) - require.Equal(t, apinetmap.Same, mss[0].GetClause()) + require.Equal(t, protonetmap.Clause_SAME, mss[0].GetClause()) require.Equal(t, "filter_0", mss[0].GetFilter()) require.Equal(t, "attribute_0", mss[0].GetAttribute()) require.Equal(t, "selector_1", mss[1].GetName()) require.EqualValues(t, 1505136737, mss[1].GetCount()) - require.Equal(t, apinetmap.Distinct, mss[1].GetClause()) + require.Equal(t, protonetmap.Clause_DISTINCT, mss[1].GetClause()) require.Equal(t, "filter_1", mss[1].GetFilter()) require.Equal(t, "attribute_1", mss[1].GetAttribute()) @@ -434,51 +421,51 @@ func TestPlacementPolicy_WriteToV2(t *testing.T) { // filter#0 require.Equal(t, "filter_0", mfs[0].GetName()) require.Zero(t, mfs[0].GetKey()) - require.Equal(t, apinetmap.AND, mfs[0].GetOp()) + require.Equal(t, protonetmap.Operation_AND, mfs[0].GetOp()) require.Zero(t, mfs[0].GetValue()) msubs := mfs[0].GetFilters() require.Len(t, msubs, 2) // sub#0 require.Equal(t, "filter_0_0", msubs[0].GetName()) require.Equal(t, "key_0_0", msubs[0].GetKey()) - require.Equal(t, apinetmap.EQ, msubs[0].GetOp()) + require.Equal(t, protonetmap.Operation_EQ, msubs[0].GetOp()) require.Equal(t, "val_0_0", msubs[0].GetValue()) require.Zero(t, msubs[0].GetFilters()) // sub#1 require.Equal(t, "filter_0_1", msubs[1].GetName()) require.Equal(t, "key_0_1", msubs[1].GetKey()) - require.Equal(t, apinetmap.NE, msubs[1].GetOp()) + require.Equal(t, protonetmap.Operation_NE, msubs[1].GetOp()) require.Equal(t, "val_0_1", msubs[1].GetValue()) require.Zero(t, msubs[1].GetFilters()) // filter#1 require.Equal(t, "filter_1", mfs[1].GetName()) require.Zero(t, mfs[1].GetKey()) - require.Equal(t, apinetmap.OR, mfs[1].GetOp()) + require.Equal(t, protonetmap.Operation_OR, mfs[1].GetOp()) require.Zero(t, mfs[1].GetValue()) msubs = mfs[1].GetFilters() require.Len(t, msubs, 4) // sub#0 require.Equal(t, "filter_1_0", msubs[0].GetName()) require.Equal(t, "key_1_0", msubs[0].GetKey()) - require.Equal(t, apinetmap.GT, msubs[0].GetOp()) + require.Equal(t, protonetmap.Operation_GT, msubs[0].GetOp()) require.Equal(t, "1889407708985023116", msubs[0].GetValue()) require.Zero(t, msubs[0].GetFilters()) // sub#1 require.Equal(t, "filter_1_1", msubs[1].GetName()) require.Equal(t, "key_1_1", msubs[1].GetKey()) - require.Equal(t, apinetmap.GE, msubs[1].GetOp()) + require.Equal(t, protonetmap.Operation_GE, msubs[1].GetOp()) require.Equal(t, "1429243097315344888", msubs[1].GetValue()) require.Zero(t, msubs[1].GetFilters()) // sub#2 require.Equal(t, "filter_1_2", msubs[2].GetName()) require.Equal(t, "key_1_2", msubs[2].GetKey()) - require.Equal(t, apinetmap.LT, msubs[2].GetOp()) + require.Equal(t, protonetmap.Operation_LT, msubs[2].GetOp()) require.Equal(t, "3722656060317482335", msubs[2].GetValue()) require.Zero(t, msubs[2].GetFilters()) // sub#3 require.Equal(t, "filter_1_3", msubs[3].GetName()) require.Equal(t, "key_1_3", msubs[3].GetKey()) - require.Equal(t, apinetmap.LE, msubs[3].GetOp()) + require.Equal(t, protonetmap.Operation_LE, msubs[3].GetOp()) require.Equal(t, "1950504987705284805", msubs[3].GetValue()) require.Zero(t, msubs[3].GetFilters()) } diff --git a/netmap/selector.go b/netmap/selector.go index 24bd90d9..13bda6c8 100644 --- a/netmap/selector.go +++ b/netmap/selector.go @@ -5,21 +5,20 @@ import ( "sort" "github.com/nspcc-dev/hrw/v2" - "github.com/nspcc-dev/neofs-api-go/v2/netmap" ) // processSelectors processes selectors and returns error is any of them is invalid. func (c *context) processSelectors(p PlacementPolicy) error { for i := range p.selectors { - fName := p.selectors[i].GetFilter() + fName := p.selectors[i].FilterName() if fName != mainFilterName { - _, ok := c.processedFilters[p.selectors[i].GetFilter()] + _, ok := c.processedFilters[p.selectors[i].FilterName()] if !ok { return fmt.Errorf("%w: SELECT FROM '%s'", errFilterNotFound, fName) } } - sName := p.selectors[i].GetName() + sName := p.selectors[i].Name() c.processedSelectors[sName] = &p.selectors[i] @@ -36,13 +35,12 @@ func (c *context) processSelectors(p PlacementPolicy) error { // calcNodesCount returns number of buckets and minimum number of nodes in every bucket // for the given selector. -func calcNodesCount(s netmap.Selector) (int, int) { - switch s.GetClause() { - case netmap.Same: - return 1, int(s.GetCount()) - default: - return int(s.GetCount()), 1 +func calcNodesCount(s Selector) (int, int) { + n := int(s.NumberOfNodes()) + if s.IsSame() { + return 1, n } + return n, 1 } // calcBucketWeight computes weight for a node bucket. @@ -56,12 +54,12 @@ func calcBucketWeight(ns nodes, a aggregator, wf weightFunc) float64 { // getSelection returns nodes grouped by s.attribute. // Last argument specifies if more buckets can be used to fulfill CBF. -func (c *context) getSelection(_ PlacementPolicy, s netmap.Selector) ([]nodes, error) { +func (c *context) getSelection(_ PlacementPolicy, s Selector) ([]nodes, error) { bucketCount, nodesInBucket := calcNodesCount(s) buckets := c.getSelectionBase(s) if len(buckets) < bucketCount { - return nil, fmt.Errorf("%w: '%s'", ErrNotEnoughNodes, s.GetName()) + return nil, fmt.Errorf("%w: '%s'", ErrNotEnoughNodes, s.Name()) } // We need deterministic output in case there is no pivot. @@ -69,7 +67,7 @@ func (c *context) getSelection(_ PlacementPolicy, s netmap.Selector) ([]nodes, e // However, because initial order influences HRW order for buckets with equal weights, // we also need to have deterministic input to HRW sorting routine. if len(c.hrwSeed) == 0 { - if s.GetAttribute() == "" { + if s.BucketAttribute() == "" { sort.Slice(buckets, func(i, j int) bool { return less(buckets[i].nodes[0], buckets[j].nodes[0]) }) @@ -97,7 +95,7 @@ func (c *context) getSelection(_ PlacementPolicy, s netmap.Selector) ([]nodes, e // Fallback to using minimum allowed backup factor (1). res = append(res, fallback...) if len(res) < bucketCount { - return nil, fmt.Errorf("%w: '%s'", ErrNotEnoughNodes, s.GetName()) + return nil, fmt.Errorf("%w: '%s'", ErrNotEnoughNodes, s.Name()) } } @@ -110,7 +108,7 @@ func (c *context) getSelection(_ PlacementPolicy, s netmap.Selector) ([]nodes, e hrw.SortWeighted(res, weights, c.hrwSeedHash) } - if s.GetAttribute() == "" { + if s.BucketAttribute() == "" { res, fallback = res[:bucketCount], res[bucketCount:] for i := range fallback { index := i % bucketCount @@ -131,13 +129,13 @@ type nodeAttrPair struct { // getSelectionBase returns nodes grouped by selector attribute. // It it guaranteed that each pair will contain at least one node. -func (c *context) getSelectionBase(s netmap.Selector) []nodeAttrPair { - fName := s.GetFilter() +func (c *context) getSelectionBase(s Selector) []nodeAttrPair { + fName := s.FilterName() f := c.processedFilters[fName] isMain := fName == mainFilterName result := []nodeAttrPair{} nodeMap := map[string][]NodeInfo{} - attr := s.GetAttribute() + attr := s.BucketAttribute() for i := range c.netMap.nodes { if isMain || c.match(f, c.netMap.nodes[i]) { diff --git a/netmap/selector_test.go b/netmap/selector_test.go index 4ab9ac40..20781428 100644 --- a/netmap/selector_test.go +++ b/netmap/selector_test.go @@ -7,8 +7,8 @@ import ( "testing" "github.com/nspcc-dev/hrw/v2" - "github.com/nspcc-dev/neofs-api-go/v2/netmap" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" + protonetmap "github.com/nspcc-dev/neofs-sdk-go/proto/netmap" "github.com/stretchr/testify/require" ) @@ -73,8 +73,8 @@ func BenchmarkPolicyHRWType(b *testing.B) { newSelector("loc1", "Location", 1, "loc1", (*Selector).SelectSame), newSelector("loc2", "Location", 1, "loc2", (*Selector).SelectSame)}, []Filter{ - newFilter("loc1", "Location", "Shanghai", netmap.EQ), - newFilter("loc2", "Location", "Shanghai", netmap.NE), + newFilter("loc1", "Location", "Shanghai", FilterOpEQ), + newFilter("loc2", "Location", "Shanghai", FilterOpNE), }) nodes := make([]NodeInfo, netmapSize) @@ -118,8 +118,8 @@ func TestPlacementPolicy_DeterministicOrder(t *testing.T) { newSelector("loc1", "Location", 1, "loc1", (*Selector).SelectSame), newSelector("loc2", "Location", 1, "loc2", (*Selector).SelectSame)}, []Filter{ - newFilter("loc1", "Location", "Shanghai", netmap.EQ), - newFilter("loc2", "Location", "Shanghai", netmap.NE), + newFilter("loc1", "Location", "Shanghai", FilterOpEQ), + newFilter("loc2", "Location", "Shanghai", FilterOpNE), }) nodeList := make([]NodeInfo, netmapSize) @@ -174,8 +174,8 @@ func TestPlacementPolicy_ProcessSelectors(t *testing.T) { newSelector("Main", "Country", 3, "*", (*Selector).SelectDistinct), }, []Filter{ - newFilter("FromRU", "Country", "Russia", netmap.EQ), - newFilter("Good", "Rating", "4", netmap.GE), + newFilter("FromRU", "Country", "Russia", FilterOpEQ), + newFilter("Good", "Rating", "4", FilterOpGE), }) nodes := []NodeInfo{ nodeInfoFromAttributes("Country", "Russia", "Rating", "1", "City", "SPB"), @@ -199,13 +199,13 @@ func TestPlacementPolicy_ProcessSelectors(t *testing.T) { require.NoError(t, c.processSelectors(p)) for _, s := range p.selectors { - sel := c.selections[s.GetName()] - s := c.processedSelectors[s.GetName()] + sel := c.selections[s.Name()] + s := c.processedSelectors[s.Name()] bucketCount, nodesInBucket := calcNodesCount(*s) nodesInBucket *= int(c.cbf) - targ := fmt.Sprintf("selector '%s'", s.GetName()) + targ := fmt.Sprintf("selector '%s'", s.Name()) require.Equal(t, bucketCount, len(sel), targ) - fName := s.GetFilter() + fName := s.FilterName() for _, res := range sel { require.Equal(t, nodesInBucket, len(res), targ) for j := range res { @@ -219,11 +219,9 @@ func TestSelector_SetName(t *testing.T) { const name = "some name" var s Selector - require.Zero(t, s.m.GetName()) require.Zero(t, s.Name()) s.SetName(name) - require.Equal(t, name, s.m.GetName()) require.Equal(t, name, s.Name()) } @@ -231,29 +229,27 @@ func TestSelector_SetNumberOfNodes(t *testing.T) { const num = 3 var s Selector - require.Zero(t, s.m.GetCount()) require.Zero(t, s.NumberOfNodes()) s.SetNumberOfNodes(num) - require.EqualValues(t, num, s.m.GetCount()) require.EqualValues(t, num, s.NumberOfNodes()) } func TestSelectorClauses(t *testing.T) { var s Selector - require.Equal(t, netmap.UnspecifiedClause, s.m.GetClause()) + require.Equal(t, protonetmap.Clause_CLAUSE_UNSPECIFIED, s.clause) require.False(t, s.IsSame()) require.False(t, s.IsDistinct()) s.SelectDistinct() - require.Equal(t, netmap.Distinct, s.m.GetClause()) + require.Equal(t, protonetmap.Clause_DISTINCT, s.clause) require.False(t, s.IsSame()) require.True(t, s.IsDistinct()) s.SelectSame() - require.Equal(t, netmap.Same, s.m.GetClause()) + require.Equal(t, protonetmap.Clause_SAME, s.clause) require.True(t, s.IsSame()) require.False(t, s.IsDistinct()) } @@ -262,18 +258,18 @@ func TestSelector_SelectByBucketAttribute(t *testing.T) { const attr = "some attribute" var s Selector - require.Zero(t, s.m.GetAttribute()) + require.Zero(t, s.BucketAttribute()) s.SelectByBucketAttribute(attr) - require.Equal(t, attr, s.m.GetAttribute()) + require.Equal(t, attr, s.BucketAttribute()) } func TestSelector_SetFilterName(t *testing.T) { const fName = "some filter" var s Selector - require.Zero(t, s.m.GetFilter()) + require.Zero(t, s.FilterName()) s.SetFilterName(fName) - require.Equal(t, fName, s.m.GetFilter()) + require.Equal(t, fName, s.FilterName()) } diff --git a/object/attribute.go b/object/attribute.go index c22d760d..cbefb0d0 100644 --- a/object/attribute.go +++ b/object/attribute.go @@ -1,11 +1,17 @@ package object import ( - "github.com/nspcc-dev/neofs-api-go/v2/object" + "errors" + "fmt" + "strconv" + + neofsproto "github.com/nspcc-dev/neofs-sdk-go/internal/proto" + protoobject "github.com/nspcc-dev/neofs-sdk-go/proto/object" ) // Various system attributes. const ( + sysAttrPrefix = "__NEOFS__" // AttributeExpirationEpoch is a key to an object attribute that determines // after what epoch the object becomes expired. Objects that do not have this // attribute never expire. @@ -17,82 +23,97 @@ const ( // Note that the value determines exactly the last epoch of the object's // relevance: for example, with the value N, the object is relevant in epoch N // and expired in any epoch starting from N+1. - AttributeExpirationEpoch = object.SysAttributeExpEpoch + AttributeExpirationEpoch = sysAttrPrefix + "EXPIRATION_EPOCH" ) -// Attribute represents v2-compatible object attribute. -type Attribute object.Attribute - -// NewAttributeFromV2 wraps v2 [object.Attribute] message to [Attribute]. -// -// Nil [object.Attribute] converts to nil. -func NewAttributeFromV2(aV2 *object.Attribute) *Attribute { - return (*Attribute)(aV2) -} +// Attribute represents an object attribute. +type Attribute struct{ k, v string } // NewAttribute creates and initializes new [Attribute]. func NewAttribute(key, value string) *Attribute { - attr := new(object.Attribute) - attr.SetKey(key) - attr.SetValue(value) - - return NewAttributeFromV2(attr) + return &Attribute{key, value} } // Key returns key to the object attribute. func (a *Attribute) Key() string { - return (*object.Attribute)(a).GetKey() + return a.k } // SetKey sets key to the object attribute. func (a *Attribute) SetKey(v string) { - (*object.Attribute)(a).SetKey(v) + a.k = v } // Value return value of the object attribute. func (a *Attribute) Value() string { - return (*object.Attribute)(a).GetValue() + return a.v } // SetValue sets value of the object attribute. func (a *Attribute) SetValue(v string) { - (*object.Attribute)(a).SetValue(v) + a.v = v } -// ToV2 converts [Attribute] to v2 [object.Attribute] message. -// -// Nil [Attribute] converts to nil. -// -// The value returned shares memory with the structure itself, so changing it can lead to data corruption. -// Make a copy if you need to change it. -func (a *Attribute) ToV2() *object.Attribute { - return (*object.Attribute)(a) +// fromProtoMessage validates m according to the NeoFS API protocol and restores +// a from it. +func (a *Attribute) fromProtoMessage(m *protoobject.Header_Attribute, checkFieldPresence bool) error { + if checkFieldPresence && m.Key == "" { + return fmt.Errorf("missing key") + } + if checkFieldPresence && m.Value == "" { + return errors.New("missing value") + } + switch m.Key { + case AttributeExpirationEpoch: + if _, err := strconv.ParseUint(m.Value, 10, 64); err != nil { + return fmt.Errorf("invalid expiration epoch (must be a uint): %w", err) + } + } + a.k, a.v = m.Key, m.Value + return nil +} + +// protoMessage converts a into message to transmit using the NeoFS API +// protocol. +func (a *Attribute) protoMessage() *protoobject.Header_Attribute { + if a != nil { + return &protoobject.Header_Attribute{Key: a.k, Value: a.v} + } + return nil } // Marshal marshals [Attribute] into a protobuf binary form. // // See also [Attribute.Unmarshal]. func (a *Attribute) Marshal() []byte { - return (*object.Attribute)(a).StableMarshal(nil) + return neofsproto.MarshalMessage(a.protoMessage()) } // Unmarshal unmarshals protobuf binary representation of [Attribute]. // // See also [Attribute.Marshal]. func (a *Attribute) Unmarshal(data []byte) error { - return (*object.Attribute)(a).Unmarshal(data) + m := new(protoobject.Header_Attribute) + if err := neofsproto.UnmarshalMessage(data, m); err != nil { + return err + } + return a.fromProtoMessage(m, false) } // MarshalJSON encodes [Attribute] to protobuf JSON format. // // See also [Attribute.UnmarshalJSON]. func (a *Attribute) MarshalJSON() ([]byte, error) { - return (*object.Attribute)(a).MarshalJSON() + return neofsproto.MarshalMessageJSON(a.protoMessage()) } // UnmarshalJSON decodes [Attribute] from protobuf JSON format. // // See also [Attribute.MarshalJSON]. func (a *Attribute) UnmarshalJSON(data []byte) error { - return (*object.Attribute)(a).UnmarshalJSON(data) + m := new(protoobject.Header_Attribute) + if err := neofsproto.UnmarshalMessageJSON(data, m); err != nil { + return err + } + return a.fromProtoMessage(m, false) } diff --git a/object/attribute_test.go b/object/attribute_test.go index 15338dfb..26733680 100644 --- a/object/attribute_test.go +++ b/object/attribute_test.go @@ -1,88 +1,59 @@ -package object +package object_test import ( "testing" - "github.com/nspcc-dev/neofs-api-go/v2/object" + "github.com/nspcc-dev/neofs-sdk-go/object" "github.com/stretchr/testify/require" ) -func TestAttribute(t *testing.T) { +func TestNewAttribute(t *testing.T) { key, val := "some key", "some value" - a := NewAttribute(key, val) + a := object.NewAttribute(key, val) require.Equal(t, key, a.Key()) require.Equal(t, val, a.Value()) - - aV2 := a.ToV2() - - require.Equal(t, key, aV2.GetKey()) - require.Equal(t, val, aV2.GetValue()) } -func TestAttributeEncoding(t *testing.T) { - a := NewAttribute("key", "value") - - t.Run("binary", func(t *testing.T) { - a2 := NewAttribute("", "") - require.NoError(t, a2.Unmarshal(a.Marshal())) - - require.Equal(t, a, a2) - }) - - t.Run("json", func(t *testing.T) { - data, err := a.MarshalJSON() - require.NoError(t, err) - - a2 := NewAttribute("", "") - require.NoError(t, a2.UnmarshalJSON(data)) - - require.Equal(t, a, a2) - }) +func TestAttribute_Marshal(t *testing.T) { + // TODO } -func TestNewAttributeFromV2(t *testing.T) { - t.Run("from nil", func(t *testing.T) { - var x *object.Attribute - - require.Nil(t, NewAttributeFromV2(x)) - }) +func TestAttribute_Unmarshal(t *testing.T) { + // TODO } -func TestAttribute_ToV2(t *testing.T) { - t.Run("nil", func(t *testing.T) { - var x *Attribute - - require.Nil(t, x.ToV2()) - }) +func TestAttribute_MarshalJSON(t *testing.T) { + // TODO } -func TestNewAttribute(t *testing.T) { - t.Run("default values", func(t *testing.T) { - a := NewAttribute("", "") - - // check initial values - require.Empty(t, a.Key()) - require.Empty(t, a.Value()) +func TestAttribute_UnmarshalJSON(t *testing.T) { + // TODO +} - // convert to v2 message - aV2 := a.ToV2() +func TestAttribute_SetKey(t *testing.T) { + var a object.Attribute + require.Zero(t, a.Key()) - require.Empty(t, aV2.GetKey()) - require.Empty(t, aV2.GetValue()) - }) + const key = "key" + a.SetKey(key) + require.Equal(t, key, a.Key()) - t.Run("pre installed key and value", func(t *testing.T) { - a := NewAttribute("key", "value") + const otherKey = key + "_other" + a.SetKey(otherKey) + require.Equal(t, otherKey, a.Key()) +} - require.NotEmpty(t, a.Key()) - require.NotEmpty(t, a.Value()) +func TestAttribute_SetValue(t *testing.T) { + var a object.Attribute + require.Zero(t, a.Value()) - // convert to v2 message - aV2 := a.ToV2() + const val = "key" + a.SetValue(val) + require.Equal(t, val, a.Value()) - require.NotEmpty(t, aV2.GetKey()) - require.NotEmpty(t, aV2.GetValue()) - }) + const otherVal = val + "_other" + a.SetKey(otherVal) + require.Equal(t, otherVal, a.Key()) } diff --git a/object/fmt.go b/object/fmt.go index 13576676..73758021 100644 --- a/object/fmt.go +++ b/object/fmt.go @@ -6,9 +6,9 @@ import ( "errors" "fmt" - "github.com/nspcc-dev/neofs-api-go/v2/object" "github.com/nspcc-dev/neofs-sdk-go/checksum" neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" + neofsproto "github.com/nspcc-dev/neofs-sdk-go/internal/proto" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" ) @@ -55,7 +55,7 @@ func (o Object) VerifyPayloadChecksum() error { // CalculateID calculates identifier for the object. func (o Object) CalculateID() (oid.ID, error) { - return sha256.Sum256(o.ToV2().GetHeader().StableMarshal(nil)), nil + return sha256.Sum256(neofsproto.MarshalMessage(o.header.protoMessage())), nil } // CalculateAndSetID calculates identifier for the object @@ -119,21 +119,15 @@ func (o Object) SignedData() []byte { // VerifySignature verifies object ID signature. func (o Object) VerifySignature() bool { - m := (*object.Object)(&o) - - sigV2 := m.GetSignature() - if sigV2 == nil { + if o.sig == nil { return false } - idV2 := m.GetObjectID() - if idV2 == nil { + if o.id.IsZero() { return false } - var sig neofscrypto.Signature - - return sig.ReadFromV2(*sigV2) == nil && sig.Verify(idV2.StableMarshal(nil)) + return o.sig.Verify(neofsproto.Marshal(o.id)) } // SetIDWithSignature sets object identifier and signature. diff --git a/object/fmt_test.go b/object/fmt_test.go index 8c714199..874ebf74 100644 --- a/object/fmt_test.go +++ b/object/fmt_test.go @@ -308,16 +308,16 @@ func TestObject_VerifySignature(t *testing.T) { scheme neofscrypto.Scheme sig []byte // of validObject }{ - {scheme: neofscrypto.ECDSA_SHA512, sig: []byte{4, 206, 167, 138, 255, 141, 241, 206, 240, 218, 17, 41, 57, 180, 37, 170, - 36, 201, 199, 112, 111, 2, 1, 73, 31, 206, 255, 101, 116, 114, 232, 98, 140, 138, 197, 59, 179, 102, 49, 137, 252, 230, - 176, 155, 116, 39, 203, 232, 51, 238, 172, 112, 42, 227, 113, 203, 177, 53, 101, 116, 97, 29, 121, 22, 183}}, - {scheme: neofscrypto.ECDSA_DETERMINISTIC_SHA256, sig: []byte{47, 239, 122, 255, 209, 184, 122, 77, 65, 84, 185, 0, - 73, 8, 244, 138, 253, 226, 200, 127, 47, 220, 119, 224, 57, 96, 95, 156, 168, 84, 237, 156, 67, 138, 42, 138, 47, 52, - 165, 244, 234, 135, 8, 196, 55, 190, 51, 55, 38, 229, 192, 68, 143, 161, 236, 27, 179, 68, 160, 12, 200, 176, 10, 245}}, - {scheme: neofscrypto.ECDSA_WALLETCONNECT, sig: []byte{7, 96, 184, 28, 216, 37, 138, 11, 78, 152, 47, 148, 183, 72, 211, - 189, 38, 204, 64, 171, 102, 52, 129, 180, 113, 228, 91, 29, 97, 63, 5, 73, 184, 219, 193, 178, 174, 40, 56, 232, 143, - 4, 52, 66, 130, 124, 232, 106, 120, 14, 7, 29, 244, 145, 10, 253, 209, 50, 113, 252, 134, 180, 49, 175, 183, 21, 69, - 4, 29, 124, 74, 160, 152, 155, 249, 216, 146, 80, 125, 194}}, + {scheme: neofscrypto.ECDSA_SHA512, sig: []byte{4, 157, 208, 48, 92, 64, 50, 239, 162, 224, 125, 240, 39, 15, 101, 98, 159, + 5, 80, 32, 234, 246, 211, 110, 50, 46, 202, 175, 185, 141, 163, 5, 7, 138, 179, 164, 161, 50, 229, 108, 209, 222, 177, 58, + 134, 56, 127, 102, 112, 223, 22, 152, 245, 14, 91, 146, 144, 16, 171, 155, 241, 3, 151, 155, 58}}, + {scheme: neofscrypto.ECDSA_DETERMINISTIC_SHA256, sig: []byte{114, 202, 254, 142, 34, 127, 224, 11, 6, 105, 200, 235, 154, + 191, 7, 168, 209, 236, 155, 34, 107, 134, 13, 44, 53, 142, 89, 191, 255, 45, 38, 102, 226, 7, 231, 148, 57, 175, 237, 91, + 4, 34, 194, 126, 157, 140, 14, 93, 159, 7, 8, 207, 165, 139, 44, 203, 114, 110, 150, 195, 12, 61, 153, 29}}, + {scheme: neofscrypto.ECDSA_WALLETCONNECT, sig: []byte{15, 138, 149, 242, 96, 138, 254, 168, 236, 255, 138, 156, 26, 169, + 55, 31, 140, 151, 99, 31, 65, 69, 170, 103, 109, 245, 87, 229, 27, 21, 200, 86, 100, 88, 191, 106, 135, 84, 34, 198, 105, + 247, 195, 74, 194, 50, 2, 204, 180, 187, 2, 113, 247, 67, 107, 100, 4, 3, 135, 50, 227, 227, 74, 224, 82, 232, 213, + 210, 119, 73, 235, 106, 131, 155, 138, 76, 85, 96, 112, 250}}, } { sig.SetScheme(tc.scheme) sig.SetPublicKeyBytes(pub) diff --git a/object/id/address.go b/object/id/address.go index 9dfe754b..6c3ea94a 100644 --- a/object/id/address.go +++ b/object/id/address.go @@ -5,8 +5,10 @@ import ( "fmt" "strings" - "github.com/nspcc-dev/neofs-api-go/v2/refs" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" + neofsproto "github.com/nspcc-dev/neofs-sdk-go/internal/proto" + "github.com/nspcc-dev/neofs-sdk-go/proto/refs" + "google.golang.org/protobuf/encoding/protojson" ) // Address represents global object identifier in NeoFS network. Each object @@ -15,8 +17,8 @@ import ( // // ID implements built-in comparable interface. // -// Address is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/refs.Address -// message. See ReadFromV2 / WriteToV2 methods. +// Address is mutually compatible with [refs.Address] message. See +// [Address.FromProtoMessage] / [Address.ProtoMessage] methods. type Address struct { cnr cid.ID @@ -35,27 +37,25 @@ func DecodeAddressString(s string) (Address, error) { return id, id.DecodeString(s) } -// ReadFromV2 reads Address from the refs.Address message. Returns an error if -// the message is malformed according to the NeoFS API V2 protocol. +// FromProtoMessage validates m according to the NeoFS API protocol and restores +// x from it. // -// See also WriteToV2. -func (x *Address) ReadFromV2(m refs.Address) error { - cnr := m.GetContainerID() - if cnr == nil { +// See also [Address.ProtoMessage]. +func (x *Address) FromProtoMessage(m *refs.Address) error { + if m.ContainerId == nil { return errors.New("missing container ID") } - obj := m.GetObjectID() - if obj == nil { + if m.ObjectId == nil { return errors.New("missing object ID") } - err := x.cnr.ReadFromV2(*cnr) + err := x.cnr.FromProtoMessage(m.ContainerId) if err != nil { return fmt.Errorf("invalid container ID: %w", err) } - err = x.obj.ReadFromV2(*obj) + err = x.obj.FromProtoMessage(m.ObjectId) if err != nil { return fmt.Errorf("invalid object ID: %w", err) } @@ -63,19 +63,15 @@ func (x *Address) ReadFromV2(m refs.Address) error { return nil } -// WriteToV2 writes Address to the refs.Address message. -// The message must not be nil. +// ProtoMessage converts x into message to transmit using the NeoFS API +// protocol. // -// See also ReadFromV2. -func (x Address) WriteToV2(m *refs.Address) { - var obj refs.ObjectID - x.obj.WriteToV2(&obj) - - var cnr refs.ContainerID - x.cnr.WriteToV2(&cnr) - - m.SetObjectID(&obj) - m.SetContainerID(&cnr) +// See also [Address.FromProtoMessage]. +func (x Address) ProtoMessage() *refs.Address { + return &refs.Address{ + ContainerId: x.cnr.ProtoMessage(), + ObjectId: x.obj.ProtoMessage(), + } } // MarshalJSON encodes Address into a JSON format of the NeoFS API protocol @@ -83,10 +79,7 @@ func (x Address) WriteToV2(m *refs.Address) { // // See also UnmarshalJSON. func (x Address) MarshalJSON() ([]byte, error) { - var m refs.Address - x.WriteToV2(&m) - - return m.MarshalJSON() + return neofsproto.MarshalMessageJSON(x.ProtoMessage()) } // UnmarshalJSON decodes NeoFS API protocol JSON format into the Address @@ -96,12 +89,12 @@ func (x Address) MarshalJSON() ([]byte, error) { func (x *Address) UnmarshalJSON(data []byte) error { var m refs.Address - err := m.UnmarshalJSON(data) + err := protojson.Unmarshal(data, &m) if err != nil { return err } - return x.ReadFromV2(m) + return x.FromProtoMessage(&m) } // Container returns unique identifier of the NeoFS object container. diff --git a/object/id/address_test.go b/object/id/address_test.go index 4217a833..0890ef21 100644 --- a/object/id/address_test.go +++ b/object/id/address_test.go @@ -5,11 +5,12 @@ import ( "strings" "testing" - "github.com/nspcc-dev/neofs-api-go/v2/refs" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test" + neofsproto "github.com/nspcc-dev/neofs-sdk-go/internal/proto" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" oidtest "github.com/nspcc-dev/neofs-sdk-go/object/id/test" + "github.com/nspcc-dev/neofs-sdk-go/proto/refs" "github.com/stretchr/testify/require" ) @@ -30,46 +31,34 @@ var invalidAddressTestcases = []struct { corrupt func(*refs.Address) }{ {name: "empty", err: "missing container ID", corrupt: func(a *refs.Address) { - a.SetContainerID(nil) - a.SetObjectID(nil) + a.ContainerId = nil + a.ObjectId = nil }}, - {name: "container/missing", err: "missing container ID", corrupt: func(a *refs.Address) { a.SetContainerID(nil) }}, + {name: "container/missing", err: "missing container ID", corrupt: func(a *refs.Address) { a.ContainerId = nil }}, {name: "container/nil value", err: "invalid container ID: invalid length 0", corrupt: func(a *refs.Address) { - a.SetContainerID(new(refs.ContainerID)) + a.ContainerId = new(refs.ContainerID) }}, {name: "container/empty value", err: "invalid container ID: invalid length 0", corrupt: func(a *refs.Address) { - var m refs.ContainerID - m.SetValue([]byte{}) - a.SetContainerID(&m) + a.ContainerId.Value = []byte{} }}, {name: "container/undersize", err: "invalid container ID: invalid length 31", corrupt: func(a *refs.Address) { - var m refs.ContainerID - m.SetValue(make([]byte, 31)) - a.SetContainerID(&m) + a.ContainerId.Value = make([]byte, 31) }}, {name: "container/oversize", err: "invalid container ID: invalid length 33", corrupt: func(a *refs.Address) { - var m refs.ContainerID - m.SetValue(make([]byte, 33)) - a.SetContainerID(&m) + a.ContainerId.Value = make([]byte, 33) }}, - {name: "object/missing", err: "missing object ID", corrupt: func(a *refs.Address) { a.SetObjectID(nil) }}, + {name: "object/missing", err: "missing object ID", corrupt: func(a *refs.Address) { a.ObjectId = nil }}, {name: "object/nil value", err: "invalid object ID: invalid length 0", corrupt: func(a *refs.Address) { - a.SetObjectID(new(refs.ObjectID)) + a.ObjectId = new(refs.ObjectID) }}, {name: "object/empty value", err: "invalid object ID: invalid length 0", corrupt: func(a *refs.Address) { - var m refs.ObjectID - m.SetValue([]byte{}) - a.SetObjectID(&m) + a.ObjectId.Value = []byte{} }}, {name: "object/undersize", err: "invalid object ID: invalid length 31", corrupt: func(a *refs.Address) { - var m refs.ObjectID - m.SetValue(make([]byte, 31)) - a.SetObjectID(&m) + a.ObjectId.Value = make([]byte, 31) }}, {name: "object/oversize", err: "invalid object ID: invalid length 33", corrupt: func(a *refs.Address) { - var m refs.ObjectID - m.SetValue(make([]byte, 33)) - a.SetObjectID(&m) + a.ObjectId.Value = make([]byte, 33) }}, } @@ -102,12 +91,11 @@ func testAddressIDField[T ~[32]byte]( t.Run("api", func(t *testing.T) { src := oidtest.Address() var dst oid.Address - var msg refs.Address set(&src, val) - src.WriteToV2(&msg) - require.EqualValues(t, val[:], getAPI(&msg)) - require.NoError(t, dst.ReadFromV2(msg)) + msg := src.ProtoMessage() + require.EqualValues(t, val[:], getAPI(msg)) + require.NoError(t, dst.FromProtoMessage(msg)) require.Equal(t, val, get(dst)) }) t.Run("json", func(t *testing.T) { @@ -125,36 +113,32 @@ func testAddressIDField[T ~[32]byte]( func TestAddress_SetContainer(t *testing.T) { testAddressIDField(t, cidtest.OtherID, oid.Address.Container, (*oid.Address).SetContainer, func(m *refs.Address) []byte { - return m.GetContainerID().GetValue() + return m.GetContainerId().GetValue() }) } func TestAddress_SetObject(t *testing.T) { testAddressIDField(t, oidtest.OtherID, oid.Address.Object, (*oid.Address).SetObject, func(m *refs.Address) []byte { - return m.GetObjectID().GetValue() + return m.GetObjectId().GetValue() }) } -func TestAddress_ReadFromV2(t *testing.T) { - var mc refs.ContainerID - mc.SetValue(validContainerBytes[:]) - var mo refs.ObjectID - mo.SetValue(validIDBytes[:]) - var m refs.Address - m.SetContainerID(&mc) - m.SetObjectID(&mo) +func TestAddress_FromProtoMessage(t *testing.T) { + m := &refs.Address{ + ContainerId: &refs.ContainerID{Value: validContainerBytes[:]}, + ObjectId: &refs.ObjectID{Value: validIDBytes[:]}, + } var a oid.Address - require.NoError(t, a.ReadFromV2(m)) + require.NoError(t, a.FromProtoMessage(m)) require.EqualValues(t, validContainerBytes, a.Container()) require.EqualValues(t, validIDBytes, a.Object()) t.Run("invalid", func(t *testing.T) { for _, tc := range invalidAddressTestcases { t.Run(tc.name, func(t *testing.T) { - var m refs.Address - oidtest.Address().WriteToV2(&m) - tc.corrupt(&m) - require.EqualError(t, new(oid.Address).ReadFromV2(m), tc.err) + m := oidtest.Address().ProtoMessage() + tc.corrupt(m) + require.EqualError(t, new(oid.Address).FromProtoMessage(m), tc.err) }) } }) @@ -250,10 +234,9 @@ func TestAddress_UnmarshalJSON(t *testing.T) { t.Run("protocol violation", func(t *testing.T) { for _, tc := range invalidAddressTestcases { t.Run(tc.name, func(t *testing.T) { - var m refs.Address - oidtest.Address().WriteToV2(&m) - tc.corrupt(&m) - b, err := m.MarshalJSON() + m := oidtest.Address().ProtoMessage() + tc.corrupt(m) + b, err := neofsproto.MarshalMessageJSON(m) require.NoError(t, err) require.EqualError(t, new(oid.Address).UnmarshalJSON(b), tc.err) }) diff --git a/object/id/id.go b/object/id/id.go index 2b24c69f..f1d4e9e9 100644 --- a/object/id/id.go +++ b/object/id/id.go @@ -6,8 +6,9 @@ import ( "fmt" "github.com/mr-tron/base58" - "github.com/nspcc-dev/neofs-api-go/v2/refs" neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" + neofsproto "github.com/nspcc-dev/neofs-sdk-go/internal/proto" + "github.com/nspcc-dev/neofs-sdk-go/proto/refs" ) // Size is the size of an [ID] in bytes. @@ -18,8 +19,8 @@ const Size = sha256.Size // // ID implements built-in comparable interface. // -// ID is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/refs.ObjectID -// message. See ReadFromV2 / WriteToV2 methods. +// ID is mutually compatible with [refs.ObjectID] message. See +// [ID.FromProtoMessage] / [ID.ProtoMessage] methods. type ID [Size]byte // ErrZero is an error returned on zero [ID] encounter. @@ -42,24 +43,24 @@ func DecodeString(s string) (ID, error) { return id, id.DecodeString(s) } -// ReadFromV2 reads ID from the refs.ObjectID message. Returns an error if -// the message is malformed according to the NeoFS API V2 protocol. +// FromProtoMessage validates m according to the NeoFS API protocol and restores +// id from it. // -// See also WriteToV2. -func (id *ID) ReadFromV2(m refs.ObjectID) error { - err := id.Decode(m.GetValue()) +// See also [ID.ProtoMessage]. +func (id *ID) FromProtoMessage(m *refs.ObjectID) error { + err := id.Decode(m.Value) if err == nil && id.IsZero() { err = ErrZero } return err } -// WriteToV2 writes ID to the refs.ObjectID message. -// The message must not be nil. +// ProtoMessage converts id into message to transmit using the NeoFS API +// protocol. // -// See also ReadFromV2. -func (id ID) WriteToV2(m *refs.ObjectID) { - m.SetValue(id[:]) +// See also [ID.FromProtoMessage]. +func (id ID) ProtoMessage() *refs.ObjectID { + return &refs.ObjectID{Value: id[:]} } // Encode encodes ID into [Size] bytes of dst. Panics if @@ -149,38 +150,22 @@ func (id ID) CalculateIDSignature(signer neofscrypto.Signer) (neofscrypto.Signat // Marshal marshals ID into a protobuf binary form. func (id ID) Marshal() []byte { - var v2 refs.ObjectID - v2.SetValue(id[:]) - - return v2.StableMarshal(nil) + return neofsproto.Marshal(id) } // Unmarshal unmarshals protobuf binary representation of ID. func (id *ID) Unmarshal(data []byte) error { - var v2 refs.ObjectID - if err := v2.Unmarshal(data); err != nil { - return err - } - - return id.ReadFromV2(v2) + return neofsproto.Unmarshal(data, id) } // MarshalJSON encodes ID to protobuf JSON format. func (id ID) MarshalJSON() ([]byte, error) { - var v2 refs.ObjectID - v2.SetValue(id[:]) - - return v2.MarshalJSON() + return neofsproto.MarshalJSON(id) } // UnmarshalJSON decodes ID from protobuf JSON format. func (id *ID) UnmarshalJSON(data []byte) error { - var v2 refs.ObjectID - if err := v2.UnmarshalJSON(data); err != nil { - return err - } - - return id.ReadFromV2(v2) + return neofsproto.UnmarshalJSON(data, id) } // IsZero checks whether ID is zero. diff --git a/object/id/id_test.go b/object/id/id_test.go index 190edefb..6249afd2 100644 --- a/object/id/id_test.go +++ b/object/id/id_test.go @@ -7,12 +7,13 @@ import ( "math/rand" "testing" - "github.com/nspcc-dev/neofs-api-go/v2/refs" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" 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" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" oidtest "github.com/nspcc-dev/neofs-sdk-go/object/id/test" + "github.com/nspcc-dev/neofs-sdk-go/proto/refs" usertest "github.com/nspcc-dev/neofs-sdk-go/user/test" "github.com/stretchr/testify/require" "google.golang.org/protobuf/encoding/protowire" @@ -45,20 +46,17 @@ var invalidValueTestcases = []invalidValueTestCase{ func toProtoBytes(b []byte) []byte { return protowire.AppendBytes([]byte{10}, b) } func toProtoJSON(b []byte) []byte { - var m refs.ObjectID - m.SetValue(b) - b, err := m.MarshalJSON() + b, err := neofsproto.MarshalMessageJSON(&refs.ObjectID{Value: b}) if err != nil { panic(fmt.Sprintf("unexpected MarshalJSON error: %v", err)) } return b } -func TestID_ReadFromV2(t *testing.T) { - var m refs.ObjectID - m.SetValue(validIDBytes[:]) +func TestID_FromProtoMessage(t *testing.T) { + m := &refs.ObjectID{Value: validIDBytes[:]} var id oid.ID - require.NoError(t, id.ReadFromV2(m)) + require.NoError(t, id.FromProtoMessage(m)) require.EqualValues(t, validIDBytes, id) t.Run("invalid", func(t *testing.T) { @@ -66,9 +64,8 @@ func TestID_ReadFromV2(t *testing.T) { name: "zero value", err: "zero object ID", val: make([]byte, cid.Size), }) { t.Run(tc.name, func(t *testing.T) { - var m refs.ObjectID - m.SetValue(tc.val) - require.EqualError(t, new(oid.ID).ReadFromV2(m), tc.err) + m := &refs.ObjectID{Value: tc.val} + require.EqualError(t, new(oid.ID).FromProtoMessage(m), tc.err) }) } }) @@ -90,10 +87,9 @@ func TestID_Decode(t *testing.T) { }) } -func TestID_WriteToV2(t *testing.T) { +func TestID_ProtoMessage(t *testing.T) { id := oidtest.ID() - var m refs.ObjectID - id.WriteToV2(&m) + m := id.ProtoMessage() require.Equal(t, id[:], m.GetValue()) } diff --git a/object/id/test/generate_test.go b/object/id/test/generate_test.go index 1313b12d..4e18cb3a 100644 --- a/object/id/test/generate_test.go +++ b/object/id/test/generate_test.go @@ -4,7 +4,6 @@ import ( "math/rand/v2" "testing" - "github.com/nspcc-dev/neofs-api-go/v2/refs" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" oidtest "github.com/nspcc-dev/neofs-sdk-go/object/id/test" "github.com/stretchr/testify/require" @@ -14,10 +13,9 @@ func TestID(t *testing.T) { id := oidtest.ID() require.NotEqual(t, id, oidtest.ID()) - var m refs.ObjectID - id.WriteToV2(&m) + m := id.ProtoMessage() var id2 oid.ID - require.NoError(t, id2.ReadFromV2(m)) + require.NoError(t, id2.FromProtoMessage(m)) } func TestNIDs(t *testing.T) { @@ -34,10 +32,9 @@ func TestAddress(t *testing.T) { a := oidtest.Address() require.NotEqual(t, a, oidtest.Address()) - var m refs.Address - a.WriteToV2(&m) + m := a.ProtoMessage() var id2 oid.Address - require.NoError(t, id2.ReadFromV2(m)) + require.NoError(t, id2.FromProtoMessage(m)) } func TestChangeAddress(t *testing.T) { diff --git a/object/link.go b/object/link.go index 4c775e6e..fabdfbdb 100644 --- a/object/link.go +++ b/object/link.go @@ -3,9 +3,9 @@ package object import ( "fmt" - v2object "github.com/nspcc-dev/neofs-api-go/v2/object" - "github.com/nspcc-dev/neofs-api-go/v2/refs" + neofsproto "github.com/nspcc-dev/neofs-sdk-go/internal/proto" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" + protolink "github.com/nspcc-dev/neofs-sdk-go/proto/link" ) // Link is a payload of helper objects that contain the full list of the split @@ -13,7 +13,9 @@ import ( // // Link instance can be written to the [Object], see // [Object.WriteLink]/[Object.ReadLink]. -type Link v2object.Link +type Link struct { + children []MeasuredObject +} // WriteLink writes a link to the Object, and sets its type to [TypeLink]. // @@ -36,100 +38,97 @@ func (o Object) ReadLink(l *Link) error { // // See also [Link.Unmarshal]. func (l *Link) Marshal() []byte { - return (*v2object.Link)(l).StableMarshal(nil) + if l == nil || len(l.children) == 0 { + return nil + } + m := &protolink.Link{ + Children: make([]*protolink.Link_MeasuredObject, len(l.children)), + } + for i := range l.children { + m.Children[i] = &protolink.Link_MeasuredObject{ + Id: l.children[i].id.ProtoMessage(), + Size: l.children[i].sz, + } + } + return neofsproto.MarshalMessage(m) } // Unmarshal decodes the [Link] from its NeoFS protocol binary representation. // // See also [Link.Marshal]. func (l *Link) Unmarshal(data []byte) error { - m := (*v2object.Link)(l) - err := m.Unmarshal(data) + m := new(protolink.Link) + err := neofsproto.UnmarshalMessage(data, m) if err != nil { return err } - var id oid.ID - var i int - m.IterateChildren(func(mo v2object.MeasuredObject) { - if err == nil { - if err = id.ReadFromV2(mo.ID); err != nil { - err = fmt.Errorf("invalid member #%d: %w", i, err) - } + if m.Children == nil { + l.children = nil + return nil + } + + l.children = make([]MeasuredObject, len(m.Children)) + for i := range m.Children { + if m.Children[i] == nil { + return fmt.Errorf("nil child #%d", i) + } + if m.Children[i].Id == nil { + return fmt.Errorf("invalid child #%d: nil ID", i) } - i++ - }) - return err + if err = l.children[i].id.FromProtoMessage(m.Children[i].Id); err != nil { + return fmt.Errorf("invalid child #%d: invalid ID: %w", i, err) + } + l.children[i].sz = m.Children[i].Size + } + + return nil } -// MeasuredObject groups object ID and its size length. It is compatible with -// NeoFS API V2 protocol. -type MeasuredObject v2object.MeasuredObject +// MeasuredObject groups object ID and its size length. +type MeasuredObject struct { + id oid.ID + sz uint32 +} // SetObjectID sets object identifier. // // See also [MeasuredObject.ObjectID]. func (m *MeasuredObject) SetObjectID(id oid.ID) { - var idV2 refs.ObjectID - id.WriteToV2(&idV2) - - m.ID = idV2 + m.id = id } // ObjectID returns object identifier. // // See also [MeasuredObject.SetObjectID]. func (m *MeasuredObject) ObjectID() oid.ID { - var id oid.ID - if m.ID.GetValue() != nil { - if err := id.ReadFromV2(m.ID); err != nil { - panic(fmt.Errorf("invalid ID: %w", err)) - } - } - - return id + return m.id } // SetObjectSize sets size of the object. // // See also [MeasuredObject.ObjectSize]. func (m *MeasuredObject) SetObjectSize(s uint32) { - m.Size = s + m.sz = s } // ObjectSize returns size of the object. // // See also [MeasuredObject.SetObjectSize]. func (m *MeasuredObject) ObjectSize() uint32 { - return m.Size + return m.sz } // Objects returns split chain's measured objects. // // See also [Link.SetObjects]. func (l *Link) Objects() []MeasuredObject { - res := make([]MeasuredObject, (*v2object.Link)(l).NumberOfChildren()) - var i int - var id oid.ID - (*v2object.Link)(l).IterateChildren(func(object v2object.MeasuredObject) { - if err := id.ReadFromV2(object.ID); err != nil { - panic(fmt.Errorf("invalid member #%d: %w", i, err)) - } - res[i] = MeasuredObject(object) - i++ - }) - - return res + return l.children } // SetObjects sets split chain's measured objects. // // See also [Link.Objects]. func (l *Link) SetObjects(oo []MeasuredObject) { - v2OO := make([]v2object.MeasuredObject, len(oo)) - for i, o := range oo { - v2OO[i] = v2object.MeasuredObject(o) - } - - (*v2object.Link)(l).SetChildren(v2OO) + l.children = oo } diff --git a/object/link_test.go b/object/link_test.go index 4655f741..66bc230e 100644 --- a/object/link_test.go +++ b/object/link_test.go @@ -69,18 +69,18 @@ func TestLink_Unmarshal(t *testing.T) { name, err string b []byte }{ - {name: "members/empty value", err: "invalid member #1: invalid length 0", + {name: "members/empty value", err: "invalid child #1: invalid ID: invalid length 0", b: []byte{10, 36, 10, 34, 10, 32, 178, 74, 58, 219, 46, 3, 110, 125, 220, 81, 238, 35, 27, 6, 228, 193, 190, 224, 77, 44, 18, 56, 117, 173, 70, 246, 8, 139, 247, 174, 53, 60, 10, 2, 10, 0, 10, 36, 10, 34, 10, 32, 206, 228, 247, 217, 41, 247, 159, 215, 79, 226, 53, 153, 133, 16, 102, 104, 2, 234, 35, 220, 236, 112, 101, 24, 235, 126, 173, 229, 161, 202, 197, 242}}, - {name: "members/undersize", err: "invalid member #1: invalid length 31", + {name: "members/undersize", err: "invalid child #1: invalid ID: invalid length 31", b: []byte{10, 36, 10, 34, 10, 32, 178, 74, 58, 219, 46, 3, 110, 125, 220, 81, 238, 35, 27, 6, 228, 193, 190, 224, 77, 44, 18, 56, 117, 173, 70, 246, 8, 139, 247, 174, 53, 60, 10, 35, 10, 33, 10, 31, 229, 77, 63, 235, 2, 9, 165, 123, 116, 123, 47, 65, 22, 34, 214, 76, 45, 225, 21, 46, 135, 32, 116, 172, 67, 213, 243, 57, 253, 127, 179, 10, 36, 10, 34, 10, 32, 206, 228, 247, 217, 41, 247, 159, 215, 79, 226, 53, 153, 133, 16, 102, 104, 2, 234, 35, 220, 236, 112, 101, 24, 235, 126, 173, 229, 161, 202, 197, 242}}, - {name: "members/oversize", err: "invalid member #1: invalid length 33", + {name: "members/oversize", err: "invalid child #1: invalid ID: invalid length 33", b: []byte{10, 36, 10, 34, 10, 32, 178, 74, 58, 219, 46, 3, 110, 125, 220, 81, 238, 35, 27, 6, 228, 193, 190, 224, 77, 44, 18, 56, 117, 173, 70, 246, 8, 139, 247, 174, 53, 60, 10, 37, 10, 35, 10, 33, 229, 77, 63, 235, 2, 9, 165, 123, 116, 123, 47, 65, 22, 34, 214, 76, 45, 225, 21, 46, 135, 32, 116, 172, 67, 213, 243, 57, diff --git a/object/lock.go b/object/lock.go index c153adb7..9e013b75 100644 --- a/object/lock.go +++ b/object/lock.go @@ -3,16 +3,19 @@ package object import ( "fmt" - v2object "github.com/nspcc-dev/neofs-api-go/v2/object" - "github.com/nspcc-dev/neofs-api-go/v2/refs" + neofsproto "github.com/nspcc-dev/neofs-sdk-go/internal/proto" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" + protolock "github.com/nspcc-dev/neofs-sdk-go/proto/lock" + "github.com/nspcc-dev/neofs-sdk-go/proto/refs" ) // Lock represents record with locked objects. It is compatible with // NeoFS API V2 protocol. // // Lock instance can be written to the [Object], see WriteLock/ReadLock. -type Lock v2object.Lock +type Lock struct { + members []oid.ID +} // WriteLock writes [Lock] to the [Object], and sets its type to [TypeLock]. // @@ -33,66 +36,63 @@ func (o Object) ReadLock(l *Lock) error { // NumberOfMembers returns number of members in lock list. func (x Lock) NumberOfMembers() int { - return (*v2object.Lock)(&x).NumberOfMembers() + return len(x.members) } // ReadMembers reads list of locked members. // // Buffer length must not be less than [Lock.NumberOfMembers]. func (x Lock) ReadMembers(buf []oid.ID) { - var i int - - (*v2object.Lock)(&x).IterateMembers(func(idV2 refs.ObjectID) { - if err := buf[i].ReadFromV2(idV2); err != nil { - panic(fmt.Errorf("invalid member #%d: %w", i, err)) - } - i++ - }) + copy(buf, x.members) } // WriteMembers writes list of locked members. // // See also [Lock.ReadMembers]. func (x *Lock) WriteMembers(ids []oid.ID) { - var members []refs.ObjectID - - if ids != nil { - members = make([]refs.ObjectID, len(ids)) - - for i := range ids { - ids[i].WriteToV2(&members[i]) - } - } - - (*v2object.Lock)(x).SetMembers(members) + x.members = ids } // Marshal encodes the [Lock] into a NeoFS protocol binary format. // // See also [Lock.Unmarshal]. func (x Lock) Marshal() []byte { - return (*v2object.Lock)(&x).StableMarshal(nil) + if len(x.members) == 0 { + return nil + } + m := &protolock.Lock{ + Members: make([]*refs.ObjectID, len(x.members)), + } + for i := range x.members { + m.Members[i] = x.members[i].ProtoMessage() + } + return neofsproto.MarshalMessage(m) } // Unmarshal decodes the [Lock] from its NeoFS protocol binary representation. // // See also [Lock.Marshal]. func (x *Lock) Unmarshal(data []byte) error { - m := (*v2object.Lock)(x) - err := m.Unmarshal(data) + m := new(protolock.Lock) + err := neofsproto.UnmarshalMessage(data, m) if err != nil { return err } - var id oid.ID - var i int - m.IterateMembers(func(mid refs.ObjectID) { - if err == nil { - if err = id.ReadFromV2(mid); err != nil { - err = fmt.Errorf("invalid member #%d: %w", i, err) - } + if m.Members == nil { + x.members = nil + return nil + } + + x.members = make([]oid.ID, len(m.Members)) + for i := range m.Members { + if m.Members[i] == nil { + return fmt.Errorf("nil member #%d", i) + } + if err = x.members[i].FromProtoMessage(m.Members[i]); err != nil { + return fmt.Errorf("invalid member #%d: %w", i, err) } - i++ - }) - return err + } + + return nil } diff --git a/object/object.go b/object/object.go index d7c0e180..26bb9ad8 100644 --- a/object/object.go +++ b/object/object.go @@ -3,30 +3,58 @@ package object import ( "bytes" "fmt" - "strconv" "strings" "github.com/google/uuid" - "github.com/nspcc-dev/neofs-api-go/v2/object" - "github.com/nspcc-dev/neofs-api-go/v2/refs" - v2session "github.com/nspcc-dev/neofs-api-go/v2/session" "github.com/nspcc-dev/neofs-sdk-go/checksum" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" + neofsproto "github.com/nspcc-dev/neofs-sdk-go/internal/proto" 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" "github.com/nspcc-dev/neofs-sdk-go/session" "github.com/nspcc-dev/neofs-sdk-go/user" "github.com/nspcc-dev/neofs-sdk-go/version" ) +type split struct { + parID oid.ID + prev oid.ID + parSig *neofscrypto.Signature + parHdr *header + children []oid.ID + id []byte + first oid.ID +} + +type header struct { + version *version.Version + owner user.ID + cnr cid.ID + created uint64 + payloadLn uint64 + pldHash *checksum.Checksum + typ Type + pldHomoHash *checksum.Checksum + session *session.Object + attrs []Attribute + split split +} + // Object represents in-memory structure of the NeoFS object. // Type is compatible with NeoFS API V2 protocol. // // Instance can be created depending on scenario: // - [Object.InitCreation] (an object to be placed in container); // - New (blank instance, usually needed for decoding); -// - [Object.ReadFromV2] (when working under NeoFS API V2 protocol). -type Object object.Object +// - [Object.FromProtoMessage] (when working under NeoFS API V2 protocol). +type Object struct { + id oid.ID + sig *neofscrypto.Signature + header header + pld []byte +} // RequiredFields contains the minimum set of object data that must be set // by the NeoFS user at the stage of creation. @@ -44,218 +72,323 @@ func (o *Object) InitCreation(rf RequiredFields) { o.SetOwner(rf.Owner) } -// NewFromV2 wraps v2 [object.Object] message to [Object]. -// -// Deprecated: BUG: fields' format is not checked. Use [Object.ReadFromV2] -// instead. -func NewFromV2(oV2 *object.Object) *Object { - return (*Object)(oV2) -} - // New creates and initializes blank [Object]. func New() *Object { return new(Object) } -func verifySplitHeaderV2(m object.SplitHeader) error { +func (x split) isZero() bool { + return x.parID.IsZero() && x.prev.IsZero() && x.parSig == nil && x.parHdr == nil && len(x.children) == 0 && + len(x.id) == 0 && x.first.IsZero() +} + +func (x *split) fromProtoMessage(m *protoobject.Header_Split) error { // parent ID - if mID := m.GetParent(); mID != nil { - if err := new(oid.ID).ReadFromV2(*mID); err != nil { + if m.Parent != nil { + if err := x.parID.FromProtoMessage(m.Parent); err != nil { return fmt.Errorf("invalid parent split member ID: %w", err) } + } else { + x.parID = oid.ID{} } // previous - if mID := m.GetPrevious(); mID != nil { - if err := new(oid.ID).ReadFromV2(*mID); err != nil { + if m.Previous != nil { + if err := x.prev.FromProtoMessage(m.Previous); err != nil { return fmt.Errorf("invalid previous split member ID: %w", err) } + } else { + x.prev = oid.ID{} } // first - if mID := m.GetFirst(); mID != nil { - if err := new(oid.ID).ReadFromV2(*mID); err != nil { + if m.First != nil { + if err := x.first.FromProtoMessage(m.First); err != nil { return fmt.Errorf("invalid first split member ID: %w", err) } + } else { + x.first = oid.ID{} } // split ID - if b := m.GetSplitID(); len(b) > 0 { + if x.id = m.SplitId; len(m.SplitId) > 0 { var uid uuid.UUID - if err := uid.UnmarshalBinary(b); err != nil { + if err := uid.UnmarshalBinary(m.SplitId); err != nil { return fmt.Errorf("invalid split ID: %w", err) } else if ver := uid.Version(); ver != 4 { return fmt.Errorf("invalid split ID: wrong UUID version %d, expected 4", ver) } } // children - if mc := m.GetChildren(); len(mc) > 0 { - for i := range mc { - if err := new(oid.ID).ReadFromV2(mc[i]); err != nil { + if len(m.Children) > 0 { + x.children = make([]oid.ID, len(m.Children)) + for i := range m.Children { + if m.Children[i] == nil { + return fmt.Errorf("nil child split member #%d", i) + } + if err := x.children[i].FromProtoMessage(m.Children[i]); err != nil { return fmt.Errorf("invalid child split member ID #%d: %w", i, err) } } + } else { + x.children = nil } // parent signature - if ms := m.GetParentSignature(); ms != nil { - if err := new(neofscrypto.Signature).ReadFromV2(*ms); err != nil { + if m.ParentSignature != nil { + if x.parSig == nil { + x.parSig = new(neofscrypto.Signature) + } + if err := x.parSig.FromProtoMessage(m.ParentSignature); err != nil { return fmt.Errorf("invalid parent signature: %w", err) } + } else { + x.parSig = nil } // parent header - if mh := m.GetParentHeader(); mh != nil { - if err := verifyHeaderV2(*mh); err != nil { + if m.ParentHeader != nil { + if x.parHdr == nil { + x.parHdr = new(header) + } + if err := x.parHdr.fromProtoMessage(m.ParentHeader); err != nil { return fmt.Errorf("invalid parent header: %w", err) } + } else { + x.parHdr = nil } return nil } -func verifyHeaderV2(m object.Header) error { +func (x split) protoMessage() *protoobject.Header_Split { + m := &protoobject.Header_Split{ + SplitId: x.id, + } + if !x.parID.IsZero() { + m.Parent = x.parID.ProtoMessage() + } + if !x.prev.IsZero() { + m.Previous = x.prev.ProtoMessage() + } + if x.parSig != nil { + m.ParentSignature = x.parSig.ProtoMessage() + } + if x.parHdr != nil { + m.ParentHeader = x.parHdr.protoMessage() + } + if len(x.children) > 0 { + m.Children = make([]*refs.ObjectID, len(x.children)) + for i := range x.children { + m.Children[i] = x.children[i].ProtoMessage() + } + } + if !x.first.IsZero() { + m.First = x.first.ProtoMessage() + } + return m +} + +func (x header) isZero() bool { + return x.version == nil && x.owner.IsZero() && x.cnr.IsZero() && x.created == 0 && x.payloadLn == 0 && + x.pldHash == nil && x.typ == 0 && x.pldHomoHash == nil && x.session == nil && len(x.attrs) == 0 && x.split.isZero() +} + +func (x *header) fromProtoMessage(m *protoobject.Header) error { // version - if mv := m.GetVersion(); mv != nil { - if err := new(version.Version).ReadFromV2(*mv); err != nil { + if m.Version != nil { + if x.version == nil { + x.version = new(version.Version) + } + if err := x.version.FromProtoMessage(m.Version); err != nil { return fmt.Errorf("invalid version: %w", err) } + } else { + x.version = nil } // owner - if mu := m.GetOwnerID(); mu != nil { - if err := new(user.ID).ReadFromV2(*mu); err != nil { + if m.OwnerId != nil { + if err := x.owner.FromProtoMessage(m.OwnerId); err != nil { return fmt.Errorf("invalid owner: %w", err) } + } else { + x.owner = user.ID{} } // container - if mc := m.GetContainerID(); mc != nil { - if err := new(cid.ID).ReadFromV2(*mc); err != nil { + if m.ContainerId != nil { + if err := x.cnr.FromProtoMessage(m.ContainerId); err != nil { return fmt.Errorf("invalid container: %w", err) } + } else { + x.cnr = cid.ID{} } // payload checksum - if mc := m.GetPayloadHash(); mc != nil { - if err := new(checksum.Checksum).ReadFromV2(*mc); err != nil { + if m.PayloadHash != nil { + if x.pldHash == nil { + x.pldHash = new(checksum.Checksum) + } + if err := x.pldHash.FromProtoMessage(m.PayloadHash); err != nil { return fmt.Errorf("invalid payload checksum: %w", err) } + } else { + x.pldHash = nil + } + // type + if m.ObjectType < 0 { + return fmt.Errorf("negative type %d", m.ObjectType) } // payload homomorphic checksum - if mc := m.GetHomomorphicHash(); mc != nil { - if err := new(checksum.Checksum).ReadFromV2(*mc); err != nil { + if m.HomomorphicHash != nil { + if x.pldHomoHash == nil { + x.pldHomoHash = new(checksum.Checksum) + } + if err := x.pldHomoHash.FromProtoMessage(m.HomomorphicHash); err != nil { return fmt.Errorf("invalid payload homomorphic checksum: %w", err) } + } else { + x.pldHomoHash = nil } // session token - if ms := m.GetSessionToken(); ms != nil { - if err := new(session.Object).ReadFromV2(*ms); err != nil { + if m.SessionToken != nil { + if x.session == nil { + x.session = new(session.Object) + } + if err := x.session.FromProtoMessage(m.SessionToken); err != nil { return fmt.Errorf("invalid session token: %w", err) } + } else { + x.session = nil } // split header - if ms := m.GetSplit(); ms != nil { - if err := verifySplitHeaderV2(*ms); err != nil { + if m.Split != nil { + if err := x.split.fromProtoMessage(m.Split); err != nil { return fmt.Errorf("invalid split header: %w", err) } + } else { + x.split = split{} } // attributes if ma := m.GetAttributes(); len(ma) > 0 { + x.attrs = make([]Attribute, len(ma)) done := make(map[string]struct{}, len(ma)) for i := range ma { - key := ma[i].GetKey() - if key == "" { - return fmt.Errorf("empty key of the attribute #%d", i) + if ma[i] == nil { + return fmt.Errorf("nil attribute #%d", i) } - if _, ok := done[key]; ok { - return fmt.Errorf("duplicated attribute %s", key) + if _, ok := done[ma[i].Key]; ok { + return fmt.Errorf("duplicated attribute %s", ma[i].Key) } - val := ma[i].GetValue() - if val == "" { - return fmt.Errorf("empty value of the attribute #%d (%s)", i, key) + if err := x.attrs[i].fromProtoMessage(ma[i], true); err != nil { + return fmt.Errorf("invalid attribute #%d: %w", i, err) } - switch key { - case AttributeExpirationEpoch: - if _, err := strconv.ParseUint(val, 10, 64); err != nil { - return fmt.Errorf("invalid expiration attribute (must be a uint): %w", err) - } - } - done[key] = struct{}{} + done[ma[i].Key] = struct{}{} } + } else { + x.attrs = nil } + x.created = m.CreationEpoch + x.payloadLn = m.PayloadLength + x.typ = Type(m.ObjectType) return nil } -// ReadFromV2 reads Object from the [object.Object] message. Returns an error if -// the message is malformed according to the NeoFS API V2 protocol. The message -// must not be nil. +func (x header) protoMessage() *protoobject.Header { + m := &protoobject.Header{ + CreationEpoch: x.created, + PayloadLength: x.payloadLn, + ObjectType: protoobject.ObjectType(x.typ), + } + if x.version != nil { + m.Version = x.version.ProtoMessage() + } + if !x.cnr.IsZero() { + m.ContainerId = x.cnr.ProtoMessage() + } + if !x.owner.IsZero() { + m.OwnerId = x.owner.ProtoMessage() + } + if x.pldHash != nil { + m.PayloadHash = x.pldHash.ProtoMessage() + } + if x.pldHomoHash != nil { + m.HomomorphicHash = x.pldHomoHash.ProtoMessage() + } + if x.session != nil { + m.SessionToken = x.session.ProtoMessage() + } + if len(x.attrs) > 0 { + m.Attributes = make([]*protoobject.Header_Attribute, len(x.attrs)) + for i := range x.attrs { + m.Attributes[i] = &protoobject.Header_Attribute{Key: x.attrs[i].Key(), Value: x.attrs[i].Value()} + } + } + if !x.split.isZero() { + m.Split = x.split.protoMessage() + } + return m +} + +// FromProtoMessage validates m according to the NeoFS API protocol and restores +// o from it. // -// ReadFromV2 is intended to be used by the NeoFS API V2 client/server -// implementation only and is not expected to be directly used by applications. -func (o *Object) ReadFromV2(m object.Object) error { +// See also [Table.ProtoMessage]. +func (o *Object) FromProtoMessage(m *protoobject.Object) error { // ID - if mID := m.GetObjectID(); mID != nil { - if err := new(oid.ID).ReadFromV2(*mID); err != nil { + if m.ObjectId != nil { + if err := o.id.FromProtoMessage(m.ObjectId); err != nil { return fmt.Errorf("invalid ID: %w", err) } + } else { + o.ResetID() } // signature - if ms := m.GetSignature(); ms != nil { - if err := new(neofscrypto.Signature).ReadFromV2(*ms); err != nil { + if m.Signature != nil { + if o.sig == nil { + o.sig = new(neofscrypto.Signature) + } + if err := o.sig.FromProtoMessage(m.Signature); err != nil { return fmt.Errorf("invalid signature: %w", err) } + } else { + o.sig = nil } // header - if mh := m.GetHeader(); mh != nil { - if err := verifyHeaderV2(*mh); err != nil { + if m.Header != nil { + if err := o.header.fromProtoMessage(m.Header); err != nil { return fmt.Errorf("invalid header: %w", err) } + } else { + o.header = header{} } - *o = Object(m) + o.pld = m.Payload return nil } -// ToV2 converts [Object] to v2 [object.Object] message. +// ProtoMessage converts o into message to transmit using the NeoFS API +// protocol. // -// The value returned shares memory with the structure itself, so changing it can lead to data corruption. -// Make a copy if you need to change it. -func (o Object) ToV2() *object.Object { - return (*object.Object)(&o) +// See also [Object.FromProtoMessage]. +func (o Object) ProtoMessage() *protoobject.Object { + m := &protoobject.Object{ + Payload: o.pld, + } + if !o.id.IsZero() { + m.ObjectId = o.id.ProtoMessage() + } + if o.sig != nil { + m.Signature = o.sig.ProtoMessage() + } + if !o.header.isZero() { + m.Header = o.header.protoMessage() + } + return m } // CopyTo writes deep copy of the [Object] to dst. func (o Object) CopyTo(dst *Object) { - id := (*object.Object)(&o).GetObjectID() - (*object.Object)(dst).SetObjectID(copyObjectID(id)) - - sig := (*object.Object)(&o).GetSignature() - (*object.Object)(dst).SetSignature(copySignature(sig)) - - header := (*object.Object)(&o).GetHeader() - (*object.Object)(dst).SetHeader(copyHeader(header)) - + o.header.copyTo(&dst.header) + dst.id = o.id + dst.sig = cloneSignature(o.sig) dst.SetPayload(bytes.Clone(o.Payload())) } // MarshalHeaderJSON marshals object's header into JSON format. func (o Object) MarshalHeaderJSON() ([]byte, error) { - return (*object.Object)(&o).GetHeader().MarshalJSON() -} - -func (o *Object) setHeaderField(setter func(*object.Header)) { - obj := (*object.Object)(o) - h := obj.GetHeader() - - if h == nil { - h = new(object.Header) - obj.SetHeader(h) - } - - setter(h) -} - -func (o *Object) setSplitFields(setter func(*object.SplitHeader)) { - o.setHeaderField(func(h *object.Header) { - split := h.GetSplit() - if split == nil { - split = new(object.SplitHeader) - h.SetSplit(split) - } - - setter(split) - }) + return neofsproto.MarshalMessageJSON(o.header.protoMessage()) } // ID returns object identifier. @@ -271,66 +404,35 @@ func (o *Object) ID() (oid.ID, bool) { // // See also [Object.SetID]. func (o Object) GetID() oid.ID { - var res oid.ID - m := (*object.Object)(&o) - if id := m.GetObjectID(); id != nil { - if err := res.ReadFromV2(*id); err != nil { - panic(fmt.Errorf("unexpected ID decoding failure: %w", err)) - } - } - - return res + return o.id } // SetID sets object identifier. // // See also [Object.GetID]. func (o *Object) SetID(v oid.ID) { - var v2 refs.ObjectID - v.WriteToV2(&v2) - - (*object.Object)(o). - SetObjectID(&v2) + o.id = v } // ResetID removes object identifier. // // See also [Object.SetID]. func (o *Object) ResetID() { - (*object.Object)(o). - SetObjectID(nil) + o.id = oid.ID{} } // Signature returns signature of the object identifier. // // See also [Object.SetSignature]. func (o Object) Signature() *neofscrypto.Signature { - sigv2 := (*object.Object)(&o).GetSignature() - if sigv2 == nil { - return nil - } - - var sig neofscrypto.Signature - if err := sig.ReadFromV2(*sigv2); err != nil { - return nil - } - - return &sig + return o.sig } // SetSignature sets signature of the object identifier. // // See also [Object.Signature]. func (o *Object) SetSignature(v *neofscrypto.Signature) { - var sigv2 *refs.Signature - - if v != nil { - sigv2 = new(refs.Signature) - - v.WriteToV2(sigv2) - } - - (*object.Object)(o).SetSignature(sigv2) + o.sig = v } // Payload returns payload bytes. @@ -340,59 +442,42 @@ func (o *Object) SetSignature(v *neofscrypto.Signature) { // // See also [Object.SetPayload]. func (o Object) Payload() []byte { - return (*object.Object)(&o).GetPayload() + return o.pld } // SetPayload sets payload bytes. // // See also [Object.Payload]. func (o *Object) SetPayload(v []byte) { - (*object.Object)(o).SetPayload(v) + o.pld = v } // Version returns version of the object. Returns nil if the version is unset. // // See also [Object.SetVersion]. func (o Object) Version() *version.Version { - verV2 := (*object.Object)(&o).GetHeader().GetVersion() - if verV2 == nil { - return nil - } - var ver version.Version - if err := ver.ReadFromV2(*verV2); err != nil { - return nil - } - return &ver + return o.header.version } // SetVersion sets version of the object. // // See also [Object.Version]. func (o *Object) SetVersion(v *version.Version) { - var verV2 refs.Version - v.WriteToV2(&verV2) - - o.setHeaderField(func(h *object.Header) { - h.SetVersion(&verV2) - }) + o.header.version = v } // PayloadSize returns payload length of the object. // // See also [Object.SetPayloadSize]. func (o Object) PayloadSize() uint64 { - return (*object.Object)(&o). - GetHeader(). - GetPayloadLength() + return o.header.payloadLn } // SetPayloadSize sets payload length of the object. // // See also [Object.PayloadSize]. func (o *Object) SetPayloadSize(v uint64) { - o.setHeaderField(func(h *object.Header) { - h.SetPayloadLength(v) - }) + o.header.payloadLn = v } // ContainerID returns identifier of the related container. @@ -408,12 +493,7 @@ func (o *Object) ContainerID() (v cid.ID, isSet bool) { // // See also [Object.GetContainerID]. func (o *Object) SetContainerID(v cid.ID) { - var cidV2 refs.ContainerID - v.WriteToV2(&cidV2) - - o.setHeaderField(func(h *object.Header) { - h.SetContainerID(&cidV2) - }) + o.header.cnr = v } // GetContainerID returns identifier of the related container. Zero means unset @@ -421,13 +501,7 @@ func (o *Object) SetContainerID(v cid.ID) { // // See also [Object.SetContainerID]. func (o Object) GetContainerID() cid.ID { - var cnr cid.ID - if m := (*object.Object)(&o).GetHeader().GetContainerID(); m != nil { - if err := cnr.ReadFromV2(*m); err != nil { - panic(fmt.Errorf("unexpected container ID decoding failure: %w", err)) - } - } - return cnr + return o.header.cnr } // OwnerID returns identifier of the object owner. @@ -440,22 +514,7 @@ func (o Object) OwnerID() *user.ID { res := o.Owner(); return &res } // // See also [Object.SetOwner]. func (o Object) Owner() user.ID { - var id user.ID - - m := (*object.Object)(&o).GetHeader().GetOwnerID() - if m != nil { - // unlike other IDs, user.ID.ReadFromV2 also expects correct prefix and checksum - // suffix. So, we cannot call it and panic on error here because nothing - // prevents user from setting incorrect owner ID (Object.SetOwnerID accepts it). - // At the same time, we always expect correct length. - b := m.GetValue() - if len(b) != user.IDSize { - panic(fmt.Errorf("unexpected user ID decoding failure: invalid length %d, expected %d", len(b), user.IDSize)) - } - copy(id[:], b) - } - - return id + return o.header.owner } // SetOwnerID sets identifier of the object owner. @@ -468,30 +527,21 @@ func (o *Object) SetOwnerID(v *user.ID) { o.SetOwner(*v) } // // See also [Object.GetOwner]. func (o *Object) SetOwner(v user.ID) { - o.setHeaderField(func(h *object.Header) { - var m refs.OwnerID - v.WriteToV2(&m) - - h.SetOwnerID(&m) - }) + o.header.owner = v } // CreationEpoch returns epoch number in which object was created. // // See also [Object.SetCreationEpoch]. func (o Object) CreationEpoch() uint64 { - return (*object.Object)(&o). - GetHeader(). - GetCreationEpoch() + return o.header.created } // SetCreationEpoch sets epoch number in which object was created. // // See also [Object.CreationEpoch]. func (o *Object) SetCreationEpoch(v uint64) { - o.setHeaderField(func(h *object.Header) { - h.SetCreationEpoch(v) - }) + o.header.created = v } // PayloadChecksum returns checksum of the object payload and @@ -501,27 +551,17 @@ func (o *Object) SetCreationEpoch(v uint64) { // // See also [Object.SetPayloadChecksum]. func (o Object) PayloadChecksum() (checksum.Checksum, bool) { - var v checksum.Checksum - v2 := (*object.Object)(&o) - - if hash := v2.GetHeader().GetPayloadHash(); hash != nil { - err := v.ReadFromV2(*hash) - return v, (err == nil) + if o.header.pldHash != nil { + return *o.header.pldHash, true } - - return v, false + return checksum.Checksum{}, false } // SetPayloadChecksum sets checksum of the object payload. // // See also [Object.PayloadChecksum]. func (o *Object) SetPayloadChecksum(v checksum.Checksum) { - var v2 refs.Checksum - v.WriteToV2(&v2) - - o.setHeaderField(func(h *object.Header) { - h.SetPayloadHash(&v2) - }) + o.header.pldHash = &v } // PayloadHomomorphicHash returns homomorphic hash of the object @@ -531,27 +571,17 @@ func (o *Object) SetPayloadChecksum(v checksum.Checksum) { // // See also [Object.SetPayloadHomomorphicHash]. func (o Object) PayloadHomomorphicHash() (checksum.Checksum, bool) { - var v checksum.Checksum - v2 := (*object.Object)(&o) - - if hash := v2.GetHeader().GetHomomorphicHash(); hash != nil { - err := v.ReadFromV2(*hash) - return v, (err == nil) + if o.header.pldHomoHash != nil { + return *o.header.pldHomoHash, true } - - return v, false + return checksum.Checksum{}, false } // SetPayloadHomomorphicHash sets homomorphic hash of the object payload. // // See also [Object.PayloadHomomorphicHash]. func (o *Object) SetPayloadHomomorphicHash(v checksum.Checksum) { - var v2 refs.Checksum - v.WriteToV2(&v2) - - o.setHeaderField(func(h *object.Header) { - h.SetHomomorphicHash(&v2) - }) + o.header.pldHomoHash = &v } // Attributes returns all object attributes. @@ -561,17 +591,7 @@ func (o *Object) SetPayloadHomomorphicHash(v checksum.Checksum) { // // See also [Object.SetAttributes], [Object.UserAttributes]. func (o Object) Attributes() []Attribute { - attrs := (*object.Object)(&o). - GetHeader(). - GetAttributes() - - res := make([]Attribute, len(attrs)) - - for i := range attrs { - res[i] = *NewAttributeFromV2(&attrs[i]) - } - - return res + return o.header.attrs } // UserAttributes returns user attributes of the Object. @@ -581,15 +601,11 @@ func (o Object) Attributes() []Attribute { // // See also [Object.SetAttributes], [Object.Attributes]. func (o Object) UserAttributes() []Attribute { - attrs := (*object.Object)(&o). - GetHeader(). - GetAttributes() - - res := make([]Attribute, 0, len(attrs)) + res := make([]Attribute, 0, len(o.header.attrs)) - for i := range attrs { - if !strings.HasPrefix(attrs[i].GetKey(), object.SysAttributePrefix) { - res = append(res, *NewAttributeFromV2(&attrs[i])) + for i := range o.header.attrs { + if !strings.HasPrefix(o.header.attrs[i].Key(), sysAttrPrefix) { + res = append(res, o.header.attrs[i]) } } @@ -598,15 +614,7 @@ func (o Object) UserAttributes() []Attribute { // SetAttributes sets object attributes. func (o *Object) SetAttributes(v ...Attribute) { - attrs := make([]object.Attribute, len(v)) - - for i := range v { - attrs[i] = *v[i].ToV2() - } - - o.setHeaderField(func(h *object.Header) { - h.SetAttributes(attrs) - }) + o.header.attrs = v } // PreviousID returns identifier of the previous sibling object. @@ -623,75 +631,35 @@ func (o *Object) PreviousID() (oid.ID, bool) { // // See also [Object.SetPreviousID]. func (o Object) GetPreviousID() oid.ID { - var id oid.ID - if m := (*object.Object)(&o).GetHeader().GetSplit().GetPrevious(); m != nil { - if err := id.ReadFromV2(*m); err != nil { - panic(fmt.Errorf("unexpected ID decoding failure: %w", err)) - } - } - return id + return o.header.split.prev } // ResetPreviousID resets identifier of the previous sibling object. // // See also [Object.SetPreviousID], [Object.GetPreviousID]. func (o *Object) ResetPreviousID() { - o.setSplitFields(func(split *object.SplitHeader) { - split.SetPrevious(nil) - }) + o.header.split.prev = oid.ID{} } // SetPreviousID sets identifier of the previous sibling object. // // See also [Object.GetPreviousID]. func (o *Object) SetPreviousID(v oid.ID) { - var v2 refs.ObjectID - v.WriteToV2(&v2) - - o.setSplitFields(func(split *object.SplitHeader) { - split.SetPrevious(&v2) - }) + o.header.split.prev = v } // Children return list of the identifiers of the child objects. // // See also [Object.SetChildren]. func (o Object) Children() []oid.ID { - v2 := (*object.Object)(&o) - ids := v2.GetHeader().GetSplit().GetChildren() - - var ( - id oid.ID - res = make([]oid.ID, len(ids)) - ) - - for i := range ids { - if err := id.ReadFromV2(ids[i]); err != nil { - panic(fmt.Errorf("unexpected child#%d decoding failure: %w", i, err)) - } - res[i] = id - } - - return res + return o.header.split.children } // SetChildren sets list of the identifiers of the child objects. // // See also [Object.Children]. func (o *Object) SetChildren(v ...oid.ID) { - var ( - v2 refs.ObjectID - ids = make([]refs.ObjectID, len(v)) - ) - - for i := range v { - v[i].WriteToV2(&v2) - ids[i] = v2 - } - - o.setSplitFields(func(split *object.SplitHeader) { - split.SetChildren(ids) - }) + o.header.split.children = v } // SetFirstID sets the first part's ID of the object's @@ -699,12 +667,7 @@ func (o *Object) SetChildren(v ...oid.ID) { // // See also [Object.GetFirstID]. func (o *Object) SetFirstID(id oid.ID) { - var v2 refs.ObjectID - id.WriteToV2(&v2) - - o.setSplitFields(func(split *object.SplitHeader) { - split.SetFirst(&v2) - }) + o.header.split.first = id } // FirstID returns the first part of the object's split chain. @@ -719,34 +682,21 @@ func (o Object) FirstID() (oid.ID, bool) { // // See also [Object.SetFirstID]. func (o Object) GetFirstID() oid.ID { - var id oid.ID - if m := (*object.Object)(&o).GetHeader().GetSplit().GetFirst(); m != nil { - if err := id.ReadFromV2(*m); err != nil { - panic(fmt.Errorf("unexpected ID decoding failure: %w", err)) - } - } - return id + return o.header.split.first } // SplitID return split identity of split object. If object is not split returns nil. // // See also [Object.SetSplitID]. func (o Object) SplitID() *SplitID { - return NewSplitIDFromV2( - (*object.Object)(&o). - GetHeader(). - GetSplit(). - GetSplitID(), - ) + return NewSplitIDFromV2(o.header.split.id) } // SetSplitID sets split identifier for the split object. // // See also [Object.SplitID]. func (o *Object) SetSplitID(id *SplitID) { - o.setSplitFields(func(split *object.SplitHeader) { - split.SetSplitID(id.ToV2()) - }) + o.header.split.id = id.ToV2() } // ParentID returns identifier of the parent object. @@ -763,118 +713,79 @@ func (o *Object) ParentID() (oid.ID, bool) { // // See also [Object.SetParentID]. func (o Object) GetParentID() oid.ID { - var id oid.ID - if m := (*object.Object)(&o).GetHeader().GetSplit().GetParent(); m != nil { - if err := id.ReadFromV2(*m); err != nil { - panic(fmt.Errorf("unexpected ID decoding failure: %w", err)) - } - } - return id + return o.header.split.parID } // SetParentID sets identifier of the parent object. // // See also [Object.GetParentID]. func (o *Object) SetParentID(v oid.ID) { - var v2 refs.ObjectID - v.WriteToV2(&v2) - - o.setSplitFields(func(split *object.SplitHeader) { - split.SetParent(&v2) - }) + o.header.split.parID = v } // ResetParentID removes identifier of the parent object. // // See also [Object.SetParentID]. func (o *Object) ResetParentID() { - o.setSplitFields(func(split *object.SplitHeader) { - split.SetParent(nil) - }) + o.header.split.parID = oid.ID{} } // Parent returns parent object w/o payload. // // See also [Object.SetParent]. func (o Object) Parent() *Object { - h := (*object.Object)(&o). - GetHeader(). - GetSplit() - - parSig := h.GetParentSignature() - parHdr := h.GetParentHeader() - - if parSig == nil && parHdr == nil { + if o.header.split.parSig == nil && o.header.split.parHdr == nil { return nil } - oV2 := new(object.Object) - oV2.SetObjectID(h.GetParent()) - oV2.SetSignature(parSig) - oV2.SetHeader(parHdr) - - return NewFromV2(oV2) + return &Object{ + header: *o.header.split.parHdr, + id: o.header.split.parID, + sig: o.header.split.parSig, + } } // SetParent sets parent object w/o payload. // // See also [Object.Parent]. func (o *Object) SetParent(v *Object) { - o.setSplitFields(func(split *object.SplitHeader) { - split.SetParent((*object.Object)(v).GetObjectID()) - split.SetParentSignature((*object.Object)(v).GetSignature()) - split.SetParentHeader((*object.Object)(v).GetHeader()) - }) + if v != nil { + o.header.split.parHdr = &v.header + o.header.split.parID = v.id + o.header.split.parSig = v.sig + return + } + o.header.split.parHdr = nil + o.header.split.parID = oid.ID{} + o.header.split.parSig = nil } // SessionToken returns token of the session within which object was created. // // See also [Object.SetSessionToken]. func (o Object) SessionToken() *session.Object { - tokv2 := (*object.Object)(&o).GetHeader().GetSessionToken() - if tokv2 == nil { - return nil - } - - var res session.Object - - _ = res.ReadFromV2(*tokv2) - - return &res + return o.header.session } // SetSessionToken sets token of the session within which object was created. // // See also [Object.SessionToken]. func (o *Object) SetSessionToken(v *session.Object) { - o.setHeaderField(func(h *object.Header) { - var tokv2 *v2session.Token - - if v != nil { - tokv2 = new(v2session.Token) - v.WriteToV2(tokv2) - } - - h.SetSessionToken(tokv2) - }) + o.header.session = v } // Type returns type of the object. // // See also [Object.SetType]. func (o Object) Type() Type { - return Type((*object.Object)(&o). - GetHeader(). - GetObjectType()) + return o.header.typ } // SetType sets type of the object. // // See also [Object.Type]. func (o *Object) SetType(v Type) { - o.setHeaderField(func(h *object.Header) { - h.SetObjectType(object.Type(v)) - }) + o.header.typ = v } // CutPayload returns [Object] w/ empty payload. @@ -882,75 +793,55 @@ func (o *Object) SetType(v Type) { // The value returned shares memory with the structure itself, so changing it can lead to data corruption. // Make a copy if you need to change it. func (o *Object) CutPayload() *Object { - ov2 := new(object.Object) - *ov2 = *(*object.Object)(o) - ov2.SetPayload(nil) - - return (*Object)(ov2) + if o == nil { + return nil + } + return &Object{ + header: o.header, + id: o.id, + sig: o.sig, + } } // HasParent checks if parent (split ID) is present. func (o Object) HasParent() bool { - return (*object.Object)(&o). - GetHeader(). - GetSplit() != nil + return !o.header.split.isZero() } // ResetRelations removes all fields of links with other objects. func (o *Object) ResetRelations() { - o.setHeaderField(func(h *object.Header) { - h.SetSplit(nil) - }) -} - -// InitRelations initializes relation field. -func (o *Object) InitRelations() { - o.setHeaderField(func(h *object.Header) { - h.SetSplit(new(object.SplitHeader)) - }) + o.header.split = split{} } // Marshal marshals object into a protobuf binary form. // // See also [Object.Unmarshal]. func (o Object) Marshal() []byte { - return (*object.Object)(&o).StableMarshal(nil) + return neofsproto.Marshal(o) } // Unmarshal unmarshals protobuf binary representation of object. // // See also [Object.Marshal]. func (o *Object) Unmarshal(data []byte) error { - var m object.Object - err := m.Unmarshal(data) - if err != nil { - return err - } - - return o.ReadFromV2(m) + return neofsproto.Unmarshal(data, o) } // MarshalJSON encodes object to protobuf JSON format. // // See also [Object.UnmarshalJSON]. func (o Object) MarshalJSON() ([]byte, error) { - return (*object.Object)(&o).MarshalJSON() + return neofsproto.MarshalJSON(o) } // UnmarshalJSON decodes object from protobuf JSON format. // // See also [Object.MarshalJSON]. func (o *Object) UnmarshalJSON(data []byte) error { - var m object.Object - err := m.UnmarshalJSON(data) - if err != nil { - return err - } - - return o.ReadFromV2(m) + return neofsproto.UnmarshalJSON(data, o) } // HeaderLen returns length of the binary header. func (o Object) HeaderLen() int { - return (*object.Object)(&o).GetHeader().StableSize() + return o.header.protoMessage().MarshaledSize() } diff --git a/object/object_copy.go b/object/object_copy.go index bdf6bde9..f23b1a98 100644 --- a/object/object_copy.go +++ b/object/object_copy.go @@ -4,153 +4,68 @@ import ( "bytes" "slices" - "github.com/nspcc-dev/neofs-api-go/v2/object" - "github.com/nspcc-dev/neofs-api-go/v2/refs" - v2session "github.com/nspcc-dev/neofs-api-go/v2/session" + "github.com/nspcc-dev/neofs-sdk-go/checksum" + neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" + "github.com/nspcc-dev/neofs-sdk-go/session" ) -func copyObjectID(id *refs.ObjectID) *refs.ObjectID { - if id == nil { +// TODO: Support CopyTo by type. +func cloneSignature(src *neofscrypto.Signature) *neofscrypto.Signature { + if src == nil { return nil } - - var newID refs.ObjectID - newID.SetValue(bytes.Clone(id.GetValue())) - - return &newID + s := neofscrypto.NewSignatureFromRawKey(src.Scheme(), bytes.Clone(src.PublicKeyBytes()), src.Value()) + return &s } -func copySignature(sig *refs.Signature) *refs.Signature { - if sig == nil { +// TODO: Support CopyTo by type. +func cloneChecksum(src *checksum.Checksum) *checksum.Checksum { + if src == nil { return nil } - - var newSig refs.Signature - newSig.SetScheme(sig.GetScheme()) - newSig.SetKey(bytes.Clone(sig.GetKey())) - newSig.SetSign(bytes.Clone(sig.GetSign())) - - return &newSig + s := checksum.New(src.Type(), bytes.Clone(src.Value())) + return &s } -func copySession(session *v2session.Token) *v2session.Token { - if session == nil { - return nil - } - - var newSession v2session.Token - if body := session.GetBody(); body != nil { - var newBody v2session.TokenBody - newBody.SetID(bytes.Clone(body.GetID())) - - if ownerID := body.GetOwnerID(); ownerID != nil { - var newOwnerID refs.OwnerID - newOwnerID.SetValue(bytes.Clone(ownerID.GetValue())) - - newBody.SetOwnerID(&newOwnerID) - } else { - newBody.SetOwnerID(nil) +func (x split) copyTo(dst *split) { + dst.parID = x.parID + dst.prev = x.prev + dst.id = x.id + dst.children = slices.Clone(x.children) + dst.first = x.first + dst.parSig = cloneSignature(x.parSig) + if x.parHdr != nil { + if dst.parHdr == nil { + dst.parHdr = new(header) } - - if lifetime := body.GetLifetime(); lifetime != nil { - newLifetime := *lifetime - newBody.SetLifetime(&newLifetime) - } else { - newBody.SetLifetime(nil) - } - - newBody.SetSessionKey(bytes.Clone(body.GetSessionKey())) - - // it is an interface. Both implementations do nothing inside implemented functions. - newBody.SetContext(body.GetContext()) - - newSession.SetBody(&newBody) + x.parHdr.copyTo(dst.parHdr) } else { - newSession.SetBody(nil) + dst.parHdr = nil } - - newSession.SetSignature(copySignature(session.GetSignature())) - - return &newSession -} - -func copySplitHeader(spl *object.SplitHeader) *object.SplitHeader { - if spl == nil { - return nil - } - - var newSpl object.SplitHeader - - newSpl.SetParent(copyObjectID(spl.GetParent())) - newSpl.SetPrevious(copyObjectID(spl.GetPrevious())) - newSpl.SetFirst(copyObjectID(spl.GetFirst())) - newSpl.SetParentSignature(copySignature(spl.GetParentSignature())) - newSpl.SetParentHeader(copyHeader(spl.GetParentHeader())) - newSpl.SetChildren(slices.Clone(spl.GetChildren())) - newSpl.SetSplitID(bytes.Clone(spl.GetSplitID())) - - return &newSpl } -func copyHeader(header *object.Header) *object.Header { - if header == nil { - return nil - } - - var newHeader object.Header - - newHeader.SetCreationEpoch(header.GetCreationEpoch()) - newHeader.SetPayloadLength(header.GetPayloadLength()) - newHeader.SetObjectType(header.GetObjectType()) - - if ver := header.GetVersion(); ver != nil { - newVer := *ver - newHeader.SetVersion(&newVer) - } else { - newHeader.SetVersion(nil) - } - - if containerID := header.GetContainerID(); containerID != nil { - var newContainerID refs.ContainerID - newContainerID.SetValue(bytes.Clone(containerID.GetValue())) - - newHeader.SetContainerID(&newContainerID) - } else { - newHeader.SetContainerID(nil) - } - - if ownerID := header.GetOwnerID(); ownerID != nil { - var newOwnerID refs.OwnerID - newOwnerID.SetValue(bytes.Clone(ownerID.GetValue())) - - newHeader.SetOwnerID(&newOwnerID) - } else { - newHeader.SetOwnerID(nil) - } - - if payloadHash := header.GetPayloadHash(); payloadHash != nil { - var newPayloadHash refs.Checksum - newPayloadHash.SetType(payloadHash.GetType()) - newPayloadHash.SetSum(bytes.Clone(payloadHash.GetSum())) - - newHeader.SetPayloadHash(&newPayloadHash) +func (x header) copyTo(dst *header) { + dst.cnr = x.cnr + dst.owner = x.owner + dst.created = x.created + dst.payloadLn = x.payloadLn + dst.typ = x.typ + dst.attrs = slices.Clone(x.attrs) + dst.pldHash = cloneChecksum(x.pldHash) + dst.pldHomoHash = cloneChecksum(x.pldHomoHash) + x.split.copyTo(&dst.split) + if x.version != nil { + ver := *x.version + dst.version = &ver } else { - newHeader.SetPayloadHash(nil) + dst.version = nil } - - if homoHash := header.GetHomomorphicHash(); homoHash != nil { - var newHomoHash refs.Checksum - newHomoHash.SetType(homoHash.GetType()) - newHomoHash.SetSum(bytes.Clone(homoHash.GetSum())) - - newHeader.SetHomomorphicHash(&newHomoHash) + if x.session != nil { + if dst.session == nil { + dst.session = new(session.Object) + } + x.session.CopyTo(dst.session) } else { - newHeader.SetHomomorphicHash(nil) + dst.session = nil } - - newHeader.SetSessionToken(copySession(header.GetSessionToken())) - newHeader.SetAttributes(slices.Clone(header.GetAttributes())) - newHeader.SetSplit(copySplitHeader(header.GetSplit())) - - return &newHeader } diff --git a/object/object_internal_test.go b/object/object_internal_test.go index 33b54984..7a33204e 100644 --- a/object/object_internal_test.go +++ b/object/object_internal_test.go @@ -5,11 +5,11 @@ import ( "testing" "github.com/google/uuid" - "github.com/nspcc-dev/neofs-api-go/v2/object" - "github.com/nspcc-dev/neofs-api-go/v2/refs" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" 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" + "github.com/nspcc-dev/neofs-sdk-go/proto/refs" "github.com/nspcc-dev/neofs-sdk-go/session" "github.com/nspcc-dev/neofs-sdk-go/user" usertest "github.com/nspcc-dev/neofs-sdk-go/user/test" @@ -81,11 +81,11 @@ func TestObject_CopyTo(t *testing.T) { var dst Object obj.CopyTo(&dst) - dstHeader := dst.ToV2().GetHeader() + dstHeader := dst.ProtoMessage().GetHeader() require.NotNil(t, dstHeader) - dstHeader.SetObjectType(object.TypeTombstone) + dstHeader.ObjectType = protoobject.ObjectType_TOMBSTONE - objHeader := obj.ToV2().GetHeader() + objHeader := obj.ProtoMessage().GetHeader() require.NotEqual(t, dstHeader.GetObjectType(), objHeader.GetObjectType()) }) @@ -145,36 +145,36 @@ func TestObject_CopyTo(t *testing.T) { t.Run("overwrite header", func(t *testing.T) { var local Object - require.Nil(t, local.ToV2().GetHeader()) + require.Nil(t, local.ProtoMessage().GetHeader()) var dst Object dst.SetAttributes(attr) - require.NotNil(t, dst.ToV2().GetHeader()) + require.NotNil(t, dst.ProtoMessage().GetHeader()) local.CopyTo(&dst) checkObjectEquals(t, local, dst) - require.Nil(t, local.ToV2().GetHeader()) - require.Nil(t, dst.ToV2().GetHeader()) + require.Nil(t, local.ProtoMessage().GetHeader()) + require.Nil(t, dst.ProtoMessage().GetHeader()) dst.SetAttributes(attr) - require.NotNil(t, dst.ToV2().GetHeader()) - require.Nil(t, local.ToV2().GetHeader()) + require.NotNil(t, dst.ProtoMessage().GetHeader()) + require.Nil(t, local.ProtoMessage().GetHeader()) }) t.Run("header, rewrite container id to nil", func(t *testing.T) { var local Object - var localHeader object.Header - local.ToV2().SetHeader(&localHeader) + var localHeader protoobject.Header + local.ProtoMessage().Header = &localHeader var dstContID refs.ContainerID - dstContID.SetValue([]byte{1}) + dstContID.Value = []byte{1} - var dstHeader object.Header - dstHeader.SetContainerID(&dstContID) + var dstHeader protoobject.Header + dstHeader.ContainerId = &dstContID var dst Object - dst.ToV2().SetHeader(&dstHeader) + dst.ProtoMessage().Header = &dstHeader local.CopyTo(&dst) checkObjectEquals(t, local, dst) @@ -182,41 +182,28 @@ func TestObject_CopyTo(t *testing.T) { t.Run("header, change container id", func(t *testing.T) { c := cidtest.ID() - b := c[:] - var mc refs.ContainerID - mc.SetValue(b) - var mh object.Header - mh.SetContainerID(&mc) - var mo object.Object - mo.SetHeader(&mh) var local, dst Object - require.NoError(t, local.ReadFromV2(mo)) - require.NoError(t, dst.ReadFromV2(mo)) + local.header.cnr = c + dst.header.cnr = c require.Equal(t, local.GetContainerID(), dst.GetContainerID()) - b[0]++ - require.Equal(t, c, local.GetContainerID()) - require.Equal(t, local.GetContainerID(), dst.GetContainerID()) - - cp := c local.CopyTo(&dst) - b[0]++ + local.header.cnr[0]++ require.NotEqual(t, local.GetContainerID(), dst.GetContainerID()) - require.Equal(t, c, local.GetContainerID()) - require.Equal(t, cp, dst.GetContainerID()) + require.Equal(t, c, dst.GetContainerID()) }) t.Run("header, rewrite payload hash", func(t *testing.T) { - var cs refs.Checksum - cs.SetType(refs.TillichZemor) - cs.SetSum([]byte{1}) - - var localHeader object.Header - localHeader.SetPayloadHash(&cs) + localHeader := protoobject.Header{ + PayloadHash: &refs.Checksum{ + Type: refs.ChecksumType_TZ, + Sum: []byte{1}, + }, + } var local Object - local.ToV2().SetHeader(&localHeader) + local.ProtoMessage().Header = &localHeader var dst Object local.CopyTo(&dst) @@ -225,15 +212,15 @@ func TestObject_CopyTo(t *testing.T) { }) t.Run("header, rewrite homo hash", func(t *testing.T) { - var cs refs.Checksum - cs.SetType(refs.TillichZemor) - cs.SetSum([]byte{1}) - - var localHeader object.Header - localHeader.SetHomomorphicHash(&cs) + localHeader := protoobject.Header{ + HomomorphicHash: &refs.Checksum{ + Type: refs.ChecksumType_TZ, + Sum: []byte{1}, + }, + } var local Object - local.ToV2().SetHeader(&localHeader) + local.ProtoMessage().Header = &localHeader var dst Object local.CopyTo(&dst) @@ -242,13 +229,10 @@ func TestObject_CopyTo(t *testing.T) { }) t.Run("header, rewrite split header", func(t *testing.T) { - var spl object.SplitHeader - - var localHeader object.Header - localHeader.SetSplit(&spl) + localHeader := protoobject.Header{Split: new(protoobject.Header_Split)} var local Object - local.ToV2().SetHeader(&localHeader) + local.ProtoMessage().Header = &localHeader var dst Object dst.SetChildren(oidtest.ID(), oidtest.ID()) @@ -267,8 +251,8 @@ func TestObject_CopyTo(t *testing.T) { var dst Object require.NotEqual(t, - local.ToV2().GetHeader().GetSessionToken().GetBody().GetOwnerID(), - dst.ToV2().GetHeader().GetSessionToken().GetBody().GetOwnerID(), + local.ProtoMessage().GetHeader().GetSessionToken().GetBody().GetOwnerId(), + dst.ProtoMessage().GetHeader().GetSessionToken().GetBody().GetOwnerId(), ) local.CopyTo(&dst) @@ -286,8 +270,8 @@ func TestObject_CopyTo(t *testing.T) { dst.SetSessionToken(sess) require.NotEqual(t, - local.ToV2().GetHeader().GetSessionToken().GetBody().GetOwnerID(), - dst.ToV2().GetHeader().GetSessionToken().GetBody().GetOwnerID(), + local.ProtoMessage().GetHeader().GetSessionToken().GetBody().GetOwnerId(), + dst.ProtoMessage().GetHeader().GetSessionToken().GetBody().GetOwnerId(), ) local.CopyTo(&dst) @@ -304,8 +288,8 @@ func TestObject_CopyTo(t *testing.T) { var dst Object require.NotEqual(t, - local.ToV2().GetHeader().GetSessionToken().GetBody().GetLifetime(), - dst.ToV2().GetHeader().GetSessionToken().GetBody().GetLifetime(), + local.ProtoMessage().GetHeader().GetSessionToken().GetBody().GetLifetime(), + dst.ProtoMessage().GetHeader().GetSessionToken().GetBody().GetLifetime(), ) local.CopyTo(&dst) @@ -317,7 +301,7 @@ func TestObject_CopyTo(t *testing.T) { sessLocal := sessionToken(cnr) local.SetSessionToken(sessLocal) - local.ToV2().GetHeader().GetSessionToken().SetBody(nil) + local.ProtoMessage().GetHeader().GetSessionToken().Body = nil sessDst := sessionToken(cnr) sessDst.SetID(uuid.New()) @@ -326,8 +310,8 @@ func TestObject_CopyTo(t *testing.T) { dst.SetSessionToken(sessDst) require.NotEqual(t, - local.ToV2().GetHeader().GetSessionToken().GetBody(), - dst.ToV2().GetHeader().GetSessionToken().GetBody(), + local.ProtoMessage().GetHeader().GetSessionToken().GetBody(), + dst.ProtoMessage().GetHeader().GetSessionToken().GetBody(), ) local.CopyTo(&dst) diff --git a/object/object_test.go b/object/object_test.go index 103b45e6..74e839aa 100644 --- a/object/object_test.go +++ b/object/object_test.go @@ -4,14 +4,10 @@ import ( "bytes" "crypto/elliptic" "encoding/json" - "math" "math/big" "testing" "github.com/google/uuid" - apiobject "github.com/nspcc-dev/neofs-api-go/v2/object" - "github.com/nspcc-dev/neofs-api-go/v2/refs" - apisession "github.com/nspcc-dev/neofs-api-go/v2/session" cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test" neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa" @@ -19,6 +15,9 @@ import ( oid "github.com/nspcc-dev/neofs-sdk-go/object/id" oidtest "github.com/nspcc-dev/neofs-sdk-go/object/id/test" objecttest "github.com/nspcc-dev/neofs-sdk-go/object/test" + 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/session" sessiontest "github.com/nspcc-dev/neofs-sdk-go/session/test" usertest "github.com/nspcc-dev/neofs-sdk-go/user/test" @@ -96,8 +95,8 @@ func init() { } // corresponds to validObject. -var validObjectID = oid.ID{104, 79, 12, 22, 52, 73, 44, 178, 246, 55, 17, 33, 163, 133, 252, 128, 121, 232, 127, 223, 91, 227, - 7, 83, 223, 223, 186, 153, 69, 172, 80, 230} +var validObjectID = oid.ID{83, 215, 222, 236, 6, 213, 115, 36, 162, 15, 57, 99, 101, 236, 160, 178, 140, 107, 14, 255, 72, 211, + 192, 154, 76, 214, 209, 36, 116, 247, 105, 172} // corresponds to validObject. var validBinObject = []byte{ @@ -133,12 +132,12 @@ var validBinObject = []byte{ 109, 95, 50, 82, 30, 10, 13, 112, 97, 114, 95, 97, 116, 116, 114, 95, 107, 101, 121, 49, 18, 13, 112, 97, 114, 95, 97, 116, 116, 114, 95, 118, 97, 108, 49, 82, 49, 10, 25, 95, 95, 78, 69, 79, 70, 83, 95, 95, 69, 88, 80, 73, 82, 65, 84, 73, 79, 78, 95, 69, 80, 79, 67, 72, 18, 20, 49, 52, 50, 48, 56, 52, 57, 55, 55, 49, 50, 55, 48, 48, 53, 56, 48, 49, 51, 48, 82, 30, 10, 13, 112, 97, 114, 95, 97, - 116, 116, 114, 95, 107, 101, 121, 50, 18, 13, 112, 97, 114, 95, 97, 116, 116, 114, 95, 118, 97, 108, 50, 42, 34, 10, 32, 173, 160, 45, - 58, 200, 168, 116, 142, 235, 209, 231, 80, 235, 186, 6, 132, 99, 95, 14, 39, 237, 139, 87, 66, 244, 72, 96, 69, 13, 83, 81, 172, - 42, 34, 10, 32, 238, 167, 85, 68, 91, 254, 165, 81, 182, 145, 16, 91, 35, 224, 17, 46, 164, 138, 86, 50, 196, 148, 215, 210, 247, - 29, 44, 153, 203, 20, 137, 169, 42, 34, 10, 32, 226, 165, 123, 249, 146, 166, 187, 202, 244, 12, 156, 43, 207, 204, 40, 230, - 145, 34, 212, 152, 148, 112, 44, 21, 195, 207, 249, 112, 34, 81, 145, 194, 50, 16, 224, 132, 3, 80, 32, 44, 69, 184, 185, 32, - 226, 201, 206, 196, 147, 41, 58, 34, 10, 32, 119, 231, 221, 167, 7, 141, 50, 77, 49, 23, 194, 169, 82, 56, 150, 162, 103, 20, + 116, 116, 114, 95, 107, 101, 121, 50, 18, 13, 112, 97, 114, 95, 97, 116, 116, 114, 95, 118, 97, 108, 50, 50, 16, 224, 132, 3, 80, 32, + 44, 69, 184, 185, 32, 226, 201, 206, 196, 147, 41, 42, 34, 10, 32, 173, 160, 45, 58, 200, 168, 116, 142, 235, 209, 231, 80, + 235, 186, 6, 132, 99, 95, 14, 39, 237, 139, 87, 66, 244, 72, 96, 69, 13, 83, 81, 172, 42, 34, 10, 32, 238, 167, 85, 68, 91, 254, + 165, 81, 182, 145, 16, 91, 35, 224, 17, 46, 164, 138, 86, 50, 196, 148, 215, 210, 247, 29, 44, 153, 203, 20, 137, 169, 42, 34, 10, + 32, 226, 165, 123, 249, 146, 166, 187, 202, 244, 12, 156, 43, 207, 204, 40, 230, 145, 34, 212, 152, 148, 112, 44, 21, 195, + 207, 249, 112, 34, 81, 145, 194, 58, 34, 10, 32, 119, 231, 221, 167, 7, 141, 50, 77, 49, 23, 194, 169, 82, 56, 150, 162, 103, 20, 124, 174, 16, 64, 169, 172, 79, 238, 242, 146, 87, 88, 5, 147, 34, 13, 72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33, } @@ -188,7 +187,7 @@ var validJSONObject = ` }, "sessionKey": "ApqQg8XWmqMjXYVrI20D2hQA/xrAIewA8Lf9TLuI16TZ", "object": { - "verb": "VERB_UNSPECIFIED", + "verb": 1047242055, "target": { "container": { "value": "h1mV27nR6Yng041Gwc34/uIecrH1qx1a1A8zVo5lm40=" @@ -691,790 +690,324 @@ func TestObject_ResetRelations(t *testing.T) { assertNoSplitFields(t, obj) } -func TestObject_InitRelations(t *testing.T) { - var obj object.Object - assertNoSplitFields(t, obj) - require.False(t, obj.HasParent()) - - obj.InitRelations() - assertNoSplitFields(t, obj) - require.True(t, obj.HasParent()) -} - -func TestObject_ReadFromV2(t *testing.T) { - var pv refs.Version - pv.SetMajor(88789927) - pv.SetMinor(2018985309) - var pcs refs.Checksum - pcs.SetType(1974315742) - pcs.SetSum([]byte("checksum_1")) - var phcs refs.Checksum - phcs.SetType(1922538608) - phcs.SetSum([]byte("checksum_2")) - pas := make([]apiobject.Attribute, 3) - pas[0].SetKey("par_attr_key1") - pas[0].SetValue("par_attr_val1") - pas[1].SetKey("__NEOFS__EXPIRATION_EPOCH") - pas[1].SetValue("14208497712700580130") - pas[2].SetKey("par_attr_key2") - pas[2].SetValue("par_attr_val2") - var ph apiobject.Header - ph.SetVersion(&pv) - ph.SetContainerID(protoContainerIDFromBytes(anyValidContainers[0][:])) - ph.SetOwnerID(protoUserIDFromBytes(anyValidUsers[0][:])) - ph.SetCreationEpoch(anyValidCreationEpoch) - ph.SetPayloadLength(anyValidPayloadSize) - ph.SetPayloadHash(&pcs) - ph.SetObjectType(apiobject.Type(anyValidType)) - ph.SetHomomorphicHash(&phcs) - ph.SetAttributes(pas) - - var mtl apisession.TokenLifetime - mtl.SetExp(16429376563136800338) - mtl.SetIat(7956510363313998522) - mtl.SetNbf(17237208928641773338) - var mtc apisession.ObjectSessionContext - mtc.SetVerb(1047242055) - mtc.SetTarget(protoContainerIDFromBytes(anyValidContainers[2][:]), - *protoIDFromBytes(anyValidIDs[8][:]), *protoIDFromBytes(anyValidIDs[9][:])) - var mtb apisession.TokenBody - mtb.SetID(anyValidSessionID[:]) - mtb.SetOwnerID(protoUserIDFromBytes(anyValidUsers[2][:])) - mtb.SetLifetime(&mtl) - mtb.SetSessionKey(anySessionIssuerPubKeyBytes) - mtb.SetContext(&mtc) - var mts refs.Signature - mts.SetScheme(1134494890) - mts.SetKey([]byte("session_signer")) - mts.SetSign([]byte("session_signature")) - var mt apisession.Token - mt.SetBody(&mtb) - mt.SetSignature(&mts) - - var mv refs.Version - mv.SetMajor(525747025) - mv.SetMinor(171993162) - var cs refs.Checksum - cs.SetType(126384577) - cs.SetSum([]byte("checksum_3")) - var hcs refs.Checksum - hcs.SetType(1001923429) - hcs.SetSum([]byte("checksum_4")) - as := make([]apiobject.Attribute, 3) - as[0].SetKey("attr_key1") - as[0].SetValue("attr_val1") - as[1].SetKey("__NEOFS__EXPIRATION_EPOCH") - as[1].SetValue("8516691293958955670") - as[2].SetKey("attr_key2") - as[2].SetValue("attr_val2") - var ps refs.Signature - ps.SetKey([]byte("pub_1")) - ps.SetSign([]byte("sig_1")) - ps.SetScheme(1277002296) - var sh apiobject.SplitHeader - sh.SetParent(protoIDFromBytes(anyValidIDs[1][:])) - sh.SetPrevious(protoIDFromBytes(anyValidIDs[2][:])) - sh.SetParentSignature(&ps) - sh.SetParentHeader(&ph) - sh.SetChildren([]refs.ObjectID{ - *protoIDFromBytes(anyValidIDs[3][:]), - *protoIDFromBytes(anyValidIDs[4][:]), - *protoIDFromBytes(anyValidIDs[5][:]), - }) - sh.SetSplitID(anyValidSplitIDBytes) - sh.SetFirst(protoIDFromBytes(anyValidIDs[6][:])) - - var mh apiobject.Header - mh.SetVersion(&mv) - mh.SetContainerID(protoContainerIDFromBytes(anyValidContainers[1][:])) - mh.SetOwnerID(protoUserIDFromBytes(anyValidUsers[1][:])) - mh.SetCreationEpoch(anyValidCreationEpoch + 1) - mh.SetPayloadLength(anyValidPayloadSize + 1) - mh.SetPayloadHash(&cs) - mh.SetObjectType(apiobject.Type(anyValidType) + 1) - mh.SetHomomorphicHash(&hcs) - mh.SetSessionToken(&mt) - mh.SetAttributes(as) - mh.SetSplit(&sh) - - var ms refs.Signature - ms.SetKey([]byte("pub_2")) - ms.SetSign([]byte("sig_2")) - ms.SetScheme(1242896683) - - var m apiobject.Object - m.SetObjectID(protoIDFromBytes(anyValidIDs[0][:])) - m.SetSignature(&ms) - m.SetHeader(&mh) - m.SetPayload(anyValidRegularPayload) +func TestObject_FromProtoMessage(t *testing.T) { + m := &protoobject.Object{ + ObjectId: protoIDFromBytes(anyValidIDs[0][:]), + Signature: &refs.Signature{Key: []byte("pub_2"), Sign: []byte("sig_2"), Scheme: 1242896683}, + Header: &protoobject.Header{ + Version: &refs.Version{Major: 525747025, Minor: 171993162}, + ContainerId: protoContainerIDFromBytes(anyValidContainers[1][:]), + OwnerId: protoUserIDFromBytes(anyValidUsers[1][:]), + CreationEpoch: anyValidCreationEpoch + 1, + PayloadLength: anyValidPayloadSize + 1, + PayloadHash: &refs.Checksum{Type: 126384577, Sum: []byte("checksum_3")}, + ObjectType: protoobject.ObjectType(anyValidType) + 1, + HomomorphicHash: &refs.Checksum{Type: 1001923429, Sum: []byte("checksum_4")}, + SessionToken: &protosession.SessionToken{ + Body: &protosession.SessionToken_Body{ + Id: anyValidSessionID[:], + OwnerId: protoUserIDFromBytes(anyValidUsers[2][:]), + Lifetime: &protosession.SessionToken_Body_TokenLifetime{ + Exp: 16429376563136800338, + Nbf: 17237208928641773338, + Iat: 7956510363313998522, + }, + SessionKey: anySessionIssuerPubKeyBytes, + Context: &protosession.SessionToken_Body_Object{Object: &protosession.ObjectSessionContext{ + Verb: 1047242055, + Target: &protosession.ObjectSessionContext_Target{ + Container: protoContainerIDFromBytes(anyValidContainers[2][:]), + Objects: []*refs.ObjectID{protoIDFromBytes(anyValidIDs[8][:]), protoIDFromBytes(anyValidIDs[9][:])}, + }, + }}, + }, + Signature: &refs.Signature{Key: []byte("session_signer"), Sign: []byte("session_signature"), Scheme: 1134494890}, + }, + Attributes: []*protoobject.Header_Attribute{ + {Key: "attr_key1", Value: "attr_val1"}, + {Key: "__NEOFS__EXPIRATION_EPOCH", Value: "8516691293958955670"}, + {Key: "attr_key2", Value: "attr_val2"}, + }, + Split: &protoobject.Header_Split{ + Parent: protoIDFromBytes(anyValidIDs[1][:]), + Previous: protoIDFromBytes(anyValidIDs[2][:]), + ParentSignature: &refs.Signature{Key: []byte("pub_1"), Sign: []byte("sig_1"), Scheme: 1277002296}, + ParentHeader: &protoobject.Header{ + Version: &refs.Version{Major: 88789927, Minor: 2018985309}, + ContainerId: protoContainerIDFromBytes(anyValidContainers[0][:]), + OwnerId: protoUserIDFromBytes(anyValidUsers[0][:]), + CreationEpoch: anyValidCreationEpoch, + PayloadLength: anyValidPayloadSize, + PayloadHash: &refs.Checksum{Type: 1974315742, Sum: []byte("checksum_1")}, + ObjectType: protoobject.ObjectType(anyValidType), + HomomorphicHash: &refs.Checksum{Type: 1922538608, Sum: []byte("checksum_2")}, + Attributes: []*protoobject.Header_Attribute{ + {Key: "par_attr_key1", Value: "par_attr_val1"}, + {Key: "__NEOFS__EXPIRATION_EPOCH", Value: "14208497712700580130"}, + {Key: "par_attr_key2", Value: "par_attr_val2"}, + }, + }, + Children: []*refs.ObjectID{ + protoIDFromBytes(anyValidIDs[3][:]), + protoIDFromBytes(anyValidIDs[4][:]), + protoIDFromBytes(anyValidIDs[5][:]), + }, + SplitId: anyValidSplitIDBytes, + First: protoIDFromBytes(anyValidIDs[6][:]), + }, + }, + Payload: anyValidRegularPayload, + } var obj object.Object - require.NoError(t, obj.ReadFromV2(m)) + require.NoError(t, obj.FromProtoMessage(m)) require.Equal(t, validObject, obj) // reset optional fields - m.SetObjectID(nil) - m.SetSignature(nil) - m.SetHeader(nil) - m.SetPayload(nil) + m.ObjectId = nil + m.Signature = nil + m.Header = nil + m.Payload = nil obj2 := obj - require.NoError(t, obj2.ReadFromV2(m)) + require.NoError(t, obj2.FromProtoMessage(m)) require.Zero(t, obj2) t.Run("invalid", func(t *testing.T) { for _, tc := range []struct { name, err string - corrupt func(*apiobject.Object) + corrupt func(*protoobject.Object) }{ {name: "id/nil value", err: "invalid ID: invalid length 0", - corrupt: func(m *apiobject.Object) { m.SetObjectID(protoIDFromBytes(nil)) }}, + corrupt: func(m *protoobject.Object) { m.ObjectId.Value = nil }}, {name: "id/empty value", err: "invalid ID: invalid length 0", - corrupt: func(m *apiobject.Object) { m.SetObjectID(protoIDFromBytes([]byte{})) }}, + corrupt: func(m *protoobject.Object) { m.ObjectId.Value = []byte{} }}, {name: "id/undersize", err: "invalid ID: invalid length 31", - corrupt: func(m *apiobject.Object) { m.SetObjectID(protoIDFromBytes(anyValidIDs[0][:31])) }}, + corrupt: func(m *protoobject.Object) { m.ObjectId.Value = anyValidIDs[0][:31] }}, {name: "id/oversize", err: "invalid ID: invalid length 33", - corrupt: func(m *apiobject.Object) { m.SetObjectID(protoIDFromBytes(append(anyValidIDs[0][:], 1))) }}, + corrupt: func(m *protoobject.Object) { m.ObjectId.Value = append(anyValidIDs[0][:], 1) }}, {name: "id/zero", err: "invalid ID: zero object ID", - corrupt: func(m *apiobject.Object) { m.SetObjectID(protoIDFromBytes(make([]byte, 32))) }}, - {name: "signature/scheme/overflow", err: "invalid signature: scheme 2147483648 overflows int32", - corrupt: func(m *apiobject.Object) { - var s refs.Signature - s.SetScheme(math.MaxInt32 + 1) - m.SetSignature(&s) - }}, + corrupt: func(m *protoobject.Object) { m.ObjectId.Value = make([]byte, 32) }}, + {name: "signature/scheme/negative", err: "invalid signature: negative scheme -1", + corrupt: func(m *protoobject.Object) { m.Signature.Scheme = -1 }}, {name: "header/owner/value/nil", err: "invalid header: invalid owner: invalid length 0, expected 25", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - h.SetOwnerID(protoUserIDFromBytes(nil)) - m.SetHeader(&h) - }}, + corrupt: func(m *protoobject.Object) { m.Header.OwnerId.Value = nil }}, {name: "header/owner/value/empty", err: "invalid header: invalid owner: invalid length 0, expected 25", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - h.SetOwnerID(protoUserIDFromBytes([]byte{})) - m.SetHeader(&h) - }}, + corrupt: func(m *protoobject.Object) { m.Header.OwnerId.Value = []byte{} }}, {name: "header/owner/value/undersize", err: "invalid header: invalid owner: invalid length 24, expected 25", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - h.SetOwnerID(protoUserIDFromBytes(anyValidUsers[0][:24])) - m.SetHeader(&h) - }}, + corrupt: func(m *protoobject.Object) { m.Header.OwnerId.Value = anyValidUsers[0][:24] }}, {name: "header/owner/value/oversize", err: "invalid header: invalid owner: invalid length 26, expected 25", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - h.SetOwnerID(protoUserIDFromBytes(append(anyValidUsers[0][:], 1))) - m.SetHeader(&h) - }}, + corrupt: func(m *protoobject.Object) { m.Header.OwnerId.Value = append(anyValidUsers[0][:], 1) }}, {name: "header/owner/value/wrong prefix", err: "invalid header: invalid owner: invalid prefix byte 0x42, expected 0x35", - corrupt: func(m *apiobject.Object) { - id := anyValidUsers[0] - id[0] = 0x42 - h := *m.GetHeader() - h.SetOwnerID(protoUserIDFromBytes(id[:])) - m.SetHeader(&h) - }}, + corrupt: func(m *protoobject.Object) { m.Header.OwnerId.Value[0] = 0x42 }}, {name: "header/owner/value/checksum mismatch", err: "invalid header: invalid owner: checksum mismatch", - corrupt: func(m *apiobject.Object) { - id := anyValidUsers[0] - id[len(id)-1]++ - h := *m.GetHeader() - h.SetOwnerID(protoUserIDFromBytes(id[:])) - m.SetHeader(&h) - }}, + corrupt: func(m *protoobject.Object) { m.Header.OwnerId.Value[24]++ }}, {name: "header/container/nil value", err: "invalid header: invalid container: invalid length 0", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - h.SetContainerID(protoContainerIDFromBytes(nil)) - m.SetHeader(&h) - }}, + corrupt: func(m *protoobject.Object) { m.Header.ContainerId.Value = nil }}, {name: "header/container/empty value", err: "invalid header: invalid container: invalid length 0", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - h.SetContainerID(protoContainerIDFromBytes([]byte{})) - m.SetHeader(&h) - }}, + corrupt: func(m *protoobject.Object) { m.Header.ContainerId.Value = []byte{} }}, {name: "header/container/undersize", err: "invalid header: invalid container: invalid length 31", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - h.SetContainerID(protoContainerIDFromBytes(anyValidContainers[0][:31])) - m.SetHeader(&h) - }}, + corrupt: func(m *protoobject.Object) { m.Header.ContainerId.Value = anyValidContainers[0][:31] }}, {name: "header/container/oversize", err: "invalid header: invalid container: invalid length 33", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - h.SetContainerID(protoContainerIDFromBytes(append(anyValidContainers[0][:], 1))) - m.SetHeader(&h) - }}, + corrupt: func(m *protoobject.Object) { m.Header.ContainerId.Value = append(anyValidContainers[0][:], 1) }}, {name: "header/container/zero", err: "invalid header: invalid container: zero container ID", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - h.SetContainerID(protoContainerIDFromBytes(make([]byte, 32))) - m.SetHeader(&h) - }}, + corrupt: func(m *protoobject.Object) { m.Header.ContainerId.Value = make([]byte, 32) }}, {name: "header/payload checksum/missing value", err: "invalid header: invalid payload checksum: missing value", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - h.SetPayloadHash(new(refs.Checksum)) - m.SetHeader(&h) - }}, + corrupt: func(m *protoobject.Object) { m.Header.PayloadHash.Sum = nil }}, + {name: "header/payload checksum/negative type", err: "invalid header: invalid payload checksum: negative type -1", + corrupt: func(m *protoobject.Object) { m.Header.PayloadHash.Type = -1 }}, {name: "header/payload homomorphic checksum/missing value", err: "invalid header: invalid payload homomorphic checksum: missing value", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - h.SetHomomorphicHash(new(refs.Checksum)) - m.SetHeader(&h) - }}, + corrupt: func(m *protoobject.Object) { m.Header.HomomorphicHash.Sum = nil }}, + {name: "header/payload checksum/negative type", err: "invalid header: invalid payload homomorphic checksum: negative type -1", + corrupt: func(m *protoobject.Object) { m.Header.HomomorphicHash.Type = -1 }}, {name: "header/session/body/ID/undersize", err: "invalid header: invalid session token: invalid session ID: invalid UUID (got 15 bytes)", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - mt := *h.GetSessionToken() - mtb := *mt.GetBody() - mtb.SetID(anyValidSessionID[:15]) - mt.SetBody(&mtb) - h.SetSessionToken(&mt) - m.SetHeader(&h) - }}, + corrupt: func(m *protoobject.Object) { m.Header.SessionToken.Body.Id = anyValidSessionID[:15] }}, {name: "header/session/body/ID/oversize", err: "invalid header: invalid session token: invalid session ID: invalid UUID (got 17 bytes)", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - mt := *h.GetSessionToken() - mtb := *mt.GetBody() - mtb.SetID(append(anyValidSessionID[:], 1)) - mt.SetBody(&mtb) - h.SetSessionToken(&mt) - m.SetHeader(&h) - }}, + corrupt: func(m *protoobject.Object) { m.Header.SessionToken.Body.Id = append(anyValidSessionID[:], 1) }}, {name: "header/session/body/ID/wrong UUID version", err: "invalid header: invalid session token: invalid session ID: wrong UUID version 3, expected 4", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - mt := *h.GetSessionToken() - mtb := *mt.GetBody() + corrupt: func(m *protoobject.Object) { b := bytes.Clone(anyValidSessionID[:]) b[6] = 3 << 4 - mtb.SetID(b) - mt.SetBody(&mtb) - h.SetSessionToken(&mt) - m.SetHeader(&h) + m.Header.SessionToken.Body.Id = b }}, {name: "header/session/body/issuer/value/empty", err: "invalid header: invalid session token: invalid session issuer: invalid length 0, expected 25", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - mt := *h.GetSessionToken() - mtb := *mt.GetBody() - mtb.SetOwnerID(protoUserIDFromBytes(nil)) - mt.SetBody(&mtb) - h.SetSessionToken(&mt) - m.SetHeader(&h) - }}, + corrupt: func(m *protoobject.Object) { m.Header.SessionToken.Body.OwnerId.Value = nil }}, {name: "header/session/body/issuer/value/undersize", err: "invalid header: invalid session token: invalid session issuer: invalid length 24, expected 25", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - mt := *h.GetSessionToken() - mtb := *mt.GetBody() - mtb.SetOwnerID(protoUserIDFromBytes(anyValidUsers[0][:24])) - mt.SetBody(&mtb) - h.SetSessionToken(&mt) - m.SetHeader(&h) - }}, + corrupt: func(m *protoobject.Object) { m.Header.SessionToken.Body.OwnerId.Value = anyValidUsers[0][:24] }}, {name: "header/session/body/issuer/value/oversize", err: "invalid header: invalid session token: invalid session issuer: invalid length 26, expected 25", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - mt := *h.GetSessionToken() - mtb := *mt.GetBody() - mtb.SetOwnerID(protoUserIDFromBytes(append(anyValidUsers[0][:], 1))) - mt.SetBody(&mtb) - h.SetSessionToken(&mt) - m.SetHeader(&h) - }}, + corrupt: func(m *protoobject.Object) { m.Header.SessionToken.Body.OwnerId.Value = append(anyValidUsers[0][:], 1) }}, {name: "header/session/body/issuer/value/wrong prefix", err: "invalid header: invalid session token: invalid session issuer: invalid prefix byte 0x42, expected 0x35", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - mt := *h.GetSessionToken() - mtb := *mt.GetBody() + corrupt: func(m *protoobject.Object) { b := bytes.Clone(anyValidUsers[0][:]) b[0] = 0x42 - mtb.SetOwnerID(protoUserIDFromBytes(b)) - mt.SetBody(&mtb) - h.SetSessionToken(&mt) - m.SetHeader(&h) + m.Header.SessionToken.Body.OwnerId.Value = b }}, {name: "header/session/body/issuer/value/checksum mismatch", err: "invalid header: invalid session token: invalid session issuer: checksum mismatch", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - mt := *h.GetSessionToken() - mtb := *mt.GetBody() + corrupt: func(m *protoobject.Object) { b := bytes.Clone(anyValidUsers[0][:]) b[len(b)-1]++ - mtb.SetOwnerID(protoUserIDFromBytes(b)) - mt.SetBody(&mtb) - h.SetSessionToken(&mt) - m.SetHeader(&h) + m.Header.SessionToken.Body.OwnerId.Value = b }}, - {name: "header/session/body/context/wrong oneof", err: "invalid header: invalid session token: invalid context: invalid context *session.ContainerSessionContext", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - mt := *h.GetSessionToken() - mtb := *mt.GetBody() - mtb.SetContext(new(apisession.ContainerSessionContext)) - mt.SetBody(&mtb) - h.SetSessionToken(&mt) - m.SetHeader(&h) + {name: "header/session/body/context/wrong oneof", err: "invalid header: invalid session token: invalid context: invalid context *session.SessionToken_Body_Container", + corrupt: func(m *protoobject.Object) { + m.Header.SessionToken.Body.Context = new(protosession.SessionToken_Body_Container) }}, {name: "header/session/body/context/container/empty value", err: "invalid header: invalid session token: invalid context: invalid container ID: invalid length 0", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - mt := *h.GetSessionToken() - mtb := *mt.GetBody() - mtc := *mtb.GetContext().(*apisession.ObjectSessionContext) - mtc.SetTarget(protoContainerIDFromBytes(nil), mtc.GetObjects()...) - mtb.SetContext(&mtc) - mt.SetBody(&mtb) - h.SetSessionToken(&mt) - m.SetHeader(&h) + corrupt: func(m *protoobject.Object) { + m.Header.SessionToken.Body.Context.(*protosession.SessionToken_Body_Object).Object.Target.Container.Value = nil }}, {name: "header/session/body/context/container/undersize", err: "invalid header: invalid session token: invalid context: invalid container ID: invalid length 31", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - mt := *h.GetSessionToken() - mtb := *mt.GetBody() - mtc := *mtb.GetContext().(*apisession.ObjectSessionContext) - mtc.SetTarget(protoContainerIDFromBytes(anyValidContainers[0][:31]), mtc.GetObjects()...) - mtb.SetContext(&mtc) - mt.SetBody(&mtb) - h.SetSessionToken(&mt) - m.SetHeader(&h) + corrupt: func(m *protoobject.Object) { + m.Header.SessionToken.Body.Context.(*protosession.SessionToken_Body_Object).Object.Target.Container.Value = anyValidContainers[0][:31] }}, {name: "header/session/body/context/container/oversize", err: "invalid header: invalid session token: invalid context: invalid container ID: invalid length 33", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - mt := *h.GetSessionToken() - mtb := *mt.GetBody() - mtc := *mtb.GetContext().(*apisession.ObjectSessionContext) - mtc.SetTarget(protoContainerIDFromBytes(append(anyValidContainers[0][:], 1)), mtc.GetObjects()...) - mtb.SetContext(&mtc) - mt.SetBody(&mtb) - h.SetSessionToken(&mt) - m.SetHeader(&h) + corrupt: func(m *protoobject.Object) { + m.Header.SessionToken.Body.Context.(*protosession.SessionToken_Body_Object).Object.Target.Container.Value = + append(anyValidContainers[0][:], 1) }}, {name: "header/session/body/context/object/empty value", err: "invalid header: invalid session token: invalid context: invalid target object: invalid length 0", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - mt := *h.GetSessionToken() - mtb := *mt.GetBody() - mtc := *mtb.GetContext().(*apisession.ObjectSessionContext) - objs := make([]refs.ObjectID, 2) - anyValidIDs[0].WriteToV2(&objs[0]) - mtc.SetTarget(protoContainerIDFromBytes(anyValidContainers[0][:]), objs...) - mtb.SetContext(&mtc) - mt.SetBody(&mtb) - h.SetSessionToken(&mt) - m.SetHeader(&h) + corrupt: func(m *protoobject.Object) { + m.Header.SessionToken.Body.Context.(*protosession.SessionToken_Body_Object).Object.Target.Objects[1].Value = nil }}, {name: "header/session/body/context/object/undersize", err: "invalid header: invalid session token: invalid context: invalid target object: invalid length 31", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - mt := *h.GetSessionToken() - mtb := *mt.GetBody() - mtc := *mtb.GetContext().(*apisession.ObjectSessionContext) - objs := make([]refs.ObjectID, 2) - anyValidIDs[0].WriteToV2(&objs[0]) - objs[1].SetValue(anyValidIDs[1][:31]) - mtc.SetTarget(protoContainerIDFromBytes(anyValidContainers[0][:]), objs...) - mtb.SetContext(&mtc) - mt.SetBody(&mtb) - h.SetSessionToken(&mt) - m.SetHeader(&h) + corrupt: func(m *protoobject.Object) { + m.Header.SessionToken.Body.Context.(*protosession.SessionToken_Body_Object).Object.Target.Objects[1].Value = anyValidIDs[1][:31] }}, {name: "header/session/body/context/object/oversize", err: "invalid header: invalid session token: invalid context: invalid target object: invalid length 33", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - mt := *h.GetSessionToken() - mtb := *mt.GetBody() - mtc := *mtb.GetContext().(*apisession.ObjectSessionContext) - objs := make([]refs.ObjectID, 2) - anyValidIDs[0].WriteToV2(&objs[0]) - objs[1].SetValue(append(anyValidIDs[1][:], 1)) - mtc.SetTarget(protoContainerIDFromBytes(anyValidContainers[0][:]), objs...) - mtb.SetContext(&mtc) - mt.SetBody(&mtb) - h.SetSessionToken(&mt) - m.SetHeader(&h) - }}, - {name: "header/session/signature/scheme/overflow", err: "invalid header: invalid session token: invalid body signature: scheme 2147483648 overflows int32", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - mt := *h.GetSessionToken() - mts := *mt.GetSignature() - mts.SetScheme(math.MaxInt32 + 1) - mt.SetSignature(&mts) - h.SetSessionToken(&mt) - m.SetHeader(&h) - }}, - {name: "attributes/no key", err: "invalid header: empty key of the attribute #1", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - as := make([]apiobject.Attribute, 3) - as[0].SetKey("k1") - as[0].SetValue("v1") - as[1].SetValue("v2") - as[2].SetKey("k3") - as[2].SetValue("v3") - h.SetAttributes(as) - m.SetHeader(&h) - }}, - {name: "attributes/no value", err: "invalid header: empty value of the attribute #1 (k2)", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - as := make([]apiobject.Attribute, 3) - as[0].SetKey("k1") - as[0].SetValue("v1") - as[1].SetKey("k2") - as[2].SetKey("k3") - as[2].SetValue("v3") - h.SetAttributes(as) - m.SetHeader(&h) + corrupt: func(m *protoobject.Object) { + m.Header.SessionToken.Body.Context.(*protosession.SessionToken_Body_Object).Object.Target.Objects[1].Value = + append(anyValidIDs[1][:], 1) + }}, + {name: "header/session/signature/scheme/negative", err: "invalid header: invalid session token: invalid body signature: negative scheme -1", + corrupt: func(m *protoobject.Object) { m.Header.SessionToken.Signature.Scheme = -1 }}, + {name: "attributes/no key", err: "invalid header: invalid attribute #1: missing key", + corrupt: func(m *protoobject.Object) { + m.Header.Attributes = []*protoobject.Header_Attribute{ + {Key: "k1", Value: "v1"}, {Key: "", Value: "v2"}, {Key: "k3", Value: "v3"}, + } + }}, + {name: "attributes/no value", err: "invalid header: invalid attribute #1: missing value", + corrupt: func(m *protoobject.Object) { + m.Header.Attributes = []*protoobject.Header_Attribute{ + {Key: "k1", Value: "v1"}, {Key: "k2", Value: ""}, {Key: "k3", Value: "v3"}, + } }}, {name: "attributes/duplicated", err: "invalid header: duplicated attribute k1", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - as := make([]apiobject.Attribute, 3) - as[0].SetKey("k1") - as[0].SetValue("v1") - as[1].SetKey("k2") - as[1].SetValue("v2") - as[2].SetKey("k1") - as[2].SetValue("v3") - h.SetAttributes(as) - m.SetHeader(&h) + corrupt: func(m *protoobject.Object) { + m.Header.Attributes = []*protoobject.Header_Attribute{ + {Key: "k1", Value: "v1"}, {Key: "k2", Value: "v2"}, {Key: "k1", Value: "v3"}, + } }}, - {name: "attributes/expiration", err: "invalid header: invalid expiration attribute (must be a uint): strconv.ParseUint: parsing \"foo\": invalid syntax", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - as := make([]apiobject.Attribute, 3) - as[0].SetKey("k1") - as[0].SetValue("v1") - as[1].SetKey("__NEOFS__EXPIRATION_EPOCH") - as[1].SetValue("foo") - as[2].SetKey("k3") - as[2].SetValue("v3") - h.SetAttributes(as) - m.SetHeader(&h) + {name: "attributes/expiration", err: "invalid header: invalid attribute #1: invalid expiration epoch (must be a uint): strconv.ParseUint: parsing \"foo\": invalid syntax", + corrupt: func(m *protoobject.Object) { + m.Header.Attributes = []*protoobject.Header_Attribute{ + {Key: "k1", Value: "v1"}, {Key: "__NEOFS__EXPIRATION_EPOCH", Value: "foo"}, {Key: "k3", Value: "v3"}, + } }}, {name: "header/split/parent ID/empty value", err: "invalid header: invalid split header: invalid parent split member ID: invalid length 0", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - sh := *h.GetSplit() - sh.SetParent(protoIDFromBytes(nil)) - h.SetSplit(&sh) - m.SetHeader(&h) - }}, + corrupt: func(m *protoobject.Object) { m.Header.Split.Parent.Value = nil }}, {name: "header/split/parent ID/undersize", err: "invalid header: invalid split header: invalid parent split member ID: invalid length 31", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - sh := *h.GetSplit() - sh.SetParent(protoIDFromBytes(anyValidIDs[0][:31])) - h.SetSplit(&sh) - m.SetHeader(&h) - }}, + corrupt: func(m *protoobject.Object) { m.Header.Split.Parent.Value = anyValidIDs[0][:31] }}, {name: "header/split/parent ID/oversize", err: "invalid header: invalid split header: invalid parent split member ID: invalid length 33", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - sh := *h.GetSplit() - sh.SetParent(protoIDFromBytes(append(anyValidIDs[0][:], 1))) - h.SetSplit(&sh) - m.SetHeader(&h) - }}, + corrupt: func(m *protoobject.Object) { m.Header.Split.Parent.Value = append(anyValidIDs[0][:], 1) }}, {name: "header/split/parent ID/zero", err: "invalid header: invalid split header: invalid parent split member ID: zero object ID", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - sh := *h.GetSplit() - sh.SetParent(protoIDFromBytes(make([]byte, 32))) - h.SetSplit(&sh) - m.SetHeader(&h) - }}, + corrupt: func(m *protoobject.Object) { m.Header.Split.Parent.Value = make([]byte, 32) }}, {name: "header/split/previous/empty value", err: "invalid header: invalid split header: invalid previous split member ID: invalid length 0", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - sh := *h.GetSplit() - sh.SetPrevious(protoIDFromBytes(nil)) - h.SetSplit(&sh) - m.SetHeader(&h) - }}, + corrupt: func(m *protoobject.Object) { m.Header.Split.Previous.Value = nil }}, {name: "header/split/previous/undersize", err: "invalid header: invalid split header: invalid previous split member ID: invalid length 31", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - sh := *h.GetSplit() - sh.SetPrevious(protoIDFromBytes(anyValidIDs[0][:31])) - h.SetSplit(&sh) - m.SetHeader(&h) - }}, + corrupt: func(m *protoobject.Object) { m.Header.Split.Previous.Value = anyValidIDs[0][:31] }}, {name: "header/split/previous/oversize", err: "invalid header: invalid split header: invalid previous split member ID: invalid length 33", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - sh := *h.GetSplit() - sh.SetPrevious(protoIDFromBytes(append(anyValidIDs[0][:], 1))) - h.SetSplit(&sh) - m.SetHeader(&h) - }}, + corrupt: func(m *protoobject.Object) { m.Header.Split.Previous.Value = append(anyValidIDs[0][:], 1) }}, {name: "header/split/previous/zero", err: "invalid header: invalid split header: invalid previous split member ID: zero object ID", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - sh := *h.GetSplit() - sh.SetPrevious(protoIDFromBytes(make([]byte, 32))) - h.SetSplit(&sh) - m.SetHeader(&h) - }}, + corrupt: func(m *protoobject.Object) { m.Header.Split.Previous.Value = make([]byte, 32) }}, {name: "header/split/first/empty value", err: "invalid header: invalid split header: invalid first split member ID: invalid length 0", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - sh := *h.GetSplit() - sh.SetFirst(protoIDFromBytes(nil)) - h.SetSplit(&sh) - m.SetHeader(&h) - }}, + corrupt: func(m *protoobject.Object) { m.Header.Split.First.Value = nil }}, {name: "header/split/first/undersize", err: "invalid header: invalid split header: invalid first split member ID: invalid length 31", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - sh := *h.GetSplit() - sh.SetFirst(protoIDFromBytes(anyValidIDs[0][:31])) - h.SetSplit(&sh) - m.SetHeader(&h) - }}, + corrupt: func(m *protoobject.Object) { m.Header.Split.First.Value = anyValidIDs[0][:31] }}, {name: "header/split/first/oversize", err: "invalid header: invalid split header: invalid first split member ID: invalid length 33", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - sh := *h.GetSplit() - sh.SetFirst(protoIDFromBytes(append(anyValidIDs[0][:], 1))) - h.SetSplit(&sh) - m.SetHeader(&h) - }}, + corrupt: func(m *protoobject.Object) { m.Header.Split.First.Value = append(anyValidIDs[0][:], 1) }}, {name: "header/split/first/zero", err: "invalid header: invalid split header: invalid first split member ID: zero object ID", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - sh := *h.GetSplit() - sh.SetFirst(protoIDFromBytes(make([]byte, 32))) - h.SetSplit(&sh) - m.SetHeader(&h) - }}, + corrupt: func(m *protoobject.Object) { m.Header.Split.First.Value = make([]byte, 32) }}, {name: "header/split/ID/undersize", err: "invalid header: invalid split header: invalid split ID: invalid UUID (got 15 bytes)", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - sh := *h.GetSplit() - sh.SetSplitID(anyValidSplitIDBytes[:15]) - h.SetSplit(&sh) - m.SetHeader(&h) - }}, + corrupt: func(m *protoobject.Object) { m.Header.Split.SplitId = anyValidSplitIDBytes[:15] }}, {name: "header/split/ID/oversize", err: "invalid header: invalid split header: invalid split ID: invalid UUID (got 17 bytes)", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - sh := *h.GetSplit() - sh.SetSplitID(append(anyValidSplitIDBytes, 1)) - h.SetSplit(&sh) - m.SetHeader(&h) - }}, + corrupt: func(m *protoobject.Object) { m.Header.Split.SplitId = append(anyValidSplitIDBytes, 1) }}, {name: "header/split/ID/wrong UUID version", err: "invalid header: invalid split header: invalid split ID: wrong UUID version 3, expected 4", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - sh := *h.GetSplit() + corrupt: func(m *protoobject.Object) { b := bytes.Clone(anyValidSplitIDBytes) b[6] = 3 << 4 - sh.SetSplitID(b) - h.SetSplit(&sh) - m.SetHeader(&h) + m.Header.Split.SplitId = b }}, {name: "header/split/children/empty value", err: "invalid header: invalid split header: invalid child split member ID #1: invalid length 0", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - sh := *h.GetSplit() - c := make([]refs.ObjectID, 3) - anyValidIDs[0].WriteToV2(&c[0]) - anyValidIDs[2].WriteToV2(&c[2]) - sh.SetChildren(c) - h.SetSplit(&sh) - m.SetHeader(&h) - }}, + corrupt: func(m *protoobject.Object) { m.Header.Split.Children[1].Value = nil }}, {name: "header/split/children/undersize", err: "invalid header: invalid split header: invalid child split member ID #1: invalid length 31", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - sh := *h.GetSplit() - c := make([]refs.ObjectID, 3) - anyValidIDs[0].WriteToV2(&c[0]) - c[1].SetValue(anyValidIDs[1][:31]) - anyValidIDs[2].WriteToV2(&c[2]) - sh.SetChildren(c) - h.SetSplit(&sh) - m.SetHeader(&h) - }}, + corrupt: func(m *protoobject.Object) { m.Header.Split.Children[1].Value = anyValidIDs[1][:31] }}, {name: "header/split/children/oversize", err: "invalid header: invalid split header: invalid child split member ID #1: invalid length 33", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - sh := *h.GetSplit() - c := make([]refs.ObjectID, 3) - anyValidIDs[0].WriteToV2(&c[0]) - c[1].SetValue(append(anyValidIDs[1][:], 1)) - anyValidIDs[2].WriteToV2(&c[2]) - sh.SetChildren(c) - h.SetSplit(&sh) - m.SetHeader(&h) - }}, + corrupt: func(m *protoobject.Object) { m.Header.Split.Children[1].Value = append(anyValidIDs[1][:], 1) }}, {name: "header/split/children/zero", err: "invalid header: invalid split header: invalid child split member ID #1: zero object ID", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - sh := *h.GetSplit() - c := make([]refs.ObjectID, 3) - anyValidIDs[0].WriteToV2(&c[0]) - c[1].SetValue(make([]byte, 32)) - anyValidIDs[2].WriteToV2(&c[2]) - sh.SetChildren(c) - h.SetSplit(&sh) - m.SetHeader(&h) - }}, - {name: "header/split/parent signature/scheme/overflow", err: "invalid header: invalid split header: invalid parent signature: scheme 2147483648 overflows int32", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - sh := *h.GetSplit() - var s refs.Signature - anyValidSignatures[0].WriteToV2(&s) - s.SetScheme(math.MaxInt32 + 1) - sh.SetParentSignature(&s) - h.SetSplit(&sh) - m.SetHeader(&h) - }}, + corrupt: func(m *protoobject.Object) { m.Header.Split.Children[1].Value = make([]byte, 32) }}, + {name: "header/split/parent signature/scheme/negative", err: "invalid header: invalid split header: invalid parent signature: negative scheme -1", + corrupt: func(m *protoobject.Object) { m.Header.Split.ParentSignature.Scheme = -1 }}, {name: "header/split/parent/owner/value/empty", err: "invalid header: invalid split header: invalid parent header: invalid owner: invalid length 0, expected 25", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - sh := *h.GetSplit() - ph := *sh.GetParentHeader() - ph.SetOwnerID(protoUserIDFromBytes(nil)) - sh.SetParentHeader(&ph) - h.SetSplit(&sh) - m.SetHeader(&h) - }}, + corrupt: func(m *protoobject.Object) { m.Header.Split.ParentHeader.OwnerId.Value = nil }}, {name: "header/split/parent/owner/value/undersize", err: "invalid header: invalid split header: invalid parent header: invalid owner: invalid length 24, expected 25", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - sh := *h.GetSplit() - ph := *sh.GetParentHeader() - ph.SetOwnerID(protoUserIDFromBytes(anyValidUsers[0][:24])) - sh.SetParentHeader(&ph) - h.SetSplit(&sh) - m.SetHeader(&h) - }}, + corrupt: func(m *protoobject.Object) { m.Header.Split.ParentHeader.OwnerId.Value = anyValidUsers[0][:24] }}, {name: "header/split/parent/owner/value/oversize", err: "invalid header: invalid split header: invalid parent header: invalid owner: invalid length 26, expected 25", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - sh := *h.GetSplit() - ph := *sh.GetParentHeader() - ph.SetOwnerID(protoUserIDFromBytes(append(anyValidUsers[0][:], 1))) - sh.SetParentHeader(&ph) - h.SetSplit(&sh) - m.SetHeader(&h) + corrupt: func(m *protoobject.Object) { + m.Header.Split.ParentHeader.OwnerId.Value = append(anyValidUsers[0][:], 1) }}, {name: "header/split/parent/owner/value/wrong prefix", err: "invalid header: invalid split header: invalid parent header: invalid owner: invalid prefix byte 0x42, expected 0x35", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - sh := *h.GetSplit() - ph := *sh.GetParentHeader() + corrupt: func(m *protoobject.Object) { b := bytes.Clone(anyValidUsers[0][:]) b[0] = 0x42 - ph.SetOwnerID(protoUserIDFromBytes(b)) - sh.SetParentHeader(&ph) - h.SetSplit(&sh) - m.SetHeader(&h) + m.Header.Split.ParentHeader.OwnerId.Value = b }}, {name: "header/split/parent/owner/value/checksum mismatch", err: "invalid header: invalid split header: invalid parent header: invalid owner: checksum mismatch", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - sh := *h.GetSplit() - ph := *sh.GetParentHeader() + corrupt: func(m *protoobject.Object) { b := bytes.Clone(anyValidUsers[0][:]) b[len(b)-1]++ - ph.SetOwnerID(protoUserIDFromBytes(b)) - sh.SetParentHeader(&ph) - h.SetSplit(&sh) - m.SetHeader(&h) + m.Header.Split.ParentHeader.OwnerId.Value = b }}, {name: "header/split/parent/container/empty value", err: "invalid header: invalid split header: invalid parent header: invalid container: invalid length 0", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - sh := *h.GetSplit() - ph := *sh.GetParentHeader() - ph.SetContainerID(protoContainerIDFromBytes(nil)) - sh.SetParentHeader(&ph) - h.SetSplit(&sh) - m.SetHeader(&h) - }}, + corrupt: func(m *protoobject.Object) { m.Header.Split.ParentHeader.ContainerId.Value = nil }}, {name: "header/split/parent/container/undersize", err: "invalid header: invalid split header: invalid parent header: invalid container: invalid length 31", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - sh := *h.GetSplit() - ph := *sh.GetParentHeader() - ph.SetContainerID(protoContainerIDFromBytes(anyValidContainers[0][:31])) - sh.SetParentHeader(&ph) - h.SetSplit(&sh) - m.SetHeader(&h) + corrupt: func(m *protoobject.Object) { + m.Header.Split.ParentHeader.ContainerId.Value = anyValidContainers[0][:31] }}, {name: "header/split/parent/container/oversize", err: "invalid header: invalid split header: invalid parent header: invalid container: invalid length 33", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - sh := *h.GetSplit() - ph := *sh.GetParentHeader() - ph.SetContainerID(protoContainerIDFromBytes(append(anyValidContainers[0][:], 1))) - sh.SetParentHeader(&ph) - h.SetSplit(&sh) - m.SetHeader(&h) + corrupt: func(m *protoobject.Object) { + m.Header.Split.ParentHeader.ContainerId.Value = append(anyValidContainers[0][:], 1) }}, {name: "header/split/parent/container/zero", err: "invalid header: invalid split header: invalid parent header: invalid container: zero container ID", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - sh := *h.GetSplit() - ph := *sh.GetParentHeader() - ph.SetContainerID(protoContainerIDFromBytes(make([]byte, 32))) - sh.SetParentHeader(&ph) - h.SetSplit(&sh) - m.SetHeader(&h) - }}, + corrupt: func(m *protoobject.Object) { m.Header.Split.ParentHeader.ContainerId.Value = make([]byte, 32) }}, {name: "header/split/parent/payload checksum/missing value", err: "invalid header: invalid split header: invalid parent header: invalid payload checksum: missing value", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - sh := *h.GetSplit() - ph := *sh.GetParentHeader() - ph.SetPayloadHash(new(refs.Checksum)) - sh.SetParentHeader(&ph) - h.SetSplit(&sh) - m.SetHeader(&h) - }}, + corrupt: func(m *protoobject.Object) { m.Header.Split.ParentHeader.PayloadHash.Sum = nil }}, + {name: "header/split/parent/payload checksum/negative type", err: "invalid header: invalid split header: invalid parent header: invalid payload checksum: negative type -1", + corrupt: func(m *protoobject.Object) { m.Header.Split.ParentHeader.PayloadHash.Type = -1 }}, {name: "header/split/parent/payload homomorphic checksum/missing value", err: "invalid header: invalid split header: invalid parent header: invalid payload homomorphic checksum: missing value", - corrupt: func(m *apiobject.Object) { - h := *m.GetHeader() - sh := *h.GetSplit() - ph := *sh.GetParentHeader() - ph.SetHomomorphicHash(new(refs.Checksum)) - sh.SetParentHeader(&ph) - h.SetSplit(&sh) - m.SetHeader(&h) - }}, + corrupt: func(m *protoobject.Object) { m.Header.Split.ParentHeader.HomomorphicHash.Sum = nil }}, + {name: "header/split/parent/payload homomorphic checksum/negative type", err: "invalid header: invalid split header: invalid parent header: invalid payload homomorphic checksum: negative type -1", + corrupt: func(m *protoobject.Object) { m.Header.Split.ParentHeader.HomomorphicHash.Type = -1 }}, } { t.Run(tc.name, func(t *testing.T) { - m := obj.ToV2() + m := obj.ProtoMessage() tc.corrupt(m) - require.EqualError(t, new(object.Object).ReadFromV2(*m), tc.err) + require.EqualError(t, new(object.Object).FromProtoMessage(m), tc.err) }) } }) } -func TestObject_ToV2(t *testing.T) { +func TestObject_ProtoMessage(t *testing.T) { // zero - m := object.Object{}.ToV2() - require.Zero(t, m.GetObjectID()) + m := object.Object{}.ProtoMessage() + require.Zero(t, m.GetObjectId()) require.Zero(t, m.GetSignature()) require.Zero(t, m.GetHeader()) require.Zero(t, m.GetPayload()) // filled - m = validObject.ToV2() - require.Equal(t, anyValidIDs[0][:], m.GetObjectID().GetValue()) + m = validObject.ProtoMessage() + require.Equal(t, anyValidIDs[0][:], m.GetObjectId().GetValue()) require.Equal(t, anyValidRegularPayload, m.GetPayload()) msig := m.GetSignature() require.Equal(t, anyValidSignatures[1].PublicKeyBytes(), msig.GetKey()) @@ -1484,8 +1017,8 @@ func TestObject_ToV2(t *testing.T) { mh := m.GetHeader() require.EqualValues(t, 525747025, mh.GetVersion().GetMajor()) require.EqualValues(t, 171993162, mh.GetVersion().GetMinor()) - require.Equal(t, anyValidContainers[1][:], mh.GetContainerID().GetValue()) - require.Equal(t, anyValidUsers[1][:], mh.GetOwnerID().GetValue()) + require.Equal(t, anyValidContainers[1][:], mh.GetContainerId().GetValue()) + require.Equal(t, anyValidUsers[1][:], mh.GetOwnerId().GetValue()) require.EqualValues(t, anyValidCreationEpoch+1, mh.GetCreationEpoch()) require.EqualValues(t, anyValidPayloadSize+1, mh.GetPayloadLength()) require.EqualValues(t, 126384577, mh.GetPayloadHash().GetType()) @@ -1496,18 +1029,18 @@ func TestObject_ToV2(t *testing.T) { mt := mh.GetSessionToken() mb := mt.GetBody() - require.Equal(t, anyValidSessionID[:], mb.GetID()) - require.Equal(t, anyValidUsers[2][:], mb.GetOwnerID().GetValue()) + require.Equal(t, anyValidSessionID[:], mb.GetId()) + require.Equal(t, anyValidUsers[2][:], mb.GetOwnerId().GetValue()) require.Equal(t, anySessionIssuerPubKeyBytes, mb.GetSessionKey()) require.EqualValues(t, uint64(16429376563136800338), mb.GetLifetime().GetExp()) require.EqualValues(t, 7956510363313998522, mb.GetLifetime().GetIat()) require.EqualValues(t, uint64(17237208928641773338), mb.GetLifetime().GetNbf()) c := mb.GetContext() - require.IsType(t, new(apisession.ObjectSessionContext), c) - co := c.(*apisession.ObjectSessionContext) + require.IsType(t, new(protosession.SessionToken_Body_Object), c) + co := c.(*protosession.SessionToken_Body_Object).Object require.EqualValues(t, 1047242055, co.GetVerb()) - require.Equal(t, anyValidContainers[2][:], co.GetContainer().GetValue()) - objs := co.GetObjects() + require.Equal(t, anyValidContainers[2][:], co.GetTarget().GetContainer().GetValue()) + objs := co.GetTarget().GetObjects() require.Len(t, objs, 2) require.Equal(t, anyValidIDs[8][:], objs[0].GetValue()) require.Equal(t, anyValidIDs[9][:], objs[1].GetValue()) @@ -1528,7 +1061,7 @@ func TestObject_ToV2(t *testing.T) { sh := mh.GetSplit() require.Equal(t, anyValidIDs[1][:], sh.GetParent().GetValue()) require.Equal(t, anyValidIDs[2][:], sh.GetPrevious().GetValue()) - require.Equal(t, anyValidSplitIDBytes, sh.GetSplitID()) + require.Equal(t, anyValidSplitIDBytes, sh.GetSplitId()) require.Equal(t, anyValidIDs[6][:], sh.GetFirst().GetValue()) ch := sh.GetChildren() require.Len(t, ch, 3) @@ -1546,8 +1079,8 @@ func TestObject_ToV2(t *testing.T) { require.Zero(t, ph.GetSplit()) require.EqualValues(t, 88789927, ph.GetVersion().GetMajor()) require.EqualValues(t, 2018985309, ph.GetVersion().GetMinor()) - require.Equal(t, anyValidContainers[0][:], ph.GetContainerID().GetValue()) - require.Equal(t, anyValidUsers[0][:], ph.GetOwnerID().GetValue()) + require.Equal(t, anyValidContainers[0][:], ph.GetContainerId().GetValue()) + require.Equal(t, anyValidUsers[0][:], ph.GetOwnerId().GetValue()) require.EqualValues(t, anyValidCreationEpoch, ph.GetCreationEpoch()) require.EqualValues(t, anyValidPayloadSize, ph.GetPayloadLength()) require.EqualValues(t, 1974315742, ph.GetPayloadHash().GetType()) @@ -1592,7 +1125,7 @@ func TestObject_Unmarshal(t *testing.T) { 18, 56, 117, 173, 70, 246, 8, 139, 247, 174, 53, 60, 1}}, {name: "id/zero", err: "invalid ID: zero object ID", b: []byte{10, 34, 10, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, - {name: "signature/scheme/overflow", err: "invalid signature: scheme 2147483648 overflows int32", + {name: "signature/negative scheme", err: "invalid signature: negative scheme -2147483648", b: []byte{18, 11, 24, 128, 128, 128, 128, 248, 255, 255, 255, 255, 1}}, {name: "header/owner/value/empty", err: "invalid header: invalid owner: invalid length 0, expected 25", b: []byte{26, 2, 26, 0}}, @@ -1725,7 +1258,7 @@ func TestObject_Unmarshal(t *testing.T) { 68, 233, 22, 158, 100, 49, 20, 181, 95, 219, 143, 53, 250, 237, 113, 64, 25, 48, 11, 54, 207, 56, 98, 99, 136, 207, 21, 18, 41, 10, 14, 115, 101, 115, 115, 105, 111, 110, 95, 115, 105, 103, 110, 101, 114, 18, 17, 115, 101, 115, 115, 105, 111, 110, 32, 115, 105, 103, 110, 97, 116, 117, 114, 101, 24, 170, 137, 252, 156, 4}}, - {name: "header/session/body/context/wrong oneof", err: "invalid header: invalid session token: invalid context: invalid context *session.ContainerSessionContext", + {name: "header/session/body/context/wrong oneof", err: "invalid header: invalid session token: invalid context: invalid context *session.SessionToken_Body_Container", b: []byte{26, 166, 1, 74, 163, 1, 10, 118, 10, 16, 118, 23, 219, 249, 117, 70, 64, 33, 157, 229, 102, 253, 142, 52, 17, 144, 18, 27, 10, 25, 53, 248, 195, 15, 196, 254, 124, 23, 169, 198, 208, 15, 219, 229, 62, 150, 151, 159, 221, 73, 224, 229, 106, 42, 222, 26, 32, 8, 210, 204, 150, 183, 128, 222, 183, 128, 228, 1, 16, 154, @@ -1809,7 +1342,7 @@ func TestObject_Unmarshal(t *testing.T) { 136, 68, 233, 22, 158, 100, 49, 20, 181, 95, 219, 143, 53, 250, 237, 113, 64, 25, 48, 11, 54, 207, 56, 98, 99, 136, 207, 21, 18, 41, 10, 14, 115, 101, 115, 115, 105, 111, 110, 95, 115, 105, 103, 110, 101, 114, 18, 17, 115, 101, 115, 115, 105, 111, 110, 32, 115, 105, 103, 110, 97, 116, 117, 114, 101, 24, 170, 137, 252, 156, 4}}, - {name: "header/session/signature/scheme/overflow", err: "invalid header: invalid session token: invalid body signature: scheme 2147483648 overflows int32", + {name: "header/session/signature/negative scheme", err: "invalid header: invalid session token: invalid body signature: negative scheme -2147483648", b: []byte{26, 253, 1, 74, 250, 1, 10, 234, 1, 10, 16, 118, 23, 219, 249, 117, 70, 64, 33, 157, 229, 102, 253, 142, 52, 17, 144, 18, 27, 10, 25, 53, 248, 195, 15, 196, 254, 124, 23, 169, 198, 208, 15, 219, 229, 62, 150, 151, 159, 221, 73, 224, 229, 106, 42, 222, 26, 32, 8, 210, 204, 150, 183, 128, 222, 183, 128, 228, 1, 16, @@ -1821,14 +1354,14 @@ func TestObject_Unmarshal(t *testing.T) { 32, 154, 122, 174, 117, 221, 138, 168, 135, 149, 238, 61, 68, 58, 34, 189, 18, 34, 10, 32, 110, 233, 102, 232, 136, 68, 233, 22, 158, 100, 49, 20, 181, 95, 219, 143, 53, 250, 237, 113, 64, 25, 48, 11, 54, 207, 56, 98, 99, 136, 207, 21, 18, 11, 24, 128, 128, 128, 128, 248, 255, 255, 255, 255, 1}}, - {name: "attributes/no key", err: "invalid header: empty key of the attribute #1", + {name: "attributes/no key", err: "invalid header: invalid attribute #1: missing key", b: []byte{26, 26, 82, 8, 10, 2, 107, 49, 18, 2, 118, 49, 82, 4, 18, 2, 118, 49, 82, 8, 10, 2, 107, 51, 18, 2, 118, 51}}, - {name: "attributes/no value", err: "invalid header: empty value of the attribute #1 (k2)", + {name: "attributes/no value", err: "invalid header: invalid attribute #1: missing value", b: []byte{26, 26, 82, 8, 10, 2, 107, 49, 18, 2, 118, 49, 82, 4, 10, 2, 107, 50, 82, 8, 10, 2, 107, 51, 18, 2, 118, 51}}, {name: "attributes/duplicated", err: "invalid header: duplicated attribute k1", b: []byte{26, 30, 82, 8, 10, 2, 107, 49, 18, 2, 118, 49, 82, 8, 10, 2, 107, 50, 18, 2, 118, 50, 82, 8, 10, 2, 107, 49, 18, 2, 118, 51}}, - {name: "attributes/expiration", err: "invalid header: invalid expiration attribute (must be a uint): strconv.ParseUint: parsing \"foo\": invalid syntax", + {name: "attributes/expiration", err: "invalid header: invalid attribute #1: invalid expiration epoch (must be a uint): strconv.ParseUint: parsing \"foo\": invalid syntax", b: []byte{26, 54, 82, 8, 10, 2, 107, 49, 18, 2, 118, 49, 82, 32, 10, 25, 95, 95, 78, 69, 79, 70, 83, 95, 95, 69, 88, 80, 73, 82, 65, 84, 73, 79, 78, 95, 69, 80, 79, 67, 72, 18, 3, 102, 111, 111, 82, 8, 10, 2, 107, 51, 18, 2, 118, 51}}, {name: "header/split/parent ID/empty value", err: "invalid header: invalid split header: invalid parent split member ID: invalid length 0", @@ -1887,7 +1420,7 @@ func TestObject_Unmarshal(t *testing.T) { b: []byte{26, 74, 90, 72, 42, 34, 10, 32, 178, 74, 58, 219, 46, 3, 110, 125, 220, 81, 238, 35, 27, 6, 228, 193, 190, 224, 77, 44, 18, 56, 117, 173, 70, 246, 8, 139, 247, 174, 53, 60, 42, 34, 10, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, - {name: "header/split/parent signature/scheme/overflow", err: "invalid header: invalid split header: invalid parent signature: scheme 2147483648 overflows int32", + {name: "header/split/parent signature/negative scheme", err: "invalid header: invalid split header: invalid parent signature: negative scheme -2147483648", b: []byte{26, 15, 90, 13, 26, 11, 24, 128, 128, 128, 128, 248, 255, 255, 255, 255, 1}}, {name: "header/split/parent/owner/value/empty", err: "invalid header: invalid split header: invalid parent header: invalid owner: invalid length 0, expected 25", b: []byte{26, 6, 90, 4, 34, 2, 26, 0}}, @@ -2022,7 +1555,7 @@ func TestObject_Unmarshal(t *testing.T) { 233, 102, 232, 136, 68, 233, 22, 158, 100, 49, 20, 181, 95, 219, 143, 53, 250, 237, 113, 64, 25, 48, 11, 54, 207, 56, 98, 99, 136, 207, 21, 18, 41, 10, 14, 115, 101, 115, 115, 105, 111, 110, 95, 115, 105, 103, 110, 101, 114, 18, 17, 115, 101, 115, 115, 105, 111, 110, 32, 115, 105, 103, 110, 97, 116, 117, 114, 101, 24, 170, 137, 252, 156, 4}}, - {name: "header/split/parent/session/body/context/wrong oneof", err: "invalid header: invalid split header: invalid parent header: invalid session token: invalid context: invalid context *session.ContainerSessionContext", + {name: "header/split/parent/session/body/context/wrong oneof", err: "invalid header: invalid split header: invalid parent header: invalid session token: invalid context: invalid context *session.SessionToken_Body_Container", b: []byte{26, 172, 1, 90, 169, 1, 34, 166, 1, 74, 163, 1, 10, 118, 10, 16, 118, 23, 219, 249, 117, 70, 64, 33, 157, 229, 102, 253, 142, 52, 17, 144, 18, 27, 10, 25, 53, 248, 195, 15, 196, 254, 124, 23, 169, 198, 208, 15, 219, 229, 62, 150, 151, 159, 221, 73, 224, 229, 106, 42, 222, 26, 32, 8, 210, 204, 150, 183, 128, 222, 183, 128, @@ -2107,7 +1640,7 @@ func TestObject_Unmarshal(t *testing.T) { 74, 58, 219, 46, 3, 110, 125, 220, 81, 238, 35, 27, 6, 228, 193, 190, 224, 77, 44, 18, 56, 117, 173, 70, 246, 8, 139, 247, 174, 53, 60, 1, 18, 41, 10, 14, 115, 101, 115, 115, 105, 111, 110, 95, 115, 105, 103, 110, 101, 114, 18, 17, 115, 101, 115, 115, 105, 111, 110, 32, 115, 105, 103, 110, 97, 116, 117, 114, 101, 24, 170, 137, 252, 156, 4}}, - {name: "header/split/parent/session/signature/scheme/overflow", err: "invalid header: invalid split header: invalid parent header: invalid session token: invalid body signature: scheme 2147483648 overflows int32", + {name: "header/split/parent/session/signature/negative scheme", err: "invalid header: invalid split header: invalid parent header: invalid session token: invalid body signature: negative scheme -2147483648", b: []byte{26, 166, 2, 90, 163, 2, 34, 160, 2, 74, 157, 2, 10, 234, 1, 10, 16, 118, 23, 219, 249, 117, 70, 64, 33, 157, 229, 102, 253, 142, 52, 17, 144, 18, 27, 10, 25, 53, 248, 195, 15, 196, 254, 124, 23, 169, 198, 208, 15, 219, 229, 62, 150, 151, 159, 221, 73, 224, 229, 106, 42, 222, 26, 32, 8, 210, 204, 150, 183, 128, 222, @@ -2183,7 +1716,7 @@ func TestObject_Unmarshal(t *testing.T) { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 34, 10, 32, 206, 228, 247, 217, 41, 247, 159, 215, 79, 226, 53, 153, 133, 16, 102, 104, 2, 234, 35, 220, 236, 112, 101, 24, 235, 126, 173, 229, 161, 202, 197, 242}}, - {name: "header/split/parent/split/parent signature/scheme/overflow", err: "invalid header: invalid split header: invalid parent header: invalid split header: invalid parent signature: scheme 2147483648 overflows int32", + {name: "header/split/parent/split/parent signature/negative scheme", err: "invalid header: invalid split header: invalid parent header: invalid split header: invalid parent signature: negative scheme -2147483648", b: []byte{26, 19, 90, 17, 34, 15, 90, 13, 26, 11, 24, 128, 128, 128, 128, 248, 255, 255, 255, 255, 1}}, } { t.Run(tc.name, func(t *testing.T) { @@ -2199,7 +1732,6 @@ func TestObject_Unmarshal(t *testing.T) { // filled require.NoError(t, obj.Unmarshal(validBinObject)) - t.Skip("https://github.com/nspcc-dev/neofs-sdk-go/issues/606") require.Equal(t, validObject, obj) } @@ -2225,7 +1757,7 @@ func TestObject_UnmarshalJSON(t *testing.T) { j: `{"objectID":{"value":"sko62y4Dbn3cUe4jGwbkwb7gTSwSOHWtRvYIi/euNTwB"}}`}, {name: "id/zero", err: "invalid ID: zero object ID", j: `{"objectID":{"value":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="}}`}, - {name: "signature/scheme/overflow", err: "invalid signature: scheme 2147483648 overflows int32", + {name: "signature/negative scheme", err: "invalid signature: negative scheme -2147483648", j: `{"signature":{"scheme":-2147483648}}`}, {name: "header/owner/value/empty", err: "invalid header: invalid owner: invalid length 0, expected 25", j: `{"header":{"ownerID":{}}}`}, @@ -2265,7 +1797,7 @@ func TestObject_UnmarshalJSON(t *testing.T) { j: `{"header":{"sessionToken":{"body":{"id":"dhfb+XVGQCGd5Wb9jjQRkA==", "ownerID":{"value":"QjsPBTSD/8YIYim45e2M1zSB09Zake2JmQ=="}}}}}`}, {name: "header/session/body/issuer/value/checksum mismatch", err: "invalid header: invalid session token: invalid session issuer: checksum mismatch", j: `{"header":{"sessionToken":{"body":{"id":"dhfb+XVGQCGd5Wb9jjQRkA==", "ownerID":{"value":"NTsPBTSD/8YIYim45e2M1zSB09Zake2Jmg=="}}}}}`}, - {name: "header/session/body/context/wrong oneof", err: "invalid header: invalid session token: invalid context: invalid context *session.ContainerSessionContext", + {name: "header/session/body/context/wrong oneof", err: "invalid header: invalid session token: invalid context: invalid context *session.SessionToken_Body_Container", j: ` { "header": { @@ -2468,7 +2000,7 @@ func TestObject_UnmarshalJSON(t *testing.T) { } } `}, - {name: "header/session/signature/scheme/overflow", err: "invalid header: invalid session token: invalid body signature: scheme 2147483648 overflows int32", + {name: "header/session/signature/negative scheme", err: "invalid header: invalid session token: invalid body signature: negative scheme -2147483648", j: ` { "header": { @@ -2509,13 +2041,13 @@ func TestObject_UnmarshalJSON(t *testing.T) { } } `}, - {name: "attributes/no key", err: "invalid header: empty key of the attribute #1", + {name: "attributes/no key", err: "invalid header: invalid attribute #1: missing key", j: `{"header": {"attributes": [{"key": "k1","value": "v1"},{"value": "v2"},{"key": "k3","value": "v3"}]}}`}, - {name: "attributes/no value", err: "invalid header: empty value of the attribute #1 (k2)", + {name: "attributes/no value", err: "invalid header: invalid attribute #1: missing value", j: `{"header": {"attributes": [{"key": "k1","value": "v1"},{"key": "k2"},{"key": "k3","value": "v3"}]}}`}, {name: "attributes/duplicated", err: "invalid header: duplicated attribute k1", j: `{"header": {"attributes": [{"key": "k1","value": "v1"},{"key": "k2", "value": "v2"},{"key": "k1","value": "v3"}]}}`}, - {name: "attributes/expiration", err: "invalid header: invalid expiration attribute (must be a uint): strconv.ParseUint: parsing \"foo\": invalid syntax", + {name: "attributes/expiration", err: "invalid header: invalid attribute #1: invalid expiration epoch (must be a uint): strconv.ParseUint: parsing \"foo\": invalid syntax", j: `{"header": {"attributes": [{"key": "k1","value": "v1"},{"key": "__NEOFS__EXPIRATION_EPOCH", "value": "foo"}]}}`}, {name: "header/split/parent ID/empty value", err: "invalid header: invalid split header: invalid parent split member ID: invalid length 0", j: `{"header": {"split":{"parent":{}}}}`}, @@ -2555,7 +2087,7 @@ func TestObject_UnmarshalJSON(t *testing.T) { j: `{"header": {"split":{"children":[{"value":"sko62y4Dbn3cUe4jGwbkwb7gTSwSOHWtRvYIi/euNTw="}, {"value":"5U0/6wIJpXt0ey9BFiLWTC3hFS6HIHSsQ9XzOf1/s+sB"}, {"value":"zuT32Sn3n9dP4jWZhRBmaALqI9zscGUY636t5aHKxfI="}]}}}`}, {name: "header/split/children/zero", err: "invalid header: invalid split header: invalid child split member ID #1: zero object ID", j: `{"header": {"split":{"children":[{"value":"sko62y4Dbn3cUe4jGwbkwb7gTSwSOHWtRvYIi/euNTw="}, {"value":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="}, {"value":"zuT32Sn3n9dP4jWZhRBmaALqI9zscGUY636t5aHKxfI="}]}}}`}, - {name: "header/split/parent signature/scheme/overflow", err: "invalid header: invalid split header: invalid parent signature: scheme 2147483648 overflows int32", + {name: "header/split/parent signature/negative scheme", err: "invalid header: invalid split header: invalid parent signature: negative scheme -2147483648", j: `{"header": {"split":{"parentSignature":{"key":"cHViXzE=","signature":"c2lnXzE=","scheme":-2147483648}}}}`}, {name: "header/split/parent/owner/value/empty", err: "invalid header: invalid split header: invalid parent header: invalid owner: invalid length 0, expected 25", j: `{"header": {"split": {"parentHeader": {"ownerID": {}}}}}`}, @@ -2617,7 +2149,7 @@ func TestObject_UnmarshalJSON(t *testing.T) { j: `{"header": {"split": {"parentHeader": {"split":{"children":[{"value":"sko62y4Dbn3cUe4jGwbkwb7gTSwSOHWtRvYIi/euNTw="},{"value":"5U0/6wIJpXt0ey9BFiLWTC3hFS6HIHSsQ9XzOf1/s+sB"},{"value":"zuT32Sn3n9dP4jWZhRBmaALqI9zscGUY636t5aHKxfI="}]}}}}}`}, {name: "header/split/parent/split/children/zero", err: "invalid header: invalid split header: invalid parent header: invalid split header: invalid child split member ID #1: zero object ID", j: `{"header": {"split": {"parentHeader": {"split":{"children":[{"value":"sko62y4Dbn3cUe4jGwbkwb7gTSwSOHWtRvYIi/euNTw="},{"value":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="},{"value":"zuT32Sn3n9dP4jWZhRBmaALqI9zscGUY636t5aHKxfI="}]}}}}}`}, - {name: "header/split/parent/split/parent signature/scheme/overflow", err: "invalid header: invalid split header: invalid parent header: invalid split header: invalid parent signature: scheme 2147483648 overflows int32", + {name: "header/split/parent/split/parent signature/negative scheme", err: "invalid header: invalid split header: invalid parent header: invalid split header: invalid parent signature: negative scheme -2147483648", j: `{"header": {"split": {"parentHeader": {"split":{"parentSignature":{"key":"cHViXzE=","signature":"c2lnXzE=","scheme":-2147483648}}}}}}`}, } { t.Run(tc.name, func(t *testing.T) { @@ -2633,7 +2165,6 @@ func TestObject_UnmarshalJSON(t *testing.T) { // filled require.NoError(t, obj.UnmarshalJSON([]byte(validJSONObject))) - t.Skip("https://github.com/nspcc-dev/neofs-sdk-go/issues/606") require.Equal(t, validObject, obj) } diff --git a/object/range.go b/object/range.go index 98eb7710..9c2260b8 100644 --- a/object/range.go +++ b/object/range.go @@ -1,18 +1,7 @@ package object -import ( - "github.com/nspcc-dev/neofs-api-go/v2/object" -) - // Range represents v2 [object.Range] object payload range. -type Range object.Range - -// NewRangeFromV2 wraps v2 [object.Range] message to [Range]. -// -// Nil [object.Range] converts to nil. -func NewRangeFromV2(rV2 *object.Range) *Range { - return (*Range)(rV2) -} +type Range struct{ off, ln uint64 } // NewRange creates and initializes blank [Range]. // @@ -20,43 +9,33 @@ func NewRangeFromV2(rV2 *object.Range) *Range { // - offset: 0; // - length: 0. func NewRange() *Range { - return NewRangeFromV2(new(object.Range)) -} - -// ToV2 converts [Range] to v2 [object.Range] message. -// -// Nil [Range] converts to nil. -// -// The value returned shares memory with the structure itself, so changing it can lead to data corruption. -// Make a copy if you need to change it. -func (r *Range) ToV2() *object.Range { - return (*object.Range)(r) + return new(Range) } // GetLength returns payload range size. // // See also [Range.SetLength]. func (r *Range) GetLength() uint64 { - return (*object.Range)(r).GetLength() + return r.ln } // SetLength sets payload range size. // // See also [Range.GetLength]. func (r *Range) SetLength(v uint64) { - (*object.Range)(r).SetLength(v) + r.ln = v } // GetOffset sets payload range offset from start. // // See also [Range.SetOffset]. func (r *Range) GetOffset() uint64 { - return (*object.Range)(r).GetOffset() + return r.off } // SetOffset gets payload range offset from start. // // See also [Range.GetOffset]. func (r *Range) SetOffset(v uint64) { - (*object.Range)(r).SetOffset(v) + r.off = v } diff --git a/object/range_test.go b/object/range_test.go index e14d3600..22fdf31e 100644 --- a/object/range_test.go +++ b/object/range_test.go @@ -3,7 +3,6 @@ package object import ( "testing" - "github.com/nspcc-dev/neofs-api-go/v2/object" "github.com/stretchr/testify/require" ) @@ -25,22 +24,6 @@ func TestRange_SetLength(t *testing.T) { require.Equal(t, ln, r.GetLength()) } -func TestNewRangeFromV2(t *testing.T) { - t.Run("from nil", func(t *testing.T) { - var x *object.Range - - require.Nil(t, NewRangeFromV2(x)) - }) -} - -func TestRange_ToV2(t *testing.T) { - t.Run("nil", func(t *testing.T) { - var x *Range - - require.Nil(t, x.ToV2()) - }) -} - func TestNewRange(t *testing.T) { t.Run("default values", func(t *testing.T) { r := NewRange() @@ -48,11 +31,5 @@ func TestNewRange(t *testing.T) { // check initial values require.Zero(t, r.GetLength()) require.Zero(t, r.GetOffset()) - - // convert to v2 message - rV2 := r.ToV2() - - require.Zero(t, rV2.GetLength()) - require.Zero(t, rV2.GetOffset()) }) } diff --git a/object/search.go b/object/search.go index 75f45dbe..55056b8f 100644 --- a/object/search.go +++ b/object/search.go @@ -4,19 +4,21 @@ import ( "crypto/sha256" "encoding/hex" "encoding/json" + "fmt" "strconv" "strings" - v2object "github.com/nspcc-dev/neofs-api-go/v2/object" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" + neofsproto "github.com/nspcc-dev/neofs-sdk-go/internal/proto" 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/user" "github.com/nspcc-dev/neofs-sdk-go/version" "github.com/nspcc-dev/tzhash/tz" ) // SearchMatchType indicates match operation on specified header. -type SearchMatchType uint32 +type SearchMatchType int32 // MatchUnknown is an SearchMatchType value used to mark operator as undefined. // Deprecated: use MatchUnspecified instead. @@ -34,18 +36,6 @@ const ( MatchNumLE ) -// ToV2 converts [SearchMatchType] to v2 [v2object.MatchType] enum value. -// Deprecated: cast instead. -func (m SearchMatchType) ToV2() v2object.MatchType { - return v2object.MatchType(m) -} - -// SearchMatchFromV2 converts v2 [v2object.MatchType] to [SearchMatchType] enum value. -// Deprecated: cast instead. -func SearchMatchFromV2(t v2object.MatchType) SearchMatchType { - return SearchMatchType(t) -} - const ( matcherStringZero = "MATCH_TYPE_UNSPECIFIED" matcherStringEqual = "STRING_EQUAL" @@ -95,7 +85,7 @@ func (m SearchMatchType) EncodeToString() string { return m.String() } func (m SearchMatchType) String() string { switch m { default: - return strconv.FormatUint(uint64(m), 10) + return strconv.FormatInt(int64(m), 10) case 0: return matcherStringZero case MatchStringEqual: @@ -124,7 +114,7 @@ func (m SearchMatchType) String() string { func (m *SearchMatchType) DecodeString(s string) bool { switch s { default: - n, err := strconv.ParseUint(s, 10, 32) + n, err := strconv.ParseInt(s, 10, 32) if err != nil { return false } @@ -163,18 +153,19 @@ type SearchFilters []SearchFilter // Various header filters. const ( - FilterVersion = v2object.FilterHeaderVersion - FilterID = v2object.FilterHeaderObjectID - FilterContainerID = v2object.FilterHeaderContainerID - FilterOwnerID = v2object.FilterHeaderOwnerID - FilterPayloadChecksum = v2object.FilterHeaderPayloadHash - FilterType = v2object.FilterHeaderObjectType - FilterPayloadHomomorphicHash = v2object.FilterHeaderHomomorphicHash - FilterParentID = v2object.FilterHeaderParent - FilterSplitID = v2object.FilterHeaderSplitID - FilterFirstSplitObject = v2object.ReservedFilterPrefix + "split.first" - FilterCreationEpoch = v2object.FilterHeaderCreationEpoch - FilterPayloadSize = v2object.FilterHeaderPayloadLength + reservedFilterPrefix = "$Object:" + FilterVersion = reservedFilterPrefix + "version" + FilterID = reservedFilterPrefix + "objectID" + FilterContainerID = reservedFilterPrefix + "containerID" + FilterOwnerID = reservedFilterPrefix + "ownerID" + FilterPayloadChecksum = reservedFilterPrefix + "payloadHash" + FilterType = reservedFilterPrefix + "objectType" + FilterPayloadHomomorphicHash = reservedFilterPrefix + "homomorphicHash" + FilterParentID = reservedFilterPrefix + "split.parent" + FilterSplitID = reservedFilterPrefix + "split.splitID" + FilterFirstSplitObject = reservedFilterPrefix + "split.first" + FilterCreationEpoch = reservedFilterPrefix + "creationEpoch" + FilterPayloadSize = reservedFilterPrefix + "payloadLength" ) // Various filters to match certain object properties. @@ -183,14 +174,22 @@ const ( // with user data that are not system-specific. In addition to such objects, the // system may contain service objects that do not fall under this property // (like split leaves, tombstones, storage groups, etc.). - FilterRoot = v2object.FilterPropertyRoot + FilterRoot = reservedFilterPrefix + "ROOT" // FilterPhysical filters indivisible objects that are intended to be stored // on the physical devices of the system. In addition to such objects, the // system may contain so-called "virtual" objects that exist in the system in // disassembled form (like "huge" user object sliced into smaller ones). - FilterPhysical = v2object.FilterPropertyPhy + FilterPhysical = reservedFilterPrefix + "PHY" ) +func (f SearchFilter) protoMessage() *protoobject.SearchRequest_Body_Filter { + return &protoobject.SearchRequest_Body_Filter{ + MatchType: protoobject.MatchType(f.Operation()), + Key: f.Header(), + Value: f.Value(), + } +} + // Header returns filter header value. func (f SearchFilter) Header() string { return f.header @@ -209,7 +208,7 @@ func (f SearchFilter) Operation() SearchMatchType { // IsNonAttribute checks if SearchFilter is non-attribute: such filter is // related to the particular property of the object instead of its attribute. func (f SearchFilter) IsNonAttribute() bool { - return strings.HasPrefix(f.header, v2object.ReservedFilterPrefix) + return strings.HasPrefix(f.header, reservedFilterPrefix) } // NewSearchFilters constructs empty filter group. @@ -217,21 +216,6 @@ func NewSearchFilters() SearchFilters { return SearchFilters{} } -// NewSearchFiltersFromV2 converts slice of [v2object.SearchFilter] to [SearchFilters]. -func NewSearchFiltersFromV2(v2 []v2object.SearchFilter) SearchFilters { - filters := make(SearchFilters, 0, len(v2)) - - for i := range v2 { - filters.AddFilter( - v2[i].GetKey(), - v2[i].GetValue(), - SearchMatchType(v2[i].GetMatchType()), - ) - } - - return filters -} - func (f *SearchFilters) addFilter(op SearchMatchType, key string, val string) { if *f == nil { *f = make(SearchFilters, 0, 1) @@ -279,17 +263,42 @@ func (f *SearchFilters) AddObjectOwnerIDFilter(m SearchMatchType, id user.ID) { f.addFilter(m, FilterOwnerID, id.EncodeToString()) } -// ToV2 converts [SearchFilters] to [v2object.SearchFilter] slice. -func (f SearchFilters) ToV2() []v2object.SearchFilter { - result := make([]v2object.SearchFilter, len(f)) +// FromProtoMessage validates m according to the NeoFS API protocol and restores +// f from it. +// +// See also [SearchFilters.ProtoMessage]. +func (f *SearchFilters) FromProtoMessage(ms []*protoobject.SearchRequest_Body_Filter) error { + fs := make(SearchFilters, len(ms)) + for i, m := range ms { + if m == nil { + return fmt.Errorf("nil filter #%d", i) + } + if m.MatchType < 0 { + return fmt.Errorf("invalid filter #%d: negative match type %d", i, m.MatchType) + } + if m.Key == "" { + return fmt.Errorf("invalid filter #%d: missing key", i) + } + fs[i] = SearchFilter{ + header: m.Key, + value: m.Value, + op: SearchMatchType(m.MatchType), + } + } + *f = fs + return nil +} +// ProtoMessage converts f into message to transmit using the NeoFS API +// protocol. +// +// See also [SearchFilters.FromProtoMessage]. +func (f SearchFilters) ProtoMessage() []*protoobject.SearchRequest_Body_Filter { + m := make([]*protoobject.SearchRequest_Body_Filter, len(f)) for i := range f { - result[i].SetKey(f[i].header) - result[i].SetValue(f[i].value) - result[i].SetMatchType(v2object.MatchType(f[i].op)) + m[i] = f[i].protoMessage() } - - return result + return m } // AddRootFilter adds filter by objects that have been created by a user explicitly. @@ -337,26 +346,43 @@ func (f *SearchFilters) AddTypeFilter(m SearchMatchType, typ Type) { f.addFilter(m, FilterType, typ.EncodeToString()) } +type fj protoobject.SearchRequest_Body_Filter + +func (x *fj) MarshalJSON() ([]byte, error) { + return neofsproto.MarshalMessageJSON((*protoobject.SearchRequest_Body_Filter)(x)) +} + // MarshalJSON encodes [SearchFilters] to protobuf JSON format. // // See also [SearchFilters.UnmarshalJSON]. func (f SearchFilters) MarshalJSON() ([]byte, error) { - return json.Marshal(f.ToV2()) + fjs := make([]*fj, len(f)) + for i := range f { + fjs[i] = (*fj)(f[i].protoMessage()) + } + return json.Marshal(fjs) +} + +func (x *fj) UnmarshalJSON(b []byte) error { + return neofsproto.UnmarshalMessageJSON(b, (*protoobject.SearchRequest_Body_Filter)(x)) } // UnmarshalJSON decodes [SearchFilters] from protobuf JSON format. // // See also [SearchFilters.MarshalJSON]. func (f *SearchFilters) UnmarshalJSON(data []byte) error { - var fsV2 []v2object.SearchFilter + var j []*fj - if err := json.Unmarshal(data, &fsV2); err != nil { + if err := json.Unmarshal(data, &j); err != nil { return err } - *f = NewSearchFiltersFromV2(fsV2) + m := make([]*protoobject.SearchRequest_Body_Filter, len(j)) + for i := range j { + m[i] = (*protoobject.SearchRequest_Body_Filter)(j[i]) + } - return nil + return f.FromProtoMessage(m) } // AddPayloadHashFilter adds filter by payload hash. diff --git a/object/search_test.go b/object/search_test.go index c113f4b1..bfdaba66 100644 --- a/object/search_test.go +++ b/object/search_test.go @@ -5,29 +5,32 @@ import ( "encoding/hex" "encoding/json" "fmt" + "math/rand/v2" + "slices" "testing" - v2object "github.com/nspcc-dev/neofs-api-go/v2/object" "github.com/nspcc-dev/neofs-sdk-go/checksum" "github.com/nspcc-dev/neofs-sdk-go/object" + protoobject "github.com/nspcc-dev/neofs-sdk-go/proto/object" "github.com/nspcc-dev/tzhash/tz" "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" ) const ( anyValidSearchMatcher = object.SearchMatchType(1937803447) ) -var eqV2Matches = map[object.SearchMatchType]v2object.MatchType{ - object.MatchUnspecified: v2object.MatchUnknown, - object.MatchStringEqual: v2object.MatchStringEqual, - object.MatchStringNotEqual: v2object.MatchStringNotEqual, - object.MatchNotPresent: v2object.MatchNotPresent, - object.MatchCommonPrefix: v2object.MatchCommonPrefix, - object.MatchNumGT: v2object.MatchNumGT, - object.MatchNumGE: v2object.MatchNumGE, - object.MatchNumLT: v2object.MatchNumLT, - object.MatchNumLE: v2object.MatchNumLE, +var protoMatches = map[object.SearchMatchType]protoobject.MatchType{ + object.MatchUnspecified: protoobject.MatchType_MATCH_TYPE_UNSPECIFIED, + object.MatchStringEqual: protoobject.MatchType_STRING_EQUAL, + object.MatchStringNotEqual: protoobject.MatchType_STRING_NOT_EQUAL, + object.MatchNotPresent: protoobject.MatchType_NOT_PRESENT, + object.MatchCommonPrefix: protoobject.MatchType_COMMON_PREFIX, + object.MatchNumGT: protoobject.MatchType_NUM_GT, + object.MatchNumGE: protoobject.MatchType_NUM_GE, + object.MatchNumLT: protoobject.MatchType_NUM_LT, + object.MatchNumLE: protoobject.MatchType_NUM_LE, } var searchMatchTypeStrings = map[object.SearchMatchType]string{ @@ -73,19 +76,19 @@ func init() { // corresponds to validSearchFilters. var validSearchFiltersProto = []struct { k string - m v2object.MatchType + m protoobject.MatchType v string }{ - {"$Object:PHY", v2object.MatchUnknown, ""}, - {"$Object:ROOT", v2object.MatchUnknown, ""}, - {"k1", v2object.MatchStringEqual, "v1"}, - {"k2", v2object.MatchStringNotEqual, "v2"}, - {"k3", v2object.MatchNotPresent, "v3"}, - {"k4", v2object.MatchCommonPrefix, "v4"}, - {"k5", v2object.MatchNumGT, "v5"}, - {"k6", v2object.MatchNumGE, "v6"}, - {"k7", v2object.MatchNumLT, "v7"}, - {"k8", v2object.MatchNumLE, "v8"}, + {"$Object:PHY", protoobject.MatchType_MATCH_TYPE_UNSPECIFIED, ""}, + {"$Object:ROOT", protoobject.MatchType_MATCH_TYPE_UNSPECIFIED, ""}, + {"k1", protoobject.MatchType_STRING_EQUAL, "v1"}, + {"k2", protoobject.MatchType_STRING_NOT_EQUAL, "v2"}, + {"k3", protoobject.MatchType_NOT_PRESENT, "v3"}, + {"k4", protoobject.MatchType_COMMON_PREFIX, "v4"}, + {"k5", protoobject.MatchType_NUM_GT, "v5"}, + {"k6", protoobject.MatchType_NUM_GE, "v6"}, + {"k7", protoobject.MatchType_NUM_LT, "v7"}, + {"k8", protoobject.MatchType_NUM_LE, "v8"}, {"$Object:version", 100, "v88789927.2018985309"}, {"$Object:objectID", 101, "CzyDjRYWpwLHxqXVFBXKQGP5XM7ebAR9ndTvBdaSxMMV"}, {"$Object:containerID", 102, "HWpbBkyxCi7nhDnn4W3v5rYt2mDfH2wedknQzRkTwquj"}, @@ -248,23 +251,8 @@ func TestSearchFilters_UnmarshalJSON(t *testing.T) { require.Equal(t, validSearchFilters, fs) } -func TestMatch(t *testing.T) { - require.EqualValues(t, object.MatchUnspecified, v2object.MatchUnknown) - t.Run("known matches", func(t *testing.T) { - for matchType, matchTypeV2 := range eqV2Matches { - require.Equal(t, matchTypeV2, matchType.ToV2()) - require.Equal(t, object.SearchMatchFromV2(matchTypeV2), matchType) - } - }) - - t.Run("unknown matches", func(t *testing.T) { - require.EqualValues(t, 1000, object.SearchMatchType(1000).ToV2()) - require.EqualValues(t, 1000, object.SearchMatchFromV2(1000)) - }) -} - func TestSearchFilters_AddFilter(t *testing.T) { - const k1, m1, v1 = "k1", object.SearchMatchType(2584744206), "v1" + const k1, m1, v1 = "k1", object.SearchMatchType(584744206), "v1" const k2, m2, v2 = "k2", object.SearchMatchType(930572326), "v2" var filters object.SearchFilters @@ -357,17 +345,7 @@ func TestSearchFilters_AddTypeFilter(t *testing.T) { } func TestSearchMatchTypeProto(t *testing.T) { - for x, y := range map[v2object.MatchType]object.SearchMatchType{ - v2object.MatchUnknown: object.MatchUnspecified, - v2object.MatchStringEqual: object.MatchStringEqual, - v2object.MatchStringNotEqual: object.MatchStringNotEqual, - v2object.MatchNotPresent: object.MatchNotPresent, - v2object.MatchCommonPrefix: object.MatchCommonPrefix, - v2object.MatchNumGT: object.MatchNumGT, - v2object.MatchNumGE: object.MatchNumGE, - v2object.MatchNumLT: object.MatchNumLT, - v2object.MatchNumLE: object.MatchNumLE, - } { + for x, y := range protoMatches { require.EqualValues(t, x, y) } } @@ -462,17 +440,17 @@ func TestNewSearchFilters(t *testing.T) { require.Empty(t, object.NewSearchFilters()) } -func TestSearchFilters_ToV2(t *testing.T) { +func TestSearchFilters_ProtoMessage(t *testing.T) { const nFilters = 22 require.Len(t, validSearchFiltersProto, nFilters, "not all applied filters are asserted") var fs object.SearchFilters // zero - m := fs.ToV2() + m := fs.ProtoMessage() require.Empty(t, m) // filled - m = validSearchFilters.ToV2() + m = validSearchFilters.ProtoMessage() require.Len(t, m, nFilters) for i, exp := range validSearchFiltersProto { @@ -483,18 +461,35 @@ func TestSearchFilters_ToV2(t *testing.T) { } } -func TestNewSearchFiltersFromV2(t *testing.T) { - // empty - require.Empty(t, object.NewSearchFiltersFromV2(nil)) - require.Empty(t, object.NewSearchFiltersFromV2([]v2object.SearchFilter{})) +func TestSearchFilters_FromProtoMessage(t *testing.T) { + ms := []*protoobject.SearchRequest_Body_Filter{ + {MatchType: protoobject.MatchType(rand.Int32()), Key: "key_1", Value: "val_1"}, + {MatchType: protoobject.MatchType(rand.Int32()), Key: "key_2", Value: "val_2"}, + } - // filled - m := make([]v2object.SearchFilter, len(validSearchFiltersProto)) - for i, f := range validSearchFiltersProto { - m[i].SetKey(f.k) - m[i].SetMatchType(f.m) - m[i].SetValue(f.v) + var fs object.SearchFilters + require.NoError(t, fs.FromProtoMessage(ms)) + require.Len(t, fs, len(ms)) + for i := range ms { + require.EqualValues(t, ms[i].MatchType, fs[i].Operation()) + require.Equal(t, ms[i].Key, fs[i].Header()) + require.Equal(t, ms[i].Value, fs[i].Value()) } - require.Equal(t, object.NewSearchFiltersFromV2(m), validSearchFilters) + require.NoError(t, fs.FromProtoMessage(nil)) + require.Empty(t, fs) + + t.Run("invalid", func(t *testing.T) { + for _, tc := range []struct { + name, err string + corrupt func([]*protoobject.SearchRequest_Body_Filter) + }{} { + cp := slices.Clone(ms) + for i := range ms { + cp[i] = proto.Clone(ms[i]).(*protoobject.SearchRequest_Body_Filter) + } + tc.corrupt(cp) + require.EqualError(t, fs.FromProtoMessage(ms), tc.err) + } + }) } diff --git a/object/slicer/slicer_test.go b/object/slicer/slicer_test.go index b58dfe7f..d5ac85c3 100644 --- a/object/slicer/slicer_test.go +++ b/object/slicer/slicer_test.go @@ -14,7 +14,6 @@ import ( "math/rand" "testing" - netmapv2 "github.com/nspcc-dev/neofs-api-go/v2/netmap" "github.com/nspcc-dev/neofs-sdk-go/checksum" "github.com/nspcc-dev/neofs-sdk-go/client" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" @@ -136,20 +135,7 @@ func benchmarkSliceDataIntoObjects(b *testing.B, size, sizeLimit uint64) { func networkInfoFromOpts(opts slicer.Options) (netmap.NetworkInfo, error) { var ni netmap.NetworkInfo - var v2 netmapv2.NetworkInfo - var netConfig netmapv2.NetworkConfig - var p1 netmapv2.NetworkParameter - - p1.SetKey(randomData(10)) - p1.SetValue(randomData(10)) - - netConfig.SetParameters(p1) - v2.SetNetworkConfig(&netConfig) - - if err := ni.ReadFromV2(v2); err != nil { - return ni, err - } - + ni.SetRawNetworkParameter(string(randomData(10)), randomData(10)) ni.SetCurrentEpoch(opts.CurrentNeoFSEpoch()) ni.SetMaxObjectSize(opts.ObjectPayloadLimit()) if !opts.IsHomomorphicChecksumEnabled() { @@ -843,7 +829,7 @@ func TestSlicedObjectsHaveSplitID(t *testing.T) { checkParentWithoutSplitInfo := func(hdr object.Object) { for o := hdr.Parent(); o != nil; o = o.Parent() { - require.Nil(t, o.ToV2().GetHeader().GetSplit()) + require.Nil(t, o.ProtoMessage().GetHeader().GetSplit()) } } diff --git a/object/splitinfo.go b/object/splitinfo.go index fea7cf00..0ac9232d 100644 --- a/object/splitinfo.go +++ b/object/splitinfo.go @@ -5,21 +5,17 @@ import ( "fmt" "github.com/google/uuid" - "github.com/nspcc-dev/neofs-api-go/v2/object" - "github.com/nspcc-dev/neofs-api-go/v2/refs" + neofsproto "github.com/nspcc-dev/neofs-sdk-go/internal/proto" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" + protoobject "github.com/nspcc-dev/neofs-sdk-go/proto/object" ) // SplitInfo is an SDK representation of [object.SplitInfo]. -type SplitInfo object.SplitInfo - -// NewSplitInfoFromV2 wraps v2 [object.SplitInfo] message to [SplitInfo]. -// -// Nil object.SplitInfo converts to nil. -// Deprecated: BUG: format of fields is not checked. Use [SplitInfo.ReadFromV2] -// instead. -func NewSplitInfoFromV2(v2 *object.SplitInfo) *SplitInfo { - return (*SplitInfo)(v2) +type SplitInfo struct { + splitID []byte + last oid.ID + link oid.ID + first oid.ID } // NewSplitInfo creates and initializes blank [SplitInfo]. @@ -32,14 +28,24 @@ func NewSplitInfo() *SplitInfo { return new(SplitInfo) } -// ToV2 converts [SplitInfo] to v2 [object.SplitInfo] message. -// -// Nil SplitInfo converts to nil. +// ProtoMessage converts s into message to transmit using the NeoFS API +// protocol. // -// The value returned shares memory with the structure itself, so changing it can lead to data corruption. -// Make a copy if you need to change it. -func (s SplitInfo) ToV2() *object.SplitInfo { - return (*object.SplitInfo)(&s) +// See also [SplitInfo.FromProtoMessage]. +func (s SplitInfo) ProtoMessage() *protoobject.SplitInfo { + m := &protoobject.SplitInfo{ + SplitId: s.splitID, + } + if !s.last.IsZero() { + m.LastPart = s.last.ProtoMessage() + } + if !s.first.IsZero() { + m.FirstPart = s.first.ProtoMessage() + } + if !s.link.IsZero() { + m.Link = s.link.ProtoMessage() + } + return m } // SplitID returns [SplitID] if it has been set. New objects may miss it, @@ -50,8 +56,7 @@ func (s SplitInfo) ToV2() *object.SplitInfo { // // See also [SplitInfo.SetSplitID]. func (s SplitInfo) SplitID() *SplitID { - return NewSplitIDFromV2( - (*object.SplitInfo)(&s).GetSplitID()) + return NewSplitIDFromV2(s.splitID) } // SetSplitID sets split ID in object ID. It resets split ID if nil passed. @@ -61,7 +66,7 @@ func (s SplitInfo) SplitID() *SplitID { // DEPRECATED.[SplitInfo.SetFirstPart] usage is required for the _new_ split // objects, it serves as chain identification. func (s *SplitInfo) SetSplitID(v *SplitID) { - (*object.SplitInfo)(s).SetSplitID(v.ToV2()) + s.splitID = v.ToV2() } // LastPart returns last object ID, can be used to retrieve original object. @@ -79,22 +84,14 @@ func (s SplitInfo) LastPart() (oid.ID, bool) { // // See also [SplitInfo.SetLastPart]. func (s SplitInfo) GetLastPart() oid.ID { - var id oid.ID - m := (*object.SplitInfo)(&s).GetLastPart() - if m != nil { - _ = id.ReadFromV2(*m) - } - return id + return s.last } // SetLastPart sets the last object ID. // // See also [SplitInfo.GetLastPart]. func (s *SplitInfo) SetLastPart(v oid.ID) { - var idV2 refs.ObjectID - v.WriteToV2(&idV2) - - (*object.SplitInfo)(s).SetLastPart(&idV2) + s.last = v } // Link returns a linker object ID. @@ -111,21 +108,14 @@ func (s SplitInfo) Link() (oid.ID, bool) { // // See also [SplitInfo.SetLink]. func (s SplitInfo) GetLink() oid.ID { - var id oid.ID - if m := (*object.SplitInfo)(&s).GetLink(); m != nil { - _ = id.ReadFromV2(*m) - } - return id + return s.link } // SetLink sets linker object ID. // // See also [SplitInfo.GetLink]. func (s *SplitInfo) SetLink(v oid.ID) { - var idV2 refs.ObjectID - v.WriteToV2(&idV2) - - (*object.SplitInfo)(s).SetLink(&idV2) + s.link = v } // FirstPart returns the first part of the split chain. @@ -141,106 +131,90 @@ func (s SplitInfo) FirstPart() (oid.ID, bool) { // // See also [SplitInfo.SetFirstPart]. func (s SplitInfo) GetFirstPart() oid.ID { - var id oid.ID - if m := (*object.SplitInfo)(&s).GetFirstPart(); m != nil { - _ = id.ReadFromV2(*m) - } - return id + return s.first } // SetFirstPart sets the first part of the split chain. // // See also [SplitInfo.GetFirstPart]. func (s *SplitInfo) SetFirstPart(v oid.ID) { - var idV2 refs.ObjectID - v.WriteToV2(&idV2) - - (*object.SplitInfo)(s).SetFirstPart(&idV2) + s.first = v } // Marshal marshals [SplitInfo] into a protobuf binary form. // // See also [SplitInfo.Unmarshal]. func (s SplitInfo) Marshal() []byte { - return (*object.SplitInfo)(&s).StableMarshal(nil) + return neofsproto.Marshal(s) } // Unmarshal unmarshals protobuf binary representation of [SplitInfo]. // // See also [SplitInfo.Marshal]. func (s *SplitInfo) Unmarshal(data []byte) error { - var m object.SplitInfo - if err := m.Unmarshal(data); err != nil { - return err - } - return s.ReadFromV2(m) + return neofsproto.Unmarshal(data, s) } // MarshalJSON implements json.Marshaler. // // See also [SplitInfo.UnmarshalJSON]. func (s SplitInfo) MarshalJSON() ([]byte, error) { - return (*object.SplitInfo)(&s).MarshalJSON() + return neofsproto.MarshalJSON(s) } // UnmarshalJSON implements json.Unmarshaler. // // See also [SplitInfo.MarshalJSON]. func (s *SplitInfo) UnmarshalJSON(data []byte) error { - var m object.SplitInfo - if err := m.UnmarshalJSON(data); err != nil { - return err - } - return s.ReadFromV2(m) + return neofsproto.UnmarshalJSON(data, s) } var errSplitInfoMissingFields = errors.New("neither link object ID nor last part object ID is set") -// ReadFromV2 reads SplitInfo from the [object.SplitInfo] message. Returns an -// error if the message is malformed according to the NeoFS API V2 protocol. +// FromProtoMessage validates m according to the NeoFS API protocol and restores +// s from it. // -// ReadFromV2 is intended to be used by the NeoFS API V2 client/server -// implementation only and is not expected to be directly used by applications. -func (s *SplitInfo) ReadFromV2(m object.SplitInfo) error { - if b := m.GetSplitID(); len(b) > 0 { +// See also [SplitInfo.ProtoMessage]. +func (s *SplitInfo) FromProtoMessage(m *protoobject.SplitInfo) error { + if s.splitID = m.SplitId; len(m.SplitId) > 0 { var uid uuid.UUID - if err := uid.UnmarshalBinary(b); err != nil { + if err := uid.UnmarshalBinary(m.SplitId); err != nil { return fmt.Errorf("invalid split ID: %w", err) } else if v := uid.Version(); v != 4 { return fmt.Errorf("invalid split ID: wrong UUID version %d, expected 4", v) } } - link := m.GetLink() - lastPart := m.GetLastPart() - if link == nil && lastPart == nil { + if m.Link == nil && m.LastPart == nil { return errSplitInfoMissingFields } - var oID oid.ID - - if link != nil { - err := oID.ReadFromV2(*link) + if m.Link != nil { + err := s.link.FromProtoMessage(m.Link) if err != nil { return fmt.Errorf("could not convert link object ID: %w", err) } + } else { + s.link = oid.ID{} } - if lastPart != nil { - err := oID.ReadFromV2(*lastPart) + if m.LastPart != nil { + err := s.last.FromProtoMessage(m.LastPart) if err != nil { return fmt.Errorf("could not convert last part object ID: %w", err) } + } else { + s.last = oid.ID{} } - firstPart := m.GetFirstPart() - if firstPart != nil { // can be missing for old objects - err := oID.ReadFromV2(*firstPart) + if m.FirstPart != nil { // can be missing for old objects + err := s.first.FromProtoMessage(m.FirstPart) if err != nil { return fmt.Errorf("could not convert first part object ID: %w", err) } + } else { + s.first = oid.ID{} } - *s = SplitInfo(m) return nil } diff --git a/object/splitinfo_test.go b/object/splitinfo_test.go index 1e14fa67..b1a44db1 100644 --- a/object/splitinfo_test.go +++ b/object/splitinfo_test.go @@ -5,10 +5,10 @@ import ( "encoding/json" "testing" - apiobject "github.com/nspcc-dev/neofs-api-go/v2/object" "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" "github.com/stretchr/testify/require" ) @@ -154,14 +154,14 @@ func TestSplitInfo(t *testing.T) { } func TestSplitInfoMarshal(t *testing.T) { - testToV2 := func(t *testing.T, s *object.SplitInfo) { - v2 := s.ToV2() - newS := object.NewSplitInfoFromV2(v2) + testMessage := func(t *testing.T, s object.SplitInfo) { + var newS object.SplitInfo + require.NoError(t, newS.FromProtoMessage(s.ProtoMessage())) require.Equal(t, s, newS) } - testMarshal := func(t *testing.T, s *object.SplitInfo) { - newS := object.NewSplitInfo() + testMarshal := func(t *testing.T, s object.SplitInfo) { + var newS object.SplitInfo err := newS.Unmarshal(s.Marshal()) require.NoError(t, err) @@ -175,24 +175,24 @@ func TestSplitInfoMarshal(t *testing.T) { s.SetLastPart(oidtest.ID()) s.SetFirstPart(oidtest.ID()) - testToV2(t, s) - testMarshal(t, s) + testMessage(t, *s) + testMarshal(t, *s) }) t.Run("good, only link is set", func(t *testing.T) { s := object.NewSplitInfo() s.SetSplitID(object.NewSplitID()) s.SetLink(oidtest.ID()) - testToV2(t, s) - testMarshal(t, s) + testMessage(t, *s) + testMarshal(t, *s) }) t.Run("good, only last part is set", func(t *testing.T) { s := object.NewSplitInfo() s.SetSplitID(object.NewSplitID()) s.SetLastPart(oidtest.ID()) - testToV2(t, s) - testMarshal(t, s) + testMessage(t, *s) + testMarshal(t, *s) }) t.Run("bad, no fields are set", func(t *testing.T) { s := object.NewSplitInfo() @@ -202,23 +202,16 @@ func TestSplitInfoMarshal(t *testing.T) { }) } -func TestNewSplitInfoFromV2(t *testing.T) { - t.Run("from nil", func(t *testing.T) { - var x *apiobject.SplitInfo - - require.Nil(t, object.NewSplitInfoFromV2(x)) - }) -} - -func TestSplitInfo_ReadFromV2(t *testing.T) { - var m apiobject.SplitInfo - m.SetSplitID(anyValidSplitIDBytes) - m.SetLastPart(protoIDFromBytes(anyValidIDs[0][:])) - m.SetLink(protoIDFromBytes(anyValidIDs[1][:])) - m.SetFirstPart(protoIDFromBytes(anyValidIDs[2][:])) +func TestSplitInfo_FromProtoMessage(t *testing.T) { + m := &protoobject.SplitInfo{ + SplitId: anyValidSplitIDBytes, + LastPart: protoIDFromBytes(anyValidIDs[0][:]), + Link: protoIDFromBytes(anyValidIDs[1][:]), + FirstPart: protoIDFromBytes(anyValidIDs[2][:]), + } var s object.SplitInfo - require.NoError(t, s.ReadFromV2(m)) + require.NoError(t, s.FromProtoMessage(m)) require.Equal(t, anyValidSplitID, s.SplitID()) require.Equal(t, anyValidIDs[0], s.GetLastPart()) id, ok := s.LastPart() @@ -234,11 +227,11 @@ func TestSplitInfo_ReadFromV2(t *testing.T) { require.Equal(t, anyValidIDs[2], id) // reset optional fields - m.SetSplitID(nil) - m.SetFirstPart(nil) - m.SetLink(nil) + m.SplitId = nil + m.FirstPart = nil + m.Link = nil s2 := s - require.NoError(t, s2.ReadFromV2(m)) + require.NoError(t, s2.FromProtoMessage(m)) require.Zero(t, s2.SplitID()) require.True(t, s2.GetFirstPart().IsZero()) @@ -253,9 +246,9 @@ func TestSplitInfo_ReadFromV2(t *testing.T) { require.False(t, ok) // either linking or last part must be set, so lets swap - m.SetLink(protoIDFromBytes(anyValidIDs[1][:])) - m.SetLastPart(nil) - require.NoError(t, s2.ReadFromV2(m)) + m.Link = protoIDFromBytes(anyValidIDs[1][:]) + m.LastPart = nil + require.NoError(t, s2.FromProtoMessage(m)) require.Equal(t, anyValidIDs[1], s2.GetLink()) require.True(t, s2.GetLastPart().IsZero()) @@ -265,66 +258,65 @@ func TestSplitInfo_ReadFromV2(t *testing.T) { t.Run("invalid", func(t *testing.T) { for _, tc := range []struct { name, err string - corrupt func(*apiobject.SplitInfo) + corrupt func(*protoobject.SplitInfo) }{ {name: "split ID/undersize", err: "invalid split ID: invalid UUID (got 15 bytes)", - corrupt: func(m *apiobject.SplitInfo) { m.SetSplitID(anyValidSplitIDBytes[:15]) }}, + corrupt: func(m *protoobject.SplitInfo) { m.SplitId = anyValidSplitIDBytes[:15] }}, {name: "split ID/oversize", err: "invalid split ID: invalid UUID (got 17 bytes)", - corrupt: func(m *apiobject.SplitInfo) { m.SetSplitID(append(anyValidSplitIDBytes[:], 1)) }}, + corrupt: func(m *protoobject.SplitInfo) { m.SplitId = append(anyValidSplitIDBytes[:], 1) }}, {name: "split ID/wrong version", err: "invalid split ID: wrong UUID version 3, expected 4", - corrupt: func(m *apiobject.SplitInfo) { - b := bytes.Clone(anyValidSplitIDBytes[:]) - b[6] = 3 << 4 - m.SetSplitID(b) + corrupt: func(m *protoobject.SplitInfo) { + m.SplitId = bytes.Clone(anyValidSplitIDBytes[:]) + m.SplitId[6] = 3 << 4 }}, {name: "last part/nil value", err: "could not convert last part object ID: invalid length 0", - corrupt: func(m *apiobject.SplitInfo) { m.SetLastPart(protoIDFromBytes(nil)) }}, + corrupt: func(m *protoobject.SplitInfo) { m.LastPart = protoIDFromBytes(nil) }}, {name: "last part/empty value", err: "could not convert last part object ID: invalid length 0", - corrupt: func(m *apiobject.SplitInfo) { m.SetLastPart(protoIDFromBytes([]byte{})) }}, + corrupt: func(m *protoobject.SplitInfo) { m.LastPart = protoIDFromBytes([]byte{}) }}, {name: "last part/undersize", err: "could not convert last part object ID: invalid length 31", - corrupt: func(m *apiobject.SplitInfo) { m.SetLastPart(protoIDFromBytes(make([]byte, 31))) }}, + corrupt: func(m *protoobject.SplitInfo) { m.LastPart = protoIDFromBytes(make([]byte, 31)) }}, {name: "last part/oversize", err: "could not convert last part object ID: invalid length 33", - corrupt: func(m *apiobject.SplitInfo) { m.SetLastPart(protoIDFromBytes(make([]byte, 33))) }}, + corrupt: func(m *protoobject.SplitInfo) { m.LastPart = protoIDFromBytes(make([]byte, 33)) }}, {name: "link/nil value", err: "could not convert link object ID: invalid length 0", - corrupt: func(m *apiobject.SplitInfo) { m.SetLink(protoIDFromBytes(nil)) }}, + corrupt: func(m *protoobject.SplitInfo) { m.Link = protoIDFromBytes(nil) }}, {name: "link/empty value", err: "could not convert link object ID: invalid length 0", - corrupt: func(m *apiobject.SplitInfo) { m.SetLink(protoIDFromBytes([]byte{})) }}, + corrupt: func(m *protoobject.SplitInfo) { m.Link = protoIDFromBytes([]byte{}) }}, {name: "link/undersize", err: "could not convert link object ID: invalid length 31", - corrupt: func(m *apiobject.SplitInfo) { m.SetLink(protoIDFromBytes(make([]byte, 31))) }}, + corrupt: func(m *protoobject.SplitInfo) { m.Link = protoIDFromBytes(make([]byte, 31)) }}, {name: "link/oversize", err: "could not convert link object ID: invalid length 33", - corrupt: func(m *apiobject.SplitInfo) { m.SetLink(protoIDFromBytes(make([]byte, 33))) }}, + corrupt: func(m *protoobject.SplitInfo) { m.Link = protoIDFromBytes(make([]byte, 33)) }}, {name: "first part/nil value", err: "could not convert first part object ID: invalid length 0", - corrupt: func(m *apiobject.SplitInfo) { m.SetFirstPart(protoIDFromBytes(nil)) }}, + corrupt: func(m *protoobject.SplitInfo) { m.FirstPart = protoIDFromBytes(nil) }}, {name: "first part/empty value", err: "could not convert first part object ID: invalid length 0", - corrupt: func(m *apiobject.SplitInfo) { m.SetFirstPart(protoIDFromBytes([]byte{})) }}, + corrupt: func(m *protoobject.SplitInfo) { m.FirstPart = protoIDFromBytes([]byte{}) }}, {name: "first part/undersize", err: "could not convert first part object ID: invalid length 31", - corrupt: func(m *apiobject.SplitInfo) { m.SetFirstPart(protoIDFromBytes(make([]byte, 31))) }}, + corrupt: func(m *protoobject.SplitInfo) { m.FirstPart = protoIDFromBytes(make([]byte, 31)) }}, {name: "first part/oversize", err: "could not convert first part object ID: invalid length 33", - corrupt: func(m *apiobject.SplitInfo) { m.SetFirstPart(protoIDFromBytes(make([]byte, 33))) }}, + corrupt: func(m *protoobject.SplitInfo) { m.FirstPart = protoIDFromBytes(make([]byte, 33)) }}, } { t.Run(tc.name, func(t *testing.T) { s2 := s - m := s2.ToV2() + m := s2.ProtoMessage() tc.corrupt(m) - require.EqualError(t, new(object.SplitInfo).ReadFromV2(*m), tc.err) + require.EqualError(t, new(object.SplitInfo).FromProtoMessage(m), tc.err) }) } }) } -func TestSplitInfo_ToV2(t *testing.T) { +func TestSplitInfo_ProtoMessage(t *testing.T) { var s object.SplitInfo // zero - m := s.ToV2() - require.Zero(t, m.GetSplitID()) + m := s.ProtoMessage() + require.Zero(t, m.GetSplitId()) require.Zero(t, m.GetFirstPart()) require.Zero(t, m.GetLink()) require.Zero(t, m.GetFirstPart()) // filled - m = validSplitInfo.ToV2() - require.EqualValues(t, anyValidSplitIDBytes, m.GetSplitID()) + m = validSplitInfo.ProtoMessage() + require.EqualValues(t, anyValidSplitIDBytes, m.GetSplitId()) require.Equal(t, anyValidIDs[0][:], m.GetLastPart().GetValue()) require.Equal(t, anyValidIDs[1][:], m.GetLink().GetValue()) require.Equal(t, anyValidIDs[2][:], m.GetFirstPart().GetValue()) @@ -424,12 +416,12 @@ func TestNewSplitInfo(t *testing.T) { require.True(t, si.GetFirstPart().IsZero()) // convert to v2 message - siV2 := si.ToV2() + m := si.ProtoMessage() - require.Nil(t, siV2.GetSplitID()) - require.Nil(t, siV2.GetLastPart()) - require.Nil(t, siV2.GetLink()) - require.Nil(t, siV2.GetFirstPart()) + require.Nil(t, m.GetSplitId()) + require.Nil(t, m.GetLastPart()) + require.Nil(t, m.GetLink()) + require.Nil(t, m.GetFirstPart()) }) } diff --git a/object/test/generate.go b/object/test/generate.go index fb5010df..371d0d9e 100644 --- a/object/test/generate.go +++ b/object/test/generate.go @@ -5,7 +5,6 @@ import ( "strconv" "github.com/google/uuid" - objecttest "github.com/nspcc-dev/neofs-api-go/v2/object/test" checksumtest "github.com/nspcc-dev/neofs-sdk-go/checksum/test" cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test" "github.com/nspcc-dev/neofs-sdk-go/object" @@ -134,5 +133,12 @@ func Lock() *object.Lock { // Link returns random object.Link. func Link() *object.Link { - return (*object.Link)(objecttest.GenerateLink(false)) + ms := make([]object.MeasuredObject, rand.Int()%10) + for i := range ms { + ms[i].SetObjectID(oidtest.ID()) + ms[i].SetObjectSize(rand.Uint32()) + } + var l object.Link + l.SetObjects(ms) + return &l } diff --git a/object/test/generate_test.go b/object/test/generate_test.go index b89f26f2..058e8105 100644 --- a/object/test/generate_test.go +++ b/object/test/generate_test.go @@ -17,7 +17,7 @@ func TestObject(t *testing.T) { require.Equal(t, obj, dst1) var dst2 object.Object - require.NoError(t, dst2.ReadFromV2(*obj.ToV2())) + require.NoError(t, dst2.FromProtoMessage(obj.ProtoMessage())) require.Equal(t, obj, dst2) j, err := obj.MarshalJSON() diff --git a/object/tombstone.go b/object/tombstone.go index 83691b1d..70b12bab 100644 --- a/object/tombstone.go +++ b/object/tombstone.go @@ -4,21 +4,17 @@ import ( "fmt" "github.com/google/uuid" - "github.com/nspcc-dev/neofs-api-go/v2/refs" - "github.com/nspcc-dev/neofs-api-go/v2/tombstone" + neofsproto "github.com/nspcc-dev/neofs-sdk-go/internal/proto" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" + "github.com/nspcc-dev/neofs-sdk-go/proto/refs" + prototombstone "github.com/nspcc-dev/neofs-sdk-go/proto/tombstone" ) -// Tombstone represents v2-compatible tombstone structure. -type Tombstone tombstone.Tombstone - -// NewTombstoneFromV2 wraps v2 [tombstone.Tombstone] message to [Tombstone]. -// -// Nil [tombstone.Tombstone] converts to nil. -// Deprecated: BUG: members' ID length is not checked. Use -// [Tombstone.ReadFromV2] instead. -func NewTombstoneFromV2(tV2 *tombstone.Tombstone) *Tombstone { - return (*Tombstone)(tV2) +// Tombstone represents object tombstone structure. +type Tombstone struct { + exp uint64 + splitID []byte + members []oid.ID } // NewTombstone creates and initializes blank [Tombstone]. @@ -31,53 +27,66 @@ func NewTombstone() *Tombstone { return new(Tombstone) } -// ReadFromV2 reads Tombstone from the [tombstone.Tombstone] message. Returns an -// error if the message is malformed according to the NeoFS API V2 protocol. -// -// ReadFromV2 is intended to be used by the NeoFS API V2 client/server -// implementation only and is not expected to be directly used by applications. -func (t *Tombstone) ReadFromV2(m tombstone.Tombstone) error { - var id oid.ID - ms := m.GetMembers() - for i := range ms { - if err := id.ReadFromV2(ms[i]); err != nil { - return fmt.Errorf("invalid member #%d: %w", i, err) +// FromProtoMessage validates m according to the NeoFS API protocol and restores +// t from it. +// +// See also [Tombstone.ProtoMessage]. +func (t *Tombstone) FromProtoMessage(m *prototombstone.Tombstone) error { + if m.Members != nil { + t.members = make([]oid.ID, len(m.Members)) + for i := range m.Members { + if m.Members[i] == nil { + return fmt.Errorf("nil member #%d", i) + } + if err := t.members[i].FromProtoMessage(m.Members[i]); err != nil { + return fmt.Errorf("invalid member #%d: %w", i, err) + } } + } else { + t.members = nil } - if b := m.GetSplitID(); len(b) > 0 { + if t.splitID = m.SplitId; len(m.SplitId) > 0 { var uid uuid.UUID - if err := uid.UnmarshalBinary(b); err != nil { + if err := uid.UnmarshalBinary(m.SplitId); err != nil { return fmt.Errorf("invalid split ID: %w", err) } else if v := uid.Version(); v != 4 { return fmt.Errorf("invalid split ID: wrong UUID version %d, expected 4", v) } } - *t = Tombstone(m) + t.exp = m.ExpirationEpoch //nolint:staticcheck // must be supported still return nil } -// ToV2 converts [Tombstone] to v2 [tombstone.Tombstone] message. -// -// Nil [Tombstone] converts to nil. +// ProtoMessage converts t into message to transmit using the NeoFS API +// protocol. // -// The value returned shares memory with the structure itself, so changing it can lead to data corruption. -// Make a copy if you need to change it. -func (t Tombstone) ToV2() *tombstone.Tombstone { - return (*tombstone.Tombstone)(&t) +// See also [Tombstone.FromProtoMessage]. +func (t Tombstone) ProtoMessage() *prototombstone.Tombstone { + m := &prototombstone.Tombstone{ + ExpirationEpoch: t.exp, + SplitId: t.splitID, + } + if t.members != nil { + m.Members = make([]*refs.ObjectID, len(t.members)) + for i := range t.members { + m.Members[i] = t.members[i].ProtoMessage() + } + } + return m } // ExpirationEpoch returns the last NeoFS epoch number of the tombstone lifetime. // // See also [Tombstone.SetExpirationEpoch]. func (t Tombstone) ExpirationEpoch() uint64 { - return (*tombstone.Tombstone)(&t).GetExpirationEpoch() + return t.exp } // SetExpirationEpoch sets the last NeoFS epoch number of the tombstone lifetime. // // See also [Tombstone.ExpirationEpoch]. func (t *Tombstone) SetExpirationEpoch(v uint64) { - (*tombstone.Tombstone)(t).SetExpirationEpoch(v) + t.exp = v } // SplitID returns identifier of object split hierarchy. @@ -87,96 +96,54 @@ func (t *Tombstone) SetExpirationEpoch(v uint64) { // // See also [Tombstone.SetSplitID]. func (t Tombstone) SplitID() *SplitID { - return NewSplitIDFromV2( - (*tombstone.Tombstone)(&t).GetSplitID()) + return NewSplitIDFromV2(t.splitID) } // SetSplitID sets identifier of object split hierarchy. // // See also [Tombstone.SplitID]. func (t *Tombstone) SetSplitID(v *SplitID) { - (*tombstone.Tombstone)(t).SetSplitID(v.ToV2()) + t.splitID = v.ToV2() } // Members returns list of objects to be deleted. // // See also [Tombstone.SetMembers]. func (t Tombstone) Members() []oid.ID { - msV2 := (*tombstone.Tombstone)(&t).GetMembers() - - if msV2 == nil { - return nil - } - - res := make([]oid.ID, len(msV2)) - for i := range msV2 { - if err := res[i].ReadFromV2(msV2[i]); err != nil { - panic(fmt.Errorf("invalid member #%d: %w", i, err)) - } - } - - return res + return t.members } // SetMembers sets list of objects to be deleted. // // See also [Tombstone.Members]. func (t *Tombstone) SetMembers(v []oid.ID) { - var ms []refs.ObjectID - - if v != nil { - ms = (*tombstone.Tombstone)(t). - GetMembers() - - if ln := len(v); cap(ms) >= ln { - ms = ms[:0] - } else { - ms = make([]refs.ObjectID, 0, ln) - } - - var idV2 refs.ObjectID - - for i := range v { - v[i].WriteToV2(&idV2) - ms = append(ms, idV2) - } - } - - (*tombstone.Tombstone)(t).SetMembers(ms) + t.members = v } // Marshal marshals [Tombstone] into a protobuf binary form. // // See also [Tombstone.Unmarshal]. func (t Tombstone) Marshal() []byte { - return (*tombstone.Tombstone)(&t).StableMarshal(nil) + return neofsproto.Marshal(t) } // Unmarshal unmarshals protobuf binary representation of [Tombstone]. // // See also [Tombstone.Marshal]. func (t *Tombstone) Unmarshal(data []byte) error { - var m tombstone.Tombstone - if err := m.Unmarshal(data); err != nil { - return err - } - return t.ReadFromV2(m) + return neofsproto.Unmarshal(data, t) } // MarshalJSON encodes [Tombstone] to protobuf JSON format. // // See also [Tombstone.UnmarshalJSON]. func (t Tombstone) MarshalJSON() ([]byte, error) { - return (*tombstone.Tombstone)(&t).MarshalJSON() + return neofsproto.MarshalJSON(t) } // UnmarshalJSON decodes [Tombstone] from protobuf JSON format. // // See also [Tombstone.MarshalJSON]. func (t *Tombstone) UnmarshalJSON(data []byte) error { - var m tombstone.Tombstone - if err := m.UnmarshalJSON(data); err != nil { - return err - } - return t.ReadFromV2(m) + return neofsproto.UnmarshalJSON(data, t) } diff --git a/object/tombstone_test.go b/object/tombstone_test.go index 37d330ba..4a67d854 100644 --- a/object/tombstone_test.go +++ b/object/tombstone_test.go @@ -3,14 +3,14 @@ package object_test import ( "bytes" "encoding/json" - "slices" "testing" - "github.com/nspcc-dev/neofs-api-go/v2/refs" - "github.com/nspcc-dev/neofs-api-go/v2/tombstone" "github.com/nspcc-dev/neofs-sdk-go/object" oidtest "github.com/nspcc-dev/neofs-sdk-go/object/id/test" + "github.com/nspcc-dev/neofs-sdk-go/proto/refs" + prototombstone "github.com/nspcc-dev/neofs-sdk-go/proto/tombstone" "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" ) var validTombstone object.Tombstone // set by init. @@ -50,93 +50,85 @@ const validJSONTombstone = ` } ` -func TestTombstone_ReadFromV2(t *testing.T) { - ms := make([]refs.ObjectID, len(anyValidIDs)) +func TestTombstone_FromProtoMessage(t *testing.T) { + ms := make([]*refs.ObjectID, len(anyValidIDs)) for i := range anyValidIDs { - ms[i].SetValue(anyValidIDs[i][:]) + ms[i] = protoIDFromBytes(anyValidIDs[i][:]) } - var m tombstone.Tombstone - m.SetExpirationEpoch(anyValidExpirationEpoch) - m.SetSplitID(anyValidSplitIDBytes) - m.SetMembers(ms) + m := &prototombstone.Tombstone{ + ExpirationEpoch: anyValidExpirationEpoch, + SplitId: anyValidSplitIDBytes, + Members: ms, + } var ts object.Tombstone - require.NoError(t, ts.ReadFromV2(m)) + require.NoError(t, ts.FromProtoMessage(m)) require.EqualValues(t, anyValidExpirationEpoch, ts.ExpirationEpoch()) require.Equal(t, anyValidSplitID, ts.SplitID()) require.Equal(t, anyValidIDs, ts.Members()) // reset optional fields - m.SetExpirationEpoch(0) - m.SetSplitID(nil) - m.SetMembers(nil) + m.ExpirationEpoch = 0 //nolint:staticcheck // must be tested still + m.SplitId = nil + m.Members = nil ts2 := ts - require.NoError(t, ts2.ReadFromV2(m)) + require.NoError(t, ts2.FromProtoMessage(m)) require.Zero(t, ts2) t.Run("invalid", func(t *testing.T) { for _, tc := range []struct { name, err string - corrupt func(*tombstone.Tombstone) + corrupt func(*prototombstone.Tombstone) }{ {name: "members/nil value", err: "invalid member #1: invalid length 0", - corrupt: func(m *tombstone.Tombstone) { - ms := slices.Clone(ms) - ms[1] = *protoIDFromBytes(nil) - m.SetMembers(ms) + corrupt: func(m *prototombstone.Tombstone) { + m.Members[1].Value = nil }}, {name: "members/empty value", err: "invalid member #1: invalid length 0", - corrupt: func(m *tombstone.Tombstone) { - ms := slices.Clone(ms) - ms[1] = *protoIDFromBytes([]byte{}) - m.SetMembers(ms) + corrupt: func(m *prototombstone.Tombstone) { + m.Members[1].Value = []byte{} }}, {name: "members/undersize", err: "invalid member #1: invalid length 31", - corrupt: func(m *tombstone.Tombstone) { - ms := slices.Clone(ms) - ms[1] = *protoIDFromBytes(make([]byte, 31)) - m.SetMembers(ms) + corrupt: func(m *prototombstone.Tombstone) { + m.Members[1].Value = make([]byte, 31) }}, {name: "members/oversize", err: "invalid member #1: invalid length 33", - corrupt: func(m *tombstone.Tombstone) { - ms := slices.Clone(ms) - ms[1] = *protoIDFromBytes(make([]byte, 33)) - m.SetMembers(ms) + corrupt: func(m *prototombstone.Tombstone) { + m.Members[1].Value = make([]byte, 33) }}, {name: "split ID/undersize", err: "invalid split ID: invalid UUID (got 15 bytes)", - corrupt: func(m *tombstone.Tombstone) { m.SetSplitID(anyValidSplitIDBytes[:15]) }}, + corrupt: func(m *prototombstone.Tombstone) { m.SplitId = anyValidSplitIDBytes[:15] }}, {name: "split ID/oversize", err: "invalid split ID: invalid UUID (got 17 bytes)", - corrupt: func(m *tombstone.Tombstone) { m.SetSplitID(append(anyValidSplitIDBytes[:], 1)) }}, + corrupt: func(m *prototombstone.Tombstone) { m.SplitId = append(anyValidSplitIDBytes[:], 1) }}, {name: "split ID/wrong version", err: "invalid split ID: wrong UUID version 3, expected 4", - corrupt: func(m *tombstone.Tombstone) { - b := bytes.Clone(anyValidSplitIDBytes[:]) - b[6] = 3 << 4 - m.SetSplitID(b) + corrupt: func(m *prototombstone.Tombstone) { + m.SplitId = bytes.Clone(anyValidSplitIDBytes[:]) + m.SplitId[6] = 3 << 4 }}, } { t.Run(tc.name, func(t *testing.T) { - m := ts.ToV2() + m := proto.Clone(ts.ProtoMessage()).(*prototombstone.Tombstone) tc.corrupt(m) - require.EqualError(t, new(object.Tombstone).ReadFromV2(*m), tc.err) + require.EqualError(t, new(object.Tombstone).FromProtoMessage(m), tc.err) }) } }) } -func TestTombstone_WriteToV2(t *testing.T) { +func TestTombstone_ProtoMessage(t *testing.T) { var ts object.Tombstone // zero - m := ts.ToV2() - require.Zero(t, m.GetExpirationEpoch()) - require.Zero(t, m.GetSplitID()) + m := ts.ProtoMessage() + require.Zero(t, m.GetExpirationEpoch()) //nolint:staticcheck // must be tested still + require.Zero(t, m.GetSplitId()) require.Zero(t, m.GetMembers()) // filled - m = validTombstone.ToV2() - require.EqualValues(t, anyValidExpirationEpoch, m.GetExpirationEpoch()) - require.EqualValues(t, anyValidSplitIDBytes, m.GetSplitID()) + m = validTombstone.ProtoMessage() + require.EqualValues(t, anyValidExpirationEpoch, m.GetExpirationEpoch()) //nolint:staticcheck // must be tested still + require.EqualValues(t, anyValidSplitIDBytes, m.GetSplitId()) ms := m.GetMembers() require.Len(t, ms, 3) for i := range ms { @@ -273,14 +265,6 @@ func TestTombstone_SetMembers(t *testing.T) { require.Equal(t, otherIDs, ts.Members()) } -func TestNewTombstoneFromV2(t *testing.T) { - t.Run("from nil", func(t *testing.T) { - var x *tombstone.Tombstone - - require.Nil(t, object.NewTombstoneFromV2(x)) - }) -} - func TestNewTombstone(t *testing.T) { t.Run("default values", func(t *testing.T) { ts := object.NewTombstone() @@ -291,10 +275,10 @@ func TestNewTombstone(t *testing.T) { require.Zero(t, ts.ExpirationEpoch()) // convert to v2 message - tsV2 := ts.ToV2() + m := ts.ProtoMessage() - require.Nil(t, tsV2.GetSplitID()) - require.Nil(t, tsV2.GetMembers()) - require.Zero(t, tsV2.GetExpirationEpoch()) + require.Nil(t, m.GetSplitId()) + require.Nil(t, m.GetMembers()) + require.Zero(t, m.GetExpirationEpoch()) //nolint:staticcheck // must be tested still }) } diff --git a/object/type.go b/object/type.go index fc5550e7..b5494e54 100644 --- a/object/type.go +++ b/object/type.go @@ -2,12 +2,10 @@ package object import ( "strconv" - - "github.com/nspcc-dev/neofs-api-go/v2/object" ) // Type is an enumerator for possible object types. -type Type object.Type +type Type int32 const ( TypeRegular Type = iota @@ -17,18 +15,6 @@ const ( TypeLink ) -// ToV2 converts [Type] to v2 [object.Type]. -// Deprecated: cast instead. -func (t Type) ToV2() object.Type { - return object.Type(t) -} - -// TypeFromV2 converts v2 [object.Type] to [Type]. -// Deprecated: cast instead. -func TypeFromV2(t object.Type) Type { - return Type(t) -} - const ( typeStringRegular = "REGULAR" typeStringTombstone = "TOMBSTONE" @@ -65,7 +51,7 @@ func (t Type) EncodeToString() string { return t.String() } func (t Type) String() string { switch t { default: - return strconv.FormatUint(uint64(t), 10) + return strconv.FormatInt(int64(t), 10) case TypeRegular: return typeStringRegular case TypeTombstone: @@ -86,7 +72,7 @@ func (t Type) String() string { func (t *Type) DecodeString(s string) bool { switch s { default: - n, err := strconv.ParseUint(s, 10, 32) + n, err := strconv.ParseInt(s, 10, 32) if err != nil { return false } diff --git a/object/type_test.go b/object/type_test.go index 40d3b40e..1b57470f 100644 --- a/object/type_test.go +++ b/object/type_test.go @@ -3,8 +3,8 @@ package object_test import ( "testing" - v2object "github.com/nspcc-dev/neofs-api-go/v2/object" "github.com/nspcc-dev/neofs-sdk-go/object" + protoobject "github.com/nspcc-dev/neofs-sdk-go/proto/object" "github.com/stretchr/testify/require" ) @@ -17,49 +17,13 @@ var typeStrings = map[object.Type]string{ 5: "5", } -func TestType_ToV2(t *testing.T) { - typs := []struct { - t object.Type - t2 v2object.Type - }{ - { - t: object.TypeRegular, - t2: v2object.TypeRegular, - }, - { - t: object.TypeTombstone, - t2: v2object.TypeTombstone, - }, - { - t: object.TypeStorageGroup, - t2: v2object.TypeStorageGroup, - }, - { - t: object.TypeLock, - t2: v2object.TypeLock, - }, - { - t: object.TypeLink, - t2: v2object.TypeLink, - }, - } - - for _, item := range typs { - t2 := item.t.ToV2() - - require.Equal(t, item.t2, t2) - - require.Equal(t, item.t, object.TypeFromV2(item.t2)) - } -} - func TestTypeProto(t *testing.T) { - for x, y := range map[v2object.Type]object.Type{ - v2object.TypeRegular: object.TypeRegular, - v2object.TypeTombstone: object.TypeTombstone, - v2object.TypeStorageGroup: object.TypeStorageGroup, - v2object.TypeLock: object.TypeLock, - v2object.TypeLink: object.TypeLink, + for x, y := range map[protoobject.ObjectType]object.Type{ + protoobject.ObjectType_REGULAR: object.TypeRegular, + protoobject.ObjectType_TOMBSTONE: object.TypeTombstone, + protoobject.ObjectType_STORAGE_GROUP: object.TypeStorageGroup, + protoobject.ObjectType_LOCK: object.TypeLock, + protoobject.ObjectType_LINK: object.TypeLink, } { require.EqualValues(t, x, y) } diff --git a/object/util_test.go b/object/util_test.go index 64cf2c39..27b0a63a 100644 --- a/object/util_test.go +++ b/object/util_test.go @@ -3,12 +3,12 @@ package object_test import ( "crypto/sha256" - "github.com/nspcc-dev/neofs-api-go/v2/refs" "github.com/nspcc-dev/neofs-sdk-go/checksum" 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" + "github.com/nspcc-dev/neofs-sdk-go/proto/refs" "github.com/nspcc-dev/neofs-sdk-go/user" "github.com/nspcc-dev/neofs-sdk-go/version" "github.com/nspcc-dev/tzhash/tz" @@ -87,19 +87,13 @@ var ( ) func protoIDFromBytes(b []byte) *refs.ObjectID { - var id refs.ObjectID - id.SetValue(b) - return &id + return &refs.ObjectID{Value: b} } func protoUserIDFromBytes(b []byte) *refs.OwnerID { - var id refs.OwnerID - id.SetValue(b) - return &id + return &refs.OwnerID{Value: b} } func protoContainerIDFromBytes(b []byte) *refs.ContainerID { - var id refs.ContainerID - id.SetValue(b) - return &id + return &refs.ContainerID{Value: b} } diff --git a/pool/mock_test.go b/pool/mock_test.go index 766e4868..49506892 100644 --- a/pool/mock_test.go +++ b/pool/mock_test.go @@ -6,7 +6,6 @@ import ( "errors" "time" - netmapv2 "github.com/nspcc-dev/neofs-api-go/v2/netmap" "github.com/nspcc-dev/neofs-sdk-go/accounting" "github.com/nspcc-dev/neofs-sdk-go/client" "github.com/nspcc-dev/neofs-sdk-go/container" @@ -91,20 +90,7 @@ func (m *mockClient) NetworkInfo(_ context.Context, _ client.PrmNetworkInfo) (ne return ni, err } - var v2 netmapv2.NetworkInfo - var netConfig netmapv2.NetworkConfig - var p1 netmapv2.NetworkParameter - - p1.SetKey(randomBytes(16)) - p1.SetValue(randomBytes(16)) - - netConfig.SetParameters(p1) - v2.SetNetworkConfig(&netConfig) - - if err := ni.ReadFromV2(v2); err != nil { - return ni, err - } - + ni.SetRawNetworkParameter(string(randomBytes(16)), randomBytes(16)) ni.SetCurrentEpoch(uint64(time.Now().Unix())) ni.SetMaxObjectSize(1024) diff --git a/proto/reputation/encoding.go b/proto/reputation/encoding.go index 839ebfd4..fd1739a9 100644 --- a/proto/reputation/encoding.go +++ b/proto/reputation/encoding.go @@ -107,6 +107,36 @@ func (x *GlobalTrust_Body) MarshalStable(b []byte) { } } +const ( + _ = iota + fieldGlobalTrustVersion + fieldGlobalTrustBody + fieldGlobalTrustSignature +) + +// MarshaledSize returns size of the GlobalTrust in Protocol Buffers V3 format +// in bytes. MarshaledSize is NPE-safe. +func (x *GlobalTrust) MarshaledSize() int { + var sz int + if x != nil { + sz = proto.SizeEmbedded(fieldGlobalTrustVersion, x.Version) + + proto.SizeEmbedded(fieldGlobalTrustBody, x.Body) + + proto.SizeEmbedded(fieldGlobalTrustSignature, x.Signature) + } + return sz +} + +// MarshalStable writes the GlobalTrust in Protocol Buffers V3 format with +// ascending order of fields by number into b. MarshalStable uses exactly +// [GlobalTrust.MarshaledSize] first bytes of b. MarshalStable is NPE-safe. +func (x *GlobalTrust) MarshalStable(b []byte) { + if x != nil { + off := proto.MarshalToEmbedded(b, fieldGlobalTrustVersion, x.Version) + off += proto.MarshalToEmbedded(b[off:], fieldGlobalTrustBody, x.Body) + proto.MarshalToEmbedded(b[off:], fieldGlobalTrustSignature, x.Signature) + } +} + const ( _ = iota fieldAnnounceLocalReqEpoch diff --git a/proto/reputation/encoding_test.go b/proto/reputation/encoding_test.go index 8de30829..95ca6c14 100644 --- a/proto/reputation/encoding_test.go +++ b/proto/reputation/encoding_test.go @@ -1,9 +1,11 @@ package reputation_test import ( + "math/rand" "testing" prototest "github.com/nspcc-dev/neofs-sdk-go/proto/internal/test" + "github.com/nspcc-dev/neofs-sdk-go/proto/refs" "github.com/nspcc-dev/neofs-sdk-go/proto/reputation" ) @@ -59,6 +61,19 @@ func TestGlobalTrust_Body_MarshalStable(t *testing.T) { }) } +func TestGlobalTrust_MarshalStable(t *testing.T) { + prototest.TestMarshalStable(t, []*reputation.GlobalTrust{ + { + Version: &refs.Version{Major: rand.Uint32(), Minor: rand.Uint32()}, + Body: &reputation.GlobalTrust_Body{ + Manager: randPeerID(), + Trust: randTrust(), + }, + Signature: &refs.Signature{Key: []byte("any_pub"), Sign: []byte("any_sig"), Scheme: refs.SignatureScheme(rand.Int31())}, + }, + }) +} + func TestAnnounceLocalTrustRequest_Body_MarshalStable(t *testing.T) { prototest.TestMarshalStable(t, []*reputation.AnnounceLocalTrustRequest_Body{ { diff --git a/reputation/example_test.go b/reputation/example_test.go deleted file mode 100644 index 675969b7..00000000 --- a/reputation/example_test.go +++ /dev/null @@ -1,20 +0,0 @@ -package reputation_test - -import ( - apiGoReputation "github.com/nspcc-dev/neofs-api-go/v2/reputation" - "github.com/nspcc-dev/neofs-sdk-go/reputation" -) - -// Instances can be also used to process NeoFS API V2 protocol messages with [https://github.com/nspcc-dev/neofs-api] package. -func ExampleGlobalTrust_marshalling() { - // import apiGoReputation "github.com/nspcc-dev/neofs-api-go/v2/reputation" - - // On the client side. - var trust reputation.GlobalTrust - var msg apiGoReputation.GlobalTrust - trust.WriteToV2(&msg) - // *send message* - - // On the server side. - _ = trust.ReadFromV2(msg) -} diff --git a/reputation/peer.go b/reputation/peer.go index b2397527..4a23d1a7 100644 --- a/reputation/peer.go +++ b/reputation/peer.go @@ -6,41 +6,37 @@ import ( "fmt" "github.com/mr-tron/base58" - "github.com/nspcc-dev/neofs-api-go/v2/reputation" + protoreputation "github.com/nspcc-dev/neofs-sdk-go/proto/reputation" ) // PeerID represents unique identifier of the peer participating in the NeoFS // reputation system. // -// ID is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/reputation.PeerID -// message. See ReadFromV2 / WriteToV2 methods. +// ID is mutually compatible with [protoreputation.PeerID] message. See +// [PeerID.FromProtoMessage] / [PeerID.ProtoMessage] methods. // // Instances can be created using built-in var declaration. type PeerID struct { - m reputation.PeerID + key []byte } -// ReadFromV2 reads PeerID from the reputation.PeerID message. Returns an -// error if the message is malformed according to the NeoFS API V2 protocol. +// FromProtoMessage validates m according to the NeoFS API protocol and restores +// x from it. // -// See also WriteToV2. -func (x *PeerID) ReadFromV2(m reputation.PeerID) error { - val := m.GetPublicKey() - if len(val) == 0 { +// See also [PeerID.ProtoMessage]. +func (x *PeerID) FromProtoMessage(m *protoreputation.PeerID) error { + if x.key = m.PublicKey; len(m.PublicKey) == 0 { return errors.New("missing ID bytes") } - - x.m = m - return nil } -// WriteToV2 writes PeerID to the reputation.PeerID message. -// The message must not be nil. +// ProtoMessage converts x into message to transmit using the NeoFS API +// protocol. // -// See also ReadFromV2. -func (x PeerID) WriteToV2(m *reputation.PeerID) { - *m = x.m +// See also [PeerID.FromProtoMessage]. +func (x PeerID) ProtoMessage() *protoreputation.PeerID { + return &protoreputation.PeerID{PublicKey: x.key} } // SetPublicKey sets [PeerID] as a binary-encoded public key which authenticates @@ -52,7 +48,7 @@ func (x PeerID) WriteToV2(m *reputation.PeerID) { // // See also [ComparePeerKey]. func (x *PeerID) SetPublicKey(key []byte) { - x.m.SetPublicKey(key) + x.key = key } // PublicKey return public key set using [PeerID.SetPublicKey]. @@ -66,7 +62,7 @@ func (x *PeerID) SetPublicKey(key []byte) { // The value returned shares memory with the structure itself, so changing it can lead to data corruption. // Make a copy if you need to change it. func (x PeerID) PublicKey() []byte { - return x.m.GetPublicKey() + return x.key } // ComparePeerKey checks if the given PeerID corresponds to the party @@ -83,7 +79,7 @@ func ComparePeerKey(peer PeerID, key []byte) bool { // // See also DecodeString. func (x PeerID) EncodeToString() string { - return base58.Encode(x.m.GetPublicKey()) + return base58.Encode(x.key) } // DecodeString decodes string into PeerID according to NeoFS API protocol. @@ -96,7 +92,7 @@ func (x *PeerID) DecodeString(s string) error { return fmt.Errorf("decode base58: %w", err) } - x.m.SetPublicKey(data) + x.key = data return nil } diff --git a/reputation/peer_test.go b/reputation/peer_test.go index bfee237a..7fbec44e 100644 --- a/reputation/peer_test.go +++ b/reputation/peer_test.go @@ -3,7 +3,6 @@ package reputation_test import ( "testing" - v2reputation "github.com/nspcc-dev/neofs-api-go/v2/reputation" "github.com/nspcc-dev/neofs-sdk-go/reputation" reputationtest "github.com/nspcc-dev/neofs-sdk-go/reputation/test" "github.com/stretchr/testify/require" @@ -18,13 +17,12 @@ func TestPeerID_PublicKey(t *testing.T) { val.SetPublicKey(key) - var m v2reputation.PeerID - val.WriteToV2(&m) + m := val.ProtoMessage() require.Equal(t, key, m.GetPublicKey()) var val2 reputation.PeerID - require.NoError(t, val2.ReadFromV2(m)) + require.NoError(t, val2.FromProtoMessage(m)) require.Equal(t, key, val.PublicKey()) diff --git a/reputation/trust.go b/reputation/trust.go index 5c0dd651..8b27041a 100644 --- a/reputation/trust.go +++ b/reputation/trust.go @@ -4,55 +4,59 @@ import ( "errors" "fmt" - "github.com/nspcc-dev/neofs-api-go/v2/refs" - "github.com/nspcc-dev/neofs-api-go/v2/reputation" neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" + neofsproto "github.com/nspcc-dev/neofs-sdk-go/internal/proto" + protoreputation "github.com/nspcc-dev/neofs-sdk-go/proto/reputation" "github.com/nspcc-dev/neofs-sdk-go/version" ) // Trust represents quantitative assessment of the trust of a participant in the // NeoFS reputation system. // -// Trust is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/reputation.Trust -// message. See ReadFromV2 / WriteToV2 methods. +// Trust is mutually compatible with [protoreputation.Trust] message. See +// [Trust.FromProtoMessage] / [Trust.ProtoMessage] methods. // // Instances can be created using built-in var declaration. type Trust struct { - m reputation.Trust + peer PeerID + val float64 } -// ReadFromV2 reads Trust from the reputation.Trust message. Returns an -// error if the message is malformed according to the NeoFS API V2 protocol. +// FromProtoMessage validates m according to the NeoFS API protocol and restores +// x from it. // -// See also WriteToV2. -func (x *Trust) ReadFromV2(m reputation.Trust) error { - if val := m.GetValue(); val < 0 || val > 1 { - return fmt.Errorf("invalid trust value %v", val) +// See also [Table.ProtoMessage]. +func (x *Trust) FromProtoMessage(m *protoreputation.Trust) error { + if m.Value < 0 || m.Value > 1 { + return fmt.Errorf("invalid trust value %v", m.Value) } - peerV2 := m.GetPeer() - if peerV2 == nil { + if m.Peer == nil { return errors.New("missing peer field") } - var peer PeerID - - err := peer.ReadFromV2(*peerV2) + err := x.peer.FromProtoMessage(m.Peer) if err != nil { return fmt.Errorf("invalid peer field: %w", err) } - x.m = m + x.val = m.Value return nil } -// WriteToV2 writes Trust to the reputation.Trust message. -// The message must not be nil. +// ProtoMessage converts t into message to transmit using the NeoFS API +// protocol. // -// See also ReadFromV2. -func (x Trust) WriteToV2(m *reputation.Trust) { - *m = x.m +// See also [Table.FromProtoMessage]. +func (x Trust) ProtoMessage() *protoreputation.Trust { + m := &protoreputation.Trust{ + Value: x.val, + } + if x.peer.key != nil { + m.Peer = x.peer.ProtoMessage() + } + return m } // SetPeer specifies identifier of the participant of the NeoFS reputation system @@ -60,26 +64,15 @@ func (x Trust) WriteToV2(m *reputation.Trust) { // // See also Peer. func (x *Trust) SetPeer(id PeerID) { - var m reputation.PeerID - id.WriteToV2(&m) - - x.m.SetPeer(&m) + x.peer = id } // Peer returns peer identifier set using SetPeer. // // Zero Trust returns zero PeerID which is incorrect according to the NeoFS API // protocol. -func (x Trust) Peer() (res PeerID) { - m := x.m.GetPeer() - if m != nil { - err := res.ReadFromV2(*m) - if err != nil { - panic(fmt.Sprintf("unexpected error from ReadFromV2: %v", err)) - } - } - - return +func (x Trust) Peer() PeerID { + return x.peer } // SetValue sets the Trust value. Value MUST be in range [0;1]. @@ -89,69 +82,70 @@ func (x *Trust) SetValue(val float64) { if val < 0 || val > 1 { panic(fmt.Sprintf("trust value is out-of-range %v", val)) } - - x.m.SetValue(val) + x.val = val } // Value returns value set using SetValue. // // Zero Trust has zero value. func (x Trust) Value() float64 { - return x.m.GetValue() + return x.val } // PeerToPeerTrust represents trust of one participant of the NeoFS reputation // system to another one. // -// Trust is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/reputation.PeerToPeerTrust -// message. See ReadFromV2 / WriteToV2 methods. +// Trust is mutually compatible [protoreputation.PeerToPeerTrust] message. See +// [PeerToPeerTrust.FromProtoMessage] / [PeerToPeerTrust.ProtoMessage] methods. // // Instances can be created using built-in var declaration. type PeerToPeerTrust struct { - m reputation.PeerToPeerTrust + peer PeerID + trust *Trust } -// ReadFromV2 reads PeerToPeerTrust from the reputation.PeerToPeerTrust message. -// Returns an error if the message is malformed according to the NeoFS API V2 -// protocol. +// FromProtoMessage validates m according to the NeoFS API protocol and restores +// x from it. // -// See also WriteToV2. -func (x *PeerToPeerTrust) ReadFromV2(m reputation.PeerToPeerTrust) error { - trustingV2 := m.GetTrustingPeer() - if trustingV2 == nil { +// See also [PeerToPeerTrust.ProtoMessage]. +func (x *PeerToPeerTrust) FromProtoMessage(m *protoreputation.PeerToPeerTrust) error { + if m.TrustingPeer == nil { return errors.New("missing trusting peer") } - var trusting PeerID - - err := trusting.ReadFromV2(*trustingV2) + err := x.peer.FromProtoMessage(m.TrustingPeer) if err != nil { return fmt.Errorf("invalid trusting peer: %w", err) } - trustV2 := m.GetTrust() - if trustV2 == nil { + if m.Trust == nil { return errors.New("missing trust") } - var trust Trust - - err = trust.ReadFromV2(*trustV2) + if x.trust == nil { + x.trust = new(Trust) + } + err = x.trust.FromProtoMessage(m.Trust) if err != nil { return fmt.Errorf("invalid trust: %w", err) } - x.m = m - return nil } -// WriteToV2 writes PeerToPeerTrust to the reputation.PeerToPeerTrust message. -// The message must not be nil. +// ProtoMessage converts x into message to transmit using the NeoFS API +// protocol. // -// See also ReadFromV2. -func (x PeerToPeerTrust) WriteToV2(m *reputation.PeerToPeerTrust) { - *m = x.m +// See also [PeerToPeerTrust.FromProtoMessage]. +func (x PeerToPeerTrust) ProtoMessage() *protoreputation.PeerToPeerTrust { + var m protoreputation.PeerToPeerTrust + if x.peer.key != nil { + m.TrustingPeer = x.peer.ProtoMessage() + } + if x.trust != nil { + m.Trust = x.trust.ProtoMessage() + } + return &m } // SetTrustingPeer specifies the peer from which trust comes in terms of the @@ -159,26 +153,15 @@ func (x PeerToPeerTrust) WriteToV2(m *reputation.PeerToPeerTrust) { // // See also TrustingPeer. func (x *PeerToPeerTrust) SetTrustingPeer(id PeerID) { - var m reputation.PeerID - id.WriteToV2(&m) - - x.m.SetTrustingPeer(&m) + x.peer = id } // TrustingPeer returns peer set using SetTrustingPeer. // // Zero PeerToPeerTrust has no trusting peer which is incorrect according // to the NeoFS API protocol. -func (x PeerToPeerTrust) TrustingPeer() (res PeerID) { - m := x.m.GetTrustingPeer() - if m != nil { - err := res.ReadFromV2(*m) - if err != nil { - panic(fmt.Sprintf("unexpected error from PeerID.ReadFromV2: %v", err)) - } - } - - return +func (x PeerToPeerTrust) TrustingPeer() PeerID { + return x.peer } // SetTrust sets trust value of the trusting peer to another participant @@ -186,94 +169,119 @@ func (x PeerToPeerTrust) TrustingPeer() (res PeerID) { // // See also Trust. func (x *PeerToPeerTrust) SetTrust(t Trust) { - var tV2 reputation.Trust - t.WriteToV2(&tV2) - - x.m.SetTrust(&tV2) + x.trust = &t } // Trust returns trust set using SetTrust. // // Zero PeerToPeerTrust returns zero Trust which is incorrect according to the // NeoFS API protocol. -func (x PeerToPeerTrust) Trust() (res Trust) { - m := x.m.GetTrust() - if m != nil { - err := res.ReadFromV2(*m) - if err != nil { - panic(fmt.Sprintf("unexpected error from Trust.ReadFromV2: %v", err)) - } +func (x PeerToPeerTrust) Trust() Trust { + if x.trust != nil { + return *x.trust } - - return + return Trust{} } // GlobalTrust represents the final assessment of trust in the participant of // the NeoFS reputation system obtained taking into account all other participants. // -// GlobalTrust is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/reputation.GlobalTrust -// message. See ReadFromV2 / WriteToV2 methods. +// GlobalTrust is mutually compatible with [protoreputation.GlobalTrust] +// message. See [GlobalTrust.FromProtoMessage] / [GlobalTrust.ProtoMessage] methods. // // To submit GlobalTrust value in NeoFS zero instance SHOULD be declared, // initialized using Init method and filled using dedicated methods. type GlobalTrust struct { - m reputation.GlobalTrust + version *version.Version + manager PeerID + trust *Trust + sig *neofscrypto.Signature } -// ReadFromV2 reads GlobalTrust from the reputation.GlobalTrust message. -// Returns an error if the message is malformed according to the NeoFS API V2 -// protocol. +// FromProtoMessage validates m according to the NeoFS API protocol and restores +// x from it. // -// See also WriteToV2. -func (x *GlobalTrust) ReadFromV2(m reputation.GlobalTrust) error { - if m.GetVersion() == nil { +// See also [PeerID.ProtoMessage]. +func (x *GlobalTrust) FromProtoMessage(m *protoreputation.GlobalTrust) error { + if m.Version == nil { return errors.New("missing version") } - if m.GetSignature() == nil { + if x.version == nil { + x.version = new(version.Version) + } + if err := x.version.FromProtoMessage(m.Version); err != nil { + return fmt.Errorf("invalid version") + } + + if m.Signature == nil { return errors.New("missing signature") } - body := m.GetBody() - if body == nil { + if x.sig == nil { + x.sig = new(neofscrypto.Signature) + } + if err := x.sig.FromProtoMessage(m.Signature); err != nil { + return fmt.Errorf("invalid signature") + } + + if m.Body == nil { return errors.New("missing body") } - managerV2 := body.GetManager() - if managerV2 == nil { + if m.Body.Manager == nil { return errors.New("missing manager") } - var manager PeerID - - err := manager.ReadFromV2(*managerV2) + err := x.manager.FromProtoMessage(m.Body.Manager) if err != nil { return fmt.Errorf("invalid manager: %w", err) } - trustV2 := body.GetTrust() - if trustV2 == nil { + if m.Body.Trust == nil { return errors.New("missing trust") } - var trust Trust - - err = trust.ReadFromV2(*trustV2) + if x.trust == nil { + x.trust = new(Trust) + } + err = x.trust.FromProtoMessage(m.Body.Trust) if err != nil { return fmt.Errorf("invalid trust: %w", err) } - x.m = m - return nil } -// WriteToV2 writes GlobalTrust to the reputation.GlobalTrust message. -// The message must not be nil. +func (x GlobalTrust) protoBodyMessage() *protoreputation.GlobalTrust_Body { + if x.trust == nil && x.manager.key == nil { + return nil + } + var m protoreputation.GlobalTrust_Body + if x.trust != nil { + m.Trust = x.trust.ProtoMessage() + } + if x.manager.key != nil { + m.Manager = x.manager.ProtoMessage() + } + return &m +} + +// ProtoMessage converts x into message to transmit using the NeoFS API +// protocol. // -// See also ReadFromV2. -func (x GlobalTrust) WriteToV2(m *reputation.GlobalTrust) { - *m = x.m +// See also [GlobalTrust.FromProtoMessage]. +func (x GlobalTrust) ProtoMessage() *protoreputation.GlobalTrust { + m := &protoreputation.GlobalTrust{ + Body: x.protoBodyMessage(), + } + if x.version != nil { + m.Version = x.version.ProtoMessage() + } + if x.sig != nil { + m.Signature = x.sig.ProtoMessage() + } + return m } // Init initializes all internal data of the GlobalTrust required by NeoFS API @@ -281,22 +289,8 @@ func (x GlobalTrust) WriteToV2(m *reputation.GlobalTrust) { // Init SHOULD NOT be called multiple times. Init SHOULD NOT be called if // the GlobalTrust instance is used for decoding only. func (x *GlobalTrust) Init() { - var ver refs.Version - version.Current().WriteToV2(&ver) - - x.m.SetVersion(&ver) -} - -func (x *GlobalTrust) setBodyField(setter func(*reputation.GlobalTrustBody)) { - if x != nil { - body := x.m.GetBody() - if body == nil { - body = new(reputation.GlobalTrustBody) - x.m.SetBody(body) - } - - setter(body) - } + ver := version.Current() + x.version = &ver } // SetManager sets identifier of the NeoFS reputation system's participant which @@ -304,28 +298,15 @@ func (x *GlobalTrust) setBodyField(setter func(*reputation.GlobalTrustBody)) { // // See also Manager. func (x *GlobalTrust) SetManager(id PeerID) { - var m reputation.PeerID - id.WriteToV2(&m) - - x.setBodyField(func(body *reputation.GlobalTrustBody) { - body.SetManager(&m) - }) + x.manager = id } // Manager returns peer set using SetManager. // // Zero GlobalTrust has zero manager which is incorrect according to the // NeoFS API protocol. -func (x GlobalTrust) Manager() (res PeerID) { - m := x.m.GetBody().GetManager() - if m != nil { - err := res.ReadFromV2(*m) - if err != nil { - panic(fmt.Sprintf("unexpected error from ReadFromV2: %v", err)) - } - } - - return +func (x GlobalTrust) Manager() PeerID { + return x.manager } // SetTrust sets the global trust score of the network to a specific network @@ -333,28 +314,18 @@ func (x GlobalTrust) Manager() (res PeerID) { // // See also Trust. func (x *GlobalTrust) SetTrust(trust Trust) { - var m reputation.Trust - trust.WriteToV2(&m) - - x.setBodyField(func(body *reputation.GlobalTrustBody) { - body.SetTrust(&m) - }) + x.trust = &trust } // Trust returns trust set using SetTrust. // // Zero GlobalTrust return zero Trust which is incorrect according to the // NeoFS API protocol. -func (x GlobalTrust) Trust() (res Trust) { - m := x.m.GetBody().GetTrust() - if m != nil { - err := res.ReadFromV2(*m) - if err != nil { - panic(fmt.Sprintf("unexpected error from ReadFromV2: %v", err)) - } +func (x GlobalTrust) Trust() Trust { + if x.trust != nil { + return *x.trust } - - return + return Trust{} } // Sign calculates and writes signature of the [GlobalTrust] data. Returns @@ -369,15 +340,12 @@ func (x GlobalTrust) Trust() (res Trust) { func (x *GlobalTrust) Sign(signer neofscrypto.Signer) error { var sig neofscrypto.Signature - err := sig.CalculateMarshalled(signer, x.m.GetBody(), nil) + err := sig.Calculate(signer, x.SignedData()) if err != nil { return fmt.Errorf("calculate signature: %w", err) } - var sigv2 refs.Signature - sig.WriteToV2(&sigv2) - - x.m.SetSignature(&sigv2) + x.sig = &sig return nil } @@ -386,7 +354,7 @@ func (x *GlobalTrust) Sign(signer neofscrypto.Signer) error { // // See also [GlobalTrust.Sign]. func (x *GlobalTrust) SignedData() []byte { - return x.m.GetBody().StableMarshal(nil) + return neofsproto.MarshalMessage(x.protoBodyMessage()) } // VerifySignature checks if GlobalTrust signature is presented and valid. @@ -395,14 +363,7 @@ func (x *GlobalTrust) SignedData() []byte { // // See also Sign. func (x GlobalTrust) VerifySignature() bool { - sigV2 := x.m.GetSignature() - if sigV2 == nil { - return false - } - - var sig neofscrypto.Signature - - return sig.ReadFromV2(*sigV2) == nil && sig.Verify(x.m.GetBody().StableMarshal(nil)) + return x.sig != nil && x.sig.Verify(x.SignedData()) } // Marshal encodes GlobalTrust into a binary format of the NeoFS API protocol @@ -410,7 +371,7 @@ func (x GlobalTrust) VerifySignature() bool { // // See also Unmarshal. func (x GlobalTrust) Marshal() []byte { - return x.m.StableMarshal(nil) + return neofsproto.Marshal(x) } // Unmarshal decodes NeoFS API protocol binary format into the GlobalTrust @@ -419,5 +380,5 @@ func (x GlobalTrust) Marshal() []byte { // // See also Marshal. func (x *GlobalTrust) Unmarshal(data []byte) error { - return x.m.Unmarshal(data) + return neofsproto.Unmarshal(data, x) } diff --git a/reputation/trust_test.go b/reputation/trust_test.go index ea9c3708..56d4a94b 100644 --- a/reputation/trust_test.go +++ b/reputation/trust_test.go @@ -3,8 +3,6 @@ package reputation_test import ( "testing" - "github.com/nspcc-dev/neofs-api-go/v2/refs" - v2reputation "github.com/nspcc-dev/neofs-api-go/v2/reputation" neofscryptotest "github.com/nspcc-dev/neofs-sdk-go/crypto/test" "github.com/nspcc-dev/neofs-sdk-go/reputation" reputationtest "github.com/nspcc-dev/neofs-sdk-go/reputation/test" @@ -23,16 +21,12 @@ func TestTrust_Peer(t *testing.T) { trust.SetPeer(peer) - var peerV2 v2reputation.PeerID - peer.WriteToV2(&peerV2) + mt := trust.ProtoMessage() - var trustV2 v2reputation.Trust - trust.WriteToV2(&trustV2) - - require.Equal(t, &peerV2, trustV2.GetPeer()) + require.Equal(t, peer.ProtoMessage(), mt.GetPeer()) var val2 reputation.Trust - require.NoError(t, val2.ReadFromV2(trustV2)) + require.NoError(t, val2.FromProtoMessage(mt)) require.Equal(t, peer, val2.Peer()) } @@ -48,13 +42,12 @@ func TestTrust_Value(t *testing.T) { val.SetValue(value) - var trustV2 v2reputation.Trust - val.WriteToV2(&trustV2) + m := val.ProtoMessage() - require.EqualValues(t, value, trustV2.GetValue()) + require.EqualValues(t, value, m.GetValue()) var val2 reputation.Trust - require.NoError(t, val2.ReadFromV2(trustV2)) + require.NoError(t, val2.FromProtoMessage(m)) require.EqualValues(t, value, val2.Value()) } @@ -70,16 +63,12 @@ func TestPeerToPeerTrust_TrustingPeer(t *testing.T) { val.SetTrustingPeer(peer) - var peerV2 v2reputation.PeerID - peer.WriteToV2(&peerV2) - - var trustV2 v2reputation.PeerToPeerTrust - val.WriteToV2(&trustV2) + m := val.ProtoMessage() - require.Equal(t, &peerV2, trustV2.GetTrustingPeer()) + require.Equal(t, peer.ProtoMessage(), m.GetTrustingPeer()) var val2 reputation.PeerToPeerTrust - require.NoError(t, val2.ReadFromV2(trustV2)) + require.NoError(t, val2.FromProtoMessage(m)) require.Equal(t, peer, val2.TrustingPeer()) } @@ -95,16 +84,12 @@ func TestPeerToPeerTrust_Trust(t *testing.T) { val.SetTrust(trust) - var trustV2 v2reputation.Trust - trust.WriteToV2(&trustV2) - - var valV2 v2reputation.PeerToPeerTrust - val.WriteToV2(&valV2) + m := val.ProtoMessage() - require.Equal(t, &trustV2, valV2.GetTrust()) + require.Equal(t, trust.ProtoMessage(), m.GetTrust()) var val2 reputation.PeerToPeerTrust - require.NoError(t, val2.ReadFromV2(valV2)) + require.NoError(t, val2.FromProtoMessage(m)) require.Equal(t, trust, val2.Trust()) } @@ -113,13 +98,9 @@ func TestGlobalTrust_Init(t *testing.T) { var val reputation.GlobalTrust val.Init() - var valV2 v2reputation.GlobalTrust - val.WriteToV2(&valV2) + m := val.ProtoMessage() - var verV2 refs.Version - version.Current().WriteToV2(&verV2) - - require.Equal(t, &verV2, valV2.GetVersion()) + require.Equal(t, version.Current().ProtoMessage(), m.GetVersion()) } func TestGlobalTrust_Manager(t *testing.T) { @@ -133,16 +114,12 @@ func TestGlobalTrust_Manager(t *testing.T) { val.SetManager(peer) - var peerV2 v2reputation.PeerID - peer.WriteToV2(&peerV2) - - var trustV2 v2reputation.GlobalTrust - val.WriteToV2(&trustV2) + m := val.ProtoMessage() - require.Equal(t, &peerV2, trustV2.GetBody().GetManager()) + require.Equal(t, peer.ProtoMessage(), m.GetBody().GetManager()) var val2 reputation.GlobalTrust - require.NoError(t, val2.ReadFromV2(trustV2)) + require.NoError(t, val2.FromProtoMessage(m)) require.Equal(t, peer, val2.Manager()) } @@ -158,16 +135,12 @@ func TestGlobalTrust_Trust(t *testing.T) { val.SetTrust(trust) - var trustV2 v2reputation.Trust - trust.WriteToV2(&trustV2) - - var valV2 v2reputation.GlobalTrust - val.WriteToV2(&valV2) + m := val.ProtoMessage() - require.Equal(t, &trustV2, valV2.GetBody().GetTrust()) + require.Equal(t, trust.ProtoMessage(), m.GetBody().GetTrust()) var val2 reputation.GlobalTrust - require.NoError(t, val2.ReadFromV2(valV2)) + require.NoError(t, val2.FromProtoMessage(m)) require.Equal(t, trust, val2.Trust()) } @@ -179,13 +152,12 @@ func TestGlobalTrust_Sign(t *testing.T) { require.NoError(t, val.Sign(neofscryptotest.Signer())) - var valV2 v2reputation.GlobalTrust - val.WriteToV2(&valV2) + m := val.ProtoMessage() - require.NotZero(t, valV2.GetSignature()) + require.NotZero(t, m.GetSignature()) var val2 reputation.GlobalTrust - require.NoError(t, val2.ReadFromV2(valV2)) + require.NoError(t, val2.FromProtoMessage(m)) require.True(t, val2.VerifySignature()) } diff --git a/session/common.go b/session/common.go index 4246cb1e..49adee3d 100644 --- a/session/common.go +++ b/session/common.go @@ -6,9 +6,9 @@ import ( "fmt" "github.com/google/uuid" - "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" + neofsproto "github.com/nspcc-dev/neofs-sdk-go/internal/proto" + protosession "github.com/nspcc-dev/neofs-sdk-go/proto/session" "github.com/nspcc-dev/neofs-sdk-go/user" ) @@ -26,7 +26,7 @@ type commonData struct { sig neofscrypto.Signature } -type contextReader func(session.TokenContext, bool) error +type contextReader func(any, bool) error func (x commonData) copyTo(dst *commonData) { dst.idSet = x.idSet @@ -46,7 +46,7 @@ func (x commonData) copyTo(dst *commonData) { // If checkFieldPresence is set, returns an error on absence of any protocol-required // field. Verifies format of any presented field according to NeoFS API V2 protocol. // Calls contextReader if session context is set. Passes checkFieldPresence into contextReader. -func (x *commonData) readFromV2(m session.Token, checkFieldPresence bool, r contextReader) error { +func (x *commonData) fromProtoMessage(m *protosession.SessionToken, checkFieldPresence bool, r contextReader) error { var err error body := m.GetBody() @@ -54,7 +54,7 @@ func (x *commonData) readFromV2(m session.Token, checkFieldPresence bool, r cont return errors.New("missing token body") } - binID := body.GetID() + binID := body.GetId() if x.idSet = len(binID) > 0; x.idSet { err = x.id.UnmarshalBinary(binID) if err != nil { @@ -64,11 +64,13 @@ func (x *commonData) readFromV2(m session.Token, checkFieldPresence bool, r cont } } else if checkFieldPresence { return errors.New("missing session ID") + } else { + x.id = uuid.Nil } - issuer := body.GetOwnerID() + issuer := body.GetOwnerId() if issuer != nil { - err = x.issuer.ReadFromV2(*issuer) + err = x.issuer.FromProtoMessage(issuer) if err != nil { return fmt.Errorf("invalid session issuer: %w", err) } @@ -98,9 +100,8 @@ func (x *commonData) readFromV2(m session.Token, checkFieldPresence bool, r cont return errors.New("missing session context") } - sig := m.GetSignature() - if x.sigSet = sig != nil; sig != nil { - if err = x.sig.ReadFromV2(*sig); err != nil { + if x.sigSet = m.Signature != nil; x.sigSet { + if err = x.sig.FromProtoMessage(m.Signature); err != nil { return fmt.Errorf("invalid body signature: %w", err) } } else if checkFieldPresence { @@ -114,68 +115,53 @@ func (x *commonData) readFromV2(m session.Token, checkFieldPresence bool, r cont return nil } -type contextWriter func() session.TokenContext - -func (x commonData) fillBody(w contextWriter) *session.TokenBody { - var body session.TokenBody +type contextWriter func(body *protosession.SessionToken_Body) - if x.idSet { - binID, err := x.id.MarshalBinary() - if err != nil { - panic(fmt.Sprintf("unexpected error from UUID.MarshalBinary: %v", err)) - } +func (x commonData) fillBody(w contextWriter) *protosession.SessionToken_Body { + var body protosession.SessionToken_Body - body.SetID(binID) + if x.id != uuid.Nil { + body.Id = x.id[:] } if !x.issuer.IsZero() { - var issuer refs.OwnerID - x.issuer.WriteToV2(&issuer) - - body.SetOwnerID(&issuer) + body.OwnerId = x.issuer.ProtoMessage() } if x.iat != 0 || x.nbf != 0 || x.exp != 0 { - var lifetime session.TokenLifetime - lifetime.SetIat(x.iat) - lifetime.SetNbf(x.nbf) - lifetime.SetExp(x.exp) - - body.SetLifetime(&lifetime) + body.Lifetime = &protosession.SessionToken_Body_TokenLifetime{ + Exp: x.exp, + Nbf: x.nbf, + Iat: x.iat, + } } - body.SetSessionKey(x.authKey) + body.SessionKey = x.authKey - body.SetContext(w()) + w(&body) return &body } -func (x commonData) writeToV2(m *session.Token, w contextWriter) { - body := x.fillBody(w) - - m.SetBody(body) - - var sig *refs.Signature +func (x commonData) protoMessage(w contextWriter) *protosession.SessionToken { + m := &protosession.SessionToken{ + Body: x.fillBody(w), + } if x.sigSet { - sig = new(refs.Signature) - x.sig.WriteToV2(sig) + m.Signature = x.sig.ProtoMessage() } - m.SetSignature(sig) + return m } func (x commonData) signedData(w contextWriter) []byte { - return x.fillBody(w).StableMarshal(nil) + return neofsproto.MarshalMessage(x.fillBody(w)) } func (x *commonData) sign(signer neofscrypto.Signer, w contextWriter) error { - err := x.sig.Calculate(signer, x.signedData(w)) - if err == nil { - x.sigSet = true - } - return err + x.sigSet = true + return x.sig.Calculate(signer, x.signedData(w)) } func (x commonData) verifySignature(w contextWriter) bool { @@ -184,39 +170,33 @@ func (x commonData) verifySignature(w contextWriter) bool { } func (x commonData) marshal(w contextWriter) []byte { - var m session.Token - x.writeToV2(&m, w) - - return m.StableMarshal(nil) + return neofsproto.MarshalMessage(x.protoMessage(w)) } func (x *commonData) unmarshal(data []byte, r contextReader) error { - var m session.Token + var m protosession.SessionToken - err := m.Unmarshal(data) + err := neofsproto.UnmarshalMessage(data, &m) if err != nil { return err } - return x.readFromV2(m, false, r) + return x.fromProtoMessage(&m, false, r) } func (x commonData) marshalJSON(w contextWriter) ([]byte, error) { - var m session.Token - x.writeToV2(&m, w) - - return m.MarshalJSON() + return neofsproto.MarshalMessageJSON(x.protoMessage(w)) } func (x *commonData) unmarshalJSON(data []byte, r contextReader) error { - var m session.Token + var m protosession.SessionToken - err := m.UnmarshalJSON(data) + err := neofsproto.UnmarshalMessageJSON(data, &m) if err != nil { return err } - return x.readFromV2(m, false, r) + return x.fromProtoMessage(&m, false, r) } // SetExp sets "exp" (expiration time) claim which identifies the expiration @@ -298,8 +278,7 @@ func (x commonData) InvalidAt(epoch uint64) bool { return !x.ValidAt(epoch) } // // See also ID. func (x *commonData) SetID(id uuid.UUID) { - x.id = id - x.idSet = true + x.id, x.idSet = id, true } // ID returns a unique identifier for the session. @@ -309,11 +288,7 @@ func (x *commonData) SetID(id uuid.UUID) { // // See also SetID. func (x commonData) ID() uuid.UUID { - if x.idSet { - return x.id - } - - return uuid.Nil + return x.id } // SetAuthKey public key corresponding to the private key bound to the session. @@ -351,7 +326,7 @@ func (x commonData) Issuer() user.ID { // IssuerPublicKeyBytes returns binary-encoded public key of the session issuer. // -// IssuerPublicKeyBytes MUST NOT be called before ReadFromV2 or Sign methods. +// IssuerPublicKeyBytes MUST NOT be called before ProtoMessage or Sign methods. // Deprecated: use Signature method. func (x *commonData) IssuerPublicKeyBytes() []byte { if sig, ok := x.Signature(); ok { diff --git a/session/common_internal_test.go b/session/common_internal_test.go index 4baab9f9..31443754 100644 --- a/session/common_internal_test.go +++ b/session/common_internal_test.go @@ -5,8 +5,8 @@ import ( "testing" "github.com/google/uuid" - "github.com/nspcc-dev/neofs-api-go/v2/session" neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" + protosession "github.com/nspcc-dev/neofs-sdk-go/proto/session" usertest "github.com/nspcc-dev/neofs-sdk-go/user/test" "github.com/stretchr/testify/require" ) @@ -32,8 +32,8 @@ func Test_commonData_copyTo(t *testing.T) { var dst commonData data.copyTo(&dst) - emptyWriter := func() session.TokenContext { - return &session.ContainerSessionContext{} + emptyWriter := func(body *protosession.SessionToken_Body) { + body.Context = &protosession.SessionToken_Body_Container{} } require.Equal(t, data, dst) @@ -68,8 +68,8 @@ func Test_commonData_copyTo(t *testing.T) { // overwrite ID data local.copyTo(&dst) - emptyWriter := func() session.TokenContext { - return &session.ContainerSessionContext{} + emptyWriter := func(body *protosession.SessionToken_Body) { + body.Context = &protosession.SessionToken_Body_Container{} } require.True(t, bytes.Equal(local.marshal(emptyWriter), dst.marshal(emptyWriter))) @@ -107,8 +107,8 @@ func Test_commonData_copyTo(t *testing.T) { require.Zero(t, local.issuer) require.Zero(t, dst.issuer) - emptyWriter := func() session.TokenContext { - return &session.ContainerSessionContext{} + emptyWriter := func(body *protosession.SessionToken_Body) { + body.Context = &protosession.SessionToken_Body_Container{} } require.True(t, bytes.Equal(local.marshal(emptyWriter), dst.marshal(emptyWriter))) @@ -150,8 +150,8 @@ func Test_commonData_copyTo(t *testing.T) { local.copyTo(&dst) - emptyWriter := func() session.TokenContext { - return &session.ContainerSessionContext{} + emptyWriter := func(body *protosession.SessionToken_Body) { + body.Context = &protosession.SessionToken_Body_Container{} } require.True(t, bytes.Equal(local.marshal(emptyWriter), dst.marshal(emptyWriter))) @@ -175,10 +175,7 @@ func Test_commonData_copyTo(t *testing.T) { var dst commonData data.copyTo(&dst) - require.Equal(t, data.sigSet, dst.sigSet) - require.Equal(t, data.sig.Scheme(), dst.sig.Scheme()) - require.True(t, bytes.Equal(data.sig.PublicKeyBytes(), dst.sig.PublicKeyBytes())) - require.True(t, bytes.Equal(data.sig.Value(), dst.sig.Value())) + require.Equal(t, data.sig, dst.sig) dst.sig.SetPublicKeyBytes([]byte{1, 2, 3}) dst.sig.SetScheme(100) diff --git a/session/common_test.go b/session/common_test.go index 89c71233..d3839035 100644 --- a/session/common_test.go +++ b/session/common_test.go @@ -7,17 +7,16 @@ import ( "crypto/sha512" "encoding/base64" "encoding/hex" - "math" "math/big" "testing" "github.com/google/uuid" "github.com/nspcc-dev/neo-go/pkg/io" - apisession "github.com/nspcc-dev/neofs-api-go/v2/session" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa" neofscryptotest "github.com/nspcc-dev/neofs-sdk-go/crypto/test" + 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/user" usertest "github.com/nspcc-dev/neofs-sdk-go/user/test" @@ -65,67 +64,66 @@ var ( type invalidProtoTokenTestcase struct { name, err string - corrupt func(*apisession.Token) + corrupt func(*protosession.SessionToken) } var invalidProtoTokenCommonTestcases = []invalidProtoTokenTestcase{ - {name: "missing body", err: "missing token body", corrupt: func(st *apisession.Token) { - st.SetBody(nil) + {name: "missing body", err: "missing token body", corrupt: func(st *protosession.SessionToken) { + st.Body = nil }}, - {name: "missing body signature", err: "missing body signature", corrupt: func(st *apisession.Token) { - st.SetSignature(nil) + {name: "missing body signature", err: "missing body signature", corrupt: func(st *protosession.SessionToken) { + st.Signature = nil }}, - {name: "body/ID/nil", err: "missing session ID", corrupt: func(st *apisession.Token) { - st.GetBody().SetID(nil) + {name: "body/ID/nil", err: "missing session ID", corrupt: func(st *protosession.SessionToken) { + st.Body.Id = nil }}, - {name: "body/ID/empty", err: "missing session ID", corrupt: func(st *apisession.Token) { - st.GetBody().SetID([]byte{}) + {name: "body/ID/empty", err: "missing session ID", corrupt: func(st *protosession.SessionToken) { + st.Body.Id = []byte{} }}, - {name: "body/ID/undersize", err: "invalid session ID: invalid UUID (got 15 bytes)", corrupt: func(st *apisession.Token) { - st.GetBody().SetID(make([]byte, 15)) + {name: "body/ID/undersize", err: "invalid session ID: invalid UUID (got 15 bytes)", corrupt: func(st *protosession.SessionToken) { + st.Body.Id = make([]byte, 15) }}, - {name: "body/ID/wrong UUID version", err: "invalid session ID: wrong UUID version 3, expected 4", corrupt: func(st *apisession.Token) { - st.GetBody().GetID()[6] = 3 << 4 + {name: "body/ID/wrong UUID version", err: "invalid session ID: wrong UUID version 3, expected 4", corrupt: func(st *protosession.SessionToken) { + st.Body.Id[6] = 3 << 4 }}, - {name: "body/ID/oversize", err: "invalid session ID: invalid UUID (got 17 bytes)", corrupt: func(st *apisession.Token) { - st.GetBody().SetID(make([]byte, 17)) + {name: "body/ID/oversize", err: "invalid session ID: invalid UUID (got 17 bytes)", corrupt: func(st *protosession.SessionToken) { + st.Body.Id = make([]byte, 17) }}, - {name: "body/issuer", err: "missing session issuer", corrupt: func(st *apisession.Token) { - st.GetBody().SetOwnerID(nil) + {name: "body/issuer", err: "missing session issuer", corrupt: func(st *protosession.SessionToken) { + st.Body.OwnerId = nil }}, - {name: "body/issuer/value/nil", err: "invalid session issuer: invalid length 0, expected 25", corrupt: func(st *apisession.Token) { - st.GetBody().GetOwnerID().SetValue(nil) + {name: "body/issuer/value/nil", err: "invalid session issuer: invalid length 0, expected 25", corrupt: func(st *protosession.SessionToken) { + st.Body.OwnerId.Value = nil }}, - {name: "body/issuer/value/empty", err: "invalid session issuer: invalid length 0, expected 25", corrupt: func(st *apisession.Token) { - st.GetBody().GetOwnerID().SetValue([]byte{}) + {name: "body/issuer/value/empty", err: "invalid session issuer: invalid length 0, expected 25", corrupt: func(st *protosession.SessionToken) { + st.Body.OwnerId.Value = []byte{} }}, - {name: "body/issuer/value/undersize", err: "invalid session issuer: invalid length 24, expected 25", corrupt: func(st *apisession.Token) { - st.GetBody().GetOwnerID().SetValue(make([]byte, 24)) + {name: "body/issuer/value/undersize", err: "invalid session issuer: invalid length 24, expected 25", corrupt: func(st *protosession.SessionToken) { + st.Body.OwnerId.Value = make([]byte, 24) }}, - {name: "body/issuer/value/oversize", err: "invalid session issuer: invalid length 26, expected 25", corrupt: func(st *apisession.Token) { - st.GetBody().GetOwnerID().SetValue(make([]byte, 26)) + {name: "body/issuer/value/oversize", err: "invalid session issuer: invalid length 26, expected 25", corrupt: func(st *protosession.SessionToken) { + st.Body.OwnerId.Value = make([]byte, 26) }}, - {name: "body/issuer/value/wrong prefix", err: "invalid session issuer: invalid prefix byte 0x42, expected 0x35", corrupt: func(st *apisession.Token) { - st.GetBody().GetOwnerID().GetValue()[0] = 0x42 + {name: "body/issuer/value/wrong prefix", err: "invalid session issuer: invalid prefix byte 0x42, expected 0x35", corrupt: func(st *protosession.SessionToken) { + st.Body.OwnerId.Value[0] = 0x42 }}, - {name: "body/issuer/value/checksum mismatch", err: "invalid session issuer: checksum mismatch", corrupt: func(st *apisession.Token) { - v := st.GetBody().GetOwnerID().GetValue() - v[len(v)-1]++ + {name: "body/issuer/value/checksum mismatch", err: "invalid session issuer: checksum mismatch", corrupt: func(st *protosession.SessionToken) { + st.Body.OwnerId.Value[24]++ }}, - {name: "body/lifetime", err: "missing token lifetime", corrupt: func(st *apisession.Token) { - st.GetBody().SetLifetime(nil) + {name: "body/lifetime", err: "missing token lifetime", corrupt: func(st *protosession.SessionToken) { + st.Body.Lifetime = nil }}, - {name: "body/session key/nil", err: "missing session public key", corrupt: func(st *apisession.Token) { - st.GetBody().SetSessionKey(nil) + {name: "body/session key/nil", err: "missing session public key", corrupt: func(st *protosession.SessionToken) { + st.Body.SessionKey = nil }}, - {name: "body/session key/empty", err: "missing session public key", corrupt: func(st *apisession.Token) { - st.GetBody().SetSessionKey([]byte{}) + {name: "body/session key/empty", err: "missing session public key", corrupt: func(st *protosession.SessionToken) { + st.Body.SessionKey = []byte{} }}, - {name: "body/context/nil", err: "missing session context", corrupt: func(st *apisession.Token) { - st.GetBody().SetContext(nil) + {name: "body/context/nil", err: "missing session context", corrupt: func(st *protosession.SessionToken) { + st.Body.Context = nil }}, - {name: "signature/invalid scheme", err: "invalid body signature: scheme 2147483648 overflows int32", corrupt: func(st *apisession.Token) { - st.GetSignature().SetScheme(math.MaxInt32 + 1) + {name: "signature/scheme/negative", err: "invalid body signature: negative scheme -1", corrupt: func(st *protosession.SessionToken) { + st.Signature.Scheme = -1 }}, } @@ -155,7 +153,7 @@ var invalidBinTokenCommonTestcases = []invalidBinTokenTestcase{ {name: "body/issuer/value/checksum mismatch", err: "invalid session issuer: checksum mismatch", b: []byte{10, 29, 18, 27, 10, 25, 53, 51, 5, 166, 111, 29, 20, 101, 192, 165, 28, 167, 57, 160, 82, 80, 41, 203, 20, 254, 30, 138, 195, 17, 93}}, - {name: "signature/invalid scheme", err: "invalid body signature: scheme 2147483648 overflows int32", + {name: "signature/invalid scheme", err: "invalid body signature: negative scheme -2147483648", b: []byte{18, 11, 24, 128, 128, 128, 128, 248, 255, 255, 255, 255, 1}}, } @@ -212,7 +210,7 @@ var invalidJSONTokenCommonTestcases = []invalidJSONTokenTestcase{ {name: "body/issuer/value/checksum mismatch", err: "invalid session issuer: checksum mismatch", j: ` {"body":{"ownerID":{"value":"NTMFpm8dFGXApRynOaBSUCnLFP4eisMRXQ=="}}} `}, - {name: "signature/invalid scheme", err: "invalid body signature: scheme 2147483648 overflows int32", j: ` + {name: "signature/invalid scheme", err: "invalid body signature: negative scheme -2147483648", j: ` {"signature":{"scheme":-2147483648}} `}, } diff --git a/session/container.go b/session/container.go index 253b5514..44ebf8c4 100644 --- a/session/container.go +++ b/session/container.go @@ -3,12 +3,11 @@ package session import ( "errors" "fmt" - "math" - "github.com/nspcc-dev/neofs-api-go/v2/refs" - "github.com/nspcc-dev/neofs-api-go/v2/session" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" + neofsproto "github.com/nspcc-dev/neofs-sdk-go/internal/proto" + protosession "github.com/nspcc-dev/neofs-sdk-go/proto/session" "github.com/nspcc-dev/neofs-sdk-go/user" ) @@ -18,8 +17,8 @@ import ( // limited validity period, and applies to a strictly defined set of operations. // See methods for details. // -// Container is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/session.Token -// message. See ReadFromV2 / WriteToV2 methods. +// Container is mutually compatible with [protosession.SessionToken] message. +// See [Container.FromProtoMessage] / [Container.ProtoMessage] methods. // // Instances can be created using built-in var declaration. type Container struct { @@ -39,18 +38,18 @@ func (x Container) CopyTo(dst *Container) { } // readContext is a contextReader needed for commonData methods. -func (x *Container) readContext(c session.TokenContext, checkFieldPresence bool) error { - cCnr, ok := c.(*session.ContainerSessionContext) - if !ok || cCnr == nil { +func (x *Container) readContext(c any, checkFieldPresence bool) error { + cc, ok := c.(*protosession.SessionToken_Body_Container) + if !ok || cc == nil { return fmt.Errorf("invalid context %T", c) } + cCnr := cc.Container - wildcard := cCnr.Wildcard() - cnr := cCnr.ContainerID() + cnr := cCnr.GetContainerId() - if !wildcard { + if !cCnr.Wildcard { if cnr != nil { - err := x.cnr.ReadFromV2(*cnr) + err := x.cnr.FromProtoMessage(cnr) if err != nil { return fmt.Errorf("invalid container ID: %w", err) } @@ -63,49 +62,46 @@ func (x *Container) readContext(c session.TokenContext, checkFieldPresence bool) return errors.New("container conflicts with wildcard flag") } - verb := cCnr.Verb() - if verb > math.MaxInt32 { - return fmt.Errorf("verb %d overflows int32", verb) + verb := cCnr.GetVerb() + if verb < 0 { + return fmt.Errorf("negative verb %d", verb) } - x.verb = ContainerVerb(verb) + x.verb = ContainerVerb(cCnr.Verb) return nil } -func (x *Container) readFromV2(m session.Token, checkFieldPresence bool) error { - return x.commonData.readFromV2(m, checkFieldPresence, x.readContext) +func (x *Container) fromProtoMessage(m *protosession.SessionToken, checkFieldPresence bool) error { + return x.commonData.fromProtoMessage(m, checkFieldPresence, x.readContext) } -// ReadFromV2 reads Container from the session.Token message. Checks if the -// message conforms to NeoFS API V2 protocol. +// FromProtoMessage validates m according to the NeoFS API protocol and restores +// x from it. // -// See also WriteToV2. -func (x *Container) ReadFromV2(m session.Token) error { - return x.readFromV2(m, true) +// See also [Container.ProtoMessage]. +func (x *Container) FromProtoMessage(m *protosession.SessionToken) error { + return x.fromProtoMessage(m, true) } -func (x Container) writeContext() session.TokenContext { - wildcard := x.cnr.IsZero() - var c session.ContainerSessionContext - c.SetWildcard(wildcard) - c.SetVerb(session.ContainerSessionVerb(x.verb)) - - if !wildcard { - var cnr refs.ContainerID - x.cnr.WriteToV2(&cnr) +func (x Container) writeContext(m *protosession.SessionToken_Body) { + c := &protosession.ContainerSessionContext{ + Verb: protosession.ContainerSessionContext_Verb(x.verb), + Wildcard: x.cnr.IsZero(), + } - c.SetContainerID(&cnr) + if !c.Wildcard { + c.ContainerId = x.cnr.ProtoMessage() } - return &c + m.Context = &protosession.SessionToken_Body_Container{Container: c} } -// WriteToV2 writes Container to the session.Token message. -// The message must not be nil. +// ProtoMessage converts x into message to transmit using the NeoFS API +// protocol. // -// See also ReadFromV2. -func (x Container) WriteToV2(m *session.Token) { - x.writeToV2(m, x.writeContext) +// See also [Container.FromProtoMessage]. +func (x Container) ProtoMessage() *protosession.SessionToken { + return x.protoMessage(x.writeContext) } // Marshal encodes Container into a binary format of the NeoFS API protocol @@ -175,15 +171,13 @@ func (x *Container) SignedData() []byte { // UnmarshalSignedData is a reverse op to [Container.SignedData]. func (x *Container) UnmarshalSignedData(data []byte) error { - var body session.TokenBody - err := body.Unmarshal(data) + var body protosession.SessionToken_Body + err := neofsproto.UnmarshalMessage(data, &body) if err != nil { return fmt.Errorf("decode body: %w", err) } - var tok session.Token - tok.SetBody(&body) - return x.readFromV2(tok, false) + return x.fromProtoMessage(&protosession.SessionToken{Body: &body}, false) } // VerifySignature checks if Container signature is presented and valid. diff --git a/session/container_internal_test.go b/session/container_internal_test.go index 0d32735f..f9877ae4 100644 --- a/session/container_internal_test.go +++ b/session/container_internal_test.go @@ -4,8 +4,8 @@ import ( "bytes" "testing" - "github.com/nspcc-dev/neofs-api-go/v2/session" cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test" + protosession "github.com/nspcc-dev/neofs-sdk-go/proto/session" "github.com/stretchr/testify/require" ) @@ -21,8 +21,8 @@ func TestContainer_CopyTo(t *testing.T) { var dst Container container.CopyTo(&dst) - emptyWriter := func() session.TokenContext { - return &session.ContainerSessionContext{} + emptyWriter := func(body *protosession.SessionToken_Body) { + body.Context = &protosession.SessionToken_Body_Container{} } require.Equal(t, container, dst) @@ -53,8 +53,8 @@ func TestContainer_CopyTo(t *testing.T) { require.NotZero(t, dst.cnr) local.CopyTo(&dst) - emptyWriter := func() session.TokenContext { - return &session.ContainerSessionContext{} + emptyWriter := func(body *protosession.SessionToken_Body) { + body.Context = &protosession.SessionToken_Body_Container{} } require.Equal(t, local, dst) diff --git a/session/container_test.go b/session/container_test.go index 5f0fe64d..f946b76e 100644 --- a/session/container_test.go +++ b/session/container_test.go @@ -5,17 +5,16 @@ import ( "crypto/ecdsa" "crypto/elliptic" "encoding/json" - "math" "math/big" "strconv" "testing" - "github.com/nspcc-dev/neofs-api-go/v2/refs" - apisession "github.com/nspcc-dev/neofs-api-go/v2/session" 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/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/user" usertest "github.com/nspcc-dev/neofs-sdk-go/user/test" @@ -49,30 +48,30 @@ var validSignedContainerTokens = [][]byte{ 51, 5, 166, 111, 29, 20, 101, 192, 165, 28, 167, 57, 160, 82, 80, 41, 203, 20, 254, 30, 138, 195, 17, 92, 26, 18, 8, 238, 215, 164, 15, 16, 183, 189, 151, 204, 221, 2, 24, 190, 132, 217, 192, 4, 34, 33, 2, 149, 43, 50, 196, 91, 177, 62, 131, 233, 126, 241, 177, 13, 78, 96, 94, 119, 71, 55, 179, - 8, 53, 241, 79, 2, 1, 95, 85, 78, 45, 197, 136, 50, 2, 16, 1}, + 8, 53, 241, 79, 2, 1, 95, 85, 78, 45, 197, 136, 50, 8, 8, 147, 236, 159, 143, 3, 16, 1}, {10, 16, 99, 24, 111, 70, 22, 172, 72, 20, 139, 187, 175, 98, 10, 255, 231, 188, 18, 27, 10, 25, 53, 51, 5, 166, 111, 29, 20, 101, 192, 165, 28, 167, 57, 160, 82, 80, 41, 203, 20, 254, 30, 138, 195, 17, 92, 26, 18, 8, 238, 215, 164, 15, 16, 183, 189, 151, 204, 221, 2, 24, 190, 132, 217, 192, 4, 34, 33, 2, 149, 43, 50, 196, 91, 177, 62, 131, 233, 126, 241, 177, 13, 78, 96, 94, 119, 71, 55, 179, - 8, 53, 241, 79, 2, 1, 95, 85, 78, 45, 197, 136, 50, 36, 26, 34, 10, 32, 243, 245, 75, 198, 48, 107, + 8, 53, 241, 79, 2, 1, 95, 85, 78, 45, 197, 136, 50, 42, 8, 147, 236, 159, 143, 3, 26, 34, 10, 32, 243, 245, 75, 198, 48, 107, 141, 121, 255, 49, 51, 168, 21, 254, 62, 66, 6, 147, 43, 35, 99, 242, 163, 20, 26, 30, 147, 240, 79, 114, 252, 227}, } // corresponds to validContainerTokens. var validBinContainerTokens = [][]byte{ - {10, 106, 10, 16, 99, 24, 111, 70, 22, 172, 72, 20, 139, 187, 175, 98, 10, 255, 231, 188, 18, 27, 10, 25, 53, + {10, 112, 10, 16, 99, 24, 111, 70, 22, 172, 72, 20, 139, 187, 175, 98, 10, 255, 231, 188, 18, 27, 10, 25, 53, 51, 5, 166, 111, 29, 20, 101, 192, 165, 28, 167, 57, 160, 82, 80, 41, 203, 20, 254, 30, 138, 195, 17, 92, 26, 18, 8, 238, 215, 164, 15, 16, 183, 189, 151, 204, 221, 2, 24, 190, 132, 217, 192, 4, 34, 33, 2, 149, 43, 50, 196, 91, 177, 62, 131, 233, 126, 241, 177, 13, 78, 96, 94, 119, 71, 55, 179, 8, 53, 241, 79, 2, - 1, 95, 85, 78, 45, 197, 136, 50, 2, 16, 1, 18, 56, 10, 33, 3, 202, 217, 142, 98, 209, 190, 188, 145, 123, + 1, 95, 85, 78, 45, 197, 136, 50, 8, 8, 147, 236, 159, 143, 3, 16, 1, 18, 56, 10, 33, 3, 202, 217, 142, 98, 209, 190, 188, 145, 123, 174, 21, 173, 239, 239, 245, 67, 148, 205, 119, 58, 223, 219, 209, 220, 113, 215, 134, 228, 101, 249, 34, 218, 18, 13, 97, 110, 121, 95, 115, 105, 103, 110, 97, 116, 117, 114, 101, 24, 236, 236, 175, 192, 4}, - {10, 140, 1, 10, 16, 99, 24, 111, 70, 22, 172, 72, 20, 139, 187, 175, 98, 10, 255, 231, 188, 18, 27, 10, 25, 53, + {10, 146, 1, 10, 16, 99, 24, 111, 70, 22, 172, 72, 20, 139, 187, 175, 98, 10, 255, 231, 188, 18, 27, 10, 25, 53, 51, 5, 166, 111, 29, 20, 101, 192, 165, 28, 167, 57, 160, 82, 80, 41, 203, 20, 254, 30, 138, 195, 17, 92, 26, 18, 8, 238, 215, 164, 15, 16, 183, 189, 151, 204, 221, 2, 24, 190, 132, 217, 192, 4, 34, 33, 2, 149, 43, 50, 196, 91, 177, 62, 131, 233, 126, 241, 177, 13, 78, 96, 94, 119, 71, 55, 179, 8, 53, 241, 79, 2, - 1, 95, 85, 78, 45, 197, 136, 50, 36, 26, 34, 10, 32, 243, 245, 75, 198, 48, 107, 141, 121, 255, 49, 51, 168, + 1, 95, 85, 78, 45, 197, 136, 50, 42, 8, 147, 236, 159, 143, 3, 26, 34, 10, 32, 243, 245, 75, 198, 48, 107, 141, 121, 255, 49, 51, 168, 21, 254, 62, 66, 6, 147, 43, 35, 99, 242, 163, 20, 26, 30, 147, 240, 79, 114, 252, 227, 18, 56, 10, 33, 3, 202, 217, 142, 98, 209, 190, 188, 145, 123, 174, 21, 173, 239, 239, 245, 67, 148, 205, 119, 58, 223, 219, 209, 220, 113, 215, 134, 228, 101, 249, 34, 218, 18, 13, 97, 110, 121, 95, 115, 105, 103, 110, 97, 116, @@ -134,34 +133,27 @@ var validJSONContainerTokens = []string{` } `} -func TestContainer_ReadFromV2(t *testing.T) { - var lt apisession.TokenLifetime - lt.SetExp(anyValidExp) - lt.SetIat(anyValidIat) - lt.SetNbf(anyValidNbf) - var mo refs.OwnerID - mo.SetValue(anyValidUserID[:]) - var mcnr refs.ContainerID - mcnr.SetValue(anyValidContainerID[:]) - var mc apisession.ContainerSessionContext - mc.SetContainerID(&mcnr) - mc.SetVerb(anyValidContainerVerb) - var mb apisession.TokenBody - mb.SetID(anyValidSessionID[:]) - mb.SetOwnerID(&mo) - mb.SetLifetime(<) - mb.SetSessionKey(anyValidSessionKeyBytes) - mb.SetContext(&mc) - var msig refs.Signature - msig.SetKey(anyValidIssuerPublicKeyBytes) - msig.SetScheme(refs.SignatureScheme(anyValidSignatureScheme)) - msig.SetSign(anyValidSignatureBytes) - var m apisession.Token - m.SetBody(&mb) - m.SetSignature(&msig) +func TestContainer_FromProtoMessage(t *testing.T) { + m := &protosession.SessionToken{ + Body: &protosession.SessionToken_Body{ + Id: anyValidSessionID[:], + OwnerId: &refs.OwnerID{Value: anyValidUserID[:]}, + Lifetime: &protosession.SessionToken_Body_TokenLifetime{Exp: anyValidExp, Nbf: anyValidNbf, Iat: anyValidIat}, + SessionKey: anyValidSessionKeyBytes, + Context: &protosession.SessionToken_Body_Container{Container: &protosession.ContainerSessionContext{ + Verb: anyValidContainerVerb, + ContainerId: &refs.ContainerID{Value: anyValidContainerID[:]}, + }}, + }, + Signature: &refs.Signature{ + Key: anyValidIssuerPublicKeyBytes, + Sign: anyValidSignatureBytes, + Scheme: anyValidSignatureScheme, + }, + } var val session.Container - require.NoError(t, val.ReadFromV2(m)) + require.NoError(t, val.FromProtoMessage(m)) require.Equal(t, val.ID(), anyValidSessionID) require.Equal(t, val.Issuer(), anyValidUserID) require.EqualValues(t, anyValidExp, val.Exp()) @@ -179,94 +171,74 @@ func TestContainer_ReadFromV2(t *testing.T) { t.Run("invalid", func(t *testing.T) { for _, tc := range append(invalidProtoTokenCommonTestcases, invalidProtoTokenTestcase{ name: "context/missing", err: "missing session context", - corrupt: func(m *apisession.Token) { m.GetBody().SetContext(nil) }, + corrupt: func(m *protosession.SessionToken) { m.Body.Context = nil }, }, invalidProtoTokenTestcase{ - name: "context/wrong", err: "invalid context: invalid context *session.ObjectSessionContext", - corrupt: func(m *apisession.Token) { m.GetBody().SetContext(new(apisession.ObjectSessionContext)) }, + name: "context/wrong", err: "invalid context: invalid context *session.SessionToken_Body_Object", + corrupt: func(m *protosession.SessionToken) { m.Body.Context = new(protosession.SessionToken_Body_Object) }, }, invalidProtoTokenTestcase{ - name: "context/invalid verb", err: "invalid context: verb 2147483648 overflows int32", - corrupt: func(m *apisession.Token) { - var c apisession.ContainerSessionContext - c.SetWildcard(true) - c.SetVerb(math.MaxInt32 + 1) - m.GetBody().SetContext(&c) + name: "context/invalid verb", err: "invalid context: negative verb -1", + corrupt: func(m *protosession.SessionToken) { + m.GetBody().GetContext().(*protosession.SessionToken_Body_Container).Container.Verb = -1 }, }, invalidProtoTokenTestcase{ name: "context/neither container nor wildcard", err: "invalid context: missing container or wildcard flag", - corrupt: func(m *apisession.Token) { m.GetBody().SetContext(new(apisession.ContainerSessionContext)) }, + corrupt: func(m *protosession.SessionToken) { + m.GetBody().GetContext().(*protosession.SessionToken_Body_Container).Container.Reset() + }, }, invalidProtoTokenTestcase{ name: "context/both container and wildcard", err: "invalid context: container conflicts with wildcard flag", - corrupt: func(m *apisession.Token) { - var c apisession.ContainerSessionContext - c.SetContainerID(new(refs.ContainerID)) - c.SetWildcard(true) - m.GetBody().SetContext(&c) + corrupt: func(m *protosession.SessionToken) { + m.GetBody().GetContext().(*protosession.SessionToken_Body_Container).Container.Wildcard = true }, }, invalidProtoTokenTestcase{ name: "context/container/nil value", err: "invalid context: invalid container ID: invalid length 0", - corrupt: func(m *apisession.Token) { - var c apisession.ContainerSessionContext - c.SetContainerID(new(refs.ContainerID)) - m.GetBody().SetContext(&c) + corrupt: func(m *protosession.SessionToken) { + m.GetBody().GetContext().(*protosession.SessionToken_Body_Container).Container.ContainerId.Value = nil }, }, invalidProtoTokenTestcase{ name: "context/container/empty value", err: "invalid context: invalid container ID: invalid length 0", - corrupt: func(m *apisession.Token) { - var id refs.ContainerID - id.SetValue([]byte{}) - var c apisession.ContainerSessionContext - c.SetContainerID(&id) - m.GetBody().SetContext(&c) + corrupt: func(m *protosession.SessionToken) { + m.GetBody().GetContext().(*protosession.SessionToken_Body_Container).Container.ContainerId.Value = []byte{} }, }, invalidProtoTokenTestcase{ name: "context/container/undersize", err: "invalid context: invalid container ID: invalid length 31", - corrupt: func(m *apisession.Token) { - var id refs.ContainerID - id.SetValue(make([]byte, 31)) - var c apisession.ContainerSessionContext - c.SetContainerID(&id) - m.GetBody().SetContext(&c) + corrupt: func(m *protosession.SessionToken) { + m.GetBody().GetContext().(*protosession.SessionToken_Body_Container).Container.ContainerId.Value = make([]byte, 31) }, }, invalidProtoTokenTestcase{ name: "context/container/oversize", err: "invalid context: invalid container ID: invalid length 33", - corrupt: func(m *apisession.Token) { - var id refs.ContainerID - id.SetValue(make([]byte, 33)) - var c apisession.ContainerSessionContext - c.SetContainerID(&id) - m.GetBody().SetContext(&c) + corrupt: func(m *protosession.SessionToken) { + m.GetBody().GetContext().(*protosession.SessionToken_Body_Container).Container.ContainerId.Value = make([]byte, 33) }, }) { t.Run(tc.name, func(t *testing.T) { st := val - var m apisession.Token - st.WriteToV2(&m) - tc.corrupt(&m) - require.EqualError(t, new(session.Container).ReadFromV2(m), tc.err) + m := st.ProtoMessage() + tc.corrupt(m) + require.EqualError(t, new(session.Container).FromProtoMessage(m), tc.err) }) } }) } -func TestContainer_WriteToV2(t *testing.T) { +func TestContainer_ProtoMessage(t *testing.T) { var val session.Container - var m apisession.Token // zero - val.WriteToV2(&m) + m := val.ProtoMessage() require.Zero(t, m.GetSignature()) body := m.GetBody() require.NotNil(t, body) - require.Zero(t, body.GetID()) - require.Zero(t, body.GetOwnerID()) + require.Zero(t, body.GetId()) + require.Zero(t, body.GetOwnerId()) require.Zero(t, body.GetLifetime()) require.Zero(t, body.GetSessionKey()) c := body.GetContext() - require.IsType(t, new(apisession.ContainerSessionContext), c) - cc := c.(*apisession.ContainerSessionContext) - require.Zero(t, cc.Verb()) - require.Zero(t, cc.ContainerID()) - require.True(t, cc.Wildcard()) + require.IsType(t, new(protosession.SessionToken_Body_Container), c) + cc := c.(*protosession.SessionToken_Body_Container).Container + require.Zero(t, cc.GetVerb()) + require.Zero(t, cc.GetContainerId()) + require.True(t, cc.GetWildcard()) // filled val.SetID(anyValidSessionID) @@ -279,11 +251,11 @@ func TestContainer_WriteToV2(t *testing.T) { val.ApplyOnlyTo(anyValidContainerID) val.AttachSignature(anyValidSignature) - val.WriteToV2(&m) + m = val.ProtoMessage() body = m.GetBody() require.NotNil(t, body) - require.Equal(t, anyValidSessionID[:], body.GetID()) - require.Equal(t, anyValidUserID[:], body.GetOwnerID().GetValue()) + require.Equal(t, anyValidSessionID[:], body.GetId()) + require.Equal(t, anyValidUserID[:], body.GetOwnerId().GetValue()) lt := body.GetLifetime() require.EqualValues(t, anyValidExp, lt.GetExp()) require.EqualValues(t, anyValidIat, lt.GetIat()) @@ -295,11 +267,11 @@ func TestContainer_WriteToV2(t *testing.T) { require.Equal(t, anyValidIssuerPublicKeyBytes, sig.GetKey()) require.Equal(t, anyValidSignatureBytes, sig.GetSign()) c = body.GetContext() - require.IsType(t, new(apisession.ContainerSessionContext), c) - cc = c.(*apisession.ContainerSessionContext) - require.EqualValues(t, anyValidContainerVerb, cc.Verb()) - require.Equal(t, anyValidContainerID[:], cc.ContainerID().GetValue()) - require.Zero(t, cc.Wildcard()) + require.IsType(t, new(protosession.SessionToken_Body_Container), c) + cc = c.(*protosession.SessionToken_Body_Container).Container + require.EqualValues(t, anyValidContainerVerb, cc.GetVerb()) + require.Equal(t, anyValidContainerID[:], cc.GetContainerId().GetValue()) + require.Zero(t, cc.GetWildcard()) } func TestContainer_Marshal(t *testing.T) { @@ -316,7 +288,7 @@ func TestContainer_Unmarshal(t *testing.T) { require.ErrorContains(t, err, "cannot parse invalid wire-format data") }) for _, tc := range append(invalidBinTokenCommonTestcases, invalidBinTokenTestcase{ - name: "body/context/wrong oneof", err: "invalid context: invalid context *session.ObjectSessionContext", + name: "body/context/wrong oneof", err: "invalid context: invalid context *session.SessionToken_Body_Object", b: []byte{10, 4, 42, 2, 18, 0}, }, invalidBinTokenTestcase{ name: "body/context/both container and wildcard", err: "invalid context: container conflicts with wildcard flag", @@ -370,17 +342,14 @@ func TestContainer_Unmarshal(t *testing.T) { for i := range validContainerTokens { err := val.Unmarshal(validBinContainerTokens[i]) require.NoError(t, err) - t.Skip("https://github.com/nspcc-dev/neofs-sdk-go/issues/606") require.Equal(t, validContainerTokens[i], val) } } func TestContainer_MarshalJSON(t *testing.T) { for i := range validContainerTokens { - //nolint:staticcheck b, err := json.MarshalIndent(validContainerTokens[i], "", " ") require.NoError(t, err, i) - t.Skip("https://github.com/nspcc-dev/neofs-sdk-go/issues/606") require.JSONEq(t, validJSONContainerTokens[i], string(b)) } } @@ -393,7 +362,7 @@ func TestContainer_UnmarshalJSON(t *testing.T) { require.ErrorContains(t, err, "syntax error") }) for _, tc := range append(invalidJSONTokenCommonTestcases, invalidJSONTokenTestcase{ - name: "body/context/wrong oneof", err: "invalid context: invalid context *session.ObjectSessionContext", j: ` + name: "body/context/wrong oneof", err: "invalid context: invalid context *session.SessionToken_Body_Object", j: ` {"body":{"object":{}}} `}, invalidJSONTokenTestcase{ name: "body/context/both container and wildcard", err: "invalid context: container conflicts with wildcard flag", j: ` @@ -431,7 +400,6 @@ func TestContainer_UnmarshalJSON(t *testing.T) { // filled for i := range validContainerTokens { require.NoError(t, val.UnmarshalJSON([]byte(validJSONContainerTokens[i])), i) - t.Skip("https://github.com/nspcc-dev/neofs-sdk-go/issues/606") require.Equal(t, validContainerTokens[i], val, i) } } @@ -539,12 +507,12 @@ func TestContainer_Sign(t *testing.T) { var c session.Container for i, rfc6979Sig := range [][]byte{ - {7, 252, 130, 23, 167, 44, 8, 109, 123, 206, 34, 95, 110, 184, 195, 141, 43, 84, 35, 138, 93, 216, 168, - 230, 242, 242, 159, 103, 133, 142, 141, 104, 77, 166, 42, 74, 3, 150, 102, 137, 185, 116, 51, 101, - 147, 33, 4, 7, 14, 65, 174, 28, 44, 91, 168, 58, 128, 38, 163, 102, 52, 239, 213, 118}, - {239, 43, 34, 239, 180, 70, 238, 28, 100, 254, 33, 4, 89, 177, 32, 18, 215, 175, 8, 126, 126, 104, 102, - 180, 121, 13, 39, 78, 50, 132, 119, 250, 114, 225, 242, 135, 253, 191, 99, 129, 229, 108, 148, 223, - 24, 240, 44, 229, 102, 141, 124, 151, 121, 196, 250, 63, 116, 107, 113, 75, 109, 169, 249, 11}, + {190, 18, 239, 30, 103, 101, 136, 235, 201, 103, 161, 14, 141, 211, 187, 115, 174, 185, 216, 240, 250, 20, + 104, 255, 159, 187, 123, 153, 69, 51, 114, 161, 38, 249, 83, 23, 227, 242, 14, 169, 163, 96, 174, + 153, 174, 130, 142, 199, 157, 243, 8, 254, 0, 177, 165, 9, 148, 18, 72, 211, 199, 188, 220, 44}, + {5, 89, 114, 211, 237, 183, 201, 129, 24, 221, 131, 188, 255, 135, 221, 55, 49, 206, 184, 128, 44, 66, + 244, 148, 51, 41, 242, 41, 97, 153, 7, 70, 72, 63, 192, 149, 12, 37, 63, 4, 192, 125, 161, 27, 123, + 242, 76, 178, 148, 202, 241, 54, 4, 108, 34, 182, 217, 246, 125, 107, 132, 53, 91, 188}, } { validContainerTokens[i].CopyTo(&c) t.Run("container#"+strconv.Itoa(i), func(t *testing.T) { @@ -576,26 +544,26 @@ func TestContainer_VerifySignature(t *testing.T) { sigs [][]byte // of validContainerTokens }{ {scheme: neofscrypto.ECDSA_SHA512, sigs: [][]byte{ - {4, 31, 195, 240, 176, 85, 91, 249, 98, 82, 96, 126, 76, 27, 6, 181, 195, 193, 197, 62, 209, 78, 170, 109, 31, 169, - 249, 24, 211, 167, 110, 165, 200, 49, 194, 72, 123, 151, 121, 63, 29, 111, 22, 71, 220, 145, 58, 135, 95, - 244, 202, 224, 70, 162, 136, 39, 30, 58, 151, 240, 9, 65, 144, 32, 184}, - {4, 43, 119, 4, 66, 20, 131, 214, 29, 233, 25, 125, 222, 56, 184, 15, 153, 70, 48, 112, 211, 193, 79, 49, 233, - 36, 188, 130, 244, 42, 19, 134, 179, 5, 32, 143, 63, 35, 52, 228, 149, 202, 170, 174, 150, 246, 116, 182, - 44, 89, 25, 91, 172, 56, 163, 22, 33, 103, 8, 245, 245, 140, 212, 146, 186}, + {4, 42, 31, 236, 138, 99, 174, 186, 104, 85, 109, 115, 31, 152, 42, 84, 148, 73, 12, 21, 206, 199, 211, 246, 191, + 185, 143, 181, 125, 99, 149, 43, 26, 49, 26, 152, 186, 161, 95, 12, 157, 144, 212, 203, 158, 233, 148, 226, + 165, 55, 67, 155, 84, 84, 129, 65, 10, 137, 254, 20, 157, 139, 229, 46, 218}, + {4, 9, 87, 55, 182, 1, 68, 11, 29, 1, 146, 125, 72, 110, 146, 231, 62, 138, 245, 54, 16, 161, 248, 28, 7, 201, + 26, 25, 158, 27, 144, 224, 99, 226, 173, 191, 116, 60, 207, 247, 101, 233, 87, 205, 55, 162, 129, 182, + 211, 149, 194, 23, 242, 124, 238, 56, 80, 109, 45, 165, 15, 129, 91, 7, 180}, }}, {scheme: neofscrypto.ECDSA_DETERMINISTIC_SHA256, sigs: [][]byte{ - {184, 33, 118, 69, 74, 185, 216, 122, 57, 209, 165, 34, 215, 252, 81, 171, 91, 211, 169, 223, 107, 78, 246, 20, - 87, 15, 37, 126, 255, 170, 43, 89, 138, 25, 255, 54, 243, 205, 122, 120, 184, 22, 43, 72, 252, 254, 109, - 91, 176, 30, 116, 54, 181, 75, 172, 137, 245, 155, 232, 0, 96, 102, 15, 228}, - {221, 183, 51, 58, 146, 202, 120, 39, 156, 110, 158, 90, 11, 13, 7, 216, 227, 69, 190, 152, 110, 159, 17, 64, 251, - 35, 96, 40, 106, 69, 211, 112, 139, 127, 24, 179, 13, 199, 161, 102, 117, 217, 61, 25, 144, 222, 171, 203, 240, - 247, 50, 152, 151, 244, 69, 69, 69, 21, 221, 232, 12, 131, 163, 87}, + {211, 170, 29, 36, 209, 239, 196, 159, 185, 21, 248, 226, 171, 179, 107, 14, 171, 214, 250, 240, 188, 188, 95, 8, + 217, 230, 5, 85, 176, 231, 159, 77, 23, 181, 10, 140, 183, 169, 166, 218, 181, 21, 216, 53, 5, 39, 29, 89, 189, + 7, 79, 67, 114, 72, 62, 136, 144, 73, 91, 76, 151, 52, 1, 205}, + {129, 98, 32, 222, 16, 112, 71, 181, 155, 28, 175, 176, 189, 243, 132, 130, 112, 157, 244, 105, 218, 22, 28, 27, + 105, 109, 49, 184, 52, 180, 37, 151, 104, 161, 105, 108, 247, 104, 201, 72, 75, 5, 233, 94, 152, 136, 202, 63, + 121, 77, 193, 129, 137, 248, 215, 211, 77, 6, 147, 100, 201, 79, 18, 125}, }}, {scheme: neofscrypto.ECDSA_WALLETCONNECT, sigs: [][]byte{ - {30, 49, 223, 33, 75, 83, 235, 194, 92, 37, 74, 128, 38, 58, 215, 178, 79, 130, 40, 59, 77, 83, 126, 46, 68, 1, - 233, 170, 162, 153, 83, 65, 53, 171, 44, 138, 187, 214, 130, 160, 167, 96, 171, 7, 164, 95, 40, 58, 108, 214, 246, - 192, 239, 15, 36, 194, 179, 189, 192, 117, 166, 80, 176, 247, 117, 104, 6, 229, 191, 221, 25, 152, 75, 103, 187, - 125, 152, 193, 180, 204}, + {105, 91, 121, 219, 8, 156, 202, 92, 24, 217, 154, 168, 237, 8, 93, 138, 226, 111, 165, 72, 22, 245, 197, 64, 14, + 26, 207, 40, 110, 182, 182, 190, 53, 107, 12, 43, 115, 20, 250, 194, 251, 194, 160, 151, 48, 244, 126, 10, 185, + 226, 201, 137, 35, 122, 186, 69, 8, 239, 68, 66, 87, 126, 116, 12, 150, 15, 108, 163, 129, 197, 192, 140, 15, 96, + 16, 38, 160, 81, 110, 250}, }}, } { sig.SetScheme(tc.scheme) @@ -638,7 +606,7 @@ func TestContainer_UnmarshalSignedData(t *testing.T) { require.ErrorContains(t, err, "cannot parse invalid wire-format data") }) for _, tc := range append(invalidSignedTokenCommonTestcases, invalidBinTokenTestcase{ - name: "body/context/wrong oneof", err: "invalid context: invalid context *session.ObjectSessionContext", + name: "body/context/wrong oneof", err: "invalid context: invalid context *session.SessionToken_Body_Object", b: []byte{42, 2, 18, 0}, }, invalidBinTokenTestcase{ name: "body/context/both container and wildcard", err: "invalid context: container conflicts with wildcard flag", @@ -677,8 +645,7 @@ func TestContainer_UnmarshalSignedData(t *testing.T) { for i := range validContainerTokens { err := val.UnmarshalSignedData(validSignedContainerTokens[i]) require.NoError(t, err) - t.Skip("https://github.com/nspcc-dev/neofs-sdk-go/issues/606") - require.Equal(t, validContainerTokens[i], val) + require.Equal(t, validSignedContainerTokens[i], val.SignedData()) } } diff --git a/session/example_test.go b/session/example_test.go index 14c39db1..d87773c2 100644 --- a/session/example_test.go +++ b/session/example_test.go @@ -1,7 +1,6 @@ package session_test import ( - apiGoSession "github.com/nspcc-dev/neofs-api-go/v2/session" 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/session" @@ -35,17 +34,14 @@ func ExampleContainer() { // Instances can be also used to process NeoFS API V2 protocol messages with [https://github.com/nspcc-dev/neofs-api] package. func ExampleObject_marshalling() { - // import apiGoSession "github.com/nspcc-dev/neofs-api-go/v2/session" - // On the client side. var tok session.Object - var msg apiGoSession.Token - tok.WriteToV2(&msg) + msg := tok.ProtoMessage() // *send message* // On the server side. - _ = tok.ReadFromV2(msg) + _ = tok.FromProtoMessage(msg) } diff --git a/session/object.go b/session/object.go index 9b1cc565..c2ee9e80 100644 --- a/session/object.go +++ b/session/object.go @@ -3,14 +3,14 @@ package session import ( "errors" "fmt" - "math" "slices" - "github.com/nspcc-dev/neofs-api-go/v2/refs" - "github.com/nspcc-dev/neofs-api-go/v2/session" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" + neofsproto "github.com/nspcc-dev/neofs-sdk-go/internal/proto" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" + "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/user" ) @@ -20,8 +20,8 @@ import ( // limited validity period, and applies to a strictly defined set of operations. // See methods for details. // -// Object is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/session.Token -// message. See ReadFromV2 / WriteToV2 methods. +// Object is mutually compatible with [protosession.SessionToken] message. See +// [Object.FromProtoMessage] / [Object.ProtoMessage] methods. // // Instances can be created using built-in var declaration. type Object struct { @@ -43,17 +43,18 @@ func (x Object) CopyTo(dst *Object) { dst.objs = slices.Clone(x.objs) } -func (x *Object) readContext(c session.TokenContext, checkFieldPresence bool) error { - cObj, ok := c.(*session.ObjectSessionContext) - if !ok || cObj == nil { +func (x *Object) readContext(c any, checkFieldPresence bool) error { + cc, ok := c.(*protosession.SessionToken_Body_Object) + if !ok || cc == nil { return fmt.Errorf("invalid context %T", c) } + cObj := cc.Object var err error - cnr := cObj.GetContainer() + cnr := cObj.Target.GetContainer() if cnr != nil { - err := x.cnr.ReadFromV2(*cnr) + err := x.cnr.FromProtoMessage(cnr) if err != nil { return fmt.Errorf("invalid container ID: %w", err) } @@ -63,12 +64,15 @@ func (x *Object) readContext(c session.TokenContext, checkFieldPresence bool) er x.cnr = cid.ID{} } - objs := cObj.GetObjects() + objs := cObj.Target.GetObjects() if objs != nil { x.objs = make([]oid.ID, len(objs)) for i := range objs { - err = x.objs[i].ReadFromV2(objs[i]) + if objs[i] == nil { + return fmt.Errorf("nil target object #%d", i) + } + err = x.objs[i].FromProtoMessage(objs[i]) if err != nil { return fmt.Errorf("invalid target object: %w", err) } @@ -78,60 +82,56 @@ func (x *Object) readContext(c session.TokenContext, checkFieldPresence bool) er } verb := cObj.GetVerb() - if verb > math.MaxInt32 { - return fmt.Errorf("verb %d overflows int32", verb) + if verb < 0 { + return fmt.Errorf("negative verb %d", verb) } x.verb = ObjectVerb(verb) return nil } -func (x *Object) readFromV2(m session.Token, checkFieldPresence bool) error { - return x.commonData.readFromV2(m, checkFieldPresence, x.readContext) +func (x *Object) fromProtoMessage(m *protosession.SessionToken, checkFieldPresence bool) error { + return x.commonData.fromProtoMessage(m, checkFieldPresence, x.readContext) } -// ReadFromV2 reads Object from the session.Token message. Checks if the -// message conforms to NeoFS API V2 protocol. +// FromProtoMessage validates m according to the NeoFS API protocol and restores +// x from it. // -// See also WriteToV2. -func (x *Object) ReadFromV2(m session.Token) error { - return x.readFromV2(m, true) +// See also [Object.ProtoMessage]. +func (x *Object) FromProtoMessage(m *protosession.SessionToken) error { + return x.fromProtoMessage(m, true) } -func (x Object) writeContext() session.TokenContext { - var c session.ObjectSessionContext - c.SetVerb(session.ObjectSessionVerb(x.verb)) +func (x Object) writeContext(m *protosession.SessionToken_Body) { + c := &protosession.ObjectSessionContext{ + Verb: protosession.ObjectSessionContext_Verb(x.verb), + } if !x.cnr.IsZero() || len(x.objs) > 0 { - var cnr *refs.ContainerID + c.Target = new(protosession.ObjectSessionContext_Target) if !x.cnr.IsZero() { - cnr = new(refs.ContainerID) - x.cnr.WriteToV2(cnr) + c.Target.Container = x.cnr.ProtoMessage() } - var objs []refs.ObjectID - if x.objs != nil { - objs = make([]refs.ObjectID, len(x.objs)) + c.Target.Objects = make([]*refs.ObjectID, len(x.objs)) for i := range x.objs { - x.objs[i].WriteToV2(&objs[i]) + c.Target.Objects[i] = x.objs[i].ProtoMessage() } } - - c.SetTarget(cnr, objs...) } - return &c + m.Context = &protosession.SessionToken_Body_Object{Object: c} } -// WriteToV2 writes Object to the session.Token message. -// The message must not be nil. +// ProtoMessage converts x into message to transmit using the NeoFS API +// protocol. // -// See also ReadFromV2. -func (x Object) WriteToV2(m *session.Token) { - x.writeToV2(m, x.writeContext) +// See also [Object.FromProtoMessage]. +func (x Object) ProtoMessage() *protosession.SessionToken { + return x.protoMessage(x.writeContext) } // Marshal encodes Object into a binary format of the NeoFS API protocol @@ -139,9 +139,6 @@ func (x Object) WriteToV2(m *session.Token) { // // See also Unmarshal. func (x Object) Marshal() []byte { - var m session.Token - x.WriteToV2(&m) - return x.marshal(x.writeContext) } @@ -202,15 +199,13 @@ func (x *Object) SignedData() []byte { // UnmarshalSignedData is a reverse op to [Object.SignedData]. func (x *Object) UnmarshalSignedData(data []byte) error { - var body session.TokenBody - err := body.Unmarshal(data) + var body protosession.SessionToken_Body + err := neofsproto.UnmarshalMessage(data, &body) if err != nil { return fmt.Errorf("decode body: %w", err) } - var tok session.Token - tok.SetBody(&body) - return x.readFromV2(tok, false) + return x.fromProtoMessage(&protosession.SessionToken{Body: &body}, false) } // VerifySignature checks if Object signature is presented and valid. diff --git a/session/object_internal_test.go b/session/object_internal_test.go index a3a44329..38d6b689 100644 --- a/session/object_internal_test.go +++ b/session/object_internal_test.go @@ -4,9 +4,9 @@ import ( "bytes" "testing" - "github.com/nspcc-dev/neofs-api-go/v2/session" cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test" oidtest "github.com/nspcc-dev/neofs-sdk-go/object/id/test" + protosession "github.com/nspcc-dev/neofs-sdk-go/proto/session" "github.com/stretchr/testify/require" ) @@ -23,8 +23,8 @@ func TestObject_CopyTo(t *testing.T) { var dst Object container.CopyTo(&dst) - emptyWriter := func() session.TokenContext { - return &session.ContainerSessionContext{} + emptyWriter := func(body *protosession.SessionToken_Body) { + body.Context = &protosession.SessionToken_Body_Container{} } require.Equal(t, container, dst) @@ -73,8 +73,8 @@ func TestObject_CopyTo(t *testing.T) { require.NotZero(t, dst.cnr) local.CopyTo(&dst) - emptyWriter := func() session.TokenContext { - return &session.ContainerSessionContext{} + emptyWriter := func(body *protosession.SessionToken_Body) { + body.Context = &protosession.SessionToken_Body_Container{} } require.Equal(t, local, dst) diff --git a/session/object_test.go b/session/object_test.go index 31c57edb..3d873e18 100644 --- a/session/object_test.go +++ b/session/object_test.go @@ -5,18 +5,17 @@ import ( "crypto/ecdsa" "crypto/elliptic" "encoding/json" - "math" "math/big" "testing" - "github.com/nspcc-dev/neofs-api-go/v2/refs" - apisession "github.com/nspcc-dev/neofs-api-go/v2/session" 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" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" oidtest "github.com/nspcc-dev/neofs-sdk-go/object/id/test" + "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/user" usertest "github.com/nspcc-dev/neofs-sdk-go/user/test" @@ -92,7 +91,7 @@ var validJSONObjectToken = ` }, "sessionKey": "ApUrMsRbsT6D6X7xsQ1OYF53RzezCDXxTwIBX1VOLcWI", "object": { - "verb": 32905430, + "verb": 837285395, "target": { "container": { "value": "8/VLxjBrjXn/MTOoFf4+QgaTKyNj8qMUGh6T8E9y/OM=" @@ -119,38 +118,34 @@ var validJSONObjectToken = ` } ` -func TestObject_ReadFromV2(t *testing.T) { - var lt apisession.TokenLifetime - lt.SetExp(anyValidExp) - lt.SetIat(anyValidIat) - lt.SetNbf(anyValidNbf) - var mo refs.OwnerID - mo.SetValue(anyValidUserID[:]) - var mcnr refs.ContainerID - mcnr.SetValue(anyValidContainerID[:]) - mobjs := make([]refs.ObjectID, len(anyValidObjectIDs)) +func TestObject_FromProtoMessage(t *testing.T) { + mobjs := make([]*refs.ObjectID, len(anyValidObjectIDs)) for i := range anyValidObjectIDs { - mobjs[i].SetValue(anyValidObjectIDs[i][:]) + mobjs[i] = &refs.ObjectID{Value: anyValidObjectIDs[i][:]} + } + m := &protosession.SessionToken{ + Body: &protosession.SessionToken_Body{ + Id: anyValidSessionID[:], + OwnerId: &refs.OwnerID{Value: anyValidUserID[:]}, + Lifetime: &protosession.SessionToken_Body_TokenLifetime{Exp: anyValidExp, Nbf: anyValidNbf, Iat: anyValidIat}, + SessionKey: anyValidSessionKeyBytes, + Context: &protosession.SessionToken_Body_Object{Object: &protosession.ObjectSessionContext{ + Verb: anyValidObjectVerb, + Target: &protosession.ObjectSessionContext_Target{ + Container: &refs.ContainerID{Value: anyValidContainerID[:]}, + Objects: mobjs, + }, + }}, + }, + Signature: &refs.Signature{ + Key: anyValidIssuerPublicKeyBytes, + Sign: anyValidSignatureBytes, + Scheme: anyValidSignatureScheme, + }, } - var mc apisession.ObjectSessionContext - mc.SetTarget(&mcnr, mobjs...) - mc.SetVerb(anyValidObjectVerb) - var mb apisession.TokenBody - mb.SetID(anyValidSessionID[:]) - mb.SetOwnerID(&mo) - mb.SetLifetime(<) - mb.SetSessionKey(anyValidSessionKeyBytes) - mb.SetContext(&mc) - var msig refs.Signature - msig.SetKey(anyValidIssuerPublicKeyBytes) - msig.SetScheme(refs.SignatureScheme(anyValidSignatureScheme)) - msig.SetSign(anyValidSignatureBytes) - var m apisession.Token - m.SetBody(&mb) - m.SetSignature(&msig) var val session.Object - require.NoError(t, val.ReadFromV2(m)) + require.NoError(t, val.FromProtoMessage(m)) require.Equal(t, val.ID(), anyValidSessionID) require.Equal(t, val.Issuer(), anyValidUserID) require.EqualValues(t, anyValidExp, val.Exp()) @@ -171,109 +166,98 @@ func TestObject_ReadFromV2(t *testing.T) { t.Run("invalid", func(t *testing.T) { for _, tc := range append(invalidProtoTokenCommonTestcases, invalidProtoTokenTestcase{ name: "context/missing", err: "missing session context", - corrupt: func(m *apisession.Token) { m.GetBody().SetContext(nil) }, + corrupt: func(m *protosession.SessionToken) { m.Body.Context = nil }, + }, invalidProtoTokenTestcase{ + name: "context/wrong", err: "invalid context: invalid context *session.SessionToken_Body_Container", + corrupt: func(m *protosession.SessionToken) { m.Body.Context = new(protosession.SessionToken_Body_Container) }, }, invalidProtoTokenTestcase{ - name: "context/wrong", err: "invalid context: invalid context *session.ContainerSessionContext", - corrupt: func(m *apisession.Token) { m.GetBody().SetContext(new(apisession.ContainerSessionContext)) }, + name: "context/invalid verb", err: "invalid context: negative verb -1", + corrupt: func(m *protosession.SessionToken) { + m.GetBody().GetContext().(*protosession.SessionToken_Body_Object).Object.Verb = -1 + }, }, invalidProtoTokenTestcase{ - name: "context/invalid verb", err: "invalid context: verb 2147483648 overflows int32", - corrupt: func(m *apisession.Token) { - m.GetBody().GetContext().(*apisession.ObjectSessionContext).SetVerb(math.MaxInt32 + 1) + name: "context/invalid verb", err: "invalid context: negative verb -1", + corrupt: func(m *protosession.SessionToken) { + m.GetBody().GetContext().(*protosession.SessionToken_Body_Object).Object.Verb = -1 }, }, invalidProtoTokenTestcase{ name: "context/missing container", err: "invalid context: missing target container", - corrupt: func(m *apisession.Token) { - m.GetBody().GetContext().(*apisession.ObjectSessionContext).SetTarget(nil) + corrupt: func(m *protosession.SessionToken) { + m.GetBody().GetContext().(*protosession.SessionToken_Body_Object).Object.Target = nil }, }, invalidProtoTokenTestcase{ name: "context/container/nil value", err: "invalid context: invalid container ID: invalid length 0", - corrupt: func(m *apisession.Token) { - m.GetBody().GetContext().(*apisession.ObjectSessionContext).SetTarget(new(refs.ContainerID)) + corrupt: func(m *protosession.SessionToken) { + m.GetBody().GetContext().(*protosession.SessionToken_Body_Object).Object.Target.Container = new(refs.ContainerID) }, }, invalidProtoTokenTestcase{ name: "context/container/empty value", err: "invalid context: invalid container ID: invalid length 0", - corrupt: func(m *apisession.Token) { - var id refs.ContainerID - id.SetValue([]byte{}) - m.GetBody().GetContext().(*apisession.ObjectSessionContext).SetTarget(&id) + corrupt: func(m *protosession.SessionToken) { + m.GetBody().GetContext().(*protosession.SessionToken_Body_Object).Object.Target.Container.Value = []byte{} }, }, invalidProtoTokenTestcase{ name: "context/container/undersize", err: "invalid context: invalid container ID: invalid length 31", - corrupt: func(m *apisession.Token) { - var id refs.ContainerID - id.SetValue(make([]byte, 31)) - m.GetBody().GetContext().(*apisession.ObjectSessionContext).SetTarget(&id) + corrupt: func(m *protosession.SessionToken) { + m.GetBody().GetContext().(*protosession.SessionToken_Body_Object).Object.Target.Container.Value = make([]byte, 31) }, }, invalidProtoTokenTestcase{ name: "context/container/oversize", err: "invalid context: invalid container ID: invalid length 33", - corrupt: func(m *apisession.Token) { - var id refs.ContainerID - id.SetValue(make([]byte, 33)) - m.GetBody().GetContext().(*apisession.ObjectSessionContext).SetTarget(&id) + corrupt: func(m *protosession.SessionToken) { + m.GetBody().GetContext().(*protosession.SessionToken_Body_Object).Object.Target.Container.Value = make([]byte, 33) + }, + }, invalidProtoTokenTestcase{ + name: "context/objects/nil element", err: "invalid context: nil target object #1", + corrupt: func(m *protosession.SessionToken) { + m.GetBody().GetContext().(*protosession.SessionToken_Body_Object).Object.Target.Objects[1] = nil }, }, invalidProtoTokenTestcase{ - name: "context/object/nil value", err: "invalid context: invalid target object: invalid length 0", - corrupt: func(m *apisession.Token) { - c := m.GetBody().GetContext().(*apisession.ObjectSessionContext) - mo := c.GetObjects() - mo[1].SetValue(nil) - c.SetTarget(c.GetContainer(), mo...) + name: "context/objects/nil value", err: "invalid context: invalid target object: invalid length 0", + corrupt: func(m *protosession.SessionToken) { + m.GetBody().GetContext().(*protosession.SessionToken_Body_Object).Object.Target.Objects[1].Value = nil }, }, invalidProtoTokenTestcase{ - name: "context/object/empty value", err: "invalid context: invalid target object: invalid length 0", - corrupt: func(m *apisession.Token) { - c := m.GetBody().GetContext().(*apisession.ObjectSessionContext) - mo := c.GetObjects() - mo[1].SetValue([]byte{}) - c.SetTarget(c.GetContainer(), mo...) + name: "context/objects/empty value", err: "invalid context: invalid target object: invalid length 0", + corrupt: func(m *protosession.SessionToken) { + m.GetBody().GetContext().(*protosession.SessionToken_Body_Object).Object.Target.Objects[1].Value = []byte{} }, }, invalidProtoTokenTestcase{ - name: "context/object/undersize", err: "invalid context: invalid target object: invalid length 31", - corrupt: func(m *apisession.Token) { - c := m.GetBody().GetContext().(*apisession.ObjectSessionContext) - mo := c.GetObjects() - mo[1].SetValue(make([]byte, 31)) - c.SetTarget(c.GetContainer(), mo...) + name: "context/objects/undersize", err: "invalid context: invalid target object: invalid length 31", + corrupt: func(m *protosession.SessionToken) { + m.GetBody().GetContext().(*protosession.SessionToken_Body_Object).Object.Target.Objects[1].Value = make([]byte, 31) }, }, invalidProtoTokenTestcase{ - name: "context/object/oversize", err: "invalid context: invalid target object: invalid length 33", - corrupt: func(m *apisession.Token) { - c := m.GetBody().GetContext().(*apisession.ObjectSessionContext) - mo := c.GetObjects() - mo[1].SetValue(make([]byte, 33)) - c.SetTarget(c.GetContainer(), mo...) + name: "context/objects/oversize", err: "invalid context: invalid target object: invalid length 33", + corrupt: func(m *protosession.SessionToken) { + m.GetBody().GetContext().(*protosession.SessionToken_Body_Object).Object.Target.Objects[1].Value = make([]byte, 33) }, }) { t.Run(tc.name, func(t *testing.T) { st := val - var m apisession.Token - st.WriteToV2(&m) - tc.corrupt(&m) - require.EqualError(t, new(session.Object).ReadFromV2(m), tc.err) + m := st.ProtoMessage() + tc.corrupt(m) + require.EqualError(t, new(session.Object).FromProtoMessage(m), tc.err) }) } }) } -func TestObject_WriteToV2(t *testing.T) { +func TestObject_ProtoMessage(t *testing.T) { var val session.Object - var m apisession.Token // zero - val.WriteToV2(&m) + m := val.ProtoMessage() require.Zero(t, m.GetSignature()) body := m.GetBody() require.NotNil(t, body) - require.Zero(t, body.GetID()) - require.Zero(t, body.GetOwnerID()) + require.Zero(t, body.GetId()) + require.Zero(t, body.GetOwnerId()) require.Zero(t, body.GetLifetime()) require.Zero(t, body.GetSessionKey()) c := body.GetContext() - require.IsType(t, new(apisession.ObjectSessionContext), c) - oc := c.(*apisession.ObjectSessionContext) + require.IsType(t, new(protosession.SessionToken_Body_Object), c) + oc := c.(*protosession.SessionToken_Body_Object).Object require.Zero(t, oc.GetVerb()) - require.Zero(t, oc.GetContainer()) - require.Zero(t, oc.GetObjects()) + require.Zero(t, oc.GetTarget()) // filled val.SetID(anyValidSessionID) @@ -287,11 +271,11 @@ func TestObject_WriteToV2(t *testing.T) { val.LimitByObjects(anyValidObjectIDs...) val.AttachSignature(anyValidSignature) - val.WriteToV2(&m) + m = val.ProtoMessage() body = m.GetBody() require.NotNil(t, body) - require.Equal(t, anyValidSessionID[:], body.GetID()) - require.Equal(t, anyValidUserID[:], body.GetOwnerID().GetValue()) + require.Equal(t, anyValidSessionID[:], body.GetId()) + require.Equal(t, anyValidUserID[:], body.GetOwnerId().GetValue()) lt := body.GetLifetime() require.EqualValues(t, anyValidExp, lt.GetExp()) require.EqualValues(t, anyValidIat, lt.GetIat()) @@ -303,11 +287,12 @@ func TestObject_WriteToV2(t *testing.T) { require.Equal(t, anyValidIssuerPublicKeyBytes, sig.GetKey()) require.Equal(t, anyValidSignatureBytes, sig.GetSign()) c = body.GetContext() - require.IsType(t, new(apisession.ObjectSessionContext), c) - oc = c.(*apisession.ObjectSessionContext) + require.IsType(t, new(protosession.SessionToken_Body_Object), c) + oc = c.(*protosession.SessionToken_Body_Object).Object require.EqualValues(t, anyValidObjectVerb, oc.GetVerb()) - require.Equal(t, anyValidContainerID[:], oc.GetContainer().GetValue()) - mo := oc.GetObjects() + require.NotNil(t, oc.Target) + require.Equal(t, anyValidContainerID[:], oc.Target.GetContainer().GetValue()) + mo := oc.Target.GetObjects() require.Len(t, mo, len(anyValidObjectIDs)) for i := range anyValidObjectIDs { require.Equal(t, anyValidObjectIDs[i][:], mo[i].GetValue()) @@ -326,7 +311,7 @@ func TestObject_Unmarshal(t *testing.T) { require.ErrorContains(t, err, "cannot parse invalid wire-format data") }) for _, tc := range append(invalidBinTokenCommonTestcases, invalidBinTokenTestcase{ - name: "body/context/wrong oneof", err: "invalid context: invalid context *session.ContainerSessionContext", + name: "body/context/wrong oneof", err: "invalid context: invalid context *session.SessionToken_Body_Container", b: []byte{10, 2, 50, 0}, }, invalidBinTokenTestcase{ name: "body/context/container/empty value", err: "invalid context: invalid container ID: invalid length 0", @@ -406,7 +391,6 @@ func TestObject_Unmarshal(t *testing.T) { // filled err := val.Unmarshal(validBinObjectToken) require.NoError(t, err) - t.Skip("https://github.com/nspcc-dev/neofs-sdk-go/issues/606") require.Equal(t, validObjectToken, val) } @@ -414,7 +398,6 @@ func TestObject_MarshalJSON(t *testing.T) { //nolint:staticcheck b, err := json.MarshalIndent(validObjectToken, "", " ") require.NoError(t, err) - t.Skip("https://github.com/nspcc-dev/neofs-sdk-go/issues/606") require.JSONEq(t, validJSONObjectToken, string(b)) } @@ -426,7 +409,7 @@ func TestObject_UnmarshalJSON(t *testing.T) { require.ErrorContains(t, err, "syntax error") }) for _, tc := range append(invalidJSONTokenCommonTestcases, invalidJSONTokenTestcase{ - name: "body/context/wrong oneof", err: "invalid context: invalid context *session.ContainerSessionContext", j: ` + name: "body/context/wrong oneof", err: "invalid context: invalid context *session.SessionToken_Body_Container", j: ` {"body":{"container":{}}} `}, invalidJSONTokenTestcase{ name: "body/context/container/empty value", err: "invalid context: invalid container ID: invalid length 0", j: ` @@ -471,7 +454,6 @@ func TestObject_UnmarshalJSON(t *testing.T) { // filled require.NoError(t, val.UnmarshalJSON([]byte(validJSONObjectToken))) - t.Skip("https://github.com/nspcc-dev/neofs-sdk-go/issues/606") require.Equal(t, validObjectToken, val) } @@ -664,7 +646,7 @@ func TestObject_UnmarshalSignedData(t *testing.T) { require.ErrorContains(t, err, "cannot parse invalid wire-format data") }) for _, tc := range append(invalidSignedTokenCommonTestcases, invalidBinTokenTestcase{ - name: "body/context/wrong oneof", err: "invalid context: invalid context *session.ContainerSessionContext", + name: "body/context/wrong oneof", err: "invalid context: invalid context *session.SessionToken_Body_Container", b: []byte{50, 0}, }, invalidBinTokenTestcase{ name: "body/context/container/empty value", err: "invalid context: invalid container ID: invalid length 0", @@ -721,8 +703,7 @@ func TestObject_UnmarshalSignedData(t *testing.T) { // filled err := val.UnmarshalSignedData(validSignedObjectToken) require.NoError(t, err) - t.Skip("https://github.com/nspcc-dev/neofs-sdk-go/issues/606") - require.Equal(t, validObjectToken, val) + require.Equal(t, validSignedObjectToken, val.SignedData()) } func TestObject_SetExp(t *testing.T) { diff --git a/storagegroup/storagegroup.go b/storagegroup/storagegroup.go index 9eb6122c..0bf4b0fd 100644 --- a/storagegroup/storagegroup.go +++ b/storagegroup/storagegroup.go @@ -5,18 +5,18 @@ import ( "fmt" "strconv" - objectV2 "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/storagegroup" "github.com/nspcc-dev/neofs-sdk-go/checksum" + neofsproto "github.com/nspcc-dev/neofs-sdk-go/internal/proto" objectSDK "github.com/nspcc-dev/neofs-sdk-go/object" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" + "github.com/nspcc-dev/neofs-sdk-go/proto/refs" + protostoragegroup "github.com/nspcc-dev/neofs-sdk-go/proto/storagegroup" ) // StorageGroup represents storage group of the NeoFS objects. // -// StorageGroup is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/storagegroup.StorageGroup -// message. See ReadFromMessageV2 / WriteToMessageV2 methods. +// StorageGroup is mutually compatible with [protostoragegroup.StorageGroup] +// message. See [StorageGroup.FromProtoMessage] / [StorageGroup.ProtoMessage] methods. // // Instances should be created using one of the constructors. type StorageGroup struct { @@ -52,12 +52,11 @@ func UnmarshalJSON(b []byte) (StorageGroup, error) { // reads StorageGroup from the storagegroup.StorageGroup message. If checkFieldPresence is set, // returns an error on absence of any protocol-required field. -func (sg *StorageGroup) readFromV2(m storagegroup.StorageGroup, checkFieldPresence bool) error { +func (sg *StorageGroup) fromProtoMessage(m *protostoragegroup.StorageGroup, checkFieldPresence bool) error { var err error - h := m.GetValidationHash() - if sg.csSet = h != nil; sg.csSet { - err = sg.cs.ReadFromV2(*h) + if sg.csSet = m.ValidationHash != nil; sg.csSet { + err = sg.cs.FromProtoMessage(m.ValidationHash) if err != nil { return fmt.Errorf("invalid hash: %w", err) } @@ -65,11 +64,13 @@ func (sg *StorageGroup) readFromV2(m storagegroup.StorageGroup, checkFieldPresen return errors.New("missing hash") } - members := m.GetMembers() - if len(members) > 0 { - sg.members = make([]oid.ID, len(members)) - for i := range members { - err = sg.members[i].ReadFromV2(members[i]) + if len(m.Members) > 0 { + sg.members = make([]oid.ID, len(m.Members)) + for i := range m.Members { + if m.Members[i] == nil { + return fmt.Errorf("nil member #%d", i) + } + err = sg.members[i].FromProtoMessage(m.Members[i]) if err != nil { return fmt.Errorf("invalid member #%d: %w", i, err) } @@ -84,42 +85,40 @@ func (sg *StorageGroup) readFromV2(m storagegroup.StorageGroup, checkFieldPresen return errors.New("missing members") } - sg.sz = m.GetValidationDataSize() + sg.sz = m.ValidationDataSize //nolint:staticcheck - sg.exp = m.GetExpirationEpoch() + sg.exp = m.ExpirationEpoch return nil } -// ReadFromV2 reads StorageGroup from the storagegroup.StorageGroup message. -// Checks if the message conforms to NeoFS API V2 protocol. +// FromProtoMessage validates m according to the NeoFS API protocol and restores +// sg from it. // -// See also WriteToV2. -func (sg *StorageGroup) ReadFromV2(m storagegroup.StorageGroup) error { - return sg.readFromV2(m, true) +// See also [StorageGroup.ProtoMessage]. +func (sg *StorageGroup) FromProtoMessage(m *protostoragegroup.StorageGroup) error { + return sg.fromProtoMessage(m, true) } -// WriteToV2 writes StorageGroup to the storagegroup.StorageGroup message. -// The message must not be nil. +// ProtoMessage converts sg into message to transmit using the NeoFS API +// protocol. // -// See also ReadFromV2. -func (sg StorageGroup) WriteToV2(m *storagegroup.StorageGroup) { +// See also [StorageGroup.FromProtoMessage]. +func (sg StorageGroup) ProtoMessage() *protostoragegroup.StorageGroup { + m := &protostoragegroup.StorageGroup{ + ValidationDataSize: sg.sz, + ExpirationEpoch: sg.exp, + } if sg.csSet { - var cs refs.Checksum - sg.cs.WriteToV2(&cs) - m.SetValidationHash(&cs) + m.ValidationHash = sg.cs.ProtoMessage() } - var members []refs.ObjectID if len(sg.members) > 0 { - members = make([]refs.ObjectID, len(sg.members)) + m.Members = make([]*refs.ObjectID, len(sg.members)) for i := range sg.members { - sg.members[i].WriteToV2(&members[i]) + m.Members[i] = sg.members[i].ProtoMessage() } } - m.SetMembers(members) - m.SetValidationDataSize(sg.sz) - //nolint:staticcheck - m.SetExpirationEpoch(sg.exp) + return m } // ValidationDataSize returns total size of the payloads @@ -204,42 +203,28 @@ func (sg *StorageGroup) SetMembers(members []oid.ID) { // // See also Unmarshal. func (sg StorageGroup) Marshal() []byte { - var m storagegroup.StorageGroup - sg.WriteToV2(&m) - return m.StableMarshal(nil) + return neofsproto.Marshal(sg) } // Unmarshal unmarshals protobuf binary representation of StorageGroup. // // See also Marshal. func (sg *StorageGroup) Unmarshal(data []byte) error { - var m storagegroup.StorageGroup - if err := m.Unmarshal(data); err != nil { - return err - } - - return sg.readFromV2(m, false) + return neofsproto.UnmarshalOptional(data, sg, (*StorageGroup).fromProtoMessage) } // MarshalJSON encodes StorageGroup to protobuf JSON format. // // See also UnmarshalJSON. func (sg StorageGroup) MarshalJSON() ([]byte, error) { - var m storagegroup.StorageGroup - sg.WriteToV2(&m) - return m.MarshalJSON() + return neofsproto.MarshalJSON(sg) } // UnmarshalJSON decodes StorageGroup from protobuf JSON format. // // See also MarshalJSON. func (sg *StorageGroup) UnmarshalJSON(data []byte) error { - var m storagegroup.StorageGroup - if err := m.UnmarshalJSON(data); err != nil { - return err - } - - return sg.readFromV2(m, false) + return neofsproto.UnmarshalJSONOptional(data, sg, (*StorageGroup).fromProtoMessage) } // ReadFromObject assemble StorageGroup from a regular @@ -261,7 +246,7 @@ func ReadFromObject(sg *StorageGroup, o objectSDK.Object) error { var expObj uint64 for _, attr := range o.Attributes() { - if attr.Key() == objectV2.SysAttributeExpEpoch { + if attr.Key() == objectSDK.AttributeExpirationEpoch { expObj, err = strconv.ParseUint(attr.Value(), 10, 64) if err != nil { return fmt.Errorf("could not get expiration from object: %w", err) @@ -299,7 +284,7 @@ func WriteToObject(sg StorageGroup, o *objectSDK.Object) { var expAttrFound bool for i := range attrs { - if attrs[i].Key() == objectV2.SysAttributeExpEpoch { + if attrs[i].Key() == objectSDK.AttributeExpirationEpoch { expAttrFound = true attrs[i].SetValue(strconv.FormatUint(sg.ExpirationEpoch(), 10)) @@ -310,7 +295,7 @@ func WriteToObject(sg StorageGroup, o *objectSDK.Object) { if !expAttrFound { var attr objectSDK.Attribute - attr.SetKey(objectV2.SysAttributeExpEpoch) + attr.SetKey(objectSDK.AttributeExpirationEpoch) attr.SetValue(strconv.FormatUint(sg.ExpirationEpoch(), 10)) attrs = append(attrs, attr) diff --git a/storagegroup/storagegroup_test.go b/storagegroup/storagegroup_test.go index 868d82cc..cd37c12b 100644 --- a/storagegroup/storagegroup_test.go +++ b/storagegroup/storagegroup_test.go @@ -7,15 +7,14 @@ import ( "strconv" "testing" - protoobject "github.com/nspcc-dev/neofs-api-go/v2/object" - "github.com/nspcc-dev/neofs-api-go/v2/refs" - protosg "github.com/nspcc-dev/neofs-api-go/v2/storagegroup" "github.com/nspcc-dev/neofs-sdk-go/checksum" checksumtest "github.com/nspcc-dev/neofs-sdk-go/checksum/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" objecttest "github.com/nspcc-dev/neofs-sdk-go/object/test" + "github.com/nspcc-dev/neofs-sdk-go/proto/refs" + protosg "github.com/nspcc-dev/neofs-sdk-go/proto/storagegroup" "github.com/nspcc-dev/neofs-sdk-go/storagegroup" storagegrouptest "github.com/nspcc-dev/neofs-sdk-go/storagegroup/test" "github.com/nspcc-dev/tzhash/tz" @@ -43,7 +42,7 @@ var anyValidMembers = []oid.ID{ } var validChecksums = []checksum.Checksum{ - checksum.New(3259832435, []byte("Hello, world!")), + checksum.New(259832435, []byte("Hello, world!")), checksum.NewSHA256(anyValidSHA256Hash), checksum.NewTillichZemor(anyValidTillichZemorHash), } @@ -60,13 +59,12 @@ func init() { // corresponds to validStorageGroups. var validBinStorageGroups = [][]byte{ - {8, 174, 210, 190, 202, 173, 196, 168, 156, 214, 1, 18, 26, 8, 243, 176, 180, 146, 252, 255, - 255, 255, 255, 1, 18, 13, 72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33, 24, - 238, 188, 144, 132, 238, 174, 251, 156, 187, 1, 34, 34, 10, 32, 138, 163, 217, 148, 183, - 203, 248, 137, 98, 245, 243, 80, 22, 7, 219, 189, 157, 190, 201, 32, 41, 255, 198, 245, - 248, 206, 65, 33, 101, 127, 122, 216, 34, 34, 10, 32, 166, 174, 16, 34, 157, 146, 167, 232, - 106, 101, 234, 123, 46, 85, 109, 169, 62, 223, 253, 39, 172, 237, 222, 223, 134, 93, - 176, 237, 93, 21, 9, 39}, + {8, 174, 210, 190, 202, 173, 196, 168, 156, 214, 1, 18, 20, 8, 243, 244, 242, 123, 18, 13, 72, + 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33, 24, 238, 188, 144, 132, 238, 174, 251, + 156, 187, 1, 34, 34, 10, 32, 138, 163, 217, 148, 183, 203, 248, 137, 98, 245, 243, 80, 22, + 7, 219, 189, 157, 190, 201, 32, 41, 255, 198, 245, 248, 206, 65, 33, 101, 127, 122, 216, 34, + 34, 10, 32, 166, 174, 16, 34, 157, 146, 167, 232, 106, 101, 234, 123, 46, 85, 109, 169, 62, + 223, 253, 39, 172, 237, 222, 223, 134, 93, 176, 237, 93, 21, 9, 39}, {8, 174, 210, 190, 202, 173, 196, 168, 156, 214, 1, 18, 36, 8, 2, 18, 32, 49, 95, 91, 219, 118, 208, 120, 196, 59, 138, 192, 6, 78, 74, 1, 100, 97, 43, 31, 206, 119, 200, 105, 52, 91, 252, 148, 199, 88, 148, 237, 211, 24, 238, 188, 144, 132, 238, 174, 251, 156, 187, 1, 34, 34, 10, @@ -88,7 +86,7 @@ var validJSONStorageGroups = []string{` { "validationDataSize": "15436265993370839342", "validationHash": { - "type": -1035134861, + "type": 259832435, "sum": "SGVsbG8sIHdvcmxkIQ==" }, "expirationEpoch": "13491075253593710190", @@ -145,69 +143,69 @@ type invalidProtoTestCase struct { } var invalidProtoTestcases = []invalidProtoTestCase{ + {name: "checksum/type/negative", err: "invalid hash: negative type -1", corrupt: func(sg *protosg.StorageGroup) { + sg.ValidationHash.Type = -1 + }}, {name: "checksum/value/nil", err: "invalid hash: missing value", corrupt: func(sg *protosg.StorageGroup) { - sg.SetValidationHash(new(refs.Checksum)) + sg.ValidationHash = new(refs.Checksum) }}, {name: "checksum/value/empty", err: "invalid hash: missing value", corrupt: func(sg *protosg.StorageGroup) { - var cs refs.Checksum - cs.SetSum([]byte{}) - sg.SetValidationHash(&cs) + sg.ValidationHash.Sum = []byte{} }}, {name: "members/value/nil", err: "invalid member #1: invalid length 0", corrupt: func(sg *protosg.StorageGroup) { - members := make([]refs.ObjectID, 2) - members[0].SetValue(anyValidMembers[0][:]) - sg.SetMembers(members) + sg.Members = []*refs.ObjectID{ + {Value: anyValidMembers[0][:]}, + {}, + } }}, {name: "members/value/empty", err: "invalid member #1: invalid length 0", corrupt: func(sg *protosg.StorageGroup) { - members := make([]refs.ObjectID, 2) - members[0].SetValue(anyValidMembers[0][:]) - members[1].SetValue([]byte{}) - sg.SetMembers(members) + sg.Members = []*refs.ObjectID{ + {Value: anyValidMembers[0][:]}, + {Value: []byte{}}, + } }}, {name: "members/value/undersize", err: "invalid member #1: invalid length 31", corrupt: func(sg *protosg.StorageGroup) { - members := make([]refs.ObjectID, 2) - members[0].SetValue(anyValidMembers[0][:]) - members[1].SetValue(anyValidMembers[1][:31]) - sg.SetMembers(members) + sg.Members = []*refs.ObjectID{ + {Value: anyValidMembers[0][:]}, + {Value: anyValidMembers[1][:31]}, + } }}, {name: "members/value/oversize", err: "invalid member #1: invalid length 33", corrupt: func(sg *protosg.StorageGroup) { - members := make([]refs.ObjectID, 2) - members[0].SetValue(anyValidMembers[0][:]) - members[1].SetValue(append(anyValidMembers[1][:], 1)) - sg.SetMembers(members) + sg.Members = []*refs.ObjectID{ + {Value: anyValidMembers[0][:]}, + {Value: append(anyValidMembers[1][:], 1)}, + } }}, {name: "members/duplicated", err: "duplicated member ALCAybSe17EF2b2e2TkfVVrMeQ6Gt6TW58rWkzzcGBoV", corrupt: func(sg *protosg.StorageGroup) { - members := make([]refs.ObjectID, 3) - members[0].SetValue(anyValidMembers[0][:]) - members[1].SetValue(anyValidMembers[1][:]) - members[2].SetValue(anyValidMembers[0][:]) - sg.SetMembers(members) + sg.Members = []*refs.ObjectID{ + {Value: anyValidMembers[0][:]}, + {Value: anyValidMembers[1][:]}, + {Value: anyValidMembers[0][:]}, + } }}, } -func TestStorageGroup_ReadFromV2(t *testing.T) { - members := make([]refs.ObjectID, 2) - members[0].SetValue(anyValidMembers[0][:]) - members[1].SetValue(anyValidMembers[1][:]) - var mcs refs.Checksum - var m protosg.StorageGroup - m.SetValidationDataSize(anyValidSize) - //nolint:staticcheck - m.SetExpirationEpoch(anyValidExp) - m.SetMembers(members) +func TestStorageGroup_FromProtoMessage(t *testing.T) { + m := &protosg.StorageGroup{ + ValidationDataSize: anyValidSize, + ExpirationEpoch: anyValidExp, + Members: []*refs.ObjectID{ + {Value: anyValidMembers[0][:]}, + {Value: anyValidMembers[1][:]}, + }, + } + var sg storagegroup.StorageGroup for i, tc := range []struct { typ refs.ChecksumType val []byte }{ {43503860, []byte("Hello, world!")}, - {refs.SHA256, anyValidSHA256Hash[:]}, - {refs.TillichZemor, anyValidTillichZemorHash[:]}, + {refs.ChecksumType_SHA256, anyValidSHA256Hash[:]}, + {refs.ChecksumType_TZ, anyValidTillichZemorHash[:]}, } { - mcs.SetType(tc.typ) - mcs.SetSum(tc.val) - m.SetValidationHash(&mcs) - require.NoError(t, sg.ReadFromV2(m), i) + m.ValidationHash = &refs.Checksum{Type: tc.typ, Sum: tc.val} + require.NoError(t, sg.FromProtoMessage(m), i) require.EqualValues(t, anyValidSize, sg.ValidationDataSize(), i) require.EqualValues(t, anyValidExp, sg.ExpirationEpoch(), i) require.Equal(t, anyValidMembers, sg.Members(), i) @@ -217,9 +215,9 @@ func TestStorageGroup_ReadFromV2(t *testing.T) { switch typ := cs.Type(); tc.typ { default: require.EqualValues(t, tc.typ, typ, i) - case refs.SHA256: + case refs.ChecksumType_SHA256: require.Equal(t, checksum.SHA256, typ, i) - case refs.TillichZemor: + case refs.ChecksumType_TZ: require.Equal(t, checksum.TillichZemor, typ, i) } } @@ -228,30 +226,28 @@ func TestStorageGroup_ReadFromV2(t *testing.T) { for _, tc := range append(invalidProtoTestcases, invalidProtoTestCase{ name: "missing checksum", err: "missing hash", - corrupt: func(sg *protosg.StorageGroup) { sg.SetValidationHash(nil) }, + corrupt: func(sg *protosg.StorageGroup) { sg.ValidationHash = nil }, }, invalidProtoTestCase{ name: "members/nil", err: "missing members", - corrupt: func(sg *protosg.StorageGroup) { sg.SetMembers(nil) }, + corrupt: func(sg *protosg.StorageGroup) { sg.Members = nil }, }, invalidProtoTestCase{ name: "members/empty", err: "missing members", - corrupt: func(sg *protosg.StorageGroup) { sg.SetMembers([]refs.ObjectID{}) }, + corrupt: func(sg *protosg.StorageGroup) { sg.Members = []*refs.ObjectID{} }, }) { t.Run(tc.name, func(t *testing.T) { - var m protosg.StorageGroup - storagegrouptest.StorageGroup().WriteToV2(&m) - tc.corrupt(&m) - require.EqualError(t, new(storagegroup.StorageGroup).ReadFromV2(m), tc.err) + m := sg.ProtoMessage() + tc.corrupt(m) + require.EqualError(t, new(storagegroup.StorageGroup).FromProtoMessage(m), tc.err) }) } }) } -func TestStorageGroup_WriteToV2(t *testing.T) { +func TestStorageGroup_ProtoMessage(t *testing.T) { for i, sg := range validStorageGroups { - var m protosg.StorageGroup - sg.WriteToV2(&m) + m := sg.ProtoMessage() require.EqualValues(t, anyValidSize, m.GetValidationDataSize(), i) //nolint:staticcheck require.EqualValues(t, anyValidExp, m.GetExpirationEpoch(), i) @@ -265,9 +261,9 @@ func TestStorageGroup_WriteToV2(t *testing.T) { default: require.EqualValues(t, typ, mcs.GetType()) case checksum.SHA256: - require.Equal(t, refs.SHA256, mcs.GetType()) + require.Equal(t, refs.ChecksumType_SHA256, mcs.GetType()) case checksum.TillichZemor: - require.Equal(t, refs.TillichZemor, mcs.GetType()) + require.Equal(t, refs.ChecksumType_TZ, mcs.GetType()) } } } @@ -473,7 +469,7 @@ func TestWriteToObject(t *testing.T) { var o object.Object var attr object.Attribute - attr.SetKey(protoobject.SysAttributeExpEpoch) + attr.SetKey("__NEOFS__EXPIRATION_EPOCH") attr.SetValue(strconv.FormatUint(sg.ExpirationEpoch()+1, 10)) o.SetAttributes(object.Attribute{}, attr, object.Attribute{}) @@ -491,7 +487,7 @@ func TestWriteToObject(t *testing.T) { func expFromObj(t *testing.T, o object.Object) (uint64, bool) { for _, attr := range o.Attributes() { - if attr.Key() == protoobject.SysAttributeExpEpoch { + if attr.Key() == "__NEOFS__EXPIRATION_EPOCH" { exp, err := strconv.ParseUint(attr.Value(), 10, 64) require.NoError(t, err) diff --git a/storagegroup/test/generate_test.go b/storagegroup/test/generate_test.go index 65144578..2bdbe602 100644 --- a/storagegroup/test/generate_test.go +++ b/storagegroup/test/generate_test.go @@ -3,7 +3,6 @@ package storagegrouptest_test import ( "testing" - apistoragegroup "github.com/nspcc-dev/neofs-api-go/v2/storagegroup" "github.com/nspcc-dev/neofs-sdk-go/storagegroup" storagegrouptest "github.com/nspcc-dev/neofs-sdk-go/storagegroup/test" "github.com/stretchr/testify/require" @@ -22,8 +21,7 @@ func TestStorageGroup(t *testing.T) { require.NoError(t, sg2.UnmarshalJSON(b)) require.Equal(t, sg, sg2) - var m apistoragegroup.StorageGroup - sg.WriteToV2(&m) - require.NoError(t, sg2.ReadFromV2(m)) + m := sg.ProtoMessage() + require.NoError(t, sg2.FromProtoMessage(m)) require.Equal(t, sg, sg2) } diff --git a/user/example_test.go b/user/example_test.go index 523bea63..cc28b993 100644 --- a/user/example_test.go +++ b/user/example_test.go @@ -2,7 +2,6 @@ package user_test import ( "github.com/nspcc-dev/neo-go/pkg/util" - apiGoRefs "github.com/nspcc-dev/neofs-api-go/v2/refs" "github.com/nspcc-dev/neofs-sdk-go/user" ) @@ -34,16 +33,13 @@ func ExampleID_DecodeString() { // Instances can be also used to process NeoFS API V2 protocol messages with [https://github.com/nspcc-dev/neofs-api] package. func ExampleID_marshalling() { - // import apiGoRefs "github.com/nspcc-dev/neofs-api-go/v2/refs" - // On the client side. var id user.ID - var msg apiGoRefs.OwnerID - id.WriteToV2(&msg) + msg := id.ProtoMessage() // *send message* // On the server side. - _ = id.ReadFromV2(msg) + _ = id.FromProtoMessage(msg) } diff --git a/user/id.go b/user/id.go index f33d419d..59f53fbd 100644 --- a/user/id.go +++ b/user/id.go @@ -11,7 +11,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/util" - "github.com/nspcc-dev/neofs-api-go/v2/refs" + "github.com/nspcc-dev/neofs-sdk-go/proto/refs" ) // IDSize is the size of an [ID] in bytes. @@ -22,8 +22,8 @@ const IDSize = 25 // // ID implements built-in comparable interface. // -// ID is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/refs.OwnerID -// message. See ReadFromV2 / WriteToV2 methods. +// ID is mutually compatible with [refs.OwnerID] message. See +// [ID.FromProtoMessage] / [ID.ProtoMessage] methods. type ID [IDSize]byte // ErrZeroID is an error returned on zero [ID] encounter. @@ -64,24 +64,24 @@ func (x *ID) decodeBytes(b []byte) error { return nil } -// ReadFromV2 reads ID from the refs.OwnerID message. Returns an error if -// the message is malformed according to the NeoFS API V2 protocol. +// FromProtoMessage validates m according to the NeoFS API protocol and restores +// x from it. // -// See also WriteToV2. -func (x *ID) ReadFromV2(m refs.OwnerID) error { - err := x.decodeBytes(m.GetValue()) +// See also [ID.ProtoMessage]. +func (x *ID) FromProtoMessage(m *refs.OwnerID) error { + err := x.decodeBytes(m.Value) if err == nil && x.IsZero() { err = ErrZeroID } return err } -// WriteToV2 writes ID to the refs.OwnerID message. -// The message must not be nil. +// ProtoMessage converts x into message to transmit using the NeoFS API +// protocol. // -// See also ReadFromV2. -func (x ID) WriteToV2(m *refs.OwnerID) { - m.SetValue(x[:]) +// See also [ID.FromProtoMessage]. +func (x ID) ProtoMessage() *refs.OwnerID { + return &refs.OwnerID{Value: x[:]} } // SetScriptHash forms user ID from wallet address scripthash. diff --git a/user/id_test.go b/user/id_test.go index eada4d85..5da6ef46 100644 --- a/user/id_test.go +++ b/user/id_test.go @@ -7,7 +7,7 @@ import ( "testing" "github.com/nspcc-dev/neo-go/pkg/util" - "github.com/nspcc-dev/neofs-api-go/v2/refs" + "github.com/nspcc-dev/neofs-sdk-go/proto/refs" "github.com/nspcc-dev/neofs-sdk-go/user" usertest "github.com/nspcc-dev/neofs-sdk-go/user/test" "github.com/stretchr/testify/require" @@ -31,11 +31,10 @@ func TestID_WalletBytes(t *testing.T) { require.Equal(t, id[:], id.WalletBytes()) } -func TestID_ReadFromV2(t *testing.T) { - var m refs.OwnerID - m.SetValue(validBytes[:]) +func TestID_FromProtoMessage(t *testing.T) { + m := &refs.OwnerID{Value: validBytes[:]} var id user.ID - require.NoError(t, id.ReadFromV2(m)) + require.NoError(t, id.FromProtoMessage(m)) require.EqualValues(t, validBytes, id) t.Run("invalid", func(t *testing.T) { @@ -51,18 +50,16 @@ func TestID_ReadFromV2(t *testing.T) { {name: "zero value", err: "invalid prefix byte 0x0, expected 0x35", val: make([]byte, user.IDSize)}, } { t.Run(tc.name, func(t *testing.T) { - var m refs.OwnerID - m.SetValue(tc.val) - require.EqualError(t, new(user.ID).ReadFromV2(m), tc.err) + m := &refs.OwnerID{Value: tc.val} + require.EqualError(t, new(user.ID).FromProtoMessage(m), tc.err) }) } }) } -func TestID_WriteToV2(t *testing.T) { +func TestID_ProtoMessage(t *testing.T) { id := usertest.ID() - var m refs.OwnerID - id.WriteToV2(&m) + m := id.ProtoMessage() require.Equal(t, id[:], m.GetValue()) } diff --git a/user/test/id_test.go b/user/test/id_test.go index 3d5e696a..7c18c1b1 100644 --- a/user/test/id_test.go +++ b/user/test/id_test.go @@ -4,7 +4,6 @@ import ( "math/rand/v2" "testing" - "github.com/nspcc-dev/neofs-api-go/v2/refs" cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test" neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa" @@ -17,10 +16,9 @@ func TestID(t *testing.T) { id := usertest.ID() require.NotEqual(t, id, usertest.ID()) - var m refs.OwnerID - id.WriteToV2(&m) + m := id.ProtoMessage() var id2 user.ID - require.NoError(t, id2.ReadFromV2(m)) + require.NoError(t, id2.FromProtoMessage(m)) } func TestNIDs(t *testing.T) { diff --git a/version/version.go b/version/version.go index f79fb25f..d714b1e6 100644 --- a/version/version.go +++ b/version/version.go @@ -3,15 +3,17 @@ package version import ( "fmt" - "github.com/nspcc-dev/neofs-api-go/v2/refs" + neofsproto "github.com/nspcc-dev/neofs-sdk-go/internal/proto" + "github.com/nspcc-dev/neofs-sdk-go/proto/refs" + "google.golang.org/protobuf/encoding/protojson" ) // Version represents revision number in SemVer scheme. // // ID implements built-in comparable interface. // -// Version is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/refs.Version -// message. See ReadFromV2 / WriteToV2 methods. +// Version is mutually compatible with [refs.Version] message. See +// [Version.FromProtoMessage] / [Version.ProtoMessage] methods. // // Instances should be created using one of the constructors. type Version struct{ mjr, mnr uint32 } @@ -58,22 +60,24 @@ func (v *Version) SetMinor(val uint32) { v.mnr = val } -// WriteToV2 writes Version to the refs.Version message. -// The message must not be nil. +// ProtoMessage converts v into message to transmit using the NeoFS API +// protocol. // -// See also ReadFromV2. -func (v Version) WriteToV2(m *refs.Version) { - m.SetMajor(v.mjr) - m.SetMinor(v.mnr) +// See also [Version.FromProtoMessage]. +func (v Version) ProtoMessage() *refs.Version { + return &refs.Version{ + Major: v.mjr, + Minor: v.mnr, + } } -// ReadFromV2 reads Version from the refs.Version message. Checks if the message -// conforms to NeoFS API V2 protocol. +// FromProtoMessage validates m according to the NeoFS API protocol and restores +// v from it. // -// See also WriteToV2. -func (v *Version) ReadFromV2(m refs.Version) error { - v.mjr = m.GetMajor() - v.mnr = m.GetMinor() +// See also [Version.ProtoMessage]. +func (v *Version) FromProtoMessage(m *refs.Version) error { + v.mjr = m.Major + v.mnr = m.Minor return nil } @@ -103,10 +107,7 @@ func (v Version) Equal(v2 Version) bool { // // See also UnmarshalJSON. func (v Version) MarshalJSON() ([]byte, error) { - var m refs.Version - v.WriteToV2(&m) - - return m.MarshalJSON() + return neofsproto.MarshalMessageJSON(v.ProtoMessage()) } // UnmarshalJSON decodes NeoFS API protocol JSON format into the Version @@ -117,10 +118,10 @@ func (v Version) MarshalJSON() ([]byte, error) { func (v *Version) UnmarshalJSON(data []byte) error { var m refs.Version - err := m.UnmarshalJSON(data) + err := protojson.Unmarshal(data, &m) if err != nil { return err } - return v.ReadFromV2(m) + return v.FromProtoMessage(&m) } diff --git a/version/version_test.go b/version/version_test.go index ca133641..8d2f169f 100644 --- a/version/version_test.go +++ b/version/version_test.go @@ -4,7 +4,7 @@ import ( "math/rand/v2" "testing" - "github.com/nspcc-dev/neofs-api-go/v2/refs" + "github.com/nspcc-dev/neofs-sdk-go/proto/refs" "github.com/nspcc-dev/neofs-sdk-go/version" versiontest "github.com/nspcc-dev/neofs-sdk-go/version/test" "github.com/stretchr/testify/require" @@ -35,18 +35,17 @@ func testVersionField( t.Run("encoding", func(t *testing.T) { t.Run("api", func(t *testing.T) { var src, dst version.Version - var msg refs.Version set(&dst, val) - src.WriteToV2(&msg) - require.Zero(t, getAPI(&msg)) - require.NoError(t, dst.ReadFromV2(msg)) + msg := src.ProtoMessage() + require.Zero(t, getAPI(msg)) + require.NoError(t, dst.FromProtoMessage(msg)) require.Zero(t, get(dst)) set(&src, val) - src.WriteToV2(&msg) - require.EqualValues(t, val, getAPI(&msg)) - err := dst.ReadFromV2(msg) + msg = src.ProtoMessage() + require.EqualValues(t, val, getAPI(msg)) + err := dst.FromProtoMessage(msg) require.NoError(t, err) require.EqualValues(t, val, get(dst)) })