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..b7665a51 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/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..755c397c 100644 --- a/object/fmt_test.go +++ b/object/fmt_test.go @@ -147,8 +147,8 @@ func TestObject_CalculateAndSetID(t *testing.T) { 115, 225, 81, 196, 171, 228, 226, 110, 237, 114, 117, 86, 8}, setHdr: func(obj *object.Object) { obj.SetPreviousID(anyValidIDs[0]) }}, - {name: "parent signature", id: oid.ID{130, 200, 147, 200, 38, 182, 90, 133, 14, 221, 74, 69, 94, 207, 16, 27, 34, 26, - 215, 171, 97, 13, 103, 237, 19, 30, 107, 199, 83, 24, 190, 211}, setHdr: func(obj *object.Object) { + {name: "parent signature", id: oid.ID{49, 71, 84, 191, 129, 210, 146, 193, 211, 42, 45, 53, 68, 91, 99, 151, 85, 118, 89, 159, + 255, 4, 8, 201, 15, 245, 0, 248, 162, 93, 0, 222}, setHdr: func(obj *object.Object) { var par object.Object par.SetSignature(&anyValidSignatures[0]) obj.SetParent(&par) @@ -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 defed141..2cab4c33 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 e3572c36..690dae61 100644 --- a/proto/reputation/encoding_test.go +++ b/proto/reputation/encoding_test.go @@ -1,10 +1,12 @@ package reputation_test import ( + "math/rand" "testing" neofsproto "github.com/nspcc-dev/neofs-sdk-go/internal/proto" 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" "github.com/stretchr/testify/require" ) @@ -61,6 +63,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) { t.Run("nil in repeated messages", func(t *testing.T) { src := &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)) })