forked from TrueCloudLab/frostfs-sdk-go
Compare commits
110 commits
master
...
fix/sessio
Author | SHA1 | Date | |
---|---|---|---|
|
72baada0bf | ||
|
5f07bcfec2 | ||
|
4d4c5fc70c | ||
|
e7a5728d6b | ||
|
74cd2b12d1 | ||
|
26c1b26eec | ||
|
22ff96ad44 | ||
|
277b8f7de4 | ||
|
e2a88bc258 | ||
|
b9ec85e5e3 | ||
|
de560d7424 | ||
|
e99e9537a2 | ||
|
2e18c3c16d | ||
|
4e1390763a | ||
|
7002b3b0df | ||
|
d1bcce5f79 | ||
|
4b0c67ea7a | ||
|
4fe5e6022d | ||
|
5dec2b49b0 | ||
|
ef887b3ab1 | ||
|
f9d740487a | ||
|
8094342b1c | ||
|
cb4acec6a2 | ||
|
cfdd870755 | ||
|
49bc3b7202 | ||
|
e377b3b4f6 | ||
|
e0afe0807c | ||
|
a4e14ab35b | ||
|
9e1079723e | ||
|
2f45caf8a5 | ||
|
548f911195 | ||
|
149b145073 | ||
|
1d952ced4e | ||
|
483aff30c0 | ||
|
9533a778a8 | ||
|
fc0bd12101 | ||
|
9d25bc7519 | ||
|
d17066dfea | ||
|
fec2f065e8 | ||
|
162a15ae4b | ||
|
0c7bfc2afe | ||
|
669c9ce9bc | ||
|
9e893fe3e9 | ||
|
c97f834c6b | ||
|
04ea0e8f6a | ||
|
626532d7dd | ||
|
3f603dc8eb | ||
|
d7a12a4846 | ||
|
f34c99d538 | ||
|
ca0f19c453 | ||
|
f1b438a2ac | ||
|
7d2cfff825 | ||
|
8ed98d6dec | ||
|
c0aaa66fe5 | ||
|
24527b7880 | ||
|
e0d06dd444 | ||
|
153695a03d | ||
|
e2011832eb | ||
|
ab5ae28fdb | ||
|
0d7d03d56f | ||
|
8eded316de | ||
|
28a3708b4e | ||
|
36b1e8442c | ||
|
25c0fd9b8e | ||
|
64c0612bdc | ||
|
570a628462 | ||
|
77c2e227b9 | ||
|
dbbb22ca28 | ||
|
83ca99ae92 | ||
|
4b6965f209 | ||
|
16daed8140 | ||
|
70df422866 | ||
|
6ac961d41c | ||
|
0bc98456e3 | ||
|
6757c0a706 | ||
|
5d3fcd6f55 | ||
|
34213c6275 | ||
|
bde3b2f4b6 | ||
|
dcdd75b751 | ||
|
32fc84a67c | ||
|
38a613fce5 | ||
|
54faed8384 | ||
|
c5f949e314 | ||
|
d1ae4ef576 | ||
|
47af8441eb | ||
|
8678b36ed9 | ||
|
c6bda422fc | ||
|
dfb799783e | ||
|
493fa50915 | ||
|
08a152b0bf | ||
|
3d42ae62d6 | ||
|
7d66148d1f | ||
|
77f557530e | ||
|
bd429c9f37 | ||
|
71350c1f42 | ||
|
9a543b6f64 | ||
|
651c17f9b3 | ||
|
8fdbf6950a | ||
|
4191e5f13e | ||
ae44191e8c | |||
72df51cfe9 | |||
9e18d50978 | |||
5fc9865577 | |||
|
170f31b7c4 | ||
|
9efc4ecd70 | ||
|
1bf41e9bc1 | ||
|
a690dcb159 | ||
|
4fbd53ba73 | ||
|
9964a83083 | ||
|
f5e1c4c31c |
137 changed files with 5092 additions and 3598 deletions
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
|
@ -1 +1 @@
|
|||
* @alexvanin @fyrchik @cthulhu-rider
|
||||
* @roman-khimov @cthulhu-rider @smallhive @notimetoname
|
||||
|
|
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
|
@ -15,7 +15,7 @@ jobs:
|
|||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
go_versions: [ '1.17.x', '1.18.x', '1.19.x' ]
|
||||
go_versions: [ '1.18.x', '1.19.x', '1.20.x' ]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -17,6 +17,7 @@ vendor/
|
|||
# IDE
|
||||
.idea
|
||||
.vscode
|
||||
*~
|
||||
|
||||
# coverage
|
||||
coverage.txt
|
||||
|
|
60
CHANGELOG.md
Normal file
60
CHANGELOG.md
Normal file
|
@ -0,0 +1,60 @@
|
|||
# Changelog
|
||||
|
||||
## [1.0.0-rc.8] - 2023-04-27
|
||||
|
||||
We've reworked the way we handle keys in the SDK and added an experimental API
|
||||
simplifying object creation. Pools were also improved and a number of other
|
||||
minor enhancements are included in this release as well. Try it in your
|
||||
applications and leave your feedback in issues, we need it to make 1.0.0 a
|
||||
really good release for all NeoFS use cases.
|
||||
|
||||
New features:
|
||||
* object/relations package for large (splitted) object data handling (#360, #348)
|
||||
* pool can now accept stream operations timeout parameter (#364)
|
||||
* pool can also accept callbacks to be invoked after every client response (#365)
|
||||
* additional marshaling/unmarshaling methods for accounting.Decimal,
|
||||
netmap.NetworkInfo and version.Version (#378)
|
||||
* it's possible to get a single client from a pool now (#388)
|
||||
* object.SearchFilters now provide more convenient APIs to search for object
|
||||
by their hashes (#386)
|
||||
* experimental client-side API for object creation (and slicing large inputs
|
||||
into split objects, #382)
|
||||
|
||||
Behaviour changes:
|
||||
* ns.ResolveContainerName changed to ns.ResolveContainerName with more
|
||||
specific input type (#356)
|
||||
* pool.ErrPoolClientUnhealthy is no longer exported (#358)
|
||||
* deprecated object.RawObject type is gone (#387)
|
||||
* pools no longer use default session tokens for read operations (they're not
|
||||
required technically, #363)
|
||||
* Go 1.18+ is required now (#394)
|
||||
* enumeration types in eacl and object packages now have
|
||||
EncodeToString/DecodeString methods replacing String/FromString for
|
||||
serialization; String is still available, but it's not guaranteed to be
|
||||
compatible across versions (#393)
|
||||
* signing APIs no longer require a specific key, it can be provided in a
|
||||
Signer wrapper (with different schemes) or a crypto.StaticSigner can be
|
||||
used for signatures calculated outside of the SDK (#398, #401)
|
||||
* (NetMap).PlacementVectors and (NetMap).ContainerNodes APIs now use more
|
||||
specific types simplifying their use (#396)
|
||||
|
||||
Improvements:
|
||||
* pool can work with some nodes being inaccessible now (#358)
|
||||
* unified error messages in the client/status package (#369)
|
||||
* documentation updates (#370, #392, #396)
|
||||
* updated NeoGo, ANTLR, zap, golang-lru dependencies (#372)
|
||||
* (*Client).ContainerSetEACL method now checks CID internally before
|
||||
generating a request to send to the network (#389)
|
||||
* neofs-api-go signature package is no longer required (#401)
|
||||
|
||||
Bugs fixed:
|
||||
* pool.DeleteObject now correctly handles large (splitted) objects (#360)
|
||||
* object structure problems are no longer considered as connection problems
|
||||
by the pool (#374)
|
||||
* private key used as a part of a map key in the pool (#401)
|
||||
|
||||
## pre-v1.0.0-rc.8
|
||||
|
||||
See git log.
|
||||
|
||||
[1.0.0-rc.8]: https://github.com/nspcc-dev/neofs-sdk-go/compare/v1.0.0-rc.7...v1.0.0-rc.8
|
10
README.md
10
README.md
|
@ -42,10 +42,11 @@ Contains client for working with NeoFS.
|
|||
```go
|
||||
var prmInit client.PrmInit
|
||||
prmInit.SetDefaultPrivateKey(key) // private key for request signing
|
||||
prmInit.ResolveNeoFSFailures() // enable erroneous status parsing
|
||||
|
||||
var c client.Client
|
||||
c.Init(prmInit)
|
||||
c, err := client.New(prmInit)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var prmDial client.PrmDial
|
||||
prmDial.SetServerURI("grpcs://localhost:40005") // endpoint address
|
||||
|
@ -77,8 +78,7 @@ if needed and perform any desired action. In the case above we may want to repor
|
|||
these details to the user as well as retry an operation, possibly with different parameters.
|
||||
Status wire-format is extendable and each node can report any set of details it wants.
|
||||
The set of reserved status codes can be found in
|
||||
[NeoFS API](https://github.com/nspcc-dev/neofs-api/blob/master/status/types.proto). There is also
|
||||
a `client.PrmInit.ResolveNeoFSFailures()` to seamlessly convert erroneous statuses into Go error type.
|
||||
[NeoFS API](https://github.com/nspcc-dev/neofs-api/blob/master/status/types.proto).
|
||||
|
||||
### policy
|
||||
Contains helpers allowing conversion of placing policy from/to JSON representation
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package accounting
|
||||
|
||||
import "github.com/nspcc-dev/neofs-api-go/v2/accounting"
|
||||
import (
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/accounting"
|
||||
)
|
||||
|
||||
// Decimal represents decimal number for accounting operations.
|
||||
//
|
||||
|
@ -62,3 +64,30 @@ func (d Decimal) Precision() uint32 {
|
|||
func (d *Decimal) SetPrecision(p uint32) {
|
||||
(*accounting.Decimal)(d).SetPrecision(p)
|
||||
}
|
||||
|
||||
// Marshal encodes Decimal into a binary format of the NeoFS API protocol
|
||||
// (Protocol Buffers with direct field order).
|
||||
//
|
||||
// See also Unmarshal.
|
||||
func (d Decimal) Marshal() []byte {
|
||||
var m accounting.Decimal
|
||||
d.WriteToV2(&m)
|
||||
|
||||
return m.StableMarshal(nil)
|
||||
}
|
||||
|
||||
// Unmarshal decodes NeoFS API protocol binary format into the Decimal
|
||||
// (Protocol Buffers with direct field order). Returns an error describing
|
||||
// a format violation.
|
||||
//
|
||||
// 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)
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
|
||||
v2accounting "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"
|
||||
)
|
||||
|
||||
|
@ -44,3 +45,12 @@ func TestDecimalMessageV2(t *testing.T) {
|
|||
require.EqualValues(t, d.Value(), m2.GetValue())
|
||||
require.EqualValues(t, d.Precision(), m2.GetPrecision())
|
||||
}
|
||||
|
||||
func TestDecimal_Marshal(t *testing.T) {
|
||||
d := *accountingtest.Decimal()
|
||||
|
||||
var d2 accounting.Decimal
|
||||
require.NoError(t, d2.Unmarshal(d.Marshal()))
|
||||
|
||||
require.Equal(t, d, d2)
|
||||
}
|
||||
|
|
76
audit/collect.go
Normal file
76
audit/collect.go
Normal file
|
@ -0,0 +1,76 @@
|
|||
package audit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"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/object"
|
||||
oid "github.com/nspcc-dev/neofs-sdk-go/object/id"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/object/relations"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/storagegroup"
|
||||
"github.com/nspcc-dev/tzhash/tz"
|
||||
)
|
||||
|
||||
type Collector interface {
|
||||
Head(ctx context.Context, addr oid.Address) (*object.Object, error)
|
||||
relations.Relations
|
||||
}
|
||||
|
||||
// CollectMembers creates new storage group structure and fills it
|
||||
// with information about members collected via HeadReceiver.
|
||||
//
|
||||
// Resulting storage group consists of physically stored objects only.
|
||||
func CollectMembers(ctx context.Context, collector Collector, cnr cid.ID, members []oid.ID, tokens relations.Tokens, calcHomoHash bool) (*storagegroup.StorageGroup, error) {
|
||||
var (
|
||||
err error
|
||||
sumPhySize uint64
|
||||
phyMembers []oid.ID
|
||||
phyHashes [][]byte
|
||||
addr oid.Address
|
||||
sg storagegroup.StorageGroup
|
||||
)
|
||||
|
||||
addr.SetContainer(cnr)
|
||||
|
||||
for i := range members {
|
||||
if phyMembers, err = relations.ListRelations(ctx, collector, cnr, members[i], tokens, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, phyMember := range phyMembers {
|
||||
addr.SetObject(phyMember)
|
||||
leaf, err := collector.Head(ctx, addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("head phy member '%s': %w", phyMember.EncodeToString(), err)
|
||||
}
|
||||
|
||||
sumPhySize += leaf.PayloadSize()
|
||||
cs, _ := leaf.PayloadHomomorphicHash()
|
||||
|
||||
if calcHomoHash {
|
||||
phyHashes = append(phyHashes, cs.Value())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sg.SetMembers(phyMembers)
|
||||
sg.SetValidationDataSize(sumPhySize)
|
||||
|
||||
if calcHomoHash {
|
||||
sumHash, err := tz.Concat(phyHashes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var cs checksum.Checksum
|
||||
tzHash := [64]byte{}
|
||||
copy(tzHash[:], sumHash)
|
||||
cs.SetTillichZemor(tzHash)
|
||||
|
||||
sg.SetValidationDataHash(cs)
|
||||
}
|
||||
|
||||
return &sg, nil
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
package bearer
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
|
@ -9,7 +8,6 @@ import (
|
|||
"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"
|
||||
neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/eacl"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/user"
|
||||
)
|
||||
|
@ -136,9 +134,10 @@ func (b Token) WriteToV2(m *acl.BearerToken) {
|
|||
}
|
||||
|
||||
// SetExp sets "exp" (expiration time) claim which identifies the
|
||||
// expiration time (in NeoFS epochs) on or after which the Token MUST NOT be
|
||||
// accepted for processing. The processing of the "exp" claim requires that the
|
||||
// current epoch MUST be before the expiration epoch listed in the "exp" claim.
|
||||
// expiration time (in NeoFS epochs) after which the Token MUST NOT be
|
||||
// accepted for processing. The processing of the "exp" claim requires
|
||||
// that the current epoch MUST be before or equal to the expiration epoch
|
||||
// listed in the "exp" claim.
|
||||
//
|
||||
// Naming is inspired by https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.4.
|
||||
//
|
||||
|
@ -179,7 +178,7 @@ func (b *Token) SetIat(iat uint64) {
|
|||
//
|
||||
// See also SetExp, SetNbf, SetIat.
|
||||
func (b Token) InvalidAt(epoch uint64) bool {
|
||||
return !b.lifetimeSet || b.nbf > epoch || b.iat > epoch || b.exp <= epoch
|
||||
return !b.lifetimeSet || b.nbf > epoch || b.iat > epoch || b.exp < epoch
|
||||
}
|
||||
|
||||
// SetEACLTable sets eacl.Table that replaces the one from the issuer's
|
||||
|
@ -244,7 +243,7 @@ func (b Token) AssertUser(id user.ID) bool {
|
|||
return !b.targetUserSet || b.targetUser.Equals(id)
|
||||
}
|
||||
|
||||
// Sign calculates and writes signature of the Token data using issuer's secret.
|
||||
// Sign calculates and writes signature of the Token data using issuer's signer.
|
||||
// Returns signature calculation errors.
|
||||
//
|
||||
// Sign MUST be called if Token is going to be transmitted over
|
||||
|
@ -254,10 +253,10 @@ func (b Token) AssertUser(id user.ID) bool {
|
|||
// expected to be calculated as a final stage of Token formation.
|
||||
//
|
||||
// See also VerifySignature, Issuer.
|
||||
func (b *Token) Sign(key ecdsa.PrivateKey) error {
|
||||
func (b *Token) Sign(signer neofscrypto.Signer) error {
|
||||
var sig neofscrypto.Signature
|
||||
|
||||
err := sig.Calculate(neofsecdsa.Signer(key), b.signedData())
|
||||
err := sig.Calculate(signer, b.signedData())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -359,9 +358,8 @@ func ResolveIssuer(b Token) (usr user.ID) {
|
|||
binKey := b.SigningKeyBytes()
|
||||
|
||||
if len(binKey) != 0 {
|
||||
var key neofsecdsa.PublicKey
|
||||
if key.Decode(binKey) == nil {
|
||||
user.IDFromKey(&usr, ecdsa.PublicKey(key))
|
||||
if err := user.IDFromKey(&usr, binKey); err != nil {
|
||||
usr = user.ID{}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,14 +5,13 @@ import (
|
|||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"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"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/bearer"
|
||||
bearertest "github.com/nspcc-dev/neofs-sdk-go/bearer/test"
|
||||
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"
|
||||
"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"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/user"
|
||||
|
@ -38,7 +37,7 @@ func isEqualEACLTables(t1, t2 eacl.Table) bool {
|
|||
func TestToken_SetEACLTable(t *testing.T) {
|
||||
var val bearer.Token
|
||||
var m acl.BearerToken
|
||||
filled := bearertest.Token()
|
||||
filled := bearertest.Token(t)
|
||||
|
||||
val.WriteToV2(&m)
|
||||
require.Zero(t, m.GetBody())
|
||||
|
@ -58,7 +57,7 @@ func TestToken_SetEACLTable(t *testing.T) {
|
|||
|
||||
// set value
|
||||
|
||||
eaclTable := *eacltest.Table()
|
||||
eaclTable := *eacltest.Table(t)
|
||||
|
||||
val.SetEACLTable(eaclTable)
|
||||
require.True(t, isEqualEACLTables(eaclTable, val.EACLTable()))
|
||||
|
@ -84,7 +83,7 @@ func TestToken_SetEACLTable(t *testing.T) {
|
|||
func TestToken_ForUser(t *testing.T) {
|
||||
var val bearer.Token
|
||||
var m acl.BearerToken
|
||||
filled := bearertest.Token()
|
||||
filled := bearertest.Token(t)
|
||||
|
||||
val.WriteToV2(&m)
|
||||
require.Zero(t, m.GetBody())
|
||||
|
@ -107,7 +106,7 @@ func TestToken_ForUser(t *testing.T) {
|
|||
require.Zero(t, m.GetBody())
|
||||
|
||||
// set value
|
||||
usr := *usertest.ID()
|
||||
usr := *usertest.ID(t)
|
||||
|
||||
var usrV2 refs.OwnerID
|
||||
usr.WriteToV2(&usrV2)
|
||||
|
@ -138,7 +137,7 @@ func TestToken_ForUser(t *testing.T) {
|
|||
func testLifetimeClaim(t *testing.T, setter func(*bearer.Token, uint64), getter func(*acl.BearerToken) uint64) {
|
||||
var val bearer.Token
|
||||
var m acl.BearerToken
|
||||
filled := bearertest.Token()
|
||||
filled := bearertest.Token(t)
|
||||
|
||||
val.WriteToV2(&m)
|
||||
require.Zero(t, m.GetBody())
|
||||
|
@ -220,7 +219,7 @@ func TestToken_InvalidAt(t *testing.T) {
|
|||
require.True(t, val.InvalidAt(1))
|
||||
require.False(t, val.InvalidAt(2))
|
||||
require.False(t, val.InvalidAt(3))
|
||||
require.True(t, val.InvalidAt(4))
|
||||
require.False(t, val.InvalidAt(4))
|
||||
require.True(t, val.InvalidAt(5))
|
||||
}
|
||||
|
||||
|
@ -230,7 +229,7 @@ func TestToken_AssertContainer(t *testing.T) {
|
|||
|
||||
require.True(t, val.AssertContainer(cnr))
|
||||
|
||||
eaclTable := *eacltest.Table()
|
||||
eaclTable := *eacltest.Table(t)
|
||||
|
||||
eaclTable.SetCID(cidtest.ID())
|
||||
val.SetEACLTable(eaclTable)
|
||||
|
@ -243,11 +242,11 @@ func TestToken_AssertContainer(t *testing.T) {
|
|||
|
||||
func TestToken_AssertUser(t *testing.T) {
|
||||
var val bearer.Token
|
||||
usr := *usertest.ID()
|
||||
usr := *usertest.ID(t)
|
||||
|
||||
require.True(t, val.AssertUser(usr))
|
||||
|
||||
val.ForUser(*usertest.ID())
|
||||
val.ForUser(*usertest.ID(t))
|
||||
require.False(t, val.AssertUser(usr))
|
||||
|
||||
val.ForUser(usr)
|
||||
|
@ -259,13 +258,11 @@ func TestToken_Sign(t *testing.T) {
|
|||
|
||||
require.False(t, val.VerifySignature())
|
||||
|
||||
k, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
signer := test.RandomSigner(t)
|
||||
|
||||
key := k.PrivateKey
|
||||
val = bearertest.Token()
|
||||
val = bearertest.Token(t)
|
||||
|
||||
require.NoError(t, val.Sign(key))
|
||||
require.NoError(t, val.Sign(signer))
|
||||
|
||||
require.True(t, val.VerifySignature())
|
||||
|
||||
|
@ -275,7 +272,7 @@ func TestToken_Sign(t *testing.T) {
|
|||
require.NotZero(t, m.GetSignature().GetKey())
|
||||
require.NotZero(t, m.GetSignature().GetSign())
|
||||
|
||||
val2 := bearertest.Token()
|
||||
val2 := bearertest.Token(t)
|
||||
|
||||
require.NoError(t, val2.Unmarshal(val.Marshal()))
|
||||
require.True(t, val2.VerifySignature())
|
||||
|
@ -283,7 +280,7 @@ func TestToken_Sign(t *testing.T) {
|
|||
jd, err := val.MarshalJSON()
|
||||
require.NoError(t, err)
|
||||
|
||||
val2 = bearertest.Token()
|
||||
val2 = bearertest.Token(t)
|
||||
require.NoError(t, val2.UnmarshalJSON(jd))
|
||||
require.True(t, val2.VerifySignature())
|
||||
}
|
||||
|
@ -299,7 +296,7 @@ func TestToken_ReadFromV2(t *testing.T) {
|
|||
|
||||
require.Error(t, val.ReadFromV2(m))
|
||||
|
||||
eaclTable := eacltest.Table().ToV2()
|
||||
eaclTable := eacltest.Table(t).ToV2()
|
||||
body.SetEACL(eaclTable)
|
||||
|
||||
require.Error(t, val.ReadFromV2(m))
|
||||
|
@ -328,7 +325,7 @@ func TestToken_ReadFromV2(t *testing.T) {
|
|||
val.WriteToV2(&m2)
|
||||
require.Equal(t, m, m2)
|
||||
|
||||
usr, usr2 := *usertest.ID(), *usertest.ID()
|
||||
usr, usr2 := *usertest.ID(t), *usertest.ID(t)
|
||||
|
||||
require.True(t, val.AssertUser(usr))
|
||||
require.True(t, val.AssertUser(usr2))
|
||||
|
@ -346,10 +343,7 @@ func TestToken_ReadFromV2(t *testing.T) {
|
|||
require.True(t, val.AssertUser(usr))
|
||||
require.False(t, val.AssertUser(usr2))
|
||||
|
||||
k, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
signer := neofsecdsa.Signer(k.PrivateKey)
|
||||
signer := test.RandomSigner(t)
|
||||
|
||||
var s neofscrypto.Signature
|
||||
|
||||
|
@ -363,8 +357,7 @@ func TestToken_ReadFromV2(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestResolveIssuer(t *testing.T) {
|
||||
k, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
signer := test.RandomSigner(t)
|
||||
|
||||
var val bearer.Token
|
||||
|
||||
|
@ -381,10 +374,10 @@ func TestResolveIssuer(t *testing.T) {
|
|||
|
||||
require.Zero(t, bearer.ResolveIssuer(val))
|
||||
|
||||
require.NoError(t, val.Sign(k.PrivateKey))
|
||||
require.NoError(t, val.Sign(signer))
|
||||
|
||||
var usr user.ID
|
||||
user.IDFromKey(&usr, k.PrivateKey.PublicKey)
|
||||
require.NoError(t, user.IDFromSigner(&usr, signer))
|
||||
|
||||
require.Equal(t, usr, bearer.ResolveIssuer(val))
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package bearertest
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neofs-sdk-go/bearer"
|
||||
eacltest "github.com/nspcc-dev/neofs-sdk-go/eacl/test"
|
||||
usertest "github.com/nspcc-dev/neofs-sdk-go/user/test"
|
||||
|
@ -9,12 +11,12 @@ import (
|
|||
// Token returns random bearer.Token.
|
||||
//
|
||||
// Resulting token is unsigned.
|
||||
func Token() (t bearer.Token) {
|
||||
t.SetExp(3)
|
||||
t.SetNbf(2)
|
||||
t.SetIat(1)
|
||||
t.ForUser(*usertest.ID())
|
||||
t.SetEACLTable(*eacltest.Table())
|
||||
func Token(t testing.TB) (tok bearer.Token) {
|
||||
tok.SetExp(3)
|
||||
tok.SetNbf(2)
|
||||
tok.SetIat(1)
|
||||
tok.ForUser(*usertest.ID(t))
|
||||
tok.SetEACLTable(*eacltest.Table(t))
|
||||
|
||||
return t
|
||||
return tok
|
||||
}
|
||||
|
|
|
@ -28,8 +28,6 @@ func (x *PrmBalanceGet) SetAccount(id user.ID) {
|
|||
|
||||
// ResBalanceGet groups resulting values of BalanceGet operation.
|
||||
type ResBalanceGet struct {
|
||||
statusRes
|
||||
|
||||
amount accounting.Decimal
|
||||
}
|
||||
|
||||
|
@ -40,23 +38,17 @@ func (x ResBalanceGet) Amount() accounting.Decimal {
|
|||
|
||||
// BalanceGet requests current balance of the NeoFS account.
|
||||
//
|
||||
// Exactly one return value is non-nil. By default, server status is returned in res structure.
|
||||
// Any client's internal or transport errors are returned as `error`,
|
||||
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful
|
||||
// NeoFS status codes are returned as `error`, otherwise, are included
|
||||
// in the returned result structure.
|
||||
// Any errors (local or remote, including returned status codes) are returned as Go errors,
|
||||
// see [apistatus] package for NeoFS-specific error types.
|
||||
//
|
||||
// Immediately panics if parameters are set incorrectly (see PrmBalanceGet docs).
|
||||
// Context is required and must not be nil. It is used for network communication.
|
||||
//
|
||||
// Return statuses:
|
||||
// - global (see Client docs).
|
||||
// Return errors:
|
||||
// - [ErrMissingAccount]
|
||||
func (c *Client) BalanceGet(ctx context.Context, prm PrmBalanceGet) (*ResBalanceGet, error) {
|
||||
switch {
|
||||
case ctx == nil:
|
||||
panic(panicMsgMissingContext)
|
||||
case !prm.accountSet:
|
||||
panic("account not set")
|
||||
return nil, ErrMissingAccount
|
||||
}
|
||||
|
||||
// form request body
|
||||
|
@ -81,7 +73,6 @@ func (c *Client) BalanceGet(ctx context.Context, prm PrmBalanceGet) (*ResBalance
|
|||
c.initCallContext(&cc)
|
||||
cc.meta = prm.prmCommonMeta
|
||||
cc.req = &req
|
||||
cc.statusRes = &res
|
||||
cc.call = func() (responseV2, error) {
|
||||
return rpcapi.Balance(&c.c, &req, client.WithContext(ctx))
|
||||
}
|
||||
|
|
|
@ -7,10 +7,13 @@ import (
|
|||
v2netmap "github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
||||
rpcapi "github.com/nspcc-dev/neofs-api-go/v2/rpc"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/rpc/client"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/session"
|
||||
)
|
||||
|
||||
// interface of NeoFS API server. Exists for test purposes only.
|
||||
type neoFSAPIServer interface {
|
||||
createSession(cli *client.Client, req *session.CreateRequest, opts ...client.CallOption) (*session.CreateResponse, error)
|
||||
|
||||
netMapSnapshot(context.Context, v2netmap.SnapshotRequest) (*v2netmap.SnapshotResponse, error)
|
||||
}
|
||||
|
||||
|
@ -33,3 +36,12 @@ func (x *coreServer) netMapSnapshot(ctx context.Context, req v2netmap.SnapshotRe
|
|||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (x *coreServer) createSession(cli *client.Client, req *session.CreateRequest, opts ...client.CallOption) (*session.CreateResponse, error) {
|
||||
resp, err := rpcapi.CreateSession(cli, req, opts...)
|
||||
if err != nil {
|
||||
return nil, rpcErr(err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
|
|
@ -2,14 +2,15 @@ package client
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
v2accounting "github.com/nspcc-dev/neofs-api-go/v2/accounting"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/rpc"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/rpc/client"
|
||||
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
|
||||
)
|
||||
|
||||
// Client represents virtual connection to the NeoFS network to communicate
|
||||
|
@ -17,28 +18,26 @@ import (
|
|||
// an abstraction interface from the protocol details of data transfer over
|
||||
// a network in NeoFS.
|
||||
//
|
||||
// Client can be created using simple Go variable declaration. Before starting
|
||||
// work with the Client, it SHOULD BE correctly initialized (see Init method).
|
||||
// Client can be created using [New].
|
||||
// Before executing the NeoFS operations using the Client, connection to the
|
||||
// server MUST BE correctly established (see Dial method and pay attention
|
||||
// to the mandatory parameters). Using the Client before connecting have
|
||||
// been established can lead to a panic. After the work, the Client SHOULD BE
|
||||
// closed (see Close method): it frees internal and system resources which were
|
||||
// allocated for the period of work of the Client. Calling Init/Dial/Close method
|
||||
// allocated for the period of work of the Client. Calling [Client.Dial]/[Client.Close] method
|
||||
// during the communication process step strongly discouraged as it leads to
|
||||
// undefined behavior.
|
||||
//
|
||||
// Each method which produces a NeoFS API call may return a server response.
|
||||
// Status responses are returned in the result structure, and can be cast
|
||||
// to built-in error instance (or in the returned error if the client is
|
||||
// configured accordingly). Certain statuses can be checked using `apistatus`
|
||||
// and standard `errors` packages. Note that package provides some helper
|
||||
// functions to work with status returns (e.g. IsErrContainerNotFound).
|
||||
// configured accordingly). Certain statuses can be checked using [apistatus]
|
||||
// and standard [errors] packages.
|
||||
// All possible responses are documented in methods, however, some may be
|
||||
// returned from all of them (pay attention to the presence of the pointer sign):
|
||||
// - *apistatus.ServerInternal on internal server error;
|
||||
// - *apistatus.NodeUnderMaintenance if a server is under maintenance;
|
||||
// - *apistatus.SuccessDefaultV2 on default success.
|
||||
// - *[apistatus.ServerInternal] on internal server error;
|
||||
// - *[apistatus.NodeUnderMaintenance] if a server is under maintenance;
|
||||
// - *[apistatus.SuccessDefaultV2] on default success.
|
||||
//
|
||||
// Client MUST NOT be copied by value: use pointer to Client instead.
|
||||
//
|
||||
|
@ -51,14 +50,21 @@ type Client struct {
|
|||
server neoFSAPIServer
|
||||
}
|
||||
|
||||
// Init brings the Client instance to its initial state.
|
||||
var errNonNeoSigner = fmt.Errorf("%w: expected ECDSA_DETERMINISTIC_SHA256 scheme", neofscrypto.ErrIncorrectSigner)
|
||||
|
||||
// New creates an instance of Client initialized with the given parameters.
|
||||
//
|
||||
// One-time method call during application init stage (before Dial) is expected.
|
||||
// Calling multiple times leads to undefined behavior.
|
||||
// See docs of [PrmInit] methods for details. See also [Client.Dial]/[Client.Close].
|
||||
//
|
||||
// See docs of PrmInit methods for details. See also Dial / Close.
|
||||
func (c *Client) Init(prm PrmInit) {
|
||||
// Returned errors:
|
||||
// - [neofscrypto.ErrIncorrectSigner]
|
||||
func New(prm PrmInit) (*Client, error) {
|
||||
var c = new(Client)
|
||||
if prm.signer != nil && prm.signer.Scheme() != neofscrypto.ECDSA_DETERMINISTIC_SHA256 {
|
||||
return nil, errNonNeoSigner
|
||||
}
|
||||
c.prm = prm
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Dial establishes a connection to the server from the NeoFS network.
|
||||
|
@ -72,18 +78,22 @@ func (c *Client) Init(prm PrmInit) {
|
|||
// Panics if required parameters are set incorrectly, look carefully
|
||||
// at the method documentation.
|
||||
//
|
||||
// One-time method call during application start-up stage (after Init ) is expected.
|
||||
// One-time method call during application start-up stage is expected.
|
||||
// Calling multiple times leads to undefined behavior.
|
||||
//
|
||||
// See also Init / Close.
|
||||
// Return client errors:
|
||||
// - [ErrMissingServer]
|
||||
// - [ErrNonPositiveTimeout]
|
||||
//
|
||||
// See also [Client.Close].
|
||||
func (c *Client) Dial(prm PrmDial) error {
|
||||
if prm.endpoint == "" {
|
||||
panic("server address is unset or empty")
|
||||
return ErrMissingServer
|
||||
}
|
||||
|
||||
if prm.timeoutDialSet {
|
||||
if prm.timeoutDial <= 0 {
|
||||
panic("non-positive timeout")
|
||||
return ErrNonPositiveTimeout
|
||||
}
|
||||
} else {
|
||||
prm.timeoutDial = 5 * time.Second
|
||||
|
@ -91,7 +101,7 @@ func (c *Client) Dial(prm PrmDial) error {
|
|||
|
||||
if prm.streamTimeoutSet {
|
||||
if prm.streamTimeout <= 0 {
|
||||
panic("non-positive timeout")
|
||||
return ErrNonPositiveTimeout
|
||||
}
|
||||
} else {
|
||||
prm.streamTimeout = 10 * time.Second
|
||||
|
@ -134,41 +144,34 @@ func (c *Client) setNeoFSAPIServer(server neoFSAPIServer) {
|
|||
// with server operations processing on running goroutines: in this case
|
||||
// they are likely to fail due to a connection error.
|
||||
//
|
||||
// One-time method call during application shutdown stage (after Init and Dial)
|
||||
// One-time method call during application shutdown stage (after [Client.Dial])
|
||||
// is expected. Calling multiple times leads to undefined behavior.
|
||||
//
|
||||
// See also Init / Dial.
|
||||
// See also [Client.Dial].
|
||||
func (c *Client) Close() error {
|
||||
return c.c.Conn().Close()
|
||||
}
|
||||
|
||||
// PrmInit groups initialization parameters of Client instances.
|
||||
//
|
||||
// See also Init.
|
||||
// See also [New].
|
||||
type PrmInit struct {
|
||||
resolveNeoFSErrors bool
|
||||
|
||||
key ecdsa.PrivateKey
|
||||
signer neofscrypto.Signer
|
||||
|
||||
cbRespInfo func(ResponseMetaInfo) error
|
||||
|
||||
netMagic uint64
|
||||
}
|
||||
|
||||
// SetDefaultPrivateKey sets Client private key to be used for the protocol
|
||||
// SetDefaultSigner sets Client private signer to be used for the protocol
|
||||
// communication by default.
|
||||
//
|
||||
// Required for operations without custom key parametrization (see corresponding Prm* docs).
|
||||
func (x *PrmInit) SetDefaultPrivateKey(key ecdsa.PrivateKey) {
|
||||
x.key = key
|
||||
}
|
||||
|
||||
// ResolveNeoFSFailures makes the Client to resolve failure statuses of the
|
||||
// NeoFS protocol into Go built-in errors. These errors are returned from
|
||||
// each protocol operation. By default, statuses aren't resolved and written
|
||||
// to the resulting structure (see corresponding Res* docs).
|
||||
func (x *PrmInit) ResolveNeoFSFailures() {
|
||||
x.resolveNeoFSErrors = true
|
||||
// Optional if you intend to sign every request separately (see Prm* docs), but
|
||||
// required if you'd like to use this signer for all operations implicitly.
|
||||
// If specified, MUST be of [neofscrypto.ECDSA_DETERMINISTIC_SHA256] scheme,
|
||||
// for example, [neofsecdsa.SignerRFC6979] can be used.
|
||||
func (x *PrmInit) SetDefaultSigner(signer neofscrypto.Signer) {
|
||||
x.signer = signer
|
||||
}
|
||||
|
||||
// SetResponseInfoCallback makes the Client to pass ResponseMetaInfo from each
|
||||
|
|
|
@ -2,12 +2,10 @@ package client
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
|
||||
apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status"
|
||||
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -15,28 +13,21 @@ import (
|
|||
File contains common functionality used for client package testing.
|
||||
*/
|
||||
|
||||
var key, _ = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
|
||||
var statusErr apistatus.ServerInternal
|
||||
|
||||
func init() {
|
||||
statusErr.SetMessage("test status error")
|
||||
}
|
||||
|
||||
func assertStatusErr(tb testing.TB, res interface{ Status() apistatus.Status }) {
|
||||
require.IsType(tb, &statusErr, res.Status())
|
||||
require.Equal(tb, statusErr.Message(), res.Status().(*apistatus.ServerInternal).Message())
|
||||
}
|
||||
|
||||
func newClient(server neoFSAPIServer) *Client {
|
||||
func newClient(t *testing.T, signer neofscrypto.Signer, server neoFSAPIServer) *Client {
|
||||
var prm PrmInit
|
||||
prm.SetDefaultPrivateKey(*key)
|
||||
prm.SetDefaultSigner(signer)
|
||||
|
||||
var c Client
|
||||
c.Init(prm)
|
||||
c, err := New(prm)
|
||||
require.NoError(t, err)
|
||||
c.setNeoFSAPIServer(server)
|
||||
|
||||
return &c
|
||||
return c
|
||||
}
|
||||
|
||||
func TestClient_DialContext(t *testing.T) {
|
||||
|
|
|
@ -1,39 +1,16 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
|
||||
"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"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/signature"
|
||||
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"
|
||||
)
|
||||
|
||||
// common interface of resulting structures with API status.
|
||||
type resCommon interface {
|
||||
setStatus(apistatus.Status)
|
||||
}
|
||||
|
||||
// structure is embedded to all resulting types in order to inherit status-related methods.
|
||||
type statusRes struct {
|
||||
st apistatus.Status
|
||||
}
|
||||
|
||||
// setStatus implements resCommon interface method.
|
||||
func (x *statusRes) setStatus(st apistatus.Status) {
|
||||
x.st = st
|
||||
}
|
||||
|
||||
// Status returns server's status return.
|
||||
//
|
||||
// Use apistatus package functionality to handle the status.
|
||||
func (x statusRes) Status() apistatus.Status {
|
||||
return x.st
|
||||
}
|
||||
|
||||
// groups meta parameters shared between all Client operations.
|
||||
type prmCommonMeta struct {
|
||||
// NeoFS request X-Headers
|
||||
|
@ -70,13 +47,6 @@ func writeXHeadersToMeta(xHeaders []string, h *v2session.RequestMetaHeader) {
|
|||
h.SetXHeaders(hs)
|
||||
}
|
||||
|
||||
// panic messages.
|
||||
const (
|
||||
panicMsgMissingContext = "missing context"
|
||||
panicMsgMissingContainer = "missing container"
|
||||
panicMsgMissingObject = "missing object"
|
||||
)
|
||||
|
||||
// groups all the details required to send a single request and process a response to it.
|
||||
type contextCall struct {
|
||||
// ==================================================
|
||||
|
@ -91,15 +61,12 @@ type contextCall struct {
|
|||
// ==================================================
|
||||
// shared parameters which are set uniformly on all calls
|
||||
|
||||
// request signing key
|
||||
key ecdsa.PrivateKey
|
||||
// request signer
|
||||
signer neofscrypto.Signer
|
||||
|
||||
// callback prior to processing the response by the client
|
||||
callbackResp func(ResponseMetaInfo) error
|
||||
|
||||
// if set, protocol errors will be expanded into a final error
|
||||
resolveAPIFailures bool
|
||||
|
||||
// NeoFS network magic
|
||||
netMagic uint64
|
||||
|
||||
|
@ -109,10 +76,7 @@ type contextCall struct {
|
|||
// ==================================================
|
||||
// custom call parameters
|
||||
|
||||
// structure of the call result
|
||||
statusRes resCommon
|
||||
|
||||
// request to be signed with a key and sent
|
||||
// request to be signed with a signer and sent
|
||||
req request
|
||||
|
||||
// function to send a request (unary) and receive a response
|
||||
|
@ -187,7 +151,7 @@ func (x *contextCall) writeRequest() bool {
|
|||
x.req.SetVerificationHeader(nil)
|
||||
|
||||
// sign the request
|
||||
x.err = signature.SignServiceMessage(&x.key, x.req)
|
||||
x.err = signServiceMessage(x.signer, x.req)
|
||||
if x.err != nil {
|
||||
x.err = fmt.Errorf("sign request: %w", x.err)
|
||||
return false
|
||||
|
@ -226,44 +190,28 @@ func (x *contextCall) processResponse() bool {
|
|||
// while verification needs marshaling
|
||||
|
||||
// verify response signature
|
||||
x.err = signature.VerifyServiceMessage(x.resp)
|
||||
x.err = verifyServiceMessage(x.resp)
|
||||
if x.err != nil {
|
||||
x.err = fmt.Errorf("invalid response signature: %w", x.err)
|
||||
return false
|
||||
}
|
||||
|
||||
// get result status
|
||||
st := apistatus.FromStatusV2(x.resp.GetMetaHeader().GetStatus())
|
||||
|
||||
// unwrap unsuccessful status and return it
|
||||
// as error if client has been configured so
|
||||
successfulStatus := apistatus.IsSuccessful(st)
|
||||
|
||||
if x.resolveAPIFailures {
|
||||
x.err = apistatus.ErrFromStatus(st)
|
||||
} else {
|
||||
x.statusRes.setStatus(st)
|
||||
x.err = apistatus.ErrorFromV2(x.resp.GetMetaHeader().GetStatus())
|
||||
return x.err == nil
|
||||
}
|
||||
|
||||
return successfulStatus
|
||||
// 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)
|
||||
}
|
||||
|
||||
// processResponse verifies response signature and converts status to an error if needed.
|
||||
func (c *Client) processResponse(resp responseV2) (apistatus.Status, error) {
|
||||
err := signature.VerifyServiceMessage(resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid response signature: %w", err)
|
||||
}
|
||||
|
||||
st := apistatus.FromStatusV2(resp.GetMetaHeader().GetStatus())
|
||||
if c.prm.resolveNeoFSErrors {
|
||||
return st, apistatus.ErrFromStatus(st)
|
||||
}
|
||||
return st, nil
|
||||
return apistatus.ErrorFromV2(resp.GetMetaHeader().GetStatus())
|
||||
}
|
||||
|
||||
// reads response (if rResp is set) and processes it. Result means success.
|
||||
// If failed, contextCall.err (or statusRes if resolveAPIFailures is set) contains the reason.
|
||||
// If failed, contextCall.err contains the reason.
|
||||
func (x *contextCall) readResponse() bool {
|
||||
if x.rResp != nil {
|
||||
x.err = x.rResp()
|
||||
|
@ -328,8 +276,7 @@ func (x *contextCall) processCall() bool {
|
|||
|
||||
// initializes static cross-call parameters inherited from client.
|
||||
func (c *Client) initCallContext(ctx *contextCall) {
|
||||
ctx.key = c.prm.key
|
||||
ctx.resolveAPIFailures = c.prm.resolveNeoFSErrors
|
||||
ctx.signer = c.prm.signer
|
||||
ctx.callbackResp = c.prm.cbRespInfo
|
||||
ctx.netMagic = c.prm.netMagic
|
||||
}
|
||||
|
|
|
@ -13,7 +13,6 @@ import (
|
|||
"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"
|
||||
neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/eacl"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/session"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/user"
|
||||
|
@ -28,6 +27,8 @@ type PrmContainerPut struct {
|
|||
|
||||
sessionSet bool
|
||||
session session.Container
|
||||
|
||||
signer neofscrypto.Signer
|
||||
}
|
||||
|
||||
// SetContainer sets structured information about new NeoFS container.
|
||||
|
@ -37,6 +38,13 @@ func (x *PrmContainerPut) SetContainer(cnr container.Container) {
|
|||
x.cnrSet = true
|
||||
}
|
||||
|
||||
// SetSigner sets signer to sign request payload.
|
||||
// Signer's scheme MUST be neofscrypto.ECDSA_DETERMINISTIC_SHA256. For example, you can use neofsecdsa.SignerRFC6979.
|
||||
// Optional parameter: defaults to internal Client signer.
|
||||
func (x *PrmContainerPut) SetSigner(signer neofscrypto.Signer) {
|
||||
x.signer = signer
|
||||
}
|
||||
|
||||
// WithinSession specifies session within which container should be saved.
|
||||
//
|
||||
// Creator of the session acquires the authorship of the request. This affects
|
||||
|
@ -44,7 +52,7 @@ func (x *PrmContainerPut) SetContainer(cnr container.Container) {
|
|||
//
|
||||
// Session is optional, if set the following requirements apply:
|
||||
// - session operation MUST be session.VerbContainerPut (ForVerb)
|
||||
// - token MUST be signed using private key of the owner of the container to be saved
|
||||
// - token MUST be signed using private signer of the owner of the container to be saved
|
||||
func (x *PrmContainerPut) WithinSession(s session.Container) {
|
||||
x.session = s
|
||||
x.sessionSet = true
|
||||
|
@ -52,8 +60,6 @@ func (x *PrmContainerPut) WithinSession(s session.Container) {
|
|||
|
||||
// ResContainerPut groups resulting values of ContainerPut operation.
|
||||
type ResContainerPut struct {
|
||||
statusRes
|
||||
|
||||
id cid.ID
|
||||
}
|
||||
|
||||
|
@ -64,41 +70,43 @@ func (x ResContainerPut) ID() cid.ID {
|
|||
return x.id
|
||||
}
|
||||
|
||||
func (c *Client) defaultSigner() neofscrypto.Signer {
|
||||
return c.prm.signer
|
||||
}
|
||||
|
||||
// ContainerPut sends request to save container in NeoFS.
|
||||
//
|
||||
// Exactly one return value is non-nil. By default, server status is returned in res structure.
|
||||
// Any client's internal or transport errors are returned as `error`.
|
||||
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful
|
||||
// NeoFS status codes are returned as `error`, otherwise, are included
|
||||
// in the returned result structure.
|
||||
// Any errors (local or remote, including returned status codes) are returned as Go errors,
|
||||
// see [apistatus] package for NeoFS-specific error types.
|
||||
//
|
||||
// Operation is asynchronous and no guaranteed even in the absence of errors.
|
||||
// The required time is also not predictable.
|
||||
//
|
||||
// Success can be verified by reading by identifier (see ResContainerPut.ID).
|
||||
//
|
||||
// Immediately panics if parameters are set incorrectly (see PrmContainerPut docs).
|
||||
// Context is required and must not be nil. It is used for network communication.
|
||||
//
|
||||
// Return statuses:
|
||||
// - global (see Client docs).
|
||||
// Return errors:
|
||||
// - [ErrMissingContainer]
|
||||
func (c *Client) ContainerPut(ctx context.Context, prm PrmContainerPut) (*ResContainerPut, error) {
|
||||
// check parameters
|
||||
switch {
|
||||
case ctx == nil:
|
||||
panic(panicMsgMissingContext)
|
||||
case !prm.cnrSet:
|
||||
panic(panicMsgMissingContainer)
|
||||
return nil, ErrMissingContainer
|
||||
}
|
||||
|
||||
// TODO: check private key is set before forming the request
|
||||
// TODO: check private signer is set before forming the request
|
||||
// sign container
|
||||
var cnr v2container.Container
|
||||
prm.cnr.WriteToV2(&cnr)
|
||||
|
||||
var sig neofscrypto.Signature
|
||||
signer := prm.signer
|
||||
if signer == nil {
|
||||
signer = c.defaultSigner()
|
||||
}
|
||||
|
||||
err := container.CalculateSignature(&sig, prm.cnr, c.prm.key)
|
||||
err := container.CalculateSignature(&sig, prm.cnr, signer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("calculate container signature: %w", err)
|
||||
}
|
||||
|
@ -138,7 +146,6 @@ func (c *Client) ContainerPut(ctx context.Context, prm PrmContainerPut) (*ResCon
|
|||
|
||||
c.initCallContext(&cc)
|
||||
cc.req = &req
|
||||
cc.statusRes = &res
|
||||
cc.call = func() (responseV2, error) {
|
||||
return rpcapi.PutContainer(&c.c, &req, client.WithContext(ctx))
|
||||
}
|
||||
|
@ -184,8 +191,6 @@ func (x *PrmContainerGet) SetContainer(id cid.ID) {
|
|||
|
||||
// ResContainerGet groups resulting values of ContainerGet operation.
|
||||
type ResContainerGet struct {
|
||||
statusRes
|
||||
|
||||
cnr container.Container
|
||||
}
|
||||
|
||||
|
@ -198,24 +203,17 @@ func (x ResContainerGet) Container() container.Container {
|
|||
|
||||
// ContainerGet reads NeoFS container by ID.
|
||||
//
|
||||
// Exactly one return value is non-nil. By default, server status is returned in res structure.
|
||||
// Any client's internal or transport errors are returned as `error`.
|
||||
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful
|
||||
// NeoFS status codes are returned as `error`, otherwise, are included
|
||||
// in the returned result structure.
|
||||
// Any errors (local or remote, including returned status codes) are returned as Go errors,
|
||||
// see [apistatus] package for NeoFS-specific error types.
|
||||
//
|
||||
// Immediately panics if parameters are set incorrectly (see PrmContainerGet docs).
|
||||
// Context is required and must not be nil. It is used for network communication.
|
||||
//
|
||||
// Return statuses:
|
||||
// - global (see Client docs);
|
||||
// - *apistatus.ContainerNotFound.
|
||||
// Return errors:
|
||||
// - [ErrMissingContainer]
|
||||
func (c *Client) ContainerGet(ctx context.Context, prm PrmContainerGet) (*ResContainerGet, error) {
|
||||
switch {
|
||||
case ctx == nil:
|
||||
panic(panicMsgMissingContext)
|
||||
case !prm.idSet:
|
||||
panic(panicMsgMissingContainer)
|
||||
return nil, ErrMissingContainer
|
||||
}
|
||||
|
||||
var cidV2 refs.ContainerID
|
||||
|
@ -240,7 +238,6 @@ func (c *Client) ContainerGet(ctx context.Context, prm PrmContainerGet) (*ResCon
|
|||
c.initCallContext(&cc)
|
||||
cc.meta = prm.prmCommonMeta
|
||||
cc.req = &req
|
||||
cc.statusRes = &res
|
||||
cc.call = func() (responseV2, error) {
|
||||
return rpcapi.GetContainer(&c.c, &req, client.WithContext(ctx))
|
||||
}
|
||||
|
@ -284,8 +281,6 @@ func (x *PrmContainerList) SetAccount(id user.ID) {
|
|||
|
||||
// ResContainerList groups resulting values of ContainerList operation.
|
||||
type ResContainerList struct {
|
||||
statusRes
|
||||
|
||||
ids []cid.ID
|
||||
}
|
||||
|
||||
|
@ -298,24 +293,18 @@ func (x ResContainerList) Containers() []cid.ID {
|
|||
|
||||
// ContainerList requests identifiers of the account-owned containers.
|
||||
//
|
||||
// Exactly one return value is non-nil. By default, server status is returned in res structure.
|
||||
// Any client's internal or transport errors are returned as `error`.
|
||||
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful
|
||||
// NeoFS status codes are returned as `error`, otherwise, are included
|
||||
// in the returned result structure.
|
||||
// Any errors (local or remote, including returned status codes) are returned as Go errors,
|
||||
// see [apistatus] package for NeoFS-specific error types.
|
||||
//
|
||||
// Immediately panics if parameters are set incorrectly (see PrmContainerList docs).
|
||||
// Context is required and must not be nil. It is used for network communication.
|
||||
//
|
||||
// Return statuses:
|
||||
// - global (see Client docs).
|
||||
// Return errors:
|
||||
// - [ErrMissingAccount]
|
||||
func (c *Client) ContainerList(ctx context.Context, prm PrmContainerList) (*ResContainerList, error) {
|
||||
// check parameters
|
||||
switch {
|
||||
case ctx == nil:
|
||||
panic(panicMsgMissingContext)
|
||||
case !prm.ownerSet:
|
||||
panic("account not set")
|
||||
return nil, ErrMissingAccount
|
||||
}
|
||||
|
||||
// form request body
|
||||
|
@ -340,7 +329,6 @@ func (c *Client) ContainerList(ctx context.Context, prm PrmContainerList) (*ResC
|
|||
c.initCallContext(&cc)
|
||||
cc.meta = prm.prmCommonMeta
|
||||
cc.req = &req
|
||||
cc.statusRes = &res
|
||||
cc.call = func() (responseV2, error) {
|
||||
return rpcapi.ListContainers(&c.c, &req, client.WithContext(ctx))
|
||||
}
|
||||
|
@ -375,6 +363,8 @@ type PrmContainerDelete struct {
|
|||
|
||||
tokSet bool
|
||||
tok session.Container
|
||||
|
||||
signer neofscrypto.Signer
|
||||
}
|
||||
|
||||
// SetContainer sets identifier of the NeoFS container to be removed.
|
||||
|
@ -384,6 +374,13 @@ func (x *PrmContainerDelete) SetContainer(id cid.ID) {
|
|||
x.idSet = true
|
||||
}
|
||||
|
||||
// SetSigner sets signer to sign request payload.
|
||||
// Signer's scheme MUST be neofscrypto.ECDSA_DETERMINISTIC_SHA256. For example, you can use neofsecdsa.SignerRFC6979.
|
||||
// Optional parameter: defaults to internal Client signer.
|
||||
func (x *PrmContainerDelete) SetSigner(signer neofscrypto.Signer) {
|
||||
x.signer = signer
|
||||
}
|
||||
|
||||
// WithinSession specifies session within which container should be removed.
|
||||
//
|
||||
// Creator of the session acquires the authorship of the request.
|
||||
|
@ -395,39 +392,28 @@ func (x *PrmContainerDelete) WithinSession(tok session.Container) {
|
|||
x.tokSet = true
|
||||
}
|
||||
|
||||
// ResContainerDelete groups resulting values of ContainerDelete operation.
|
||||
type ResContainerDelete struct {
|
||||
statusRes
|
||||
}
|
||||
|
||||
// ContainerDelete sends request to remove the NeoFS container.
|
||||
//
|
||||
// Exactly one return value is non-nil. By default, server status is returned in res structure.
|
||||
// Any client's internal or transport errors are returned as `error`.
|
||||
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful
|
||||
// NeoFS status codes are returned as `error`, otherwise, are included
|
||||
// in the returned result structure.
|
||||
// Any errors (local or remote, including returned status codes) are returned as Go errors,
|
||||
// see [apistatus] package for NeoFS-specific error types.
|
||||
//
|
||||
// Operation is asynchronous and no guaranteed even in the absence of errors.
|
||||
// The required time is also not predictable.
|
||||
//
|
||||
// Success can be verified by reading by identifier (see GetContainer).
|
||||
//
|
||||
// Immediately panics if parameters are set incorrectly (see PrmContainerDelete docs).
|
||||
// Context is required and must not be nil. It is used for network communication.
|
||||
//
|
||||
// Exactly one return value is non-nil. Server status return is returned in ResContainerDelete.
|
||||
// Reflects all internal errors in second return value (transport problems, response processing, etc.).
|
||||
// Return errors:
|
||||
// - [ErrMissingContainer]
|
||||
// - [neofscrypto.ErrIncorrectSigner]
|
||||
//
|
||||
// Return statuses:
|
||||
// - global (see Client docs).
|
||||
func (c *Client) ContainerDelete(ctx context.Context, prm PrmContainerDelete) (*ResContainerDelete, error) {
|
||||
// Reflects all internal errors in second return value (transport problems, response processing, etc.).
|
||||
func (c *Client) ContainerDelete(ctx context.Context, prm PrmContainerDelete) error {
|
||||
// check parameters
|
||||
switch {
|
||||
case ctx == nil:
|
||||
panic(panicMsgMissingContext)
|
||||
case !prm.idSet:
|
||||
panic(panicMsgMissingContainer)
|
||||
return ErrMissingContainer
|
||||
}
|
||||
|
||||
// sign container ID
|
||||
|
@ -439,10 +425,17 @@ func (c *Client) ContainerDelete(ctx context.Context, prm PrmContainerDelete) (*
|
|||
data := cidV2.GetValue()
|
||||
|
||||
var sig neofscrypto.Signature
|
||||
signer := prm.signer
|
||||
if signer == nil {
|
||||
signer = c.defaultSigner()
|
||||
}
|
||||
|
||||
err := sig.Calculate(neofsecdsa.SignerRFC6979(c.prm.key), data)
|
||||
if signer.Scheme() != neofscrypto.ECDSA_DETERMINISTIC_SHA256 {
|
||||
return errNonNeoSigner
|
||||
}
|
||||
err := sig.Calculate(signer, data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("calculate signature: %w", err)
|
||||
return fmt.Errorf("calculate signature: %w", err)
|
||||
}
|
||||
|
||||
var sigv2 refs.Signature
|
||||
|
@ -475,22 +468,20 @@ func (c *Client) ContainerDelete(ctx context.Context, prm PrmContainerDelete) (*
|
|||
|
||||
var (
|
||||
cc contextCall
|
||||
res ResContainerDelete
|
||||
)
|
||||
|
||||
c.initCallContext(&cc)
|
||||
cc.req = &req
|
||||
cc.statusRes = &res
|
||||
cc.call = func() (responseV2, error) {
|
||||
return rpcapi.DeleteContainer(&c.c, &req, client.WithContext(ctx))
|
||||
}
|
||||
|
||||
// process call
|
||||
if !cc.processCall() {
|
||||
return nil, cc.err
|
||||
return cc.err
|
||||
}
|
||||
|
||||
return &res, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// PrmContainerEACL groups parameters of ContainerEACL operation.
|
||||
|
@ -510,8 +501,6 @@ func (x *PrmContainerEACL) SetContainer(id cid.ID) {
|
|||
|
||||
// ResContainerEACL groups resulting values of ContainerEACL operation.
|
||||
type ResContainerEACL struct {
|
||||
statusRes
|
||||
|
||||
table eacl.Table
|
||||
}
|
||||
|
||||
|
@ -522,26 +511,18 @@ func (x ResContainerEACL) Table() eacl.Table {
|
|||
|
||||
// ContainerEACL reads eACL table of the NeoFS container.
|
||||
//
|
||||
// Exactly one return value is non-nil. By default, server status is returned in res structure.
|
||||
// Any client's internal or transport errors are returned as `error`.
|
||||
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful
|
||||
// NeoFS status codes are returned as `error`, otherwise, are included
|
||||
// in the returned result structure.
|
||||
// Any errors (local or remote, including returned status codes) are returned as Go errors,
|
||||
// see [apistatus] package for NeoFS-specific error types.
|
||||
//
|
||||
// Immediately panics if parameters are set incorrectly (see PrmContainerEACL docs).
|
||||
// Context is required and must not be nil. It is used for network communication.
|
||||
//
|
||||
// Return statuses:
|
||||
// - global (see Client docs);
|
||||
// - *apistatus.ContainerNotFound;
|
||||
// - *apistatus.EACLNotFound.
|
||||
// Return errors:
|
||||
// - [ErrMissingContainer]
|
||||
func (c *Client) ContainerEACL(ctx context.Context, prm PrmContainerEACL) (*ResContainerEACL, error) {
|
||||
// check parameters
|
||||
switch {
|
||||
case ctx == nil:
|
||||
panic(panicMsgMissingContext)
|
||||
case !prm.idSet:
|
||||
panic(panicMsgMissingContainer)
|
||||
return nil, ErrMissingContainer
|
||||
}
|
||||
|
||||
var cidV2 refs.ContainerID
|
||||
|
@ -566,7 +547,6 @@ func (c *Client) ContainerEACL(ctx context.Context, prm PrmContainerEACL) (*ResC
|
|||
c.initCallContext(&cc)
|
||||
cc.meta = prm.prmCommonMeta
|
||||
cc.req = &req
|
||||
cc.statusRes = &res
|
||||
cc.call = func() (responseV2, error) {
|
||||
return rpcapi.GetEACL(&c.c, &req, client.WithContext(ctx))
|
||||
}
|
||||
|
@ -599,15 +579,24 @@ type PrmContainerSetEACL struct {
|
|||
|
||||
sessionSet bool
|
||||
session session.Container
|
||||
|
||||
signer neofscrypto.Signer
|
||||
}
|
||||
|
||||
// SetTable sets eACL table structure to be set for the container.
|
||||
// Required parameter.
|
||||
// Required parameter and CID must be set inside the table.
|
||||
func (x *PrmContainerSetEACL) SetTable(table eacl.Table) {
|
||||
x.table = table
|
||||
x.tableSet = true
|
||||
}
|
||||
|
||||
// SetSigner sets signer to sign request payload.
|
||||
// Signer's scheme MUST be neofscrypto.ECDSA_DETERMINISTIC_SHA256. For example, you can use neofsecdsa.SignerRFC6979.
|
||||
// Optional parameter: defaults to internal Client signer.
|
||||
func (x *PrmContainerSetEACL) SetSigner(signer neofscrypto.Signer) {
|
||||
x.signer = signer
|
||||
}
|
||||
|
||||
// WithinSession specifies session within which extended ACL of the container
|
||||
// should be saved.
|
||||
//
|
||||
|
@ -618,52 +607,56 @@ func (x *PrmContainerSetEACL) SetTable(table eacl.Table) {
|
|||
// - if particular container is specified (ApplyOnlyTo), it MUST equal the container
|
||||
// for which extended ACL is going to be set
|
||||
// - session operation MUST be session.VerbContainerSetEACL (ForVerb)
|
||||
// - token MUST be signed using private key of the owner of the container to be saved
|
||||
// - token MUST be signed using private signer of the owner of the container to be saved
|
||||
func (x *PrmContainerSetEACL) WithinSession(s session.Container) {
|
||||
x.session = s
|
||||
x.sessionSet = true
|
||||
}
|
||||
|
||||
// ResContainerSetEACL groups resulting values of ContainerSetEACL operation.
|
||||
type ResContainerSetEACL struct {
|
||||
statusRes
|
||||
}
|
||||
|
||||
// ContainerSetEACL sends request to update eACL table of the NeoFS container.
|
||||
//
|
||||
// Exactly one return value is non-nil. By default, server status is returned in res structure.
|
||||
// Any client's internal or transport errors are returned as `error`.
|
||||
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful
|
||||
// NeoFS status codes are returned as `error`, otherwise, are included
|
||||
// in the returned result structure.
|
||||
// Any errors (local or remote, including returned status codes) are returned as Go errors,
|
||||
// see [apistatus] package for NeoFS-specific error types.
|
||||
//
|
||||
// Operation is asynchronous and no guaranteed even in the absence of errors.
|
||||
// The required time is also not predictable.
|
||||
//
|
||||
// Success can be verified by reading by identifier (see EACL).
|
||||
//
|
||||
// Immediately panics if parameters are set incorrectly (see PrmContainerSetEACL docs).
|
||||
// Context is required and must not be nil. It is used for network communication.
|
||||
// Return errors:
|
||||
// - [ErrMissingEACL]
|
||||
// - [ErrMissingEACLContainer]
|
||||
// - [neofscrypto.ErrIncorrectSigner]
|
||||
//
|
||||
// Return statuses:
|
||||
// - global (see Client docs).
|
||||
func (c *Client) ContainerSetEACL(ctx context.Context, prm PrmContainerSetEACL) (*ResContainerSetEACL, error) {
|
||||
// Context is required and must not be nil. It is used for network communication.
|
||||
func (c *Client) ContainerSetEACL(ctx context.Context, prm PrmContainerSetEACL) error {
|
||||
// check parameters
|
||||
switch {
|
||||
case ctx == nil:
|
||||
panic(panicMsgMissingContext)
|
||||
case !prm.tableSet:
|
||||
panic("eACL table not set")
|
||||
return ErrMissingEACL
|
||||
}
|
||||
|
||||
_, isCIDSet := prm.table.CID()
|
||||
if !isCIDSet {
|
||||
return ErrMissingEACLContainer
|
||||
}
|
||||
|
||||
// sign the eACL table
|
||||
eaclV2 := prm.table.ToV2()
|
||||
|
||||
var sig neofscrypto.Signature
|
||||
signer := prm.signer
|
||||
if signer == nil {
|
||||
signer = c.defaultSigner()
|
||||
}
|
||||
|
||||
err := sig.Calculate(neofsecdsa.SignerRFC6979(c.prm.key), eaclV2.StableMarshal(nil))
|
||||
if signer.Scheme() != neofscrypto.ECDSA_DETERMINISTIC_SHA256 {
|
||||
return errNonNeoSigner
|
||||
}
|
||||
|
||||
err := sig.Calculate(signer, eaclV2.StableMarshal(nil))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("calculate signature: %w", err)
|
||||
return fmt.Errorf("calculate signature: %w", err)
|
||||
}
|
||||
|
||||
var sigv2 refs.Signature
|
||||
|
@ -696,22 +689,20 @@ func (c *Client) ContainerSetEACL(ctx context.Context, prm PrmContainerSetEACL)
|
|||
|
||||
var (
|
||||
cc contextCall
|
||||
res ResContainerSetEACL
|
||||
)
|
||||
|
||||
c.initCallContext(&cc)
|
||||
cc.req = &req
|
||||
cc.statusRes = &res
|
||||
cc.call = func() (responseV2, error) {
|
||||
return rpcapi.SetEACL(&c.c, &req, client.WithContext(ctx))
|
||||
}
|
||||
|
||||
// process call
|
||||
if !cc.processCall() {
|
||||
return nil, cc.err
|
||||
return cc.err
|
||||
}
|
||||
|
||||
return &res, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// PrmAnnounceSpace groups parameters of ContainerAnnounceUsedSpace operation.
|
||||
|
@ -729,36 +720,25 @@ func (x *PrmAnnounceSpace) SetValues(vs []container.SizeEstimation) {
|
|||
x.announcements = vs
|
||||
}
|
||||
|
||||
// ResAnnounceSpace groups resulting values of ContainerAnnounceUsedSpace operation.
|
||||
type ResAnnounceSpace struct {
|
||||
statusRes
|
||||
}
|
||||
|
||||
// ContainerAnnounceUsedSpace sends request to announce volume of the space used for the container objects.
|
||||
//
|
||||
// Exactly one return value is non-nil. By default, server status is returned in res structure.
|
||||
// Any client's internal or transport errors are returned as `error`.
|
||||
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful
|
||||
// NeoFS status codes are returned as `error`, otherwise, are included
|
||||
// in the returned result structure.
|
||||
// Any errors (local or remote, including returned status codes) are returned as Go errors,
|
||||
// see [apistatus] package for NeoFS-specific error types.
|
||||
//
|
||||
// Operation is asynchronous and no guaranteed even in the absence of errors.
|
||||
// The required time is also not predictable.
|
||||
//
|
||||
// At this moment success can not be checked.
|
||||
//
|
||||
// Immediately panics if parameters are set incorrectly (see PrmAnnounceSpace docs).
|
||||
// Context is required and must not be nil. It is used for network communication.
|
||||
//
|
||||
// Return statuses:
|
||||
// - global (see Client docs).
|
||||
func (c *Client) ContainerAnnounceUsedSpace(ctx context.Context, prm PrmAnnounceSpace) (*ResAnnounceSpace, error) {
|
||||
// Return errors:
|
||||
// - [ErrMissingAnnouncements]
|
||||
func (c *Client) ContainerAnnounceUsedSpace(ctx context.Context, prm PrmAnnounceSpace) error {
|
||||
// check parameters
|
||||
switch {
|
||||
case ctx == nil:
|
||||
panic(panicMsgMissingContext)
|
||||
case len(prm.announcements) == 0:
|
||||
panic("missing announcements")
|
||||
return ErrMissingAnnouncements
|
||||
}
|
||||
|
||||
// convert list of SDK announcement structures into NeoFS-API v2 list
|
||||
|
@ -780,23 +760,21 @@ func (c *Client) ContainerAnnounceUsedSpace(ctx context.Context, prm PrmAnnounce
|
|||
|
||||
var (
|
||||
cc contextCall
|
||||
res ResAnnounceSpace
|
||||
)
|
||||
|
||||
c.initCallContext(&cc)
|
||||
cc.meta = prm.prmCommonMeta
|
||||
cc.req = &req
|
||||
cc.statusRes = &res
|
||||
cc.call = func() (responseV2, error) {
|
||||
return rpcapi.AnnounceUsedSpace(&c.c, &req, client.WithContext(ctx))
|
||||
}
|
||||
|
||||
// process call
|
||||
if !cc.processCall() {
|
||||
return nil, cc.err
|
||||
return cc.err
|
||||
}
|
||||
|
||||
return &res, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// SyncContainerWithNetwork requests network configuration using passed client
|
||||
|
|
|
@ -6,21 +6,17 @@ and provides methods for executing operations on the server.
|
|||
|
||||
Create client instance:
|
||||
|
||||
var c client.Client
|
||||
|
||||
Initialize client state:
|
||||
|
||||
var prm client.PrmInit
|
||||
prm.SetDefaultPrivateKey(key)
|
||||
prm.SetDefaultSigner(signer)
|
||||
// ...
|
||||
|
||||
c.Init(prm)
|
||||
c, err := client.New(prm)
|
||||
|
||||
Connect to the NeoFS server:
|
||||
|
||||
var prm client.PrmDial
|
||||
prm.SetServerURI("localhost:8080")
|
||||
prm.SetDefaultPrivateKey(key)
|
||||
prm.SetDefaultSigner(signer)
|
||||
// ...
|
||||
|
||||
err := c.Dial(prm)
|
||||
|
|
162
client/errors.go
162
client/errors.go
|
@ -3,100 +3,72 @@ package client
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status"
|
||||
)
|
||||
|
||||
// unwraps err using errors.Unwrap and returns the result.
|
||||
func unwrapErr(err error) error {
|
||||
for e := errors.Unwrap(err); e != nil; e = errors.Unwrap(err) {
|
||||
err = e
|
||||
var (
|
||||
// ErrMissingServer is returned when server endpoint is empty in parameters.
|
||||
ErrMissingServer = errors.New("server address is unset or empty")
|
||||
// ErrNonPositiveTimeout is returned when any timeout is below zero in parameters.
|
||||
ErrNonPositiveTimeout = errors.New("non-positive timeout")
|
||||
|
||||
// ErrMissingContainer is returned when container is not provided.
|
||||
ErrMissingContainer = errors.New("missing container")
|
||||
// ErrMissingObject is returned when object is not provided.
|
||||
ErrMissingObject = errors.New("missing object")
|
||||
// ErrMissingAccount is returned when account/owner is not provided.
|
||||
ErrMissingAccount = errors.New("missing account")
|
||||
// ErrMissingSigner is returned when signer is not provided.
|
||||
ErrMissingSigner = errors.New("missing signer")
|
||||
// ErrMissingEACL is returned when eACL table is not provided.
|
||||
ErrMissingEACL = errors.New("missing eACL table")
|
||||
// ErrMissingEACLContainer is returned when container info is not provided in eACL table.
|
||||
ErrMissingEACLContainer = errors.New("missing container in eACL table")
|
||||
// ErrMissingAnnouncements is returned when announcements are not provided.
|
||||
ErrMissingAnnouncements = errors.New("missing announcements")
|
||||
// ErrZeroRangeLength is returned when range parameter has zero length.
|
||||
ErrZeroRangeLength = errors.New("zero range length")
|
||||
// ErrMissingRanges is returned when empty ranges list is provided.
|
||||
ErrMissingRanges = errors.New("missing ranges")
|
||||
// ErrZeroEpoch is returned when zero epoch is provided.
|
||||
ErrZeroEpoch = errors.New("zero epoch")
|
||||
// ErrMissingTrusts is returned when empty slice of trusts is provided.
|
||||
ErrMissingTrusts = errors.New("missing trusts")
|
||||
// ErrMissingTrust is returned when empty trust is not provided.
|
||||
ErrMissingTrust = errors.New("missing trust")
|
||||
|
||||
// ErrUnexpectedReadCall is returned when we already got all data but truing to get more.
|
||||
ErrUnexpectedReadCall = errors.New("unexpected call to `Read`")
|
||||
|
||||
// ErrSign is returned when unable to sign service message.
|
||||
ErrSign SignError
|
||||
|
||||
// ErrMissingResponseField is returned when required field is not exists in NeoFS api response.
|
||||
ErrMissingResponseField MissingResponseFieldErr
|
||||
)
|
||||
|
||||
// MissingResponseFieldErr contains field name which should be in NeoFS API response.
|
||||
type MissingResponseFieldErr struct {
|
||||
name string
|
||||
}
|
||||
|
||||
return err
|
||||
// Error implements the error interface.
|
||||
func (e MissingResponseFieldErr) Error() string {
|
||||
return fmt.Sprintf("missing %s field in the response", e.name)
|
||||
}
|
||||
|
||||
// IsErrContainerNotFound checks if err corresponds to NeoFS status
|
||||
// return corresponding to missing container. Supports wrapped errors.
|
||||
func IsErrContainerNotFound(err error) bool {
|
||||
switch unwrapErr(err).(type) {
|
||||
// Is implements interface for correct checking current error type with [errors.Is].
|
||||
func (e MissingResponseFieldErr) Is(target error) bool {
|
||||
switch target.(type) {
|
||||
default:
|
||||
return false
|
||||
case
|
||||
apistatus.ContainerNotFound,
|
||||
*apistatus.ContainerNotFound:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// IsErrEACLNotFound checks if err corresponds to NeoFS status
|
||||
// return corresponding to missing eACL table. Supports wrapped errors.
|
||||
func IsErrEACLNotFound(err error) bool {
|
||||
switch unwrapErr(err).(type) {
|
||||
default:
|
||||
return false
|
||||
case
|
||||
apistatus.EACLNotFound,
|
||||
*apistatus.EACLNotFound:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// IsErrObjectNotFound checks if err corresponds to NeoFS status
|
||||
// return corresponding to missing object. Supports wrapped errors.
|
||||
func IsErrObjectNotFound(err error) bool {
|
||||
switch unwrapErr(err).(type) {
|
||||
default:
|
||||
return false
|
||||
case
|
||||
apistatus.ObjectNotFound,
|
||||
*apistatus.ObjectNotFound:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// IsErrObjectAlreadyRemoved checks if err corresponds to NeoFS status
|
||||
// return corresponding to already removed object. Supports wrapped errors.
|
||||
func IsErrObjectAlreadyRemoved(err error) bool {
|
||||
switch unwrapErr(err).(type) {
|
||||
default:
|
||||
return false
|
||||
case
|
||||
apistatus.ObjectAlreadyRemoved,
|
||||
*apistatus.ObjectAlreadyRemoved:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// IsErrSessionExpired checks if err corresponds to NeoFS status return
|
||||
// corresponding to expired session. Supports wrapped errors.
|
||||
func IsErrSessionExpired(err error) bool {
|
||||
switch unwrapErr(err).(type) {
|
||||
default:
|
||||
return false
|
||||
case
|
||||
apistatus.SessionTokenExpired,
|
||||
*apistatus.SessionTokenExpired:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// IsErrSessionNotFound checks if err corresponds to NeoFS status return
|
||||
// corresponding to missing session. Supports wrapped errors.
|
||||
func IsErrSessionNotFound(err error) bool {
|
||||
switch unwrapErr(err).(type) {
|
||||
default:
|
||||
return false
|
||||
case
|
||||
apistatus.SessionTokenNotFound,
|
||||
*apistatus.SessionTokenNotFound:
|
||||
case MissingResponseFieldErr, *MissingResponseFieldErr:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// returns error describing missing field with the given name.
|
||||
func newErrMissingResponseField(name string) error {
|
||||
return fmt.Errorf("missing %s field in the response", name)
|
||||
return MissingResponseFieldErr{name: name}
|
||||
}
|
||||
|
||||
// returns error describing invalid field (according to the NeoFS protocol)
|
||||
|
@ -104,3 +76,33 @@ func newErrMissingResponseField(name string) error {
|
|||
func newErrInvalidResponseField(name string, err error) error {
|
||||
return fmt.Errorf("invalid %s field in the response: %w", name, err)
|
||||
}
|
||||
|
||||
// SignError wraps another error with reason why sign process was failed.
|
||||
type SignError struct {
|
||||
err error
|
||||
}
|
||||
|
||||
// NewSignError is a constructor for [SignError].
|
||||
func NewSignError(err error) SignError {
|
||||
return SignError{err: err}
|
||||
}
|
||||
|
||||
// Error implements the error interface.
|
||||
func (e SignError) Error() string {
|
||||
return fmt.Sprintf("sign: %v", e.err)
|
||||
}
|
||||
|
||||
// Unwrap implements the error interface.
|
||||
func (e SignError) Unwrap() error {
|
||||
return e.err
|
||||
}
|
||||
|
||||
// Is implements interface for correct checking current error type with [errors.Is].
|
||||
func (e SignError) Is(target error) bool {
|
||||
switch target.(type) {
|
||||
default:
|
||||
return false
|
||||
case SignError, *SignError:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,68 +1,17 @@
|
|||
package client_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neofs-sdk-go/client"
|
||||
apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestErrors(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
check func(error) bool
|
||||
errs []error
|
||||
}{
|
||||
{
|
||||
check: client.IsErrContainerNotFound,
|
||||
errs: []error{
|
||||
apistatus.ContainerNotFound{},
|
||||
new(apistatus.ContainerNotFound),
|
||||
},
|
||||
},
|
||||
{
|
||||
check: client.IsErrEACLNotFound,
|
||||
errs: []error{
|
||||
apistatus.EACLNotFound{},
|
||||
new(apistatus.EACLNotFound),
|
||||
},
|
||||
},
|
||||
{
|
||||
check: client.IsErrObjectNotFound,
|
||||
errs: []error{
|
||||
apistatus.ObjectNotFound{},
|
||||
new(apistatus.ObjectNotFound),
|
||||
},
|
||||
},
|
||||
{
|
||||
check: client.IsErrObjectAlreadyRemoved,
|
||||
errs: []error{
|
||||
apistatus.ObjectAlreadyRemoved{},
|
||||
new(apistatus.ObjectAlreadyRemoved),
|
||||
},
|
||||
},
|
||||
{
|
||||
check: client.IsErrSessionExpired,
|
||||
errs: []error{
|
||||
apistatus.SessionTokenExpired{},
|
||||
new(apistatus.SessionTokenExpired),
|
||||
},
|
||||
}, {
|
||||
check: client.IsErrSessionNotFound,
|
||||
errs: []error{
|
||||
apistatus.SessionTokenNotFound{},
|
||||
new(apistatus.SessionTokenNotFound),
|
||||
},
|
||||
},
|
||||
} {
|
||||
require.NotEmpty(t, tc.errs)
|
||||
func Test_SignError(t *testing.T) {
|
||||
someErr := errors.New("some error")
|
||||
signErr := client.NewSignError(someErr)
|
||||
|
||||
for i := range tc.errs {
|
||||
require.True(t, tc.check(tc.errs[i]), tc.errs[i])
|
||||
require.True(t, tc.check(fmt.Errorf("top-level context: :%w",
|
||||
fmt.Errorf("inner context: %w", tc.errs[i])),
|
||||
), tc.errs[i])
|
||||
}
|
||||
}
|
||||
require.ErrorIs(t, signErr, someErr)
|
||||
require.ErrorIs(t, signErr, client.ErrSign)
|
||||
}
|
||||
|
|
118
client/example_container_put_test.go
Normal file
118
client/example_container_put_test.go
Normal file
|
@ -0,0 +1,118 @@
|
|||
package client_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"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"
|
||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||
neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/netmap"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/user"
|
||||
)
|
||||
|
||||
func ExampleClient_ContainerPut() {
|
||||
ctx := context.Background()
|
||||
var accountID user.ID
|
||||
|
||||
// The account was taken from https://github.com/nspcc-dev/neofs-aio
|
||||
key, err := keys.NEP2Decrypt("6PYM8VdX2BSm7BSXKzV4Fz6S3R9cDLLWNrD9nMjxW352jEv3fsC8N3wNLY", "one", keys.NEP2ScryptParams())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
signer := neofsecdsa.SignerRFC6979(key.PrivateKey)
|
||||
|
||||
// decode account from user's signer
|
||||
if err = user.IDFromSigner(&accountID, signer); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// prepare client
|
||||
var prmInit client.PrmInit
|
||||
prmInit.SetDefaultSigner(signer) // private signer for request signing
|
||||
|
||||
c, err := client.New(prmInit)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("New: %w", err))
|
||||
}
|
||||
|
||||
// connect to NeoFS gateway
|
||||
var prmDial client.PrmDial
|
||||
prmDial.SetServerURI("grpc://localhost:8080") // endpoint address
|
||||
prmDial.SetTimeout(15 * time.Second)
|
||||
prmDial.SetStreamTimeout(15 * time.Second)
|
||||
|
||||
if err = c.Dial(prmDial); err != nil {
|
||||
panic(fmt.Errorf("dial %v", err))
|
||||
}
|
||||
|
||||
// describe new container
|
||||
cont := container.Container{}
|
||||
// set version and nonce
|
||||
cont.Init()
|
||||
cont.SetOwner(accountID)
|
||||
cont.SetBasicACL(acl.PublicRW)
|
||||
|
||||
// set reserved attributes
|
||||
container.SetName(&cont, "name-1")
|
||||
container.SetCreationTime(&cont, time.Now().UTC())
|
||||
|
||||
// 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)
|
||||
|
||||
var placementPolicy netmap.PlacementPolicy
|
||||
if err = placementPolicy.ReadFromV2(placementPolicyV2); err != nil {
|
||||
panic(fmt.Errorf("ReadFromV2 %w", err))
|
||||
}
|
||||
|
||||
placementPolicy.SetContainerBackupFactor(1)
|
||||
cont.SetPlacementPolicy(placementPolicy)
|
||||
|
||||
// prepare command to create container
|
||||
var prmCon client.PrmContainerPut
|
||||
prmCon.SetContainer(cont)
|
||||
|
||||
putResult, err := c.ContainerPut(ctx, prmCon)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("ContainerPut %w", err))
|
||||
}
|
||||
|
||||
// containerID already exists
|
||||
containerID = putResult.ID()
|
||||
fmt.Println(containerID)
|
||||
// example output: 76wa5UNiT8gk8Q5rdCVCV4pKuZSmYsifh6g84BcL6Hqs
|
||||
|
||||
// but container creation is async operation. We should wait some time or make polling to ensure container created
|
||||
// for simplifying we just wait
|
||||
<-time.After(2 * time.Second)
|
||||
|
||||
// take our created container
|
||||
var cmdGet client.PrmContainerGet
|
||||
cmdGet.SetContainer(containerID)
|
||||
|
||||
getResp, err := c.ContainerGet(ctx, cmdGet)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("ContainerGet %w", err))
|
||||
}
|
||||
|
||||
jsonData, err := getResp.Container().MarshalJSON()
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("MarshalJSON %w", err))
|
||||
}
|
||||
|
||||
fmt.Println(string(jsonData))
|
||||
// example output: {"version":{"major":2,"minor":13},"ownerID":{"value":"Ne6eoiwn40vQFI/EEI4I906PUEiy8ZXKcw=="},"nonce":"rPVd/iw2RW6Q6d66FVnIqg==","basicACL":532660223,"attributes":[{"key":"Name","value":"name-1"},{"key":"Timestamp","value":"1681738627"}],"placementPolicy":{"replicas":[{"count":1,"selector":""}],"containerBackupFactor":1,"selectors":[],"filters":[],"subnetId":{"value":0}}}
|
||||
}
|
|
@ -8,8 +8,6 @@ import (
|
|||
rpcapi "github.com/nspcc-dev/neofs-api-go/v2/rpc"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/rpc/client"
|
||||
v2session "github.com/nspcc-dev/neofs-api-go/v2/session"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/signature"
|
||||
apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/netmap"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/version"
|
||||
)
|
||||
|
@ -21,8 +19,6 @@ type PrmEndpointInfo struct {
|
|||
|
||||
// ResEndpointInfo group resulting values of EndpointInfo operation.
|
||||
type ResEndpointInfo struct {
|
||||
statusRes
|
||||
|
||||
version version.Version
|
||||
|
||||
ni netmap.NodeInfo
|
||||
|
@ -42,25 +38,14 @@ func (x ResEndpointInfo) NodeInfo() netmap.NodeInfo {
|
|||
//
|
||||
// Method can be used as a health check to see if node is alive and responds to requests.
|
||||
//
|
||||
// Any client's internal or transport errors are returned as `error`.
|
||||
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful
|
||||
// NeoFS status codes are returned as `error`, otherwise, are included
|
||||
// in the returned result structure.
|
||||
// Any client's internal or transport errors are returned as `error`,
|
||||
// see [apistatus] package for NeoFS-specific error types.
|
||||
//
|
||||
// Immediately panics if parameters are set incorrectly (see PrmEndpointInfo docs).
|
||||
// Context is required and must not be nil. It is used for network communication.
|
||||
//
|
||||
// Exactly one return value is non-nil. Server status return is returned in ResEndpointInfo.
|
||||
// Reflects all internal errors in second return value (transport problems, response processing, etc.).
|
||||
//
|
||||
// Return statuses:
|
||||
// - global (see Client docs).
|
||||
func (c *Client) EndpointInfo(ctx context.Context, prm PrmEndpointInfo) (*ResEndpointInfo, error) {
|
||||
// check context
|
||||
if ctx == nil {
|
||||
panic(panicMsgMissingContext)
|
||||
}
|
||||
|
||||
// form request
|
||||
var req v2netmap.LocalNodeInfoRequest
|
||||
|
||||
|
@ -74,7 +59,6 @@ func (c *Client) EndpointInfo(ctx context.Context, prm PrmEndpointInfo) (*ResEnd
|
|||
c.initCallContext(&cc)
|
||||
cc.meta = prm.prmCommonMeta
|
||||
cc.req = &req
|
||||
cc.statusRes = &res
|
||||
cc.call = func() (responseV2, error) {
|
||||
return rpcapi.LocalNodeInfo(&c.c, &req, client.WithContext(ctx))
|
||||
}
|
||||
|
@ -127,8 +111,6 @@ type PrmNetworkInfo struct {
|
|||
|
||||
// ResNetworkInfo groups resulting values of NetworkInfo operation.
|
||||
type ResNetworkInfo struct {
|
||||
statusRes
|
||||
|
||||
info netmap.NetworkInfo
|
||||
}
|
||||
|
||||
|
@ -139,25 +121,14 @@ func (x ResNetworkInfo) Info() netmap.NetworkInfo {
|
|||
|
||||
// NetworkInfo requests information about the NeoFS network of which the remote server is a part.
|
||||
//
|
||||
// Any client's internal or transport errors are returned as `error`.
|
||||
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful
|
||||
// NeoFS status codes are returned as `error`, otherwise, are included
|
||||
// in the returned result structure.
|
||||
// Any client's internal or transport errors are returned as `error`,
|
||||
// see [apistatus] package for NeoFS-specific error types.
|
||||
//
|
||||
// Immediately panics if parameters are set incorrectly (see PrmNetworkInfo docs).
|
||||
// Context is required and must not be nil. It is used for network communication.
|
||||
//
|
||||
// Exactly one return value is non-nil. Server status return is returned in ResNetworkInfo.
|
||||
// Reflects all internal errors in second return value (transport problems, response processing, etc.).
|
||||
//
|
||||
// Return statuses:
|
||||
// - global (see Client docs).
|
||||
func (c *Client) NetworkInfo(ctx context.Context, prm PrmNetworkInfo) (*ResNetworkInfo, error) {
|
||||
// check context
|
||||
if ctx == nil {
|
||||
panic(panicMsgMissingContext)
|
||||
}
|
||||
|
||||
// form request
|
||||
var req v2netmap.NetworkInfoRequest
|
||||
|
||||
|
@ -171,7 +142,6 @@ func (c *Client) NetworkInfo(ctx context.Context, prm PrmNetworkInfo) (*ResNetwo
|
|||
c.initCallContext(&cc)
|
||||
cc.meta = prm.prmCommonMeta
|
||||
cc.req = &req
|
||||
cc.statusRes = &res
|
||||
cc.call = func() (responseV2, error) {
|
||||
return rpcapi.NetworkInfo(&c.c, &req, client.WithContext(ctx))
|
||||
}
|
||||
|
@ -207,8 +177,6 @@ type PrmNetMapSnapshot struct {
|
|||
|
||||
// ResNetMapSnapshot groups resulting values of NetMapSnapshot operation.
|
||||
type ResNetMapSnapshot struct {
|
||||
statusRes
|
||||
|
||||
netMap netmap.NetMap
|
||||
}
|
||||
|
||||
|
@ -219,24 +187,14 @@ func (x ResNetMapSnapshot) NetMap() netmap.NetMap {
|
|||
|
||||
// NetMapSnapshot requests current network view of the remote server.
|
||||
//
|
||||
// Any client's internal or transport errors are returned as `error`.
|
||||
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful
|
||||
// NeoFS status codes are returned as `error`, otherwise, are included
|
||||
// in the returned result structure.
|
||||
// Any client's internal or transport errors are returned as `error`,
|
||||
// see [apistatus] package for NeoFS-specific error types.
|
||||
//
|
||||
// Context is required and MUST NOT be nil. It is used for network communication.
|
||||
//
|
||||
// Exactly one return value is non-nil. Server status return is returned in ResNetMapSnapshot.
|
||||
// Reflects all internal errors in second return value (transport problems, response processing, etc.).
|
||||
//
|
||||
// Return statuses:
|
||||
// - global (see Client docs).
|
||||
func (c *Client) NetMapSnapshot(ctx context.Context, _ PrmNetMapSnapshot) (*ResNetMapSnapshot, error) {
|
||||
// check context
|
||||
if ctx == nil {
|
||||
panic(panicMsgMissingContext)
|
||||
}
|
||||
|
||||
// form request body
|
||||
var body v2netmap.SnapshotRequestBody
|
||||
|
||||
|
@ -248,7 +206,7 @@ func (c *Client) NetMapSnapshot(ctx context.Context, _ PrmNetMapSnapshot) (*ResN
|
|||
req.SetBody(&body)
|
||||
c.prepareRequest(&req, &meta)
|
||||
|
||||
err := signature.SignServiceMessage(&c.prm.key, &req)
|
||||
err := signServiceMessage(c.prm.signer, &req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("sign request: %w", err)
|
||||
}
|
||||
|
@ -259,15 +217,10 @@ func (c *Client) NetMapSnapshot(ctx context.Context, _ PrmNetMapSnapshot) (*ResN
|
|||
}
|
||||
|
||||
var res ResNetMapSnapshot
|
||||
res.st, err = c.processResponse(resp)
|
||||
if err != nil {
|
||||
if err = c.processResponse(resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !apistatus.IsSuccessful(res.st) {
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
const fieldNetMap = "network map"
|
||||
|
||||
netMapV2 := resp.GetBody().NetMap()
|
||||
|
|
|
@ -7,9 +7,11 @@ import (
|
|||
"testing"
|
||||
|
||||
v2netmap "github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/rpc/client"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/session"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/signature"
|
||||
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/crypto/test"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/netmap"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
@ -23,10 +25,16 @@ type serverNetMap struct {
|
|||
|
||||
setNetMap bool
|
||||
netMap v2netmap.NetMap
|
||||
|
||||
signer neofscrypto.Signer
|
||||
}
|
||||
|
||||
func (x *serverNetMap) netMapSnapshot(ctx context.Context, req v2netmap.SnapshotRequest) (*v2netmap.SnapshotResponse, error) {
|
||||
err := signature.VerifyServiceMessage(&req)
|
||||
func (x *serverNetMap) createSession(*client.Client, *session.CreateRequest, ...client.CallOption) (*session.CreateResponse, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (x *serverNetMap) netMapSnapshot(_ context.Context, req v2netmap.SnapshotRequest) (*v2netmap.SnapshotResponse, error) {
|
||||
err := verifyServiceMessage(&req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -44,7 +52,7 @@ func (x *serverNetMap) netMapSnapshot(ctx context.Context, req v2netmap.Snapshot
|
|||
var meta session.ResponseMetaHeader
|
||||
|
||||
if !x.statusOK {
|
||||
meta.SetStatus(statusErr.ToStatusV2())
|
||||
meta.SetStatus(statusErr.ErrorToV2())
|
||||
}
|
||||
|
||||
var resp v2netmap.SnapshotResponse
|
||||
|
@ -52,7 +60,7 @@ func (x *serverNetMap) netMapSnapshot(ctx context.Context, req v2netmap.Snapshot
|
|||
resp.SetMetaHeader(&meta)
|
||||
|
||||
if x.signResponse {
|
||||
err = signature.SignServiceMessage(key, &resp)
|
||||
err = signServiceMessage(x.signer, &resp)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("sign response: %v", err))
|
||||
}
|
||||
|
@ -66,14 +74,13 @@ func TestClient_NetMapSnapshot(t *testing.T) {
|
|||
var prm PrmNetMapSnapshot
|
||||
var res *ResNetMapSnapshot
|
||||
var srv serverNetMap
|
||||
c := newClient(&srv)
|
||||
ctx := context.Background()
|
||||
|
||||
// missing context
|
||||
require.PanicsWithValue(t, panicMsgMissingContext, func() {
|
||||
//nolint:staticcheck
|
||||
_, _ = c.NetMapSnapshot(nil, prm)
|
||||
})
|
||||
signer := test.RandomSignerRFC6979(t)
|
||||
|
||||
srv.signer = signer
|
||||
|
||||
c := newClient(t, signer, &srv)
|
||||
ctx := context.Background()
|
||||
|
||||
// request signature
|
||||
srv.errTransport = errors.New("any error")
|
||||
|
@ -89,10 +96,10 @@ func TestClient_NetMapSnapshot(t *testing.T) {
|
|||
|
||||
srv.signResponse = true
|
||||
|
||||
// status failure
|
||||
res, err = c.NetMapSnapshot(ctx, prm)
|
||||
require.NoError(t, err)
|
||||
assertStatusErr(t, res)
|
||||
// failure error
|
||||
_, err = c.NetMapSnapshot(ctx, prm)
|
||||
require.Error(t, err)
|
||||
require.ErrorIs(t, err, apistatus.ErrServerInternal)
|
||||
|
||||
srv.statusOK = true
|
||||
|
||||
|
@ -132,6 +139,5 @@ func TestClient_NetMapSnapshot(t *testing.T) {
|
|||
|
||||
res, err = c.NetMapSnapshot(ctx, prm)
|
||||
require.NoError(t, err)
|
||||
require.True(t, apistatus.IsSuccessful(res.Status()))
|
||||
require.Equal(t, netMap, res.NetMap())
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package client
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/acl"
|
||||
|
@ -11,10 +10,9 @@ import (
|
|||
rpcapi "github.com/nspcc-dev/neofs-api-go/v2/rpc"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/rpc/client"
|
||||
v2session "github.com/nspcc-dev/neofs-api-go/v2/session"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/signature"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/bearer"
|
||||
apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status"
|
||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
|
||||
oid "github.com/nspcc-dev/neofs-sdk-go/object/id"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/session"
|
||||
)
|
||||
|
@ -28,7 +26,7 @@ type PrmObjectDelete struct {
|
|||
addr v2refs.Address
|
||||
|
||||
keySet bool
|
||||
key ecdsa.PrivateKey
|
||||
signer neofscrypto.Signer
|
||||
}
|
||||
|
||||
// WithinSession specifies session within which object should be read.
|
||||
|
@ -56,7 +54,7 @@ func (x *PrmObjectDelete) WithBearerToken(t bearer.Token) {
|
|||
}
|
||||
|
||||
// FromContainer specifies NeoFS container of the object.
|
||||
// Required parameter.
|
||||
// Required parameter. It is an alternative to [PrmObjectDelete.ByAddress].
|
||||
func (x *PrmObjectDelete) FromContainer(id cid.ID) {
|
||||
var cidV2 v2refs.ContainerID
|
||||
id.WriteToV2(&cidV2)
|
||||
|
@ -65,7 +63,7 @@ func (x *PrmObjectDelete) FromContainer(id cid.ID) {
|
|||
}
|
||||
|
||||
// ByID specifies identifier of the requested object.
|
||||
// Required parameter.
|
||||
// Required parameter. It is an alternative to [PrmObjectDelete.ByAddress].
|
||||
func (x *PrmObjectDelete) ByID(id oid.ID) {
|
||||
var idV2 v2refs.ObjectID
|
||||
id.WriteToV2(&idV2)
|
||||
|
@ -73,11 +71,17 @@ func (x *PrmObjectDelete) ByID(id oid.ID) {
|
|||
x.addr.SetObjectID(&idV2)
|
||||
}
|
||||
|
||||
// UseKey specifies private key to sign the requests.
|
||||
// If key is not provided, then Client default key is used.
|
||||
func (x *PrmObjectDelete) UseKey(key ecdsa.PrivateKey) {
|
||||
// ByAddress specifies address of the requested object.
|
||||
// Required parameter. It is an alternative to [PrmObjectDelete.ByID], [PrmObjectDelete.FromContainer].
|
||||
func (x *PrmObjectDelete) ByAddress(addr oid.Address) {
|
||||
addr.WriteToV2(&x.addr)
|
||||
}
|
||||
|
||||
// UseSigner specifies private signer to sign the requests.
|
||||
// If signer is not provided, then Client default signer is used.
|
||||
func (x *PrmObjectDelete) UseSigner(signer neofscrypto.Signer) {
|
||||
x.keySet = true
|
||||
x.key = key
|
||||
x.signer = signer
|
||||
}
|
||||
|
||||
// WithXHeaders specifies list of extended headers (string key-value pairs)
|
||||
|
@ -90,8 +94,6 @@ func (x *PrmObjectDelete) WithXHeaders(hs ...string) {
|
|||
|
||||
// ResObjectDelete groups resulting values of ObjectDelete operation.
|
||||
type ResObjectDelete struct {
|
||||
statusRes
|
||||
|
||||
tomb oid.ID
|
||||
}
|
||||
|
||||
|
@ -110,27 +112,24 @@ func (x ResObjectDelete) Tombstone() oid.ID {
|
|||
//
|
||||
// Exactly one return value is non-nil. By default, server status is returned in res structure.
|
||||
// Any client's internal or transport errors are returned as `error`,
|
||||
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful
|
||||
// NeoFS status codes are returned as `error`, otherwise, are included
|
||||
// in the returned result structure.
|
||||
// see [apistatus] package for NeoFS-specific error types.
|
||||
//
|
||||
// Immediately panics if parameters are set incorrectly (see PrmObjectDelete docs).
|
||||
// Context is required and must not be nil. It is used for network communication.
|
||||
//
|
||||
// Return statuses:
|
||||
// Return errors:
|
||||
// - global (see Client docs)
|
||||
// - *apistatus.ContainerNotFound;
|
||||
// - *apistatus.ObjectAccessDenied;
|
||||
// - *apistatus.ObjectLocked;
|
||||
// - *apistatus.SessionTokenExpired.
|
||||
// - [ErrMissingContainer];
|
||||
// - [ErrMissingObject];
|
||||
// - [apistatus.ErrContainerNotFound];
|
||||
// - [apistatus.ErrObjectAccessDenied];
|
||||
// - [apistatus.ErrObjectLocked];
|
||||
// - [apistatus.ErrSessionTokenExpired].
|
||||
func (c *Client) ObjectDelete(ctx context.Context, prm PrmObjectDelete) (*ResObjectDelete, error) {
|
||||
switch {
|
||||
case ctx == nil:
|
||||
panic(panicMsgMissingContext)
|
||||
case prm.addr.GetContainerID() == nil:
|
||||
panic(panicMsgMissingContainer)
|
||||
return nil, ErrMissingContainer
|
||||
case prm.addr.GetObjectID() == nil:
|
||||
panic(panicMsgMissingObject)
|
||||
return nil, ErrMissingObject
|
||||
}
|
||||
|
||||
// form request body
|
||||
|
@ -141,12 +140,12 @@ func (c *Client) ObjectDelete(ctx context.Context, prm PrmObjectDelete) (*ResObj
|
|||
req.SetBody(&prm.body)
|
||||
c.prepareRequest(&req, &prm.meta)
|
||||
|
||||
key := c.prm.key
|
||||
if prm.keySet {
|
||||
key = prm.key
|
||||
signer := prm.signer
|
||||
if signer == nil {
|
||||
signer = c.prm.signer
|
||||
}
|
||||
|
||||
err := signature.SignServiceMessage(&key, &req)
|
||||
err := signServiceMessage(signer, &req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("sign request: %w", err)
|
||||
}
|
||||
|
@ -157,15 +156,10 @@ func (c *Client) ObjectDelete(ctx context.Context, prm PrmObjectDelete) (*ResObj
|
|||
}
|
||||
|
||||
var res ResObjectDelete
|
||||
res.st, err = c.processResponse(resp)
|
||||
if err != nil {
|
||||
if err = c.processResponse(resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !apistatus.IsSuccessful(res.st) {
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
const fieldTombstone = "tombstone"
|
||||
|
||||
idTombV2 := resp.GetBody().GetTombstone().GetObjectID()
|
||||
|
|
73
client/object_delete_test.go
Normal file
73
client/object_delete_test.go
Normal file
|
@ -0,0 +1,73 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"testing"
|
||||
|
||||
v2refs "github.com/nspcc-dev/neofs-api-go/v2/refs"
|
||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||
oid "github.com/nspcc-dev/neofs-sdk-go/object/id"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func randOID(t *testing.T) oid.ID {
|
||||
var id oid.ID
|
||||
id.SetSHA256(randSHA256Checksum(t))
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
func randCID(t *testing.T) cid.ID {
|
||||
var id cid.ID
|
||||
id.SetSHA256(randSHA256Checksum(t))
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
func randSHA256Checksum(t *testing.T) (cs [sha256.Size]byte) {
|
||||
_, err := rand.Read(cs[:])
|
||||
require.NoError(t, err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func TestPrmObjectDelete_ByAddress(t *testing.T) {
|
||||
var prm PrmObjectDelete
|
||||
|
||||
var (
|
||||
objID oid.ID
|
||||
contID cid.ID
|
||||
oidV2 v2refs.ObjectID
|
||||
cidV2 v2refs.ContainerID
|
||||
)
|
||||
|
||||
t.Run("ByID", func(t *testing.T) {
|
||||
objID = randOID(t)
|
||||
prm.ByID(objID)
|
||||
|
||||
objID.WriteToV2(&oidV2)
|
||||
|
||||
require.True(t, bytes.Equal(oidV2.GetValue(), prm.addr.GetObjectID().GetValue()))
|
||||
})
|
||||
|
||||
t.Run("FromContainer", func(t *testing.T) {
|
||||
contID = randCID(t)
|
||||
prm.FromContainer(contID)
|
||||
|
||||
contID.WriteToV2(&cidV2)
|
||||
|
||||
require.True(t, bytes.Equal(cidV2.GetValue(), prm.addr.GetContainerID().GetValue()))
|
||||
})
|
||||
|
||||
t.Run("ByAddress", func(t *testing.T) {
|
||||
var addr oid.Address
|
||||
addr.SetObject(objID)
|
||||
addr.SetContainer(contID)
|
||||
|
||||
prm.ByAddress(addr)
|
||||
require.True(t, bytes.Equal(oidV2.GetValue(), prm.addr.GetObjectID().GetValue()))
|
||||
require.True(t, bytes.Equal(cidV2.GetValue(), prm.addr.GetContainerID().GetValue()))
|
||||
})
|
||||
}
|
|
@ -2,7 +2,6 @@ package client
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
@ -13,10 +12,9 @@ import (
|
|||
rpcapi "github.com/nspcc-dev/neofs-api-go/v2/rpc"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/rpc/client"
|
||||
v2session "github.com/nspcc-dev/neofs-api-go/v2/session"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/signature"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/bearer"
|
||||
apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status"
|
||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||
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/session"
|
||||
|
@ -73,7 +71,7 @@ func (x *prmObjectRead) WithBearerToken(t bearer.Token) {
|
|||
}
|
||||
|
||||
// FromContainer specifies NeoFS container of the object.
|
||||
// Required parameter.
|
||||
// Required parameter. It is an alternative to ByAddress.
|
||||
func (x *prmObjectRead) FromContainer(id cid.ID) {
|
||||
var cnrV2 v2refs.ContainerID
|
||||
id.WriteToV2(&cnrV2)
|
||||
|
@ -81,23 +79,24 @@ func (x *prmObjectRead) FromContainer(id cid.ID) {
|
|||
}
|
||||
|
||||
// ByID specifies identifier of the requested object.
|
||||
// Required parameter.
|
||||
// Required parameter. It is an alternative to ByAddress.
|
||||
func (x *prmObjectRead) ByID(id oid.ID) {
|
||||
var objV2 v2refs.ObjectID
|
||||
id.WriteToV2(&objV2)
|
||||
x.addr.SetObjectID(&objV2)
|
||||
}
|
||||
|
||||
// ByAddress specifies address of the requested object.
|
||||
// Required parameter. It is an alternative to ByID, FromContainer.
|
||||
func (x *prmObjectRead) ByAddress(addr oid.Address) {
|
||||
addr.WriteToV2(&x.addr)
|
||||
}
|
||||
|
||||
// PrmObjectGet groups parameters of ObjectGetInit operation.
|
||||
type PrmObjectGet struct {
|
||||
prmObjectRead
|
||||
|
||||
key *ecdsa.PrivateKey
|
||||
}
|
||||
|
||||
// ResObjectGet groups the final result values of ObjectGetInit operation.
|
||||
type ResObjectGet struct {
|
||||
statusRes
|
||||
signer neofscrypto.Signer
|
||||
}
|
||||
|
||||
// ObjectReader is designed to read one object from NeoFS system.
|
||||
|
@ -112,7 +111,6 @@ type ObjectReader struct {
|
|||
Read(resp *v2object.GetResponse) error
|
||||
}
|
||||
|
||||
res ResObjectGet
|
||||
err error
|
||||
|
||||
tailPayload []byte
|
||||
|
@ -120,10 +118,10 @@ type ObjectReader struct {
|
|||
remainingPayloadLen int
|
||||
}
|
||||
|
||||
// UseKey specifies private key to sign the requests.
|
||||
// If key is not provided, then Client default key is used.
|
||||
func (x *PrmObjectGet) UseKey(key ecdsa.PrivateKey) {
|
||||
x.key = &key
|
||||
// UseSigner specifies private signer to sign the requests.
|
||||
// If signer is not provided, then Client default signer is used.
|
||||
func (x *PrmObjectGet) UseSigner(signer neofscrypto.Signer) {
|
||||
x.signer = signer
|
||||
}
|
||||
|
||||
// ReadHeader reads header of the object. Result means success.
|
||||
|
@ -135,8 +133,8 @@ func (x *ObjectReader) ReadHeader(dst *object.Object) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
x.res.st, x.err = x.client.processResponse(&resp)
|
||||
if x.err != nil || !apistatus.IsSuccessful(x.res.st) {
|
||||
x.err = x.client.processResponse(&resp)
|
||||
if x.err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -188,8 +186,8 @@ func (x *ObjectReader) readChunk(buf []byte) (int, bool) {
|
|||
return read, false
|
||||
}
|
||||
|
||||
x.res.st, x.err = x.client.processResponse(&resp)
|
||||
if x.err != nil || !apistatus.IsSuccessful(x.res.st) {
|
||||
x.err = x.client.processResponse(&resp)
|
||||
if x.err != nil {
|
||||
return read, false
|
||||
}
|
||||
|
||||
|
@ -228,44 +226,40 @@ func (x *ObjectReader) ReadChunk(buf []byte) (int, bool) {
|
|||
return x.readChunk(buf)
|
||||
}
|
||||
|
||||
func (x *ObjectReader) close(ignoreEOF bool) (*ResObjectGet, error) {
|
||||
func (x *ObjectReader) close(ignoreEOF bool) error {
|
||||
defer x.cancelCtxStream()
|
||||
|
||||
if x.err != nil {
|
||||
if !errors.Is(x.err, io.EOF) {
|
||||
return nil, x.err
|
||||
return x.err
|
||||
} else if !ignoreEOF {
|
||||
if x.remainingPayloadLen > 0 {
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
|
||||
return nil, io.EOF
|
||||
return io.EOF
|
||||
}
|
||||
}
|
||||
|
||||
return &x.res, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close ends reading the object and returns the result of the operation
|
||||
// along with the final results. Must be called after using the ObjectReader.
|
||||
//
|
||||
// Exactly one return value is non-nil. By default, server status is returned in res structure.
|
||||
// Any client's internal or transport errors are returned as Go built-in error.
|
||||
// If Client is tuned to resolve NeoFS API statuses, then NeoFS failures
|
||||
// codes are returned as error.
|
||||
//
|
||||
// Return errors:
|
||||
//
|
||||
// *object.SplitInfoError (returned on virtual objects with PrmObjectGet.MakeRaw).
|
||||
//
|
||||
// Return statuses:
|
||||
// - global (see Client docs);
|
||||
// - *apistatus.ContainerNotFound;
|
||||
// - *apistatus.ObjectNotFound;
|
||||
// - *apistatus.ObjectAccessDenied;
|
||||
// - *apistatus.ObjectAlreadyRemoved;
|
||||
// - *apistatus.SessionTokenExpired.
|
||||
func (x *ObjectReader) Close() (*ResObjectGet, error) {
|
||||
// - *[object.SplitInfoError] (returned on virtual objects with PrmObjectGet.MakeRaw).
|
||||
// - [apistatus.ErrContainerNotFound];
|
||||
// - [apistatus.ErrObjectNotFound];
|
||||
// - [apistatus.ErrObjectAccessDenied];
|
||||
// - [apistatus.ErrObjectAlreadyRemoved];
|
||||
// - [apistatus.ErrSessionTokenExpired].
|
||||
func (x *ObjectReader) Close() error {
|
||||
return x.close(true)
|
||||
}
|
||||
|
||||
|
@ -276,12 +270,11 @@ func (x *ObjectReader) Read(p []byte) (int, error) {
|
|||
x.remainingPayloadLen -= n
|
||||
|
||||
if !ok {
|
||||
res, err := x.close(false)
|
||||
if err != nil {
|
||||
if err := x.close(false); err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
return n, apistatus.ErrFromStatus(res.Status())
|
||||
return n, x.err
|
||||
}
|
||||
|
||||
if x.remainingPayloadLen < 0 {
|
||||
|
@ -296,17 +289,18 @@ func (x *ObjectReader) Read(p []byte) (int, error) {
|
|||
// The call only opens the transmission channel, explicit fetching is done using the ObjectReader.
|
||||
// Exactly one return value is non-nil. Resulting reader must be finally closed.
|
||||
//
|
||||
// Immediately panics if parameters are set incorrectly (see PrmObjectGet docs).
|
||||
// Context is required and must not be nil. It is used for network communication.
|
||||
//
|
||||
// Return errors:
|
||||
// - [ErrMissingContainer]
|
||||
// - [ErrMissingObject]
|
||||
func (c *Client) ObjectGetInit(ctx context.Context, prm PrmObjectGet) (*ObjectReader, error) {
|
||||
// check parameters
|
||||
switch {
|
||||
case ctx == nil:
|
||||
panic(panicMsgMissingContext)
|
||||
case prm.addr.GetContainerID() == nil:
|
||||
panic(panicMsgMissingContainer)
|
||||
return nil, ErrMissingContainer
|
||||
case prm.addr.GetObjectID() == nil:
|
||||
panic(panicMsgMissingObject)
|
||||
return nil, ErrMissingObject
|
||||
}
|
||||
|
||||
// form request body
|
||||
|
@ -321,12 +315,12 @@ func (c *Client) ObjectGetInit(ctx context.Context, prm PrmObjectGet) (*ObjectRe
|
|||
req.SetBody(&body)
|
||||
c.prepareRequest(&req, &prm.meta)
|
||||
|
||||
key := prm.key
|
||||
if key == nil {
|
||||
key = &c.prm.key
|
||||
signer := prm.signer
|
||||
if signer == nil {
|
||||
signer = c.prm.signer
|
||||
}
|
||||
|
||||
err := signature.SignServiceMessage(key, &req)
|
||||
err := signServiceMessage(signer, &req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("sign request: %w", err)
|
||||
}
|
||||
|
@ -351,21 +345,17 @@ func (c *Client) ObjectGetInit(ctx context.Context, prm PrmObjectGet) (*ObjectRe
|
|||
type PrmObjectHead struct {
|
||||
prmObjectRead
|
||||
|
||||
keySet bool
|
||||
key ecdsa.PrivateKey
|
||||
signer neofscrypto.Signer
|
||||
}
|
||||
|
||||
// UseKey specifies private key to sign the requests.
|
||||
// If key is not provided, then Client default key is used.
|
||||
func (x *PrmObjectHead) UseKey(key ecdsa.PrivateKey) {
|
||||
x.keySet = true
|
||||
x.key = key
|
||||
// UseSigner specifies private signer to sign the requests.
|
||||
// If signer is not provided, then Client default signer is used.
|
||||
func (x *PrmObjectHead) UseSigner(signer neofscrypto.Signer) {
|
||||
x.signer = signer
|
||||
}
|
||||
|
||||
// ResObjectHead groups resulting values of ObjectHead operation.
|
||||
type ResObjectHead struct {
|
||||
statusRes
|
||||
|
||||
// requested object (response doesn't carry the ID)
|
||||
idObj oid.ID
|
||||
|
||||
|
@ -396,32 +386,26 @@ func (x *ResObjectHead) ReadHeader(dst *object.Object) bool {
|
|||
//
|
||||
// Exactly one return value is non-nil. By default, server status is returned in res structure.
|
||||
// Any client's internal or transport errors are returned as `error`,
|
||||
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful
|
||||
// NeoFS status codes are returned as `error`, otherwise, are included
|
||||
// in the returned result structure.
|
||||
// see [apistatus] package for NeoFS-specific error types.
|
||||
//
|
||||
// Immediately panics if parameters are set incorrectly (see PrmObjectHead docs).
|
||||
// Context is required and must not be nil. It is used for network communication.
|
||||
//
|
||||
// Return errors:
|
||||
//
|
||||
// *object.SplitInfoError (returned on virtual objects with PrmObjectHead.MakeRaw).
|
||||
//
|
||||
// Return statuses:
|
||||
// - global (see Client docs);
|
||||
// - *apistatus.ContainerNotFound;
|
||||
// - *apistatus.ObjectNotFound;
|
||||
// - *apistatus.ObjectAccessDenied;
|
||||
// - *apistatus.ObjectAlreadyRemoved;
|
||||
// - *apistatus.SessionTokenExpired.
|
||||
// - [ErrMissingContainer];
|
||||
// - [ErrMissingObject];
|
||||
// - *[object.SplitInfoError] (returned on virtual objects with PrmObjectHead.MakeRaw).
|
||||
// - [apistatus.ErrContainerNotFound];
|
||||
// - [apistatus.ErrObjectNotFound];
|
||||
// - [apistatus.ErrObjectAccessDenied];
|
||||
// - [apistatus.ErrObjectAlreadyRemoved];
|
||||
// - [apistatus.ErrSessionTokenExpired].
|
||||
func (c *Client) ObjectHead(ctx context.Context, prm PrmObjectHead) (*ResObjectHead, error) {
|
||||
switch {
|
||||
case ctx == nil:
|
||||
panic(panicMsgMissingContext)
|
||||
case prm.addr.GetContainerID() == nil:
|
||||
panic(panicMsgMissingContainer)
|
||||
return nil, ErrMissingContainer
|
||||
case prm.addr.GetObjectID() == nil:
|
||||
panic(panicMsgMissingObject)
|
||||
return nil, ErrMissingObject
|
||||
}
|
||||
|
||||
var body v2object.HeadRequestBody
|
||||
|
@ -432,13 +416,13 @@ func (c *Client) ObjectHead(ctx context.Context, prm PrmObjectHead) (*ResObjectH
|
|||
req.SetBody(&body)
|
||||
c.prepareRequest(&req, &prm.meta)
|
||||
|
||||
key := c.prm.key
|
||||
if prm.keySet {
|
||||
key = prm.key
|
||||
signer := prm.signer
|
||||
if signer == nil {
|
||||
signer = c.prm.signer
|
||||
}
|
||||
|
||||
// sign the request
|
||||
err := signature.SignServiceMessage(&key, &req)
|
||||
err := signServiceMessage(signer, &req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("sign request: %w", err)
|
||||
}
|
||||
|
@ -449,15 +433,10 @@ func (c *Client) ObjectHead(ctx context.Context, prm PrmObjectHead) (*ResObjectH
|
|||
}
|
||||
|
||||
var res ResObjectHead
|
||||
res.st, err = c.processResponse(resp)
|
||||
if err != nil {
|
||||
if err = c.processResponse(resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !apistatus.IsSuccessful(res.st) {
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
_ = res.idObj.ReadFromV2(*prm.addr.GetObjectID())
|
||||
|
||||
switch v := resp.GetBody().GetHeaderPart().(type) {
|
||||
|
@ -476,32 +455,33 @@ func (c *Client) ObjectHead(ctx context.Context, prm PrmObjectHead) (*ResObjectH
|
|||
type PrmObjectRange struct {
|
||||
prmObjectRead
|
||||
|
||||
key *ecdsa.PrivateKey
|
||||
signer neofscrypto.Signer
|
||||
|
||||
rng v2object.Range
|
||||
}
|
||||
|
||||
// SetOffset sets offset of the payload range to be read.
|
||||
// Zero by default.
|
||||
// Zero by default. It is an alternative to [PrmObjectRange.SetRange].
|
||||
func (x *PrmObjectRange) SetOffset(off uint64) {
|
||||
x.rng.SetOffset(off)
|
||||
}
|
||||
|
||||
// SetLength sets length of the payload range to be read.
|
||||
// Must be positive.
|
||||
// Must be positive. It is an alternative to [PrmObjectRange.SetRange].
|
||||
func (x *PrmObjectRange) SetLength(ln uint64) {
|
||||
x.rng.SetLength(ln)
|
||||
}
|
||||
|
||||
// UseKey specifies private key to sign the requests.
|
||||
// If key is not provided, then Client default key is used.
|
||||
func (x *PrmObjectRange) UseKey(key ecdsa.PrivateKey) {
|
||||
x.key = &key
|
||||
// SetRange sets range of the payload to be read.
|
||||
// It is an alternative to [PrmObjectRange.SetOffset], [PrmObjectRange.SetLength].
|
||||
func (x *PrmObjectRange) SetRange(rng object.Range) {
|
||||
x.rng = *rng.ToV2()
|
||||
}
|
||||
|
||||
// ResObjectRange groups the final result values of ObjectRange operation.
|
||||
type ResObjectRange struct {
|
||||
statusRes
|
||||
// UseSigner specifies private signer to sign the requests.
|
||||
// If signer is not provided, then Client default signer is used.
|
||||
func (x *PrmObjectRange) UseSigner(signer neofscrypto.Signer) {
|
||||
x.signer = signer
|
||||
}
|
||||
|
||||
// ObjectRangeReader is designed to read payload range of one object
|
||||
|
@ -514,7 +494,6 @@ type ObjectRangeReader struct {
|
|||
|
||||
client *Client
|
||||
|
||||
res ResObjectRange
|
||||
err error
|
||||
|
||||
stream interface {
|
||||
|
@ -549,8 +528,8 @@ func (x *ObjectRangeReader) readChunk(buf []byte) (int, bool) {
|
|||
return read, false
|
||||
}
|
||||
|
||||
x.res.st, x.err = x.client.processResponse(&resp)
|
||||
if x.err != nil || !apistatus.IsSuccessful(x.res.st) {
|
||||
x.err = x.client.processResponse(&resp)
|
||||
if x.err != nil {
|
||||
return read, false
|
||||
}
|
||||
|
||||
|
@ -593,45 +572,41 @@ func (x *ObjectRangeReader) ReadChunk(buf []byte) (int, bool) {
|
|||
return x.readChunk(buf)
|
||||
}
|
||||
|
||||
func (x *ObjectRangeReader) close(ignoreEOF bool) (*ResObjectRange, error) {
|
||||
func (x *ObjectRangeReader) close(ignoreEOF bool) error {
|
||||
defer x.cancelCtxStream()
|
||||
|
||||
if x.err != nil {
|
||||
if !errors.Is(x.err, io.EOF) {
|
||||
return nil, x.err
|
||||
return x.err
|
||||
} else if !ignoreEOF {
|
||||
if x.remainingPayloadLen > 0 {
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
|
||||
return nil, io.EOF
|
||||
return io.EOF
|
||||
}
|
||||
}
|
||||
|
||||
return &x.res, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close ends reading the payload range and returns the result of the operation
|
||||
// along with the final results. Must be called after using the ObjectRangeReader.
|
||||
//
|
||||
// Exactly one return value is non-nil. By default, server status is returned in res structure.
|
||||
// Any client's internal or transport errors are returned as Go built-in error.
|
||||
// If Client is tuned to resolve NeoFS API statuses, then NeoFS failures
|
||||
// codes are returned as error.
|
||||
//
|
||||
// Return errors:
|
||||
//
|
||||
// *object.SplitInfoError (returned on virtual objects with PrmObjectRange.MakeRaw).
|
||||
//
|
||||
// Return statuses:
|
||||
// - global (see Client docs);
|
||||
// - *apistatus.ContainerNotFound;
|
||||
// - *apistatus.ObjectNotFound;
|
||||
// - *apistatus.ObjectAccessDenied;
|
||||
// - *apistatus.ObjectAlreadyRemoved;
|
||||
// - *apistatus.ObjectOutOfRange;
|
||||
// - *apistatus.SessionTokenExpired.
|
||||
func (x *ObjectRangeReader) Close() (*ResObjectRange, error) {
|
||||
// - *[object.SplitInfoError] (returned on virtual objects with PrmObjectRange.MakeRaw).
|
||||
// - [apistatus.ErrContainerNotFound];
|
||||
// - [apistatus.ErrObjectNotFound];
|
||||
// - [apistatus.ErrObjectAccessDenied];
|
||||
// - [apistatus.ErrObjectAlreadyRemoved];
|
||||
// - [apistatus.ErrObjectOutOfRange];
|
||||
// - [apistatus.ErrSessionTokenExpired].
|
||||
func (x *ObjectRangeReader) Close() error {
|
||||
return x.close(true)
|
||||
}
|
||||
|
||||
|
@ -642,12 +617,12 @@ func (x *ObjectRangeReader) Read(p []byte) (int, error) {
|
|||
x.remainingPayloadLen -= n
|
||||
|
||||
if !ok {
|
||||
res, err := x.close(false)
|
||||
err := x.close(false)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
return n, apistatus.ErrFromStatus(res.Status())
|
||||
return n, x.err
|
||||
}
|
||||
|
||||
if x.remainingPayloadLen < 0 {
|
||||
|
@ -663,19 +638,21 @@ func (x *ObjectRangeReader) Read(p []byte) (int, error) {
|
|||
// The call only opens the transmission channel, explicit fetching is done using the ObjectRangeReader.
|
||||
// Exactly one return value is non-nil. Resulting reader must be finally closed.
|
||||
//
|
||||
// Immediately panics if parameters are set incorrectly (see PrmObjectRange docs).
|
||||
// Context is required and must not be nil. It is used for network communication.
|
||||
//
|
||||
// Return errors:
|
||||
// - [ErrMissingContainer]
|
||||
// - [ErrMissingObject]
|
||||
// - [ErrZeroRangeLength]
|
||||
func (c *Client) ObjectRangeInit(ctx context.Context, prm PrmObjectRange) (*ObjectRangeReader, error) {
|
||||
// check parameters
|
||||
switch {
|
||||
case ctx == nil:
|
||||
panic(panicMsgMissingContext)
|
||||
case prm.addr.GetContainerID() == nil:
|
||||
panic(panicMsgMissingContainer)
|
||||
return nil, ErrMissingContainer
|
||||
case prm.addr.GetObjectID() == nil:
|
||||
panic(panicMsgMissingObject)
|
||||
return nil, ErrMissingObject
|
||||
case prm.rng.GetLength() == 0:
|
||||
panic("zero range length")
|
||||
return nil, ErrZeroRangeLength
|
||||
}
|
||||
|
||||
// form request body
|
||||
|
@ -691,12 +668,12 @@ func (c *Client) ObjectRangeInit(ctx context.Context, prm PrmObjectRange) (*Obje
|
|||
req.SetBody(&body)
|
||||
c.prepareRequest(&req, &prm.meta)
|
||||
|
||||
key := prm.key
|
||||
if key == nil {
|
||||
key = &c.prm.key
|
||||
signer := prm.signer
|
||||
if signer == nil {
|
||||
signer = c.prm.signer
|
||||
}
|
||||
|
||||
err := signature.SignServiceMessage(key, &req)
|
||||
err := signServiceMessage(signer, &req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("sign request: %w", err)
|
||||
}
|
||||
|
|
86
client/object_get_test.go
Normal file
86
client/object_get_test.go
Normal file
|
@ -0,0 +1,86 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
v2refs "github.com/nspcc-dev/neofs-api-go/v2/refs"
|
||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/object"
|
||||
oid "github.com/nspcc-dev/neofs-sdk-go/object/id"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPrmObjectRead_ByAddress(t *testing.T) {
|
||||
var prm PrmObjectHead
|
||||
|
||||
var (
|
||||
objID oid.ID
|
||||
contID cid.ID
|
||||
oidV2 v2refs.ObjectID
|
||||
cidV2 v2refs.ContainerID
|
||||
)
|
||||
|
||||
t.Run("ByID", func(t *testing.T) {
|
||||
objID = randOID(t)
|
||||
prm.ByID(objID)
|
||||
|
||||
objID.WriteToV2(&oidV2)
|
||||
|
||||
require.True(t, bytes.Equal(oidV2.GetValue(), prm.addr.GetObjectID().GetValue()))
|
||||
})
|
||||
|
||||
t.Run("FromContainer", func(t *testing.T) {
|
||||
contID = randCID(t)
|
||||
prm.FromContainer(contID)
|
||||
|
||||
contID.WriteToV2(&cidV2)
|
||||
|
||||
require.True(t, bytes.Equal(cidV2.GetValue(), prm.addr.GetContainerID().GetValue()))
|
||||
})
|
||||
|
||||
t.Run("ByAddress", func(t *testing.T) {
|
||||
var addr oid.Address
|
||||
addr.SetObject(objID)
|
||||
addr.SetContainer(contID)
|
||||
|
||||
prm.ByAddress(addr)
|
||||
require.True(t, bytes.Equal(oidV2.GetValue(), prm.addr.GetObjectID().GetValue()))
|
||||
require.True(t, bytes.Equal(cidV2.GetValue(), prm.addr.GetContainerID().GetValue()))
|
||||
})
|
||||
}
|
||||
|
||||
func TestPrmObjectRange_SetRange(t *testing.T) {
|
||||
var prm PrmObjectRange
|
||||
|
||||
var (
|
||||
ln = rand.Uint64()
|
||||
off = rand.Uint64()
|
||||
rng *object.Range
|
||||
)
|
||||
|
||||
t.Run("SetLength", func(t *testing.T) {
|
||||
prm.SetLength(ln)
|
||||
rng = object.NewRangeFromV2(&prm.rng)
|
||||
|
||||
require.Equal(t, ln, rng.GetLength())
|
||||
})
|
||||
|
||||
t.Run("SetOffset", func(t *testing.T) {
|
||||
prm.SetOffset(off)
|
||||
rng = object.NewRangeFromV2(&prm.rng)
|
||||
|
||||
require.Equal(t, off, rng.GetOffset())
|
||||
})
|
||||
|
||||
t.Run("SetRange", func(t *testing.T) {
|
||||
var tmp object.Range
|
||||
tmp.SetLength(ln)
|
||||
tmp.SetOffset(off)
|
||||
|
||||
prm.SetRange(tmp)
|
||||
require.Equal(t, ln, tmp.ToV2().GetLength())
|
||||
require.Equal(t, off, tmp.ToV2().GetOffset())
|
||||
})
|
||||
}
|
|
@ -10,10 +10,9 @@ import (
|
|||
rpcapi "github.com/nspcc-dev/neofs-api-go/v2/rpc"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/rpc/client"
|
||||
v2session "github.com/nspcc-dev/neofs-api-go/v2/session"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/signature"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/bearer"
|
||||
apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status"
|
||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
|
||||
oid "github.com/nspcc-dev/neofs-sdk-go/object/id"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/session"
|
||||
)
|
||||
|
@ -27,6 +26,14 @@ type PrmObjectHash struct {
|
|||
csAlgo v2refs.ChecksumType
|
||||
|
||||
addr v2refs.Address
|
||||
|
||||
signer neofscrypto.Signer
|
||||
}
|
||||
|
||||
// UseSigner specifies private signer to sign the requests.
|
||||
// If signer is not provided, then Client default signer is used.
|
||||
func (x *PrmObjectHash) UseSigner(signer neofscrypto.Signer) {
|
||||
x.signer = signer
|
||||
}
|
||||
|
||||
// MarkLocal tells the server to execute the operation locally.
|
||||
|
@ -59,7 +66,7 @@ func (x *PrmObjectHash) WithBearerToken(t bearer.Token) {
|
|||
}
|
||||
|
||||
// FromContainer specifies NeoFS container of the object.
|
||||
// Required parameter.
|
||||
// Required parameter. It is an alternative to [PrmObjectHash.ByAddress].
|
||||
func (x *PrmObjectHash) FromContainer(id cid.ID) {
|
||||
var cidV2 v2refs.ContainerID
|
||||
id.WriteToV2(&cidV2)
|
||||
|
@ -68,7 +75,7 @@ func (x *PrmObjectHash) FromContainer(id cid.ID) {
|
|||
}
|
||||
|
||||
// ByID specifies identifier of the requested object.
|
||||
// Required parameter.
|
||||
// Required parameter. It is an alternative to [PrmObjectHash.ByAddress].
|
||||
func (x *PrmObjectHash) ByID(id oid.ID) {
|
||||
var idV2 v2refs.ObjectID
|
||||
id.WriteToV2(&idV2)
|
||||
|
@ -76,6 +83,12 @@ func (x *PrmObjectHash) ByID(id oid.ID) {
|
|||
x.addr.SetObjectID(&idV2)
|
||||
}
|
||||
|
||||
// ByAddress specifies address of the requested object.
|
||||
// Required parameter. It is an alternative to [PrmObjectHash.ByID], [PrmObjectHash.FromContainer].
|
||||
func (x *PrmObjectHash) ByAddress(addr oid.Address) {
|
||||
addr.WriteToV2(&x.addr)
|
||||
}
|
||||
|
||||
// SetRangeList sets list of ranges in (offset, length) pair format.
|
||||
// Required parameter.
|
||||
//
|
||||
|
@ -121,8 +134,6 @@ func (x *PrmObjectHash) WithXHeaders(hs ...string) {
|
|||
|
||||
// ResObjectHash groups resulting values of ObjectHash operation.
|
||||
type ResObjectHash struct {
|
||||
statusRes
|
||||
|
||||
checksums [][]byte
|
||||
}
|
||||
|
||||
|
@ -139,30 +150,22 @@ func (x ResObjectHash) Checksums() [][]byte {
|
|||
//
|
||||
// Exactly one return value is non-nil. By default, server status is returned in res structure.
|
||||
// Any client's internal or transport errors are returned as `error`,
|
||||
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful
|
||||
// NeoFS status codes are returned as `error`, otherwise, are included
|
||||
// in the returned result structure.
|
||||
// see [apistatus] package for NeoFS-specific error types.
|
||||
//
|
||||
// Immediately panics if parameters are set incorrectly (see PrmObjectHash docs).
|
||||
// Context is required and must not be nil. It is used for network communication.
|
||||
//
|
||||
// Return statuses:
|
||||
// - global (see Client docs);
|
||||
// - *apistatus.ContainerNotFound;
|
||||
// - *apistatus.ObjectNotFound;
|
||||
// - *apistatus.ObjectAccessDenied;
|
||||
// - *apistatus.ObjectOutOfRange;
|
||||
// - *apistatus.SessionTokenExpired.
|
||||
// Return errors:
|
||||
// - [ErrMissingContainer]
|
||||
// - [ErrMissingObject]
|
||||
// - [ErrMissingRanges]
|
||||
func (c *Client) ObjectHash(ctx context.Context, prm PrmObjectHash) (*ResObjectHash, error) {
|
||||
switch {
|
||||
case ctx == nil:
|
||||
panic(panicMsgMissingContext)
|
||||
case prm.addr.GetContainerID() == nil:
|
||||
panic(panicMsgMissingContainer)
|
||||
return nil, ErrMissingContainer
|
||||
case prm.addr.GetObjectID() == nil:
|
||||
panic(panicMsgMissingObject)
|
||||
return nil, ErrMissingObject
|
||||
case len(prm.body.GetRanges()) == 0:
|
||||
panic("missing ranges")
|
||||
return nil, ErrMissingRanges
|
||||
}
|
||||
|
||||
prm.body.SetAddress(&prm.addr)
|
||||
|
@ -176,7 +179,12 @@ func (c *Client) ObjectHash(ctx context.Context, prm PrmObjectHash) (*ResObjectH
|
|||
c.prepareRequest(&req, &prm.meta)
|
||||
req.SetBody(&prm.body)
|
||||
|
||||
err := signature.SignServiceMessage(&c.prm.key, &req)
|
||||
signer := prm.signer
|
||||
if signer == nil {
|
||||
signer = c.prm.signer
|
||||
}
|
||||
|
||||
err := signServiceMessage(signer, &req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("sign request: %w", err)
|
||||
}
|
||||
|
@ -187,15 +195,10 @@ func (c *Client) ObjectHash(ctx context.Context, prm PrmObjectHash) (*ResObjectH
|
|||
}
|
||||
|
||||
var res ResObjectHash
|
||||
res.st, err = c.processResponse(resp)
|
||||
if err != nil {
|
||||
if err = c.processResponse(resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !apistatus.IsSuccessful(res.st) {
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
res.checksums = resp.GetBody().GetHashList()
|
||||
if len(res.checksums) == 0 {
|
||||
return nil, newErrMissingResponseField("hash list")
|
||||
|
|
50
client/object_hash_test.go
Normal file
50
client/object_hash_test.go
Normal file
|
@ -0,0 +1,50 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
v2refs "github.com/nspcc-dev/neofs-api-go/v2/refs"
|
||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||
oid "github.com/nspcc-dev/neofs-sdk-go/object/id"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPrmObjectHash_ByAddress(t *testing.T) {
|
||||
var prm PrmObjectHash
|
||||
|
||||
var (
|
||||
objID oid.ID
|
||||
contID cid.ID
|
||||
oidV2 v2refs.ObjectID
|
||||
cidV2 v2refs.ContainerID
|
||||
)
|
||||
|
||||
t.Run("ByID", func(t *testing.T) {
|
||||
objID = randOID(t)
|
||||
prm.ByID(objID)
|
||||
|
||||
objID.WriteToV2(&oidV2)
|
||||
|
||||
require.True(t, bytes.Equal(oidV2.GetValue(), prm.addr.GetObjectID().GetValue()))
|
||||
})
|
||||
|
||||
t.Run("FromContainer", func(t *testing.T) {
|
||||
contID = randCID(t)
|
||||
prm.FromContainer(contID)
|
||||
|
||||
contID.WriteToV2(&cidV2)
|
||||
|
||||
require.True(t, bytes.Equal(cidV2.GetValue(), prm.addr.GetContainerID().GetValue()))
|
||||
})
|
||||
|
||||
t.Run("ByAddress", func(t *testing.T) {
|
||||
var addr oid.Address
|
||||
addr.SetObject(objID)
|
||||
addr.SetContainer(contID)
|
||||
|
||||
prm.ByAddress(addr)
|
||||
require.True(t, bytes.Equal(oidV2.GetValue(), prm.addr.GetObjectID().GetValue()))
|
||||
require.True(t, bytes.Equal(cidV2.GetValue(), prm.addr.GetContainerID().GetValue()))
|
||||
})
|
||||
}
|
|
@ -2,7 +2,6 @@ package client
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
@ -12,18 +11,20 @@ import (
|
|||
rpcapi "github.com/nspcc-dev/neofs-api-go/v2/rpc"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/rpc/client"
|
||||
v2session "github.com/nspcc-dev/neofs-api-go/v2/session"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/signature"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/bearer"
|
||||
apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status"
|
||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||
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/object/slicer"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/session"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/user"
|
||||
)
|
||||
|
||||
// PrmObjectPutInit groups parameters of ObjectPutInit operation.
|
||||
type PrmObjectPutInit struct {
|
||||
copyNum uint32
|
||||
key *ecdsa.PrivateKey
|
||||
signer neofscrypto.Signer
|
||||
meta v2session.RequestMetaHeader
|
||||
}
|
||||
|
||||
|
@ -34,8 +35,6 @@ func (x *PrmObjectPutInit) SetCopiesNumber(copiesNumber uint32) {
|
|||
|
||||
// ResObjectPut groups the final result values of ObjectPutInit operation.
|
||||
type ResObjectPut struct {
|
||||
statusRes
|
||||
|
||||
obj oid.ID
|
||||
}
|
||||
|
||||
|
@ -57,7 +56,7 @@ type ObjectWriter struct {
|
|||
Close() error
|
||||
}
|
||||
|
||||
key *ecdsa.PrivateKey
|
||||
signer neofscrypto.Signer
|
||||
res ResObjectPut
|
||||
err error
|
||||
|
||||
|
@ -69,10 +68,10 @@ type ObjectWriter struct {
|
|||
partChunk v2object.PutObjectPartChunk
|
||||
}
|
||||
|
||||
// UseKey specifies private key to sign the requests.
|
||||
// If key is not provided, then Client default key is used.
|
||||
func (x *PrmObjectPutInit) UseKey(key ecdsa.PrivateKey) {
|
||||
x.key = &key
|
||||
// UseSigner specifies private signer to sign the requests.
|
||||
// If signer is not provided, then Client default signer is used.
|
||||
func (x *PrmObjectPutInit) UseSigner(signer neofscrypto.Signer) {
|
||||
x.signer = signer
|
||||
}
|
||||
|
||||
// WithBearerToken attaches bearer token to be used for the operation.
|
||||
|
@ -117,7 +116,7 @@ func (x *ObjectWriter) WriteHeader(hdr object.Object) bool {
|
|||
x.req.GetBody().SetObjectPart(&x.partInit)
|
||||
x.req.SetVerificationHeader(nil)
|
||||
|
||||
x.err = signature.SignServiceMessage(x.key, &x.req)
|
||||
x.err = signServiceMessage(x.signer, &x.req)
|
||||
if x.err != nil {
|
||||
x.err = fmt.Errorf("sign message: %w", x.err)
|
||||
return false
|
||||
|
@ -159,7 +158,7 @@ func (x *ObjectWriter) WritePayloadChunk(chunk []byte) bool {
|
|||
x.partChunk.SetChunk(chunk[:ln])
|
||||
x.req.SetVerificationHeader(nil)
|
||||
|
||||
x.err = signature.SignServiceMessage(x.key, &x.req)
|
||||
x.err = signServiceMessage(x.signer, &x.req)
|
||||
if x.err != nil {
|
||||
x.err = fmt.Errorf("sign message: %w", x.err)
|
||||
return false
|
||||
|
@ -184,14 +183,14 @@ func (x *ObjectWriter) WritePayloadChunk(chunk []byte) bool {
|
|||
// If Client is tuned to resolve NeoFS API statuses, then NeoFS failures
|
||||
// codes are returned as error.
|
||||
//
|
||||
// Return statuses:
|
||||
// Return errors:
|
||||
// - global (see Client docs);
|
||||
// - *apistatus.ContainerNotFound;
|
||||
// - *apistatus.ObjectAccessDenied;
|
||||
// - *apistatus.ObjectLocked;
|
||||
// - *apistatus.LockNonRegularObject;
|
||||
// - *apistatus.SessionTokenNotFound;
|
||||
// - *apistatus.SessionTokenExpired.
|
||||
// - [apistatus.ErrContainerNotFound];
|
||||
// - [apistatus.ErrObjectAccessDenied];
|
||||
// - [apistatus.ErrObjectLocked];
|
||||
// - [apistatus.ErrLockNonRegularObject];
|
||||
// - [apistatus.ErrSessionTokenNotFound];
|
||||
// - [apistatus.ErrSessionTokenExpired].
|
||||
func (x *ObjectWriter) Close() (*ResObjectPut, error) {
|
||||
defer x.cancelCtxStream()
|
||||
|
||||
|
@ -206,15 +205,10 @@ func (x *ObjectWriter) Close() (*ResObjectPut, error) {
|
|||
return nil, x.err
|
||||
}
|
||||
|
||||
x.res.st, x.err = x.client.processResponse(&x.respV2)
|
||||
if x.err != nil {
|
||||
if x.err = x.client.processResponse(&x.respV2); x.err != nil {
|
||||
return nil, x.err
|
||||
}
|
||||
|
||||
if !apistatus.IsSuccessful(x.res.st) {
|
||||
return &x.res, nil
|
||||
}
|
||||
|
||||
const fieldID = "ID"
|
||||
|
||||
idV2 := x.respV2.GetBody().GetObjectID()
|
||||
|
@ -237,11 +231,6 @@ func (x *ObjectWriter) Close() (*ResObjectPut, error) {
|
|||
//
|
||||
// Context is required and must not be nil. It is used for network communication.
|
||||
func (c *Client) ObjectPutInit(ctx context.Context, prm PrmObjectPutInit) (*ObjectWriter, error) {
|
||||
// check parameters
|
||||
if ctx == nil {
|
||||
panic(panicMsgMissingContext)
|
||||
}
|
||||
|
||||
var w ObjectWriter
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
|
@ -251,9 +240,9 @@ func (c *Client) ObjectPutInit(ctx context.Context, prm PrmObjectPutInit) (*Obje
|
|||
return nil, fmt.Errorf("open stream: %w", err)
|
||||
}
|
||||
|
||||
w.key = &c.prm.key
|
||||
if prm.key != nil {
|
||||
w.key = prm.key
|
||||
w.signer = prm.signer
|
||||
if w.signer == nil {
|
||||
w.signer = c.prm.signer
|
||||
}
|
||||
w.cancelCtxStream = cancel
|
||||
w.client = c
|
||||
|
@ -264,3 +253,106 @@ func (c *Client) ObjectPutInit(ctx context.Context, prm PrmObjectPutInit) (*Obje
|
|||
|
||||
return &w, nil
|
||||
}
|
||||
|
||||
type objectWriter struct {
|
||||
context context.Context
|
||||
client *Client
|
||||
}
|
||||
|
||||
func (x *objectWriter) InitDataStream(header object.Object) (io.Writer, error) {
|
||||
var prm PrmObjectPutInit
|
||||
|
||||
stream, err := x.client.ObjectPutInit(x.context, prm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("init object stream: %w", err)
|
||||
}
|
||||
|
||||
if stream.WriteHeader(header) {
|
||||
return &payloadWriter{
|
||||
stream: stream,
|
||||
}, nil
|
||||
}
|
||||
|
||||
_, err = stream.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, errors.New("unexpected error")
|
||||
}
|
||||
|
||||
type payloadWriter struct {
|
||||
stream *ObjectWriter
|
||||
}
|
||||
|
||||
func (x *payloadWriter) Write(p []byte) (int, error) {
|
||||
if !x.stream.WritePayloadChunk(p) {
|
||||
return 0, x.Close()
|
||||
}
|
||||
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (x *payloadWriter) Close() error {
|
||||
_, err := x.stream.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateObject creates new NeoFS object with given payload data and stores it
|
||||
// in specified container of the NeoFS network using provided Client connection.
|
||||
// The object is created on behalf of provided neofscrypto.Signer, and owned by
|
||||
// the specified user.ID.
|
||||
//
|
||||
// In terms of NeoFS, parameterized neofscrypto.Signer represents object owner,
|
||||
// object signer and request sender. Container SHOULD be public-write or sender
|
||||
// SHOULD have corresponding rights.
|
||||
//
|
||||
// Client connection MUST be opened in advance, see Dial method for details.
|
||||
// Network communication is carried out within a given context, so it MUST NOT
|
||||
// be nil.
|
||||
//
|
||||
// Notice: This API is EXPERIMENTAL and is planned to be replaced/changed in the
|
||||
// future. Be ready to refactor your code regarding imports and call mechanics,
|
||||
// in essence the operation will not change.
|
||||
func CreateObject(ctx context.Context, cli *Client, signer neofscrypto.Signer, cnr cid.ID, owner user.ID, data io.Reader, attributes ...string) (oid.ID, error) {
|
||||
s, err := NewDataSlicer(ctx, cli, signer, cnr, owner)
|
||||
if err != nil {
|
||||
return oid.ID{}, err
|
||||
}
|
||||
|
||||
return s.Slice(data, attributes...)
|
||||
}
|
||||
|
||||
// NewDataSlicer creates slicer.Slicer that saves data in the NeoFS network
|
||||
// through provided Client. The data is packaged into NeoFS objects stored in
|
||||
// the specified container. Provided signer is being used to sign the resulting
|
||||
// objects as a system requirement. Produced objects are owned by the
|
||||
// parameterized NeoFS user.
|
||||
//
|
||||
// Notice: This API is EXPERIMENTAL and is planned to be replaced/changed in the
|
||||
// future. Be ready to refactor your code regarding imports and call mechanics,
|
||||
// in essence the operation will not change.
|
||||
func NewDataSlicer(ctx context.Context, cli *Client, signer neofscrypto.Signer, cnr cid.ID, owner user.ID) (*slicer.Slicer, error) {
|
||||
resNetInfo, err := cli.NetworkInfo(ctx, PrmNetworkInfo{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read current network info: %w", err)
|
||||
}
|
||||
|
||||
netInfo := resNetInfo.Info()
|
||||
|
||||
var opts slicer.Options
|
||||
opts.SetObjectPayloadLimit(netInfo.MaxObjectSize())
|
||||
opts.SetCurrentNeoFSEpoch(netInfo.CurrentEpoch())
|
||||
if !netInfo.HomomorphicHashingDisabled() {
|
||||
opts.CalculateHomomorphicChecksum()
|
||||
}
|
||||
|
||||
return slicer.New(signer, cnr, owner, &objectWriter{
|
||||
context: ctx,
|
||||
client: cli,
|
||||
}, opts), nil
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package client
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
@ -13,10 +12,9 @@ import (
|
|||
rpcapi "github.com/nspcc-dev/neofs-api-go/v2/rpc"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/rpc/client"
|
||||
v2session "github.com/nspcc-dev/neofs-api-go/v2/session"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/signature"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/bearer"
|
||||
apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status"
|
||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||
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/session"
|
||||
|
@ -26,7 +24,7 @@ import (
|
|||
type PrmObjectSearch struct {
|
||||
meta v2session.RequestMetaHeader
|
||||
|
||||
key *ecdsa.PrivateKey
|
||||
signer neofscrypto.Signer
|
||||
|
||||
cnrSet bool
|
||||
cnrID cid.ID
|
||||
|
@ -70,10 +68,10 @@ func (x *PrmObjectSearch) WithXHeaders(hs ...string) {
|
|||
writeXHeadersToMeta(hs, &x.meta)
|
||||
}
|
||||
|
||||
// UseKey specifies private key to sign the requests.
|
||||
// If key is not provided, then Client default key is used.
|
||||
func (x *PrmObjectSearch) UseKey(key ecdsa.PrivateKey) {
|
||||
x.key = &key
|
||||
// UseSigner specifies private signer to sign the requests.
|
||||
// If signer is not provided, then Client default signer is used.
|
||||
func (x *PrmObjectSearch) UseSigner(signer neofscrypto.Signer) {
|
||||
x.signer = signer
|
||||
}
|
||||
|
||||
// InContainer specifies the container in which to look for objects.
|
||||
|
@ -89,11 +87,6 @@ func (x *PrmObjectSearch) SetFilters(filters object.SearchFilters) {
|
|||
x.filters = filters
|
||||
}
|
||||
|
||||
// ResObjectSearch groups the final result values of ObjectSearch operation.
|
||||
type ResObjectSearch struct {
|
||||
statusRes
|
||||
}
|
||||
|
||||
// ObjectListReader is designed to read list of object identifiers from NeoFS system.
|
||||
//
|
||||
// Must be initialized using Client.ObjectSearch, any other usage is unsafe.
|
||||
|
@ -101,7 +94,6 @@ type ObjectListReader struct {
|
|||
client *Client
|
||||
cancelCtxStream context.CancelFunc
|
||||
err error
|
||||
res ResObjectSearch
|
||||
stream interface {
|
||||
Read(resp *v2object.SearchResponse) error
|
||||
}
|
||||
|
@ -133,8 +125,8 @@ func (x *ObjectListReader) Read(buf []oid.ID) (int, bool) {
|
|||
return read, false
|
||||
}
|
||||
|
||||
x.res.st, x.err = x.client.processResponse(&resp)
|
||||
if x.err != nil || !apistatus.IsSuccessful(x.res.st) {
|
||||
x.err = x.client.processResponse(&resp)
|
||||
if x.err != nil {
|
||||
return read, false
|
||||
}
|
||||
|
||||
|
@ -177,11 +169,7 @@ func (x *ObjectListReader) Iterate(f func(oid.ID) bool) error {
|
|||
// so false means nothing was read.
|
||||
_, ok := x.Read(buf)
|
||||
if !ok {
|
||||
res, err := x.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return apistatus.ErrFromStatus(res.Status())
|
||||
return x.Close()
|
||||
}
|
||||
if f(buf[0]) {
|
||||
return nil
|
||||
|
@ -192,24 +180,23 @@ func (x *ObjectListReader) Iterate(f func(oid.ID) bool) error {
|
|||
// Close ends reading list of the matched objects and returns the result of the operation
|
||||
// along with the final results. Must be called after using the ObjectListReader.
|
||||
//
|
||||
// Exactly one return value is non-nil. By default, server status is returned in res structure.
|
||||
// Any client's internal or transport errors are returned as Go built-in error.
|
||||
// If Client is tuned to resolve NeoFS API statuses, then NeoFS failures
|
||||
// codes are returned as error.
|
||||
//
|
||||
// Return statuses:
|
||||
// Return errors:
|
||||
// - global (see Client docs);
|
||||
// - *apistatus.ContainerNotFound;
|
||||
// - *apistatus.ObjectAccessDenied;
|
||||
// - *apistatus.SessionTokenExpired.
|
||||
func (x *ObjectListReader) Close() (*ResObjectSearch, error) {
|
||||
// - [apistatus.ErrContainerNotFound];
|
||||
// - [apistatus.ErrObjectAccessDenied];
|
||||
// - [apistatus.ErrSessionTokenExpired].
|
||||
func (x *ObjectListReader) Close() error {
|
||||
defer x.cancelCtxStream()
|
||||
|
||||
if x.err != nil && !errors.Is(x.err, io.EOF) {
|
||||
return nil, x.err
|
||||
return x.err
|
||||
}
|
||||
|
||||
return &x.res, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// ObjectSearchInit initiates object selection through a remote server using NeoFS API protocol.
|
||||
|
@ -218,15 +205,15 @@ func (x *ObjectListReader) Close() (*ResObjectSearch, error) {
|
|||
// is done using the ObjectListReader. Exactly one return value is non-nil.
|
||||
// Resulting reader must be finally closed.
|
||||
//
|
||||
// Immediately panics if parameters are set incorrectly (see PrmObjectSearch docs).
|
||||
// Context is required and must not be nil. It is used for network communication.
|
||||
//
|
||||
// Return errors:
|
||||
// - [ErrMissingContainer]
|
||||
func (c *Client) ObjectSearchInit(ctx context.Context, prm PrmObjectSearch) (*ObjectListReader, error) {
|
||||
// check parameters
|
||||
switch {
|
||||
case ctx == nil:
|
||||
panic(panicMsgMissingContext)
|
||||
case !prm.cnrSet:
|
||||
panic(panicMsgMissingContainer)
|
||||
return nil, ErrMissingContainer
|
||||
}
|
||||
|
||||
var cidV2 v2refs.ContainerID
|
||||
|
@ -242,12 +229,12 @@ func (c *Client) ObjectSearchInit(ctx context.Context, prm PrmObjectSearch) (*Ob
|
|||
req.SetBody(&body)
|
||||
c.prepareRequest(&req, &prm.meta)
|
||||
|
||||
key := prm.key
|
||||
if key == nil {
|
||||
key = &c.prm.key
|
||||
signer := prm.signer
|
||||
if signer == nil {
|
||||
signer = c.prm.signer
|
||||
}
|
||||
|
||||
err := signature.SignServiceMessage(key, &req)
|
||||
err := signServiceMessage(signer, &req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("sign request: %w", err)
|
||||
}
|
||||
|
|
|
@ -1,16 +1,14 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
v2object "github.com/nspcc-dev/neofs-api-go/v2/object"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
||||
signatureV2 "github.com/nspcc-dev/neofs-api-go/v2/signature"
|
||||
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
|
||||
"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/stretchr/testify/require"
|
||||
|
@ -86,7 +84,7 @@ func TestObjectIterate(t *testing.T) {
|
|||
p, resp := testListReaderResponse(t)
|
||||
|
||||
var actual []oid.ID
|
||||
resp.stream = &singleStreamResponder{key: p, idList: [][]oid.ID{ids}}
|
||||
resp.stream = &singleStreamResponder{signer: p, idList: [][]oid.ID{ids}}
|
||||
require.NoError(t, resp.Iterate(func(id oid.ID) bool {
|
||||
actual = append(actual, id)
|
||||
return len(actual) == 2
|
||||
|
@ -109,27 +107,24 @@ func TestObjectIterate(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func testListReaderResponse(t *testing.T) (*ecdsa.PrivateKey, *ObjectListReader) {
|
||||
p, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
return &p.PrivateKey, &ObjectListReader{
|
||||
func testListReaderResponse(t *testing.T) (neofscrypto.Signer, *ObjectListReader) {
|
||||
return test.RandomSigner(t), &ObjectListReader{
|
||||
cancelCtxStream: func() {},
|
||||
client: &Client{},
|
||||
tail: nil,
|
||||
}
|
||||
}
|
||||
|
||||
func newSearchStream(key *ecdsa.PrivateKey, endError error, idList ...[]oid.ID) *singleStreamResponder {
|
||||
func newSearchStream(signer neofscrypto.Signer, endError error, idList ...[]oid.ID) *singleStreamResponder {
|
||||
return &singleStreamResponder{
|
||||
key: key,
|
||||
signer: signer,
|
||||
endError: endError,
|
||||
idList: idList,
|
||||
}
|
||||
}
|
||||
|
||||
type singleStreamResponder struct {
|
||||
key *ecdsa.PrivateKey
|
||||
signer neofscrypto.Signer
|
||||
n int
|
||||
endError error
|
||||
idList [][]oid.ID
|
||||
|
@ -140,7 +135,7 @@ func (s *singleStreamResponder) Read(resp *v2object.SearchResponse) error {
|
|||
if s.endError != nil {
|
||||
return s.endError
|
||||
}
|
||||
panic("unexpected call to `Read`")
|
||||
return ErrUnexpectedReadCall
|
||||
}
|
||||
|
||||
var body v2object.SearchResponseBody
|
||||
|
@ -154,9 +149,9 @@ func (s *singleStreamResponder) Read(resp *v2object.SearchResponse) error {
|
|||
}
|
||||
resp.SetBody(&body)
|
||||
|
||||
err := signatureV2.SignServiceMessage(s.key, resp)
|
||||
err := signServiceMessage(s.signer, resp)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("error: %w", err))
|
||||
return err
|
||||
}
|
||||
|
||||
s.n++
|
||||
|
|
|
@ -32,33 +32,23 @@ func (x *PrmAnnounceLocalTrust) SetValues(trusts []reputation.Trust) {
|
|||
x.trusts = trusts
|
||||
}
|
||||
|
||||
// ResAnnounceLocalTrust groups results of AnnounceLocalTrust operation.
|
||||
type ResAnnounceLocalTrust struct {
|
||||
statusRes
|
||||
}
|
||||
|
||||
// AnnounceLocalTrust sends client's trust values to the NeoFS network participants.
|
||||
//
|
||||
// Exactly one return value is non-nil. By default, server status is returned in res structure.
|
||||
// Any client's internal or transport errors are returned as `error`.
|
||||
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful
|
||||
// NeoFS status codes are returned as `error`, otherwise, are included
|
||||
// in the returned result structure.
|
||||
// Any errors (local or remote, including returned status codes) are returned as Go errors,
|
||||
// see [apistatus] package for NeoFS-specific error types.
|
||||
//
|
||||
// Immediately panics if parameters are set incorrectly (see PrmAnnounceLocalTrust docs).
|
||||
// Context is required and must not be nil. It is used for network communication.
|
||||
//
|
||||
// Return statuses:
|
||||
// - global (see Client docs).
|
||||
func (c *Client) AnnounceLocalTrust(ctx context.Context, prm PrmAnnounceLocalTrust) (*ResAnnounceLocalTrust, error) {
|
||||
// Return errors:
|
||||
// - [ErrZeroEpoch]
|
||||
// - [ErrMissingTrusts]
|
||||
func (c *Client) AnnounceLocalTrust(ctx context.Context, prm PrmAnnounceLocalTrust) error {
|
||||
// check parameters
|
||||
switch {
|
||||
case ctx == nil:
|
||||
panic(panicMsgMissingContext)
|
||||
case prm.epoch == 0:
|
||||
panic("zero epoch")
|
||||
return ErrZeroEpoch
|
||||
case len(prm.trusts) == 0:
|
||||
panic("missing trusts")
|
||||
return ErrMissingTrusts
|
||||
}
|
||||
|
||||
// form request body
|
||||
|
@ -82,23 +72,21 @@ func (c *Client) AnnounceLocalTrust(ctx context.Context, prm PrmAnnounceLocalTru
|
|||
|
||||
var (
|
||||
cc contextCall
|
||||
res ResAnnounceLocalTrust
|
||||
)
|
||||
|
||||
c.initCallContext(&cc)
|
||||
cc.meta = prm.prmCommonMeta
|
||||
cc.req = &req
|
||||
cc.statusRes = &res
|
||||
cc.call = func() (responseV2, error) {
|
||||
return rpcapi.AnnounceLocalTrust(&c.c, &req, client.WithContext(ctx))
|
||||
}
|
||||
|
||||
// process call
|
||||
if !cc.processCall() {
|
||||
return nil, cc.err
|
||||
return cc.err
|
||||
}
|
||||
|
||||
return &res, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// PrmAnnounceIntermediateTrust groups parameters of AnnounceIntermediateTrust operation.
|
||||
|
@ -132,34 +120,24 @@ func (x *PrmAnnounceIntermediateTrust) SetCurrentValue(trust reputation.PeerToPe
|
|||
x.trustSet = true
|
||||
}
|
||||
|
||||
// ResAnnounceIntermediateTrust groups results of AnnounceIntermediateTrust operation.
|
||||
type ResAnnounceIntermediateTrust struct {
|
||||
statusRes
|
||||
}
|
||||
|
||||
// AnnounceIntermediateTrust sends global trust values calculated for the specified NeoFS network participants
|
||||
// at some stage of client's calculation algorithm.
|
||||
//
|
||||
// Exactly one return value is non-nil. By default, server status is returned in res structure.
|
||||
// Any client's internal or transport errors are returned as `error`.
|
||||
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful
|
||||
// NeoFS status codes are returned as `error`, otherwise, are included
|
||||
// in the returned result structure.
|
||||
// Any errors (local or remote, including returned status codes) are returned as Go errors,
|
||||
// see [apistatus] package for NeoFS-specific error types.
|
||||
//
|
||||
// Immediately panics if parameters are set incorrectly (see PrmAnnounceIntermediateTrust docs).
|
||||
// Context is required and must not be nil. It is used for network communication.
|
||||
//
|
||||
// Return statuses:
|
||||
// - global (see Client docs).
|
||||
func (c *Client) AnnounceIntermediateTrust(ctx context.Context, prm PrmAnnounceIntermediateTrust) (*ResAnnounceIntermediateTrust, error) {
|
||||
// Return errors:
|
||||
// - [ErrZeroEpoch]
|
||||
// - [ErrMissingTrust]
|
||||
func (c *Client) AnnounceIntermediateTrust(ctx context.Context, prm PrmAnnounceIntermediateTrust) error {
|
||||
// check parameters
|
||||
switch {
|
||||
case ctx == nil:
|
||||
panic(panicMsgMissingContext)
|
||||
case prm.epoch == 0:
|
||||
panic("zero epoch")
|
||||
return ErrZeroEpoch
|
||||
case !prm.trustSet:
|
||||
panic("current trust value not set")
|
||||
return ErrMissingTrust
|
||||
}
|
||||
|
||||
var trust v2reputation.PeerToPeerTrust
|
||||
|
@ -180,21 +158,19 @@ func (c *Client) AnnounceIntermediateTrust(ctx context.Context, prm PrmAnnounceI
|
|||
|
||||
var (
|
||||
cc contextCall
|
||||
res ResAnnounceIntermediateTrust
|
||||
)
|
||||
|
||||
c.initCallContext(&cc)
|
||||
cc.meta = prm.prmCommonMeta
|
||||
cc.req = &req
|
||||
cc.statusRes = &res
|
||||
cc.call = func() (responseV2, error) {
|
||||
return rpcapi.AnnounceIntermediateResult(&c.c, &req, client.WithContext(ctx))
|
||||
}
|
||||
|
||||
// process call
|
||||
if !cc.processCall() {
|
||||
return nil, cc.err
|
||||
return cc.err
|
||||
}
|
||||
|
||||
return &res, nil
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -2,12 +2,12 @@ package client
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
||||
rpcapi "github.com/nspcc-dev/neofs-api-go/v2/rpc"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/rpc/client"
|
||||
v2session "github.com/nspcc-dev/neofs-api-go/v2/session"
|
||||
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/user"
|
||||
)
|
||||
|
||||
|
@ -17,8 +17,7 @@ type PrmSessionCreate struct {
|
|||
|
||||
exp uint64
|
||||
|
||||
keySet bool
|
||||
key ecdsa.PrivateKey
|
||||
signer neofscrypto.Signer
|
||||
}
|
||||
|
||||
// SetExp sets number of the last NepFS epoch in the lifetime of the session after which it will be expired.
|
||||
|
@ -26,17 +25,14 @@ func (x *PrmSessionCreate) SetExp(exp uint64) {
|
|||
x.exp = exp
|
||||
}
|
||||
|
||||
// UseKey specifies private key to sign the requests and compute token owner.
|
||||
// If key is not provided, then Client default key is used.
|
||||
func (x *PrmSessionCreate) UseKey(key ecdsa.PrivateKey) {
|
||||
x.keySet = true
|
||||
x.key = key
|
||||
// UseSigner specifies private signer to sign the requests and compute token owner.
|
||||
// If signer is not provided, then Client default signer is used.
|
||||
func (x *PrmSessionCreate) UseSigner(signer neofscrypto.Signer) {
|
||||
x.signer = signer
|
||||
}
|
||||
|
||||
// ResSessionCreate groups resulting values of SessionCreate operation.
|
||||
type ResSessionCreate struct {
|
||||
statusRes
|
||||
|
||||
id []byte
|
||||
|
||||
sessionKey []byte
|
||||
|
@ -66,29 +62,27 @@ func (x ResSessionCreate) PublicKey() []byte {
|
|||
// The session lifetime coincides with the server lifetime. Results can be written
|
||||
// to session token which can be later attached to the requests.
|
||||
//
|
||||
// Exactly one return value is non-nil. By default, server status is returned in res structure.
|
||||
// Any client's internal or transport errors are returned as `error`.
|
||||
// If PrmInit.ResolveNeoFSFailures has been called, unsuccessful
|
||||
// NeoFS status codes are returned as `error`, otherwise, are included
|
||||
// in the returned result structure.
|
||||
// Any errors (local or remote, including returned status codes) are returned as Go errors,
|
||||
// see [apistatus] package for NeoFS-specific error types.
|
||||
//
|
||||
// Immediately panics if parameters are set incorrectly (see PrmSessionCreate docs).
|
||||
// Context is required and must not be nil. It is used for network communication.
|
||||
//
|
||||
// Return statuses:
|
||||
// - global (see Client docs).
|
||||
// Return errors:
|
||||
// - [ErrMissingSigner]
|
||||
func (c *Client) SessionCreate(ctx context.Context, prm PrmSessionCreate) (*ResSessionCreate, error) {
|
||||
// check context
|
||||
if ctx == nil {
|
||||
panic(panicMsgMissingContext)
|
||||
signer := prm.signer
|
||||
if signer == nil {
|
||||
signer = c.prm.signer
|
||||
}
|
||||
|
||||
ownerKey := c.prm.key.PublicKey
|
||||
if prm.keySet {
|
||||
ownerKey = prm.key.PublicKey
|
||||
if signer == nil {
|
||||
return nil, ErrMissingSigner
|
||||
}
|
||||
|
||||
var ownerID user.ID
|
||||
user.IDFromKey(&ownerID, ownerKey)
|
||||
if err := user.IDFromSigner(&ownerID, signer); err != nil {
|
||||
return nil, fmt.Errorf("IDFromSigner: %w", err)
|
||||
}
|
||||
|
||||
var ownerIDV2 refs.OwnerID
|
||||
ownerID.WriteToV2(&ownerIDV2)
|
||||
|
@ -111,21 +105,27 @@ func (c *Client) SessionCreate(ctx context.Context, prm PrmSessionCreate) (*ResS
|
|||
)
|
||||
|
||||
c.initCallContext(&cc)
|
||||
if prm.keySet {
|
||||
cc.key = prm.key
|
||||
}
|
||||
|
||||
cc.signer = signer
|
||||
cc.meta = prm.prmCommonMeta
|
||||
cc.req = &req
|
||||
cc.statusRes = &res
|
||||
cc.call = func() (responseV2, error) {
|
||||
return rpcapi.CreateSession(&c.c, &req, client.WithContext(ctx))
|
||||
return c.server.createSession(&c.c, &req, client.WithContext(ctx))
|
||||
}
|
||||
cc.result = func(r responseV2) {
|
||||
resp := r.(*v2session.CreateResponse)
|
||||
|
||||
body := resp.GetBody()
|
||||
|
||||
if len(body.GetID()) == 0 {
|
||||
cc.err = newErrMissingResponseField("session id")
|
||||
return
|
||||
}
|
||||
|
||||
if len(body.GetSessionKey()) == 0 {
|
||||
cc.err = newErrMissingResponseField("session key")
|
||||
return
|
||||
}
|
||||
|
||||
res.setID(body.GetID())
|
||||
res.setSessionKey(body.GetSessionKey())
|
||||
}
|
||||
|
|
69
client/session_test.go
Normal file
69
client/session_test.go
Normal file
|
@ -0,0 +1,69 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
v2netmap "github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/rpc/client"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/session"
|
||||
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/crypto/test"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type sessionAPIServer struct {
|
||||
signer neofscrypto.Signer
|
||||
setBody func(body *session.CreateResponseBody)
|
||||
}
|
||||
|
||||
func (m sessionAPIServer) netMapSnapshot(context.Context, v2netmap.SnapshotRequest) (*v2netmap.SnapshotResponse, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m sessionAPIServer) createSession(*client.Client, *session.CreateRequest, ...client.CallOption) (*session.CreateResponse, error) {
|
||||
var body session.CreateResponseBody
|
||||
m.setBody(&body)
|
||||
|
||||
var resp session.CreateResponse
|
||||
resp.SetBody(&body)
|
||||
|
||||
if err := signServiceMessage(m.signer, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
func TestClient_SessionCreate(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
signer := test.RandomSignerRFC6979(t)
|
||||
|
||||
c := newClient(t, signer, nil)
|
||||
|
||||
var prmSessionCreate PrmSessionCreate
|
||||
prmSessionCreate.UseSigner(signer)
|
||||
prmSessionCreate.SetExp(1)
|
||||
|
||||
t.Run("missing session id", func(t *testing.T) {
|
||||
c.setNeoFSAPIServer(&sessionAPIServer{signer: signer, setBody: func(body *session.CreateResponseBody) {
|
||||
body.SetSessionKey([]byte{1})
|
||||
}})
|
||||
|
||||
result, err := c.SessionCreate(ctx, prmSessionCreate)
|
||||
require.Nil(t, result)
|
||||
require.ErrorIs(t, err, ErrMissingResponseField)
|
||||
require.Equal(t, "missing session id field in the response", err.Error())
|
||||
})
|
||||
|
||||
t.Run("missing session key", func(t *testing.T) {
|
||||
c.setNeoFSAPIServer(&sessionAPIServer{signer: signer, setBody: func(body *session.CreateResponseBody) {
|
||||
body.SetID([]byte{1})
|
||||
}})
|
||||
|
||||
result, err := c.SessionCreate(ctx, prmSessionCreate)
|
||||
require.Nil(t, result)
|
||||
require.ErrorIs(t, err, ErrMissingResponseField)
|
||||
require.Equal(t, "missing session key field in the response", err.Error())
|
||||
})
|
||||
}
|
396
client/sign.go
Normal file
396
client/sign.go
Normal file
|
@ -0,0 +1,396 @@
|
|||
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 interface{}) 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); err != nil {
|
||||
return NewSignError(fmt.Errorf("body: %w", err))
|
||||
}
|
||||
}
|
||||
|
||||
// sign meta header
|
||||
if err := signServiceMessagePart(signer, meta, verifyHdr.SetMetaSignature); err != nil {
|
||||
return NewSignError(fmt.Errorf("meta header: %w", err))
|
||||
}
|
||||
|
||||
// sign verification header origin
|
||||
if err := signServiceMessagePart(signer, verifyOrigin, verifyHdr.SetOriginSignature); 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)) error {
|
||||
var sig neofscrypto.Signature
|
||||
var sigv2 refs.Signature
|
||||
|
||||
m := &stableMarshalerWrapper{part}
|
||||
data, err := m.ReadSignedData(nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ReadSignedData %w", err)
|
||||
}
|
||||
|
||||
if err = sig.Calculate(signer, data); err != nil {
|
||||
return fmt.Errorf("calculate %w", err)
|
||||
}
|
||||
|
||||
sig.WriteToV2(&sigv2)
|
||||
sigWrite(&sigv2)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func verifyServiceMessage(msg interface{}) 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()
|
||||
}
|
||||
}
|
247
client/sign_test.go
Normal file
247
client/sign_test.go
Normal file
|
@ -0,0 +1,247 @@
|
|||
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"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/crypto/test"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/user"
|
||||
"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))
|
||||
|
||||
// verification must pass
|
||||
require.NoError(t, verifyServiceMessage(req))
|
||||
|
||||
meta.SetOrigin(req.GetMetaHeader())
|
||||
req.SetMetaHeader(meta)
|
||||
|
||||
// sign request
|
||||
require.NoError(t, signServiceMessage(signer, req))
|
||||
|
||||
// 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))
|
||||
|
||||
// verification must pass
|
||||
require.NoError(t, verifyServiceMessage(resp))
|
||||
|
||||
meta.SetOrigin(resp.GetMetaHeader())
|
||||
resp.SetMetaHeader(meta)
|
||||
|
||||
// sign request
|
||||
require.NoError(t, signServiceMessage(signer, resp))
|
||||
|
||||
// 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 := test.RandomSignerRFC6979(t)
|
||||
|
||||
require.NoError(t, verifyServiceMessage(nil))
|
||||
require.NoError(t, signServiceMessage(signer, nil))
|
||||
}
|
||||
|
||||
func TestBalanceRequest(t *testing.T) {
|
||||
signer := test.RandomSignerRFC6979(t)
|
||||
|
||||
var id user.ID
|
||||
require.NoError(t, user.IDFromSigner(&id, signer))
|
||||
|
||||
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 := test.RandomSignerRFC6979(t)
|
||||
|
||||
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 := test.RandomSignerRFC6979(t)
|
||||
|
||||
var id user.ID
|
||||
require.NoError(t, user.IDFromSigner(&id, signer))
|
||||
|
||||
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 := test.RandomSignerRFC6979(t)
|
||||
|
||||
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)
|
||||
}
|
|
@ -2,12 +2,32 @@ package apistatus
|
|||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/status"
|
||||
)
|
||||
|
||||
// Error describes common error which is a grouping type for any [apistatus] errors. Any [apistatus] error may be checked
|
||||
// explicitly via it's type of just check the group via errors.Is(err, [apistatus.Error]).
|
||||
var Error = errors.New("api error")
|
||||
|
||||
var (
|
||||
// ErrServerInternal is an instance of ServerInternal error status. It's expected to be used for [errors.Is]
|
||||
// and MUST NOT be changed.
|
||||
ErrServerInternal ServerInternal
|
||||
// ErrWrongMagicNumber is an instance of WrongMagicNumber error status. It's expected to be used for [errors.Is]
|
||||
// and MUST NOT be changed.
|
||||
ErrWrongMagicNumber WrongMagicNumber
|
||||
// ErrSignatureVerification is an instance of SignatureVerification error status. It's expected to be used for [errors.Is]
|
||||
// and MUST NOT be changed.
|
||||
ErrSignatureVerification SignatureVerification
|
||||
// ErrNodeUnderMaintenance is an instance of NodeUnderMaintenance error status. It's expected to be used for [errors.Is]
|
||||
// and MUST NOT be changed.
|
||||
ErrNodeUnderMaintenance NodeUnderMaintenance
|
||||
)
|
||||
|
||||
// ServerInternal describes failure statuses related to internal server errors.
|
||||
// Instances provide Status and StatusV2 interfaces.
|
||||
// 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 {
|
||||
|
@ -21,18 +41,28 @@ func (x ServerInternal) Error() string {
|
|||
)
|
||||
}
|
||||
|
||||
// implements local interface defined in FromStatusV2 func.
|
||||
// Is implements interface for correct checking current error type with [errors.Is].
|
||||
func (x ServerInternal) Is(target error) bool {
|
||||
switch target.(type) {
|
||||
default:
|
||||
return errors.Is(Error, target)
|
||||
case ServerInternal, *ServerInternal:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// implements local interface defined in [ErrorFromV2] func.
|
||||
func (x *ServerInternal) fromStatusV2(st *status.Status) {
|
||||
x.v2 = *st
|
||||
}
|
||||
|
||||
// ToStatusV2 implements StatusV2 interface method.
|
||||
// If the value was returned by FromStatusV2, returns the source message.
|
||||
// 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) ToStatusV2() *status.Status {
|
||||
func (x ServerInternal) ErrorToV2() *status.Status {
|
||||
x.v2.SetCode(globalizeCodeV2(status.Internal, status.GlobalizeCommonFail))
|
||||
return &x.v2
|
||||
}
|
||||
|
@ -57,7 +87,7 @@ func WriteInternalServerErr(x *ServerInternal, err error) {
|
|||
}
|
||||
|
||||
// WrongMagicNumber describes failure status related to incorrect network magic.
|
||||
// Instances provide Status and StatusV2 interfaces.
|
||||
// Instances provide [StatusV2] and error interfaces.
|
||||
type WrongMagicNumber struct {
|
||||
v2 status.Status
|
||||
}
|
||||
|
@ -69,18 +99,28 @@ func (x WrongMagicNumber) Error() string {
|
|||
)
|
||||
}
|
||||
|
||||
// implements local interface defined in FromStatusV2 func.
|
||||
// Is implements interface for correct checking current error type with [errors.Is].
|
||||
func (x WrongMagicNumber) Is(target error) bool {
|
||||
switch target.(type) {
|
||||
default:
|
||||
return errors.Is(Error, target)
|
||||
case WrongMagicNumber, *WrongMagicNumber:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// implements local interface defined in [ErrorFromV2] func.
|
||||
func (x *WrongMagicNumber) fromStatusV2(st *status.Status) {
|
||||
x.v2 = *st
|
||||
}
|
||||
|
||||
// ToStatusV2 implements StatusV2 interface method.
|
||||
// If the value was returned by FromStatusV2, returns the source message.
|
||||
// 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) ToStatusV2() *status.Status {
|
||||
func (x WrongMagicNumber) ErrorToV2() *status.Status {
|
||||
x.v2.SetCode(globalizeCodeV2(status.WrongMagicNumber, status.GlobalizeCommonFail))
|
||||
return &x.v2
|
||||
}
|
||||
|
@ -125,35 +165,52 @@ func (x WrongMagicNumber) CorrectMagic() (magic uint64, ok int8) {
|
|||
}
|
||||
|
||||
// SignatureVerification describes failure status related to signature verification.
|
||||
// Instances provide Status and StatusV2 interfaces.
|
||||
// Instances provide [StatusV2] and error interfaces.
|
||||
type SignatureVerification struct {
|
||||
v2 status.Status
|
||||
}
|
||||
|
||||
const defaultSignatureVerificationMsg = "signature verification failed"
|
||||
|
||||
func (x SignatureVerification) Error() string {
|
||||
msg := x.v2.Message()
|
||||
if msg == "" {
|
||||
msg = defaultSignatureVerificationMsg
|
||||
}
|
||||
|
||||
return errMessageStatusV2(
|
||||
globalizeCodeV2(status.SignatureVerificationFail, status.GlobalizeCommonFail),
|
||||
x.v2.Message(),
|
||||
msg,
|
||||
)
|
||||
}
|
||||
|
||||
// implements local interface defined in FromStatusV2 func.
|
||||
// Is implements interface for correct checking current error type with [errors.Is].
|
||||
func (x SignatureVerification) Is(target error) bool {
|
||||
switch target.(type) {
|
||||
default:
|
||||
return errors.Is(Error, target)
|
||||
case SignatureVerification, *SignatureVerification:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// implements local interface defined in [ErrorFromV2] func.
|
||||
func (x *SignatureVerification) fromStatusV2(st *status.Status) {
|
||||
x.v2 = *st
|
||||
}
|
||||
|
||||
// ToStatusV2 implements StatusV2 interface method.
|
||||
// If the value was returned by FromStatusV2, returns the source message.
|
||||
// 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 SetMessage or
|
||||
// - string message: written message via [SignatureVerification.SetMessage] or
|
||||
// "signature verification failed" as a default message;
|
||||
// - details: empty.
|
||||
func (x SignatureVerification) ToStatusV2() *status.Status {
|
||||
func (x SignatureVerification) ErrorToV2() *status.Status {
|
||||
x.v2.SetCode(globalizeCodeV2(status.SignatureVerificationFail, status.GlobalizeCommonFail))
|
||||
|
||||
if x.v2.Message() == "" {
|
||||
x.v2.SetMessage("signature verification failed")
|
||||
x.v2.SetMessage(defaultSignatureVerificationMsg)
|
||||
}
|
||||
|
||||
return &x.v2
|
||||
|
@ -176,16 +233,18 @@ func (x SignatureVerification) Message() string {
|
|||
}
|
||||
|
||||
// NodeUnderMaintenance describes failure status for nodes being under maintenance.
|
||||
// Instances provide Status and StatusV2 interfaces.
|
||||
// Instances provide [StatusV2] and error interfaces.
|
||||
type NodeUnderMaintenance struct {
|
||||
v2 status.Status
|
||||
}
|
||||
|
||||
const defaultNodeUnderMaintenanceMsg = "node is under maintenance"
|
||||
|
||||
// Error implements the error interface.
|
||||
func (x NodeUnderMaintenance) Error() string {
|
||||
msg := x.Message()
|
||||
if msg == "" {
|
||||
msg = "node is under maintenance"
|
||||
msg = defaultNodeUnderMaintenanceMsg
|
||||
}
|
||||
|
||||
return errMessageStatusV2(
|
||||
|
@ -194,18 +253,33 @@ func (x NodeUnderMaintenance) Error() string {
|
|||
)
|
||||
}
|
||||
|
||||
// Is implements interface for correct checking current error type with [errors.Is].
|
||||
func (x NodeUnderMaintenance) Is(target error) bool {
|
||||
switch target.(type) {
|
||||
default:
|
||||
return errors.Is(Error, target)
|
||||
case NodeUnderMaintenance, *NodeUnderMaintenance:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func (x *NodeUnderMaintenance) fromStatusV2(st *status.Status) {
|
||||
x.v2 = *st
|
||||
}
|
||||
|
||||
// ToStatusV2 implements StatusV2 interface method.
|
||||
// If the value was returned by FromStatusV2, returns the source message.
|
||||
// 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 SetMessage;
|
||||
// - string message: written message via [NodeUnderMaintenance.SetMessage] or
|
||||
// "node is under maintenance" as a default message;
|
||||
// - details: empty.
|
||||
func (x NodeUnderMaintenance) ToStatusV2() *status.Status {
|
||||
func (x NodeUnderMaintenance) ErrorToV2() *status.Status {
|
||||
x.v2.SetCode(globalizeCodeV2(status.NodeUnderMaintenance, status.GlobalizeCommonFail))
|
||||
if x.v2.Message() == "" {
|
||||
x.v2.SetMessage(defaultNodeUnderMaintenanceMsg)
|
||||
}
|
||||
|
||||
return &x.v2
|
||||
}
|
||||
|
||||
|
|
|
@ -14,14 +14,14 @@ func TestServerInternal_Message(t *testing.T) {
|
|||
var st apistatus.ServerInternal
|
||||
|
||||
res := st.Message()
|
||||
resv2 := apistatus.ToStatusV2(st).Message()
|
||||
resv2 := apistatus.ErrorToV2(st).Message()
|
||||
require.Empty(t, res)
|
||||
require.Empty(t, resv2)
|
||||
|
||||
st.SetMessage(msg)
|
||||
|
||||
res = st.Message()
|
||||
resv2 = apistatus.ToStatusV2(st).Message()
|
||||
resv2 = apistatus.ErrorToV2(st).Message()
|
||||
require.Equal(t, msg, res)
|
||||
require.Equal(t, msg, resv2)
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ func TestWrongMagicNumber_CorrectMagic(t *testing.T) {
|
|||
require.EqualValues(t, 1, ok)
|
||||
|
||||
// corrupt the value
|
||||
apistatus.ToStatusV2(st).IterateDetails(func(d *status.Detail) bool {
|
||||
apistatus.ErrorToV2(st).IterateDetails(func(d *status.Detail) bool {
|
||||
d.SetValue([]byte{1, 2, 3}) // any slice with len != 8
|
||||
return true
|
||||
})
|
||||
|
@ -64,7 +64,7 @@ func TestSignatureVerification(t *testing.T) {
|
|||
|
||||
st.SetMessage(msg)
|
||||
|
||||
stV2 := st.ToStatusV2()
|
||||
stV2 := st.ErrorToV2()
|
||||
|
||||
require.Equal(t, msg, st.Message())
|
||||
require.Equal(t, msg, stV2.Message())
|
||||
|
@ -73,7 +73,7 @@ func TestSignatureVerification(t *testing.T) {
|
|||
t.Run("empty to V2", func(t *testing.T) {
|
||||
var st apistatus.SignatureVerification
|
||||
|
||||
stV2 := st.ToStatusV2()
|
||||
stV2 := st.ErrorToV2()
|
||||
|
||||
require.Equal(t, "signature verification failed", stV2.Message())
|
||||
})
|
||||
|
@ -84,7 +84,7 @@ func TestSignatureVerification(t *testing.T) {
|
|||
|
||||
st.SetMessage(msg)
|
||||
|
||||
stV2 := st.ToStatusV2()
|
||||
stV2 := st.ErrorToV2()
|
||||
|
||||
require.Equal(t, msg, stV2.Message())
|
||||
})
|
||||
|
@ -103,7 +103,7 @@ func TestNodeUnderMaintenance(t *testing.T) {
|
|||
|
||||
st.SetMessage(msg)
|
||||
|
||||
stV2 := st.ToStatusV2()
|
||||
stV2 := st.ErrorToV2()
|
||||
|
||||
require.Equal(t, msg, st.Message())
|
||||
require.Equal(t, msg, stV2.Message())
|
||||
|
@ -112,7 +112,7 @@ func TestNodeUnderMaintenance(t *testing.T) {
|
|||
t.Run("empty to V2", func(t *testing.T) {
|
||||
var st apistatus.NodeUnderMaintenance
|
||||
|
||||
stV2 := st.ToStatusV2()
|
||||
stV2 := st.ErrorToV2()
|
||||
|
||||
require.Empty(t, "", stV2.Message())
|
||||
})
|
||||
|
@ -123,7 +123,7 @@ func TestNodeUnderMaintenance(t *testing.T) {
|
|||
|
||||
st.SetMessage(msg)
|
||||
|
||||
stV2 := st.ToStatusV2()
|
||||
stV2 := st.ErrorToV2()
|
||||
|
||||
require.Equal(t, msg, stV2.Message())
|
||||
})
|
||||
|
|
|
@ -1,67 +1,112 @@
|
|||
package apistatus
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/container"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/status"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrEACLNotFound is an instance of EACLNotFound error status. It's expected to be used for [errors.Is]
|
||||
// and MUST NOT be changed.
|
||||
ErrEACLNotFound EACLNotFound
|
||||
// ErrContainerNotFound is an instance of ContainerNotFound error status. It's expected to be used for [errors.Is]
|
||||
// and MUST NOT be changed.
|
||||
ErrContainerNotFound ContainerNotFound
|
||||
)
|
||||
|
||||
// ContainerNotFound describes status of the failure because of the missing container.
|
||||
// Instances provide Status and StatusV2 interfaces.
|
||||
// Instances provide [StatusV2] and error interfaces.
|
||||
type ContainerNotFound struct {
|
||||
v2 status.Status
|
||||
}
|
||||
|
||||
const defaultContainerNotFoundMsg = "container not found"
|
||||
|
||||
func (x ContainerNotFound) Error() string {
|
||||
msg := x.v2.Message()
|
||||
if msg == "" {
|
||||
msg = defaultContainerNotFoundMsg
|
||||
}
|
||||
|
||||
return errMessageStatusV2(
|
||||
globalizeCodeV2(container.StatusNotFound, container.GlobalizeFail),
|
||||
x.v2.Message(),
|
||||
msg,
|
||||
)
|
||||
}
|
||||
|
||||
// implements local interface defined in FromStatusV2 func.
|
||||
// Is implements interface for correct checking current error type with [errors.Is].
|
||||
func (x ContainerNotFound) Is(target error) bool {
|
||||
switch target.(type) {
|
||||
default:
|
||||
return errors.Is(Error, target)
|
||||
case ContainerNotFound, *ContainerNotFound:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// implements local interface defined in [ErrorFromV2] func.
|
||||
func (x *ContainerNotFound) fromStatusV2(st *status.Status) {
|
||||
x.v2 = *st
|
||||
}
|
||||
|
||||
// ToStatusV2 implements StatusV2 interface method.
|
||||
// If the value was returned by FromStatusV2, returns the source message.
|
||||
// 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) ToStatusV2() *status.Status {
|
||||
func (x ContainerNotFound) ErrorToV2() *status.Status {
|
||||
x.v2.SetCode(globalizeCodeV2(container.StatusNotFound, container.GlobalizeFail))
|
||||
x.v2.SetMessage("container not found")
|
||||
x.v2.SetMessage(defaultContainerNotFoundMsg)
|
||||
return &x.v2
|
||||
}
|
||||
|
||||
// EACLNotFound describes status of the failure because of the missing eACL
|
||||
// table.
|
||||
// Instances provide Status and StatusV2 interfaces.
|
||||
// Instances provide [StatusV2] and error interfaces.
|
||||
type EACLNotFound struct {
|
||||
v2 status.Status
|
||||
}
|
||||
|
||||
const defaultEACLNotFoundMsg = "eACL not found"
|
||||
|
||||
func (x EACLNotFound) Error() string {
|
||||
msg := x.v2.Message()
|
||||
if msg == "" {
|
||||
msg = defaultEACLNotFoundMsg
|
||||
}
|
||||
|
||||
return errMessageStatusV2(
|
||||
globalizeCodeV2(container.StatusEACLNotFound, container.GlobalizeFail),
|
||||
x.v2.Message(),
|
||||
msg,
|
||||
)
|
||||
}
|
||||
|
||||
// implements local interface defined in FromStatusV2 func.
|
||||
// Is implements interface for correct checking current error type with [errors.Is].
|
||||
func (x EACLNotFound) Is(target error) bool {
|
||||
switch target.(type) {
|
||||
default:
|
||||
return errors.Is(Error, target)
|
||||
case EACLNotFound, *EACLNotFound:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// implements local interface defined in [ErrorFromV2] func.
|
||||
func (x *EACLNotFound) fromStatusV2(st *status.Status) {
|
||||
x.v2 = *st
|
||||
}
|
||||
|
||||
// ToStatusV2 implements StatusV2 interface method.
|
||||
// If the value was returned by FromStatusV2, returns the source message.
|
||||
// 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) ToStatusV2() *status.Status {
|
||||
func (x EACLNotFound) ErrorToV2() *status.Status {
|
||||
x.v2.SetCode(globalizeCodeV2(container.StatusEACLNotFound, container.GlobalizeFail))
|
||||
x.v2.SetMessage("eACL not found")
|
||||
x.v2.SetMessage(defaultEACLNotFoundMsg)
|
||||
return &x.v2
|
||||
}
|
||||
|
|
93
client/status/errors_test.go
Normal file
93
client/status/errors_test.go
Normal file
|
@ -0,0 +1,93 @@
|
|||
package apistatus
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestErrors(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
errs []error
|
||||
errVariable error
|
||||
}{
|
||||
{
|
||||
errs: []error{ServerInternal{}, new(ServerInternal)},
|
||||
errVariable: ErrServerInternal,
|
||||
},
|
||||
{
|
||||
errs: []error{WrongMagicNumber{}, new(WrongMagicNumber)},
|
||||
errVariable: ErrWrongMagicNumber,
|
||||
},
|
||||
{
|
||||
errs: []error{SignatureVerification{}, new(SignatureVerification)},
|
||||
errVariable: ErrSignatureVerification,
|
||||
},
|
||||
{
|
||||
errs: []error{NodeUnderMaintenance{}, new(NodeUnderMaintenance)},
|
||||
errVariable: ErrNodeUnderMaintenance,
|
||||
},
|
||||
|
||||
{
|
||||
errs: []error{ObjectLocked{}, new(ObjectLocked)},
|
||||
errVariable: ErrObjectLocked,
|
||||
},
|
||||
{
|
||||
errs: []error{LockNonRegularObject{}, new(LockNonRegularObject)},
|
||||
errVariable: ErrLockNonRegularObject,
|
||||
},
|
||||
{
|
||||
errs: []error{ObjectAccessDenied{}, new(ObjectAccessDenied)},
|
||||
errVariable: ErrObjectAccessDenied,
|
||||
},
|
||||
{
|
||||
errs: []error{ObjectNotFound{}, new(ObjectNotFound)},
|
||||
errVariable: ErrObjectNotFound,
|
||||
},
|
||||
{
|
||||
errs: []error{ObjectAlreadyRemoved{}, new(ObjectAlreadyRemoved)},
|
||||
errVariable: ErrObjectAlreadyRemoved,
|
||||
},
|
||||
{
|
||||
errs: []error{ObjectOutOfRange{}, new(ObjectOutOfRange)},
|
||||
errVariable: ErrObjectOutOfRange,
|
||||
},
|
||||
|
||||
{
|
||||
errs: []error{ContainerNotFound{}, new(ContainerNotFound)},
|
||||
errVariable: ErrContainerNotFound,
|
||||
},
|
||||
{
|
||||
errs: []error{EACLNotFound{}, new(EACLNotFound)},
|
||||
errVariable: ErrEACLNotFound,
|
||||
},
|
||||
|
||||
{
|
||||
errs: []error{SessionTokenExpired{}, new(SessionTokenExpired)},
|
||||
errVariable: ErrSessionTokenExpired,
|
||||
},
|
||||
{
|
||||
errs: []error{SessionTokenNotFound{}, new(SessionTokenNotFound)},
|
||||
errVariable: ErrSessionTokenNotFound,
|
||||
},
|
||||
|
||||
{
|
||||
errs: []error{UnrecognizedStatusV2{}, new(UnrecognizedStatusV2)},
|
||||
errVariable: ErrUnrecognizedStatusV2,
|
||||
},
|
||||
} {
|
||||
require.NotEmpty(t, tc.errs)
|
||||
require.NotNil(t, tc.errVariable)
|
||||
|
||||
for i := range tc.errs {
|
||||
require.ErrorIs(t, tc.errs[i], tc.errVariable)
|
||||
|
||||
wrapped := fmt.Errorf("some message %w", tc.errs[i])
|
||||
require.ErrorIs(t, wrapped, tc.errVariable)
|
||||
|
||||
wrappedTwice := fmt.Errorf("another message %w", wrapped)
|
||||
require.ErrorIs(t, wrappedTwice, tc.errVariable)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,97 +1,171 @@
|
|||
package apistatus
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/object"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/status"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrObjectLocked is an instance of ObjectLocked error status. It's expected to be used for [errors.Is]
|
||||
// and MUST NOT be changed.
|
||||
ErrObjectLocked ObjectLocked
|
||||
// ErrObjectAlreadyRemoved is an instance of ObjectAlreadyRemoved error status. It's expected to be used for [errors.Is]
|
||||
// and MUST NOT be changed.
|
||||
ErrObjectAlreadyRemoved ObjectAlreadyRemoved
|
||||
// ErrLockNonRegularObject is an instance of LockNonRegularObject error status. It's expected to be used for [errors.Is]
|
||||
// and MUST NOT be changed.
|
||||
ErrLockNonRegularObject LockNonRegularObject
|
||||
// ErrObjectAccessDenied is an instance of ObjectAccessDenied error status. It's expected to be used for [errors.Is]
|
||||
// and MUST NOT be changed.
|
||||
ErrObjectAccessDenied ObjectAccessDenied
|
||||
// ErrObjectNotFound is an instance of ObjectNotFound error status. It's expected to be used for [errors.Is]
|
||||
// and MUST NOT be changed.
|
||||
ErrObjectNotFound ObjectNotFound
|
||||
// ErrObjectOutOfRange is an instance of ObjectOutOfRange error status. It's expected to be used for [errors.Is]
|
||||
// and MUST NOT be changed.
|
||||
ErrObjectOutOfRange ObjectOutOfRange
|
||||
)
|
||||
|
||||
// ObjectLocked describes status of the failure because of the locked object.
|
||||
// Instances provide Status and StatusV2 interfaces.
|
||||
// Instances provide [StatusV2] and error interfaces.
|
||||
type ObjectLocked struct {
|
||||
v2 status.Status
|
||||
}
|
||||
|
||||
const defaultObjectLockedMsg = "object is locked"
|
||||
|
||||
func (x ObjectLocked) Error() string {
|
||||
msg := x.v2.Message()
|
||||
if msg == "" {
|
||||
msg = defaultObjectLockedMsg
|
||||
}
|
||||
|
||||
return errMessageStatusV2(
|
||||
globalizeCodeV2(object.StatusLocked, object.GlobalizeFail),
|
||||
x.v2.Message(),
|
||||
msg,
|
||||
)
|
||||
}
|
||||
|
||||
// implements local interface defined in FromStatusV2 func.
|
||||
// Is implements interface for correct checking current error type with [errors.Is].
|
||||
func (x ObjectLocked) Is(target error) bool {
|
||||
switch target.(type) {
|
||||
default:
|
||||
return errors.Is(Error, target)
|
||||
case ObjectLocked, *ObjectLocked:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// implements local interface defined in [ErrorFromV2] func.
|
||||
func (x *ObjectLocked) fromStatusV2(st *status.Status) {
|
||||
x.v2 = *st
|
||||
}
|
||||
|
||||
// ToStatusV2 implements StatusV2 interface method.
|
||||
// If the value was returned by FromStatusV2, returns the source message.
|
||||
// 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) ToStatusV2() *status.Status {
|
||||
func (x ObjectLocked) ErrorToV2() *status.Status {
|
||||
x.v2.SetCode(globalizeCodeV2(object.StatusLocked, object.GlobalizeFail))
|
||||
x.v2.SetMessage("object is locked")
|
||||
x.v2.SetMessage(defaultObjectLockedMsg)
|
||||
return &x.v2
|
||||
}
|
||||
|
||||
// LockNonRegularObject describes status returned on locking the non-regular object.
|
||||
// Instances provide Status and StatusV2 interfaces.
|
||||
// Instances provide [StatusV2] and error interfaces.
|
||||
type LockNonRegularObject struct {
|
||||
v2 status.Status
|
||||
}
|
||||
|
||||
const defaultLockNonRegularObjectMsg = "locking non-regular object is forbidden"
|
||||
|
||||
func (x LockNonRegularObject) Error() string {
|
||||
msg := x.v2.Message()
|
||||
if msg == "" {
|
||||
msg = defaultLockNonRegularObjectMsg
|
||||
}
|
||||
|
||||
return errMessageStatusV2(
|
||||
globalizeCodeV2(object.StatusLockNonRegularObject, object.GlobalizeFail),
|
||||
x.v2.Message(),
|
||||
msg,
|
||||
)
|
||||
}
|
||||
|
||||
// implements local interface defined in FromStatusV2 func.
|
||||
// Is implements interface for correct checking current error type with [errors.Is].
|
||||
func (x LockNonRegularObject) Is(target error) bool {
|
||||
switch target.(type) {
|
||||
default:
|
||||
return errors.Is(Error, target)
|
||||
case LockNonRegularObject, *LockNonRegularObject:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// implements local interface defined in [ErrorFromV2] func.
|
||||
func (x *LockNonRegularObject) fromStatusV2(st *status.Status) {
|
||||
x.v2 = *st
|
||||
}
|
||||
|
||||
// ToStatusV2 implements StatusV2 interface method.
|
||||
// If the value was returned by FromStatusV2, returns the source message.
|
||||
// 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) ToStatusV2() *status.Status {
|
||||
func (x LockNonRegularObject) ErrorToV2() *status.Status {
|
||||
x.v2.SetCode(globalizeCodeV2(object.StatusLockNonRegularObject, object.GlobalizeFail))
|
||||
x.v2.SetMessage("locking non-regular object is forbidden")
|
||||
x.v2.SetMessage(defaultLockNonRegularObjectMsg)
|
||||
return &x.v2
|
||||
}
|
||||
|
||||
// ObjectAccessDenied describes status of the failure because of the access control violation.
|
||||
// Instances provide Status and StatusV2 interfaces.
|
||||
// Instances provide [StatusV2] and error interfaces.
|
||||
type ObjectAccessDenied struct {
|
||||
v2 status.Status
|
||||
}
|
||||
|
||||
const defaultObjectAccessDeniedMsg = "access to object operation denied"
|
||||
|
||||
func (x ObjectAccessDenied) Error() string {
|
||||
msg := x.v2.Message()
|
||||
if msg == "" {
|
||||
msg = defaultObjectAccessDeniedMsg
|
||||
}
|
||||
|
||||
return errMessageStatusV2(
|
||||
globalizeCodeV2(object.StatusAccessDenied, object.GlobalizeFail),
|
||||
x.v2.Message(),
|
||||
msg,
|
||||
)
|
||||
}
|
||||
|
||||
// implements local interface defined in FromStatusV2 func.
|
||||
// Is implements interface for correct checking current error type with [errors.Is].
|
||||
func (x ObjectAccessDenied) Is(target error) bool {
|
||||
switch target.(type) {
|
||||
default:
|
||||
return errors.Is(Error, target)
|
||||
case ObjectAccessDenied, *ObjectAccessDenied:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// implements local interface defined in [ErrorFromV2] func.
|
||||
func (x *ObjectAccessDenied) fromStatusV2(st *status.Status) {
|
||||
x.v2 = *st
|
||||
}
|
||||
|
||||
// ToStatusV2 implements StatusV2 interface method.
|
||||
// If the value was returned by FromStatusV2, returns the source message.
|
||||
// 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) ToStatusV2() *status.Status {
|
||||
func (x ObjectAccessDenied) ErrorToV2() *status.Status {
|
||||
x.v2.SetCode(globalizeCodeV2(object.StatusAccessDenied, object.GlobalizeFail))
|
||||
x.v2.SetMessage("access to object operation denied")
|
||||
x.v2.SetMessage(defaultObjectAccessDeniedMsg)
|
||||
return &x.v2
|
||||
}
|
||||
|
||||
|
@ -107,32 +181,49 @@ func (x ObjectAccessDenied) Reason() string {
|
|||
}
|
||||
|
||||
// ObjectNotFound describes status of the failure because of the missing object.
|
||||
// Instances provide Status and StatusV2 interfaces.
|
||||
// Instances provide [StatusV2] and error interfaces.
|
||||
type ObjectNotFound struct {
|
||||
v2 status.Status
|
||||
}
|
||||
|
||||
const defaultObjectNotFoundMsg = "object not found"
|
||||
|
||||
func (x ObjectNotFound) Error() string {
|
||||
msg := x.v2.Message()
|
||||
if msg == "" {
|
||||
msg = defaultObjectNotFoundMsg
|
||||
}
|
||||
|
||||
return errMessageStatusV2(
|
||||
globalizeCodeV2(object.StatusNotFound, object.GlobalizeFail),
|
||||
x.v2.Message(),
|
||||
msg,
|
||||
)
|
||||
}
|
||||
|
||||
// implements local interface defined in FromStatusV2 func.
|
||||
// Is implements interface for correct checking current error type with [errors.Is].
|
||||
func (x ObjectNotFound) Is(target error) bool {
|
||||
switch target.(type) {
|
||||
default:
|
||||
return errors.Is(Error, target)
|
||||
case ObjectNotFound, *ObjectNotFound:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// implements local interface defined in [ErrorFromV2] func.
|
||||
func (x *ObjectNotFound) fromStatusV2(st *status.Status) {
|
||||
x.v2 = *st
|
||||
}
|
||||
|
||||
// ToStatusV2 implements StatusV2 interface method.
|
||||
// If the value was returned by FromStatusV2, returns the source message.
|
||||
// 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) ToStatusV2() *status.Status {
|
||||
func (x ObjectNotFound) ErrorToV2() *status.Status {
|
||||
x.v2.SetCode(globalizeCodeV2(object.StatusNotFound, object.GlobalizeFail))
|
||||
x.v2.SetMessage("object not found")
|
||||
x.v2.SetMessage(defaultObjectNotFoundMsg)
|
||||
return &x.v2
|
||||
}
|
||||
|
||||
|
@ -142,57 +233,91 @@ type ObjectAlreadyRemoved struct {
|
|||
v2 status.Status
|
||||
}
|
||||
|
||||
const defaultObjectAlreadyRemovedMsg = "object already removed"
|
||||
|
||||
func (x ObjectAlreadyRemoved) Error() string {
|
||||
msg := x.v2.Message()
|
||||
if msg == "" {
|
||||
msg = defaultObjectAlreadyRemovedMsg
|
||||
}
|
||||
|
||||
return errMessageStatusV2(
|
||||
globalizeCodeV2(object.StatusAlreadyRemoved, object.GlobalizeFail),
|
||||
x.v2.Message(),
|
||||
msg,
|
||||
)
|
||||
}
|
||||
|
||||
// implements local interface defined in FromStatusV2 func.
|
||||
// Is implements interface for correct checking current error type with [errors.Is].
|
||||
func (x ObjectAlreadyRemoved) Is(target error) bool {
|
||||
switch target.(type) {
|
||||
default:
|
||||
return errors.Is(Error, target)
|
||||
case ObjectAlreadyRemoved, *ObjectAlreadyRemoved:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// implements local interface defined in [ErrorFromV2] func.
|
||||
func (x *ObjectAlreadyRemoved) fromStatusV2(st *status.Status) {
|
||||
x.v2 = *st
|
||||
}
|
||||
|
||||
// ToStatusV2 implements StatusV2 interface method.
|
||||
// If the value was returned by FromStatusV2, returns the source message.
|
||||
// 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) ToStatusV2() *status.Status {
|
||||
func (x ObjectAlreadyRemoved) ErrorToV2() *status.Status {
|
||||
x.v2.SetCode(globalizeCodeV2(object.StatusAlreadyRemoved, object.GlobalizeFail))
|
||||
x.v2.SetMessage("object already removed")
|
||||
x.v2.SetMessage(defaultObjectAlreadyRemovedMsg)
|
||||
return &x.v2
|
||||
}
|
||||
|
||||
// ObjectOutOfRange describes status of the failure because of the incorrect
|
||||
// provided object ranges.
|
||||
// Instances provide Status and StatusV2 interfaces.
|
||||
// Instances provide [StatusV2] and error interfaces.
|
||||
type ObjectOutOfRange struct {
|
||||
v2 status.Status
|
||||
}
|
||||
|
||||
const defaultObjectOutOfRangeMsg = "out of range"
|
||||
|
||||
func (x ObjectOutOfRange) Error() string {
|
||||
msg := x.v2.Message()
|
||||
if msg == "" {
|
||||
msg = defaultObjectOutOfRangeMsg
|
||||
}
|
||||
|
||||
return errMessageStatusV2(
|
||||
globalizeCodeV2(object.StatusOutOfRange, object.GlobalizeFail),
|
||||
x.v2.Message(),
|
||||
msg,
|
||||
)
|
||||
}
|
||||
|
||||
// implements local interface defined in FromStatusV2 func.
|
||||
// Is implements interface for correct checking current error type with [errors.Is].
|
||||
func (x ObjectOutOfRange) Is(target error) bool {
|
||||
switch target.(type) {
|
||||
default:
|
||||
return errors.Is(Error, target)
|
||||
case ObjectOutOfRange, *ObjectOutOfRange:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// implements local interface defined in [ErrorFromV2] func.
|
||||
func (x *ObjectOutOfRange) fromStatusV2(st *status.Status) {
|
||||
x.v2 = *st
|
||||
}
|
||||
|
||||
// ToStatusV2 implements StatusV2 interface method.
|
||||
// If the value was returned by FromStatusV2, returns the source message.
|
||||
// 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) ToStatusV2() *status.Status {
|
||||
func (x ObjectOutOfRange) ErrorToV2() *status.Status {
|
||||
x.v2.SetCode(globalizeCodeV2(object.StatusOutOfRange, object.GlobalizeFail))
|
||||
x.v2.SetMessage("out of range")
|
||||
x.v2.SetMessage(defaultObjectOutOfRangeMsg)
|
||||
return &x.v2
|
||||
}
|
||||
|
|
|
@ -14,13 +14,13 @@ func TestObjectAccessDenied_WriteReason(t *testing.T) {
|
|||
|
||||
res := st.Reason()
|
||||
require.Empty(t, res)
|
||||
detailNum := apistatus.ToStatusV2(st).NumberOfDetails()
|
||||
detailNum := apistatus.ErrorToV2(st).NumberOfDetails()
|
||||
require.Zero(t, detailNum)
|
||||
|
||||
st.WriteReason(reason)
|
||||
|
||||
res = st.Reason()
|
||||
require.Equal(t, reason, res)
|
||||
detailNum = apistatus.ToStatusV2(st).NumberOfDetails()
|
||||
detailNum = apistatus.ErrorToV2(st).NumberOfDetails()
|
||||
require.EqualValues(t, 1, detailNum)
|
||||
}
|
||||
|
|
|
@ -1,66 +1,111 @@
|
|||
package apistatus
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/session"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/status"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrSessionTokenNotFound is an instance of SessionTokenNotFound error status. It's expected to be used for [errors.Is]
|
||||
// and MUST NOT be changed.
|
||||
ErrSessionTokenNotFound SessionTokenNotFound
|
||||
// ErrSessionTokenExpired is an instance of SessionTokenExpired error status. It's expected to be used for [errors.Is]
|
||||
// and MUST NOT be changed.
|
||||
ErrSessionTokenExpired SessionTokenExpired
|
||||
)
|
||||
|
||||
// SessionTokenNotFound describes status of the failure because of the missing session token.
|
||||
// Instances provide Status and StatusV2 interfaces.
|
||||
// Instances provide [StatusV2] and error interfaces.
|
||||
type SessionTokenNotFound struct {
|
||||
v2 status.Status
|
||||
}
|
||||
|
||||
const defaultSessionTokenNotFoundMsg = "session token not found"
|
||||
|
||||
func (x SessionTokenNotFound) Error() string {
|
||||
msg := x.v2.Message()
|
||||
if msg == "" {
|
||||
msg = defaultSessionTokenNotFoundMsg
|
||||
}
|
||||
|
||||
return errMessageStatusV2(
|
||||
globalizeCodeV2(session.StatusTokenNotFound, session.GlobalizeFail),
|
||||
x.v2.Message(),
|
||||
msg,
|
||||
)
|
||||
}
|
||||
|
||||
// implements local interface defined in FromStatusV2 func.
|
||||
// Is implements interface for correct checking current error type with [errors.Is].
|
||||
func (x SessionTokenNotFound) Is(target error) bool {
|
||||
switch target.(type) {
|
||||
default:
|
||||
return errors.Is(Error, target)
|
||||
case SessionTokenNotFound, *SessionTokenNotFound:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// implements local interface defined in [ErrorFromV2] func.
|
||||
func (x *SessionTokenNotFound) fromStatusV2(st *status.Status) {
|
||||
x.v2 = *st
|
||||
}
|
||||
|
||||
// ToStatusV2 implements StatusV2 interface method.
|
||||
// If the value was returned by FromStatusV2, returns the source message.
|
||||
// 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) ToStatusV2() *status.Status {
|
||||
func (x SessionTokenNotFound) ErrorToV2() *status.Status {
|
||||
x.v2.SetCode(globalizeCodeV2(session.StatusTokenNotFound, session.GlobalizeFail))
|
||||
x.v2.SetMessage("session token not found")
|
||||
x.v2.SetMessage(defaultSessionTokenNotFoundMsg)
|
||||
return &x.v2
|
||||
}
|
||||
|
||||
// SessionTokenExpired describes status of the failure because of the expired session token.
|
||||
// Instances provide Status and StatusV2 interfaces.
|
||||
// Instances provide [StatusV2] and error interfaces.
|
||||
type SessionTokenExpired struct {
|
||||
v2 status.Status
|
||||
}
|
||||
|
||||
const defaultSessionTokenExpiredMsg = "expired session token"
|
||||
|
||||
func (x SessionTokenExpired) Error() string {
|
||||
msg := x.v2.Message()
|
||||
if msg == "" {
|
||||
msg = defaultSessionTokenExpiredMsg
|
||||
}
|
||||
|
||||
return errMessageStatusV2(
|
||||
globalizeCodeV2(session.StatusTokenExpired, session.GlobalizeFail),
|
||||
x.v2.Message(),
|
||||
msg,
|
||||
)
|
||||
}
|
||||
|
||||
// implements local interface defined in FromStatusV2 func.
|
||||
// Is implements interface for correct checking current error type with [errors.Is].
|
||||
func (x SessionTokenExpired) Is(target error) bool {
|
||||
switch target.(type) {
|
||||
default:
|
||||
return errors.Is(Error, target)
|
||||
case SessionTokenExpired, *SessionTokenExpired:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// implements local interface defined in [ErrorFromV2] func.
|
||||
func (x *SessionTokenExpired) fromStatusV2(st *status.Status) {
|
||||
x.v2 = *st
|
||||
}
|
||||
|
||||
// ToStatusV2 implements StatusV2 interface method.
|
||||
// If the value was returned by FromStatusV2, returns the source message.
|
||||
// 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) ToStatusV2() *status.Status {
|
||||
func (x SessionTokenExpired) ErrorToV2() *status.Status {
|
||||
x.v2.SetCode(globalizeCodeV2(session.StatusTokenExpired, session.GlobalizeFail))
|
||||
x.v2.SetMessage("expired session token")
|
||||
x.v2.SetMessage(defaultSessionTokenExpiredMsg)
|
||||
return &x.v2
|
||||
}
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
package apistatus
|
||||
|
||||
// Status defines a variety of NeoFS API status returns.
|
||||
//
|
||||
// All statuses are split into two disjoint subsets: successful and failed, and:
|
||||
// - statuses that implement the build-in error interface are considered failed statuses;
|
||||
// - all other value types are considered successes (nil is a default success).
|
||||
//
|
||||
// In Go code type of success can be determined by a type switch, failure - by a switch with errors.As calls.
|
||||
// Nil should be considered as a success, and default switch section - as an unrecognized Status.
|
||||
//
|
||||
// To convert statuses into errors and vice versa, use functions ErrToStatus and ErrFromStatus, respectively.
|
||||
// ErrFromStatus function returns nil for successful statuses. However, to simplify the check of statuses for success,
|
||||
// IsSuccessful function should be used (try to avoid nil comparison).
|
||||
// It should be noted that using direct typecasting is not a compatible approach.
|
||||
//
|
||||
// To transport statuses using the NeoFS API V2 protocol, see StatusV2 interface and FromStatusV2 and ToStatusV2 functions.
|
||||
type Status interface{}
|
||||
|
||||
// ErrFromStatus converts Status instance to error if it is failed. Returns nil on successful Status.
|
||||
//
|
||||
// Note: direct assignment may not be compatibility-safe.
|
||||
func ErrFromStatus(st Status) error {
|
||||
if err, ok := st.(error); ok {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ErrToStatus converts the error instance to Status instance.
|
||||
//
|
||||
// Note: direct assignment may not be compatibility-safe.
|
||||
func ErrToStatus(err error) Status {
|
||||
return err
|
||||
}
|
||||
|
||||
// IsSuccessful checks if status is successful.
|
||||
//
|
||||
// Note: direct cast may not be compatibility-safe.
|
||||
func IsSuccessful(st Status) bool {
|
||||
_, ok := st.(error)
|
||||
return !ok
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
package apistatus_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestErrors(t *testing.T) {
|
||||
t.Run("error source", func(t *testing.T) {
|
||||
err := errors.New("some error")
|
||||
|
||||
st := apistatus.ErrToStatus(err)
|
||||
|
||||
success := apistatus.IsSuccessful(st)
|
||||
require.False(t, success)
|
||||
|
||||
res := apistatus.ErrFromStatus(st)
|
||||
|
||||
require.Equal(t, err, res)
|
||||
})
|
||||
|
||||
t.Run("non-error source", func(t *testing.T) {
|
||||
var st apistatus.Status = "any non-error type"
|
||||
|
||||
success := apistatus.IsSuccessful(st)
|
||||
require.True(t, success)
|
||||
|
||||
res := apistatus.ErrFromStatus(st)
|
||||
|
||||
require.Nil(t, res)
|
||||
})
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
package apistatus
|
||||
|
||||
import (
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/status"
|
||||
)
|
||||
|
||||
// SuccessDefaultV2 represents Status instance of default success. Implements StatusV2.
|
||||
type SuccessDefaultV2 struct {
|
||||
isNil bool
|
||||
|
||||
v2 *status.Status
|
||||
}
|
||||
|
||||
// implements local interface defined in FromStatusV2 func.
|
||||
func (x *SuccessDefaultV2) fromStatusV2(st *status.Status) {
|
||||
x.isNil = st == nil
|
||||
x.v2 = st
|
||||
}
|
||||
|
||||
// ToStatusV2 implements StatusV2 interface method.
|
||||
// If the value was returned by FromStatusV2, returns the source message.
|
||||
// Otherwise, returns message with
|
||||
// - code: OK;
|
||||
// - string message: empty;
|
||||
// - details: empty.
|
||||
func (x SuccessDefaultV2) ToStatusV2() *status.Status {
|
||||
if x.isNil || x.v2 != nil {
|
||||
return x.v2
|
||||
}
|
||||
|
||||
return newStatusV2WithLocalCode(status.OK, status.GlobalizeSuccess)
|
||||
}
|
|
@ -4,15 +4,31 @@ import (
|
|||
"github.com/nspcc-dev/neofs-api-go/v2/status"
|
||||
)
|
||||
|
||||
type unrecognizedStatusV2 struct {
|
||||
// ErrUnrecognizedStatusV2 is an instance of UnrecognizedStatusV2 error status. It's expected to be used for [errors.Is]
|
||||
// and MUST NOT be changed.
|
||||
var ErrUnrecognizedStatusV2 UnrecognizedStatusV2
|
||||
|
||||
// UnrecognizedStatusV2 describes status of the uncertain failure.
|
||||
// Instances provide [StatusV2] and error interfaces.
|
||||
type UnrecognizedStatusV2 struct {
|
||||
v2 status.Status
|
||||
}
|
||||
|
||||
func (x unrecognizedStatusV2) Error() string {
|
||||
func (x UnrecognizedStatusV2) Error() string {
|
||||
return errMessageStatusV2("unrecognized", x.v2.Message())
|
||||
}
|
||||
|
||||
// implements local interface defined in FromStatusV2 func.
|
||||
func (x *unrecognizedStatusV2) fromStatusV2(st *status.Status) {
|
||||
// Is implements interface for correct checking current error type with [errors.Is].
|
||||
func (x UnrecognizedStatusV2) Is(target error) bool {
|
||||
switch target.(type) {
|
||||
default:
|
||||
return false
|
||||
case UnrecognizedStatusV2, *UnrecognizedStatusV2:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// implements local interface defined in [ErrorFromV2] func.
|
||||
func (x *UnrecognizedStatusV2) fromStatusV2(st *status.Status) {
|
||||
x.v2 = *st
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package apistatus
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/container"
|
||||
|
@ -9,38 +10,50 @@ import (
|
|||
"github.com/nspcc-dev/neofs-api-go/v2/status"
|
||||
)
|
||||
|
||||
// StatusV2 defines a variety of Status instances compatible with NeoFS API V2 protocol.
|
||||
// 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 {
|
||||
Status
|
||||
|
||||
// ToStatusV2 returns the status as github.com/nspcc-dev/neofs-api-go/v2/status.Status message structure.
|
||||
ToStatusV2() *status.Status
|
||||
// ErrorToV2 returns the status as github.com/nspcc-dev/neofs-api-go/v2/status.Status message structure.
|
||||
ErrorToV2() *status.Status
|
||||
}
|
||||
|
||||
// FromStatusV2 converts status.Status message structure to Status instance. Inverse to ToStatusV2 operation.
|
||||
// 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 ToStatusV2, casts are not compatibility-safe.
|
||||
// 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.
|
||||
//
|
||||
// Below is the mapping of return codes to Status instance types (with a description of parsing details).
|
||||
// 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.
|
||||
//
|
||||
// Successes:
|
||||
// - status.OK: *SuccessDefaultV2 (this also includes nil argument).
|
||||
// - [status.OK]: nil (this also includes nil argument).
|
||||
//
|
||||
// Common failures:
|
||||
// - status.Internal: *ServerInternal;
|
||||
// - status.SignatureVerificationFail: *SignatureVerification.
|
||||
// - [status.Internal]: *[ServerInternal];
|
||||
// - [status.SignatureVerificationFail]: *[SignatureVerification].
|
||||
// - [status.WrongMagicNumber]: *[WrongMagicNumber].
|
||||
// - [status.NodeUnderMaintenance]: *[NodeUnderMaintenance].
|
||||
//
|
||||
// Object failures:
|
||||
// - object.StatusLocked: *ObjectLocked;
|
||||
// - object.StatusLockNonRegularObject: *LockNonRegularObject.
|
||||
// - object.StatusAccessDenied: *ObjectAccessDenied.
|
||||
func FromStatusV2(st *status.Status) Status {
|
||||
// - [object.StatusLocked]: *[ObjectLocked];
|
||||
// - [object.StatusLockNonRegularObject]: *[LockNonRegularObject].
|
||||
// - [object.StatusAccessDenied]: *[ObjectAccessDenied].
|
||||
// - [object.StatusNotFound]: *[ObjectNotFound].
|
||||
// - [object.StatusAlreadyRemoved]: *[ObjectAlreadyRemoved].
|
||||
// - [object.StatusOutOfRange]: *[ObjectOutOfRange].
|
||||
//
|
||||
// Container failures:
|
||||
// - [container.StatusNotFound]: *[ContainerNotFound];
|
||||
// - [container.StatusEACLNotFound]: *[EACLNotFound];
|
||||
//
|
||||
// Session failures:
|
||||
// - [session.StatusTokenNotFound]: *[SessionTokenNotFound];
|
||||
// - [session.StatusTokenExpired]: *[SessionTokenExpired];
|
||||
func ErrorFromV2(st *status.Status) error {
|
||||
var decoder interface {
|
||||
fromStatusV2(*status.Status)
|
||||
Error() string
|
||||
}
|
||||
|
||||
switch code := st.Code(); {
|
||||
|
@ -48,7 +61,7 @@ func FromStatusV2(st *status.Status) Status {
|
|||
//nolint:exhaustive
|
||||
switch status.LocalizeSuccess(&code); code {
|
||||
case status.OK:
|
||||
decoder = new(SuccessDefaultV2)
|
||||
return nil
|
||||
}
|
||||
case status.IsCommonFail(code):
|
||||
switch status.LocalizeCommonFail(&code); code {
|
||||
|
@ -95,7 +108,7 @@ func FromStatusV2(st *status.Status) Status {
|
|||
}
|
||||
|
||||
if decoder == nil {
|
||||
decoder = new(unrecognizedStatusV2)
|
||||
decoder = new(UnrecognizedStatusV2)
|
||||
}
|
||||
|
||||
decoder.fromStatusV2(st)
|
||||
|
@ -103,27 +116,28 @@ func FromStatusV2(st *status.Status) Status {
|
|||
return decoder
|
||||
}
|
||||
|
||||
// ToStatusV2 converts Status instance to status.Status message structure. Inverse to FromStatusV2 operation.
|
||||
// ErrorToV2 converts error to status.Status message structure. Inverse to [ErrorFromV2] 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 ToStatusV2(st Status) *status.Status {
|
||||
if v, ok := st.(StatusV2); ok {
|
||||
return v.ToStatusV2()
|
||||
}
|
||||
|
||||
if IsSuccessful(st) {
|
||||
// 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 {
|
||||
if err == nil {
|
||||
return newStatusV2WithLocalCode(status.OK, status.GlobalizeSuccess)
|
||||
}
|
||||
|
||||
var instance StatusV2
|
||||
if errors.As(err, &instance) {
|
||||
return instance.ErrorToV2()
|
||||
}
|
||||
|
||||
internalErrorStatus := newStatusV2WithLocalCode(status.Internal, status.GlobalizeCommonFail)
|
||||
internalErrorStatus.SetMessage(st.(error).Error()) // type cast never panics because IsSuccessful() checks cast
|
||||
internalErrorStatus.SetMessage(err.Error())
|
||||
|
||||
return internalErrorStatus
|
||||
}
|
||||
|
||||
func errMessageStatusV2(code interface{}, msg string) string {
|
||||
func errMessageStatusV2(code any, msg string) string {
|
||||
const (
|
||||
noMsgFmt = "status: code = %v"
|
||||
msgFmt = noMsgFmt + " message = %s"
|
||||
|
|
|
@ -8,292 +8,189 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestToStatusV2(t *testing.T) {
|
||||
type statusConstructor func() apistatus.Status
|
||||
|
||||
for _, testItem := range [...]struct {
|
||||
status interface{} // Status or statusConstructor
|
||||
codeV2 uint64
|
||||
messageV2 string
|
||||
}{
|
||||
{
|
||||
status: errors.New("some error"),
|
||||
codeV2: 1024,
|
||||
messageV2: "some error",
|
||||
},
|
||||
{
|
||||
status: 1,
|
||||
codeV2: 0,
|
||||
},
|
||||
{
|
||||
status: "text",
|
||||
codeV2: 0,
|
||||
},
|
||||
{
|
||||
status: true,
|
||||
codeV2: 0,
|
||||
},
|
||||
{
|
||||
status: true,
|
||||
codeV2: 0,
|
||||
},
|
||||
{
|
||||
status: nil,
|
||||
codeV2: 0,
|
||||
},
|
||||
{
|
||||
status: (statusConstructor)(func() apistatus.Status {
|
||||
var st apistatus.ServerInternal
|
||||
|
||||
st.SetMessage("internal error message")
|
||||
|
||||
return st
|
||||
}),
|
||||
codeV2: 1024,
|
||||
},
|
||||
{
|
||||
status: (statusConstructor)(func() apistatus.Status {
|
||||
var st apistatus.WrongMagicNumber
|
||||
|
||||
st.WriteCorrectMagic(322)
|
||||
|
||||
return st
|
||||
}),
|
||||
codeV2: 1025,
|
||||
},
|
||||
{
|
||||
status: (statusConstructor)(func() apistatus.Status {
|
||||
return new(apistatus.ObjectLocked)
|
||||
}),
|
||||
codeV2: 2050,
|
||||
},
|
||||
{
|
||||
status: (statusConstructor)(func() apistatus.Status {
|
||||
return new(apistatus.LockNonRegularObject)
|
||||
}),
|
||||
codeV2: 2051,
|
||||
},
|
||||
{
|
||||
status: (statusConstructor)(func() apistatus.Status {
|
||||
var st apistatus.ObjectAccessDenied
|
||||
|
||||
st.WriteReason("any reason")
|
||||
|
||||
return st
|
||||
}),
|
||||
codeV2: 2048,
|
||||
},
|
||||
{
|
||||
status: (statusConstructor)(func() apistatus.Status {
|
||||
return new(apistatus.ObjectNotFound)
|
||||
}),
|
||||
codeV2: 2049,
|
||||
},
|
||||
{
|
||||
status: (statusConstructor)(func() apistatus.Status {
|
||||
return new(apistatus.ObjectAlreadyRemoved)
|
||||
}),
|
||||
codeV2: 2052,
|
||||
},
|
||||
{
|
||||
status: (statusConstructor)(func() apistatus.Status {
|
||||
return new(apistatus.ObjectOutOfRange)
|
||||
}),
|
||||
codeV2: 2053,
|
||||
},
|
||||
{
|
||||
status: (statusConstructor)(func() apistatus.Status {
|
||||
return new(apistatus.ContainerNotFound)
|
||||
}),
|
||||
codeV2: 3072,
|
||||
},
|
||||
{
|
||||
status: (statusConstructor)(func() apistatus.Status {
|
||||
return new(apistatus.EACLNotFound)
|
||||
}),
|
||||
codeV2: 3073,
|
||||
},
|
||||
{
|
||||
status: (statusConstructor)(func() apistatus.Status {
|
||||
return new(apistatus.SessionTokenNotFound)
|
||||
}),
|
||||
codeV2: 4096,
|
||||
},
|
||||
{
|
||||
status: (statusConstructor)(func() apistatus.Status {
|
||||
return new(apistatus.SessionTokenExpired)
|
||||
}),
|
||||
codeV2: 4097,
|
||||
},
|
||||
{
|
||||
status: (statusConstructor)(func() apistatus.Status {
|
||||
return new(apistatus.NodeUnderMaintenance)
|
||||
}),
|
||||
codeV2: 1027,
|
||||
},
|
||||
} {
|
||||
var st apistatus.Status
|
||||
|
||||
if cons, ok := testItem.status.(statusConstructor); ok {
|
||||
st = cons()
|
||||
} else {
|
||||
st = testItem.status
|
||||
}
|
||||
|
||||
stv2 := apistatus.ToStatusV2(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())
|
||||
}
|
||||
|
||||
_, ok := st.(apistatus.StatusV2)
|
||||
if ok {
|
||||
// restore and convert again
|
||||
restored := apistatus.FromStatusV2(stv2)
|
||||
|
||||
res := apistatus.ToStatusV2(restored)
|
||||
|
||||
// must generate the same status.Status message
|
||||
require.Equal(t, stv2, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFromStatusV2(t *testing.T) {
|
||||
type statusConstructor func() apistatus.Status
|
||||
type statusConstructor func() error
|
||||
|
||||
for _, testItem := range [...]struct {
|
||||
status interface{} // Status or statusConstructor
|
||||
status any // Status or statusConstructor
|
||||
codeV2 uint64
|
||||
messageV2 string
|
||||
compatibleErrs []error
|
||||
checkAsErr func(error) bool
|
||||
}{
|
||||
{
|
||||
status: errors.New("some error"),
|
||||
status: (statusConstructor)(func() error {
|
||||
return errors.New("some error")
|
||||
}),
|
||||
codeV2: 1024,
|
||||
messageV2: "some error",
|
||||
},
|
||||
{
|
||||
status: 1,
|
||||
status: (statusConstructor)(func() error {
|
||||
return nil
|
||||
}),
|
||||
codeV2: 0,
|
||||
},
|
||||
{
|
||||
status: "text",
|
||||
codeV2: 0,
|
||||
},
|
||||
{
|
||||
status: true,
|
||||
codeV2: 0,
|
||||
},
|
||||
{
|
||||
status: true,
|
||||
codeV2: 0,
|
||||
},
|
||||
{
|
||||
status: nil,
|
||||
codeV2: 0,
|
||||
},
|
||||
{
|
||||
status: (statusConstructor)(func() apistatus.Status {
|
||||
var st apistatus.ServerInternal
|
||||
|
||||
status: (statusConstructor)(func() error {
|
||||
st := new(apistatus.ServerInternal)
|
||||
st.SetMessage("internal error message")
|
||||
|
||||
return st
|
||||
}),
|
||||
codeV2: 1024,
|
||||
compatibleErrs: []error{apistatus.ErrServerInternal, apistatus.ServerInternal{}, &apistatus.ServerInternal{}, apistatus.Error},
|
||||
checkAsErr: func(err error) bool {
|
||||
var target *apistatus.ServerInternal
|
||||
return errors.As(err, &target)
|
||||
},
|
||||
},
|
||||
{
|
||||
status: (statusConstructor)(func() apistatus.Status {
|
||||
var st apistatus.WrongMagicNumber
|
||||
|
||||
status: (statusConstructor)(func() error {
|
||||
st := new(apistatus.WrongMagicNumber)
|
||||
st.WriteCorrectMagic(322)
|
||||
|
||||
return st
|
||||
}),
|
||||
codeV2: 1025,
|
||||
compatibleErrs: []error{apistatus.ErrWrongMagicNumber, apistatus.WrongMagicNumber{}, &apistatus.WrongMagicNumber{}, apistatus.Error},
|
||||
checkAsErr: func(err error) bool {
|
||||
var target *apistatus.WrongMagicNumber
|
||||
return errors.As(err, &target)
|
||||
},
|
||||
},
|
||||
{
|
||||
status: (statusConstructor)(func() apistatus.Status {
|
||||
status: (statusConstructor)(func() error {
|
||||
return new(apistatus.ObjectLocked)
|
||||
}),
|
||||
codeV2: 2050,
|
||||
compatibleErrs: []error{apistatus.ErrObjectLocked, apistatus.ObjectLocked{}, &apistatus.ObjectLocked{}, apistatus.Error},
|
||||
checkAsErr: func(err error) bool {
|
||||
var target *apistatus.ObjectLocked
|
||||
return errors.As(err, &target)
|
||||
},
|
||||
},
|
||||
{
|
||||
status: (statusConstructor)(func() apistatus.Status {
|
||||
status: (statusConstructor)(func() error {
|
||||
return new(apistatus.LockNonRegularObject)
|
||||
}),
|
||||
codeV2: 2051,
|
||||
compatibleErrs: []error{apistatus.ErrLockNonRegularObject, apistatus.LockNonRegularObject{}, &apistatus.LockNonRegularObject{}, apistatus.Error},
|
||||
checkAsErr: func(err error) bool {
|
||||
var target *apistatus.LockNonRegularObject
|
||||
return errors.As(err, &target)
|
||||
},
|
||||
},
|
||||
{
|
||||
status: (statusConstructor)(func() apistatus.Status {
|
||||
var st apistatus.ObjectAccessDenied
|
||||
|
||||
status: (statusConstructor)(func() error {
|
||||
st := new(apistatus.ObjectAccessDenied)
|
||||
st.WriteReason("any reason")
|
||||
|
||||
return st
|
||||
}),
|
||||
codeV2: 2048,
|
||||
compatibleErrs: []error{apistatus.ErrObjectAccessDenied, apistatus.ObjectAccessDenied{}, &apistatus.ObjectAccessDenied{}, apistatus.Error},
|
||||
checkAsErr: func(err error) bool {
|
||||
var target *apistatus.ObjectAccessDenied
|
||||
return errors.As(err, &target)
|
||||
},
|
||||
},
|
||||
{
|
||||
status: (statusConstructor)(func() apistatus.Status {
|
||||
status: (statusConstructor)(func() error {
|
||||
return new(apistatus.ObjectNotFound)
|
||||
}),
|
||||
codeV2: 2049,
|
||||
compatibleErrs: []error{apistatus.ErrObjectNotFound, apistatus.ObjectNotFound{}, &apistatus.ObjectNotFound{}, apistatus.Error},
|
||||
checkAsErr: func(err error) bool {
|
||||
var target *apistatus.ObjectNotFound
|
||||
return errors.As(err, &target)
|
||||
},
|
||||
},
|
||||
{
|
||||
status: (statusConstructor)(func() apistatus.Status {
|
||||
status: (statusConstructor)(func() error {
|
||||
return new(apistatus.ObjectAlreadyRemoved)
|
||||
}),
|
||||
codeV2: 2052,
|
||||
compatibleErrs: []error{apistatus.ErrObjectAlreadyRemoved, apistatus.ObjectAlreadyRemoved{}, &apistatus.ObjectAlreadyRemoved{}, apistatus.Error},
|
||||
checkAsErr: func(err error) bool {
|
||||
var target *apistatus.ObjectAlreadyRemoved
|
||||
return errors.As(err, &target)
|
||||
},
|
||||
},
|
||||
{
|
||||
status: statusConstructor(func() apistatus.Status {
|
||||
status: statusConstructor(func() error {
|
||||
return new(apistatus.ObjectOutOfRange)
|
||||
}),
|
||||
codeV2: 2053,
|
||||
compatibleErrs: []error{apistatus.ErrObjectOutOfRange, apistatus.ObjectOutOfRange{}, &apistatus.ObjectOutOfRange{}, apistatus.Error},
|
||||
checkAsErr: func(err error) bool {
|
||||
var target *apistatus.ObjectOutOfRange
|
||||
return errors.As(err, &target)
|
||||
},
|
||||
},
|
||||
{
|
||||
status: (statusConstructor)(func() apistatus.Status {
|
||||
status: (statusConstructor)(func() error {
|
||||
return new(apistatus.ContainerNotFound)
|
||||
}),
|
||||
codeV2: 3072,
|
||||
compatibleErrs: []error{apistatus.ErrContainerNotFound, apistatus.ContainerNotFound{}, &apistatus.ContainerNotFound{}, apistatus.Error},
|
||||
checkAsErr: func(err error) bool {
|
||||
var target *apistatus.ContainerNotFound
|
||||
return errors.As(err, &target)
|
||||
},
|
||||
},
|
||||
{
|
||||
status: (statusConstructor)(func() apistatus.Status {
|
||||
status: (statusConstructor)(func() error {
|
||||
return new(apistatus.EACLNotFound)
|
||||
}),
|
||||
codeV2: 3073,
|
||||
compatibleErrs: []error{apistatus.ErrEACLNotFound, apistatus.EACLNotFound{}, &apistatus.EACLNotFound{}, apistatus.Error},
|
||||
checkAsErr: func(err error) bool {
|
||||
var target *apistatus.EACLNotFound
|
||||
return errors.As(err, &target)
|
||||
},
|
||||
},
|
||||
{
|
||||
status: (statusConstructor)(func() apistatus.Status {
|
||||
status: (statusConstructor)(func() error {
|
||||
return new(apistatus.SessionTokenNotFound)
|
||||
}),
|
||||
codeV2: 4096,
|
||||
compatibleErrs: []error{apistatus.ErrSessionTokenNotFound, apistatus.SessionTokenNotFound{}, &apistatus.SessionTokenNotFound{}, apistatus.Error},
|
||||
checkAsErr: func(err error) bool {
|
||||
var target *apistatus.SessionTokenNotFound
|
||||
return errors.As(err, &target)
|
||||
},
|
||||
},
|
||||
{
|
||||
status: (statusConstructor)(func() apistatus.Status {
|
||||
status: (statusConstructor)(func() error {
|
||||
return new(apistatus.SessionTokenExpired)
|
||||
}),
|
||||
codeV2: 4097,
|
||||
compatibleErrs: []error{apistatus.ErrSessionTokenExpired, apistatus.SessionTokenExpired{}, &apistatus.SessionTokenExpired{}, apistatus.Error},
|
||||
checkAsErr: func(err error) bool {
|
||||
var target *apistatus.SessionTokenExpired
|
||||
return errors.As(err, &target)
|
||||
},
|
||||
},
|
||||
{
|
||||
status: (statusConstructor)(func() apistatus.Status {
|
||||
status: (statusConstructor)(func() error {
|
||||
return new(apistatus.NodeUnderMaintenance)
|
||||
}),
|
||||
codeV2: 1027,
|
||||
compatibleErrs: []error{apistatus.ErrNodeUnderMaintenance, apistatus.NodeUnderMaintenance{}, &apistatus.NodeUnderMaintenance{}, apistatus.Error},
|
||||
checkAsErr: func(err error) bool {
|
||||
var target *apistatus.NodeUnderMaintenance
|
||||
return errors.As(err, &target)
|
||||
},
|
||||
},
|
||||
} {
|
||||
var st apistatus.Status
|
||||
var st error
|
||||
cons, ok := testItem.status.(statusConstructor)
|
||||
require.True(t, ok)
|
||||
|
||||
if cons, ok := testItem.status.(statusConstructor); ok {
|
||||
st = cons()
|
||||
} else {
|
||||
st = testItem.status
|
||||
}
|
||||
|
||||
stv2 := apistatus.ToStatusV2(st)
|
||||
stv2 := apistatus.ErrorToV2(st)
|
||||
|
||||
// must generate the same status.Status message
|
||||
require.EqualValues(t, testItem.codeV2, stv2.Code())
|
||||
|
@ -301,15 +198,25 @@ func TestFromStatusV2(t *testing.T) {
|
|||
require.Equal(t, testItem.messageV2, stv2.Message())
|
||||
}
|
||||
|
||||
_, ok := st.(apistatus.StatusV2)
|
||||
_, ok = st.(apistatus.StatusV2)
|
||||
if ok {
|
||||
// restore and convert again
|
||||
restored := apistatus.FromStatusV2(stv2)
|
||||
restored := apistatus.ErrorFromV2(stv2)
|
||||
|
||||
res := apistatus.ToStatusV2(restored)
|
||||
res := apistatus.ErrorToV2(restored)
|
||||
|
||||
// must generate the same status.Status message
|
||||
require.Equal(t, stv2, res)
|
||||
}
|
||||
|
||||
randomError := errors.New("garbage")
|
||||
for _, err := range testItem.compatibleErrs {
|
||||
require.ErrorIs(t, st, err)
|
||||
require.NotErrorIs(t, randomError, err)
|
||||
}
|
||||
|
||||
if testItem.checkAsErr != nil {
|
||||
require.True(t, testItem.checkAsErr(st))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package container
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
@ -15,9 +14,7 @@ import (
|
|||
"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"
|
||||
neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/netmap"
|
||||
subnetid "github.com/nspcc-dev/neofs-sdk-go/subnet/id"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/user"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/version"
|
||||
)
|
||||
|
@ -118,8 +115,6 @@ func (x *Container) readFromV2(m container.Container, checkFieldPresence bool) e
|
|||
}
|
||||
|
||||
switch key {
|
||||
case container.SysAttributeSubnet:
|
||||
err = new(subnetid.ID).DecodeString(val)
|
||||
case attributeTimestamp:
|
||||
_, err = strconv.ParseInt(val, 10, 64)
|
||||
}
|
||||
|
@ -383,28 +378,6 @@ func CreatedAt(cnr Container) time.Time {
|
|||
return time.Unix(sec, 0)
|
||||
}
|
||||
|
||||
// SetSubnet places the Container on the specified NeoFS subnet. If called,
|
||||
// container nodes will only be selected from the given subnet, otherwise from
|
||||
// the entire network.
|
||||
func SetSubnet(cnr *Container, subNet subnetid.ID) {
|
||||
cnr.SetAttribute(container.SysAttributeSubnet, subNet.EncodeToString())
|
||||
}
|
||||
|
||||
// Subnet return container subnet set using SetSubnet.
|
||||
//
|
||||
// Zero Container is bound to zero subnet.
|
||||
func Subnet(cnr Container) (res subnetid.ID) {
|
||||
val := cnr.Attribute(container.SysAttributeSubnet)
|
||||
if val != "" {
|
||||
err := res.DecodeString(val)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("invalid subnet attribute: %s (%v)", val, err))
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const attributeHomoHashEnabled = "true"
|
||||
|
||||
// DisableHomomorphicHashing sets flag to disable homomorphic hashing of the
|
||||
|
@ -478,11 +451,19 @@ func ReadDomain(cnr Container) (res Domain) {
|
|||
// and writes it into dst. Signature instance MUST NOT be nil. CalculateSignature
|
||||
// is expected to be called after all the Container data is filled and before
|
||||
// saving the Container in the NeoFS network. Note that мany subsequent change
|
||||
// will most likely break the signature.
|
||||
// will most likely break the signature. signer MUST be of
|
||||
// [neofscrypto.ECDSA_DETERMINISTIC_SHA256] scheme, for example, [neofsecdsa.SignerRFC6979]
|
||||
// can be used.
|
||||
//
|
||||
// See also VerifySignature.
|
||||
func CalculateSignature(dst *neofscrypto.Signature, cnr Container, signer ecdsa.PrivateKey) error {
|
||||
return dst.Calculate(neofsecdsa.SignerRFC6979(signer), cnr.Marshal())
|
||||
//
|
||||
// Returned errors:
|
||||
// - [neofscrypto.ErrIncorrectSigner]
|
||||
func CalculateSignature(dst *neofscrypto.Signature, cnr Container, signer neofscrypto.Signer) error {
|
||||
if signer.Scheme() != neofscrypto.ECDSA_DETERMINISTIC_SHA256 {
|
||||
return fmt.Errorf("%w: expected ECDSA_DETERMINISTIC_SHA256 scheme", neofscrypto.ErrIncorrectSigner)
|
||||
}
|
||||
return dst.Calculate(signer, cnr.Marshal())
|
||||
}
|
||||
|
||||
// VerifySignature verifies Container signature calculated using CalculateSignature.
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
v2container "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"
|
||||
|
@ -16,16 +15,15 @@ import (
|
|||
cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test"
|
||||
containertest "github.com/nspcc-dev/neofs-sdk-go/container/test"
|
||||
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/crypto/test"
|
||||
netmaptest "github.com/nspcc-dev/neofs-sdk-go/netmap/test"
|
||||
subnetid "github.com/nspcc-dev/neofs-sdk-go/subnet/id"
|
||||
subnetidtest "github.com/nspcc-dev/neofs-sdk-go/subnet/id/test"
|
||||
usertest "github.com/nspcc-dev/neofs-sdk-go/user/test"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/version"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPlacementPolicyEncoding(t *testing.T) {
|
||||
v := containertest.Container()
|
||||
v := containertest.Container(t)
|
||||
|
||||
t.Run("binary", func(t *testing.T) {
|
||||
var v2 container.Container
|
||||
|
@ -46,7 +44,7 @@ func TestPlacementPolicyEncoding(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestContainer_Init(t *testing.T) {
|
||||
val := containertest.Container()
|
||||
val := containertest.Container(t)
|
||||
|
||||
val.Init()
|
||||
|
||||
|
@ -78,9 +76,9 @@ func TestContainer_Owner(t *testing.T) {
|
|||
|
||||
require.Zero(t, val.Owner())
|
||||
|
||||
val = containertest.Container()
|
||||
val = containertest.Container(t)
|
||||
|
||||
owner := *usertest.ID()
|
||||
owner := *usertest.ID(t)
|
||||
|
||||
val.SetOwner(owner)
|
||||
|
||||
|
@ -103,7 +101,7 @@ func TestContainer_BasicACL(t *testing.T) {
|
|||
|
||||
require.Zero(t, val.BasicACL())
|
||||
|
||||
val = containertest.Container()
|
||||
val = containertest.Container(t)
|
||||
|
||||
basicACL := containertest.BasicACL()
|
||||
val.SetBasicACL(basicACL)
|
||||
|
@ -124,7 +122,7 @@ func TestContainer_PlacementPolicy(t *testing.T) {
|
|||
|
||||
require.Zero(t, val.PlacementPolicy())
|
||||
|
||||
val = containertest.Container()
|
||||
val = containertest.Container(t)
|
||||
|
||||
pp := netmaptest.PlacementPolicy()
|
||||
val.SetPlacementPolicy(pp)
|
||||
|
@ -155,7 +153,7 @@ func TestContainer_Attribute(t *testing.T) {
|
|||
const attrKey1, attrKey2 = "key1", "key2"
|
||||
const attrVal1, attrVal2 = "val1", "val2"
|
||||
|
||||
val := containertest.Container()
|
||||
val := containertest.Container(t)
|
||||
|
||||
val.SetAttribute(attrKey1, attrVal1)
|
||||
val.SetAttribute(attrKey2, attrVal2)
|
||||
|
@ -194,7 +192,7 @@ func TestSetName(t *testing.T) {
|
|||
container.SetName(&val, "")
|
||||
})
|
||||
|
||||
val = containertest.Container()
|
||||
val = containertest.Container(t)
|
||||
|
||||
const name = "some name"
|
||||
|
||||
|
@ -216,7 +214,7 @@ func TestSetCreationTime(t *testing.T) {
|
|||
|
||||
require.Zero(t, container.CreatedAt(val).Unix())
|
||||
|
||||
val = containertest.Container()
|
||||
val = containertest.Container(t)
|
||||
|
||||
creat := time.Now()
|
||||
|
||||
|
@ -233,34 +231,12 @@ func TestSetCreationTime(t *testing.T) {
|
|||
require.Equal(t, creat.Unix(), container.CreatedAt(val2).Unix())
|
||||
}
|
||||
|
||||
func TestSetSubnet(t *testing.T) {
|
||||
var val container.Container
|
||||
|
||||
require.True(t, subnetid.IsZero(container.Subnet(val)))
|
||||
|
||||
val = containertest.Container()
|
||||
|
||||
sub := subnetidtest.ID()
|
||||
|
||||
container.SetSubnet(&val, sub)
|
||||
|
||||
var msg v2container.Container
|
||||
val.WriteToV2(&msg)
|
||||
|
||||
assertContainsAttribute(t, msg, v2container.SysAttributeSubnet, sub.EncodeToString())
|
||||
|
||||
var val2 container.Container
|
||||
require.NoError(t, val2.ReadFromV2(msg))
|
||||
|
||||
require.Equal(t, sub, container.Subnet(val))
|
||||
}
|
||||
|
||||
func TestDisableHomomorphicHashing(t *testing.T) {
|
||||
var val container.Container
|
||||
|
||||
require.False(t, container.IsHomomorphicHashingDisabled(val))
|
||||
|
||||
val = containertest.Container()
|
||||
val = containertest.Container(t)
|
||||
|
||||
container.DisableHomomorphicHashing(&val)
|
||||
|
||||
|
@ -280,7 +256,7 @@ func TestWriteDomain(t *testing.T) {
|
|||
|
||||
require.Zero(t, container.ReadDomain(val).Name())
|
||||
|
||||
val = containertest.Container()
|
||||
val = containertest.Container(t)
|
||||
|
||||
const name = "domain name"
|
||||
|
||||
|
@ -312,7 +288,7 @@ func TestWriteDomain(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCalculateID(t *testing.T) {
|
||||
val := containertest.Container()
|
||||
val := containertest.Container(t)
|
||||
|
||||
require.False(t, container.AssertID(cidtest.ID(), val))
|
||||
|
||||
|
@ -332,14 +308,12 @@ func TestCalculateID(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCalculateSignature(t *testing.T) {
|
||||
key, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
val := containertest.Container()
|
||||
val := containertest.Container(t)
|
||||
|
||||
var sig neofscrypto.Signature
|
||||
|
||||
require.NoError(t, container.CalculateSignature(&sig, val, key.PrivateKey))
|
||||
require.Error(t, container.CalculateSignature(&sig, val, test.RandomSigner(t)))
|
||||
require.NoError(t, container.CalculateSignature(&sig, val, test.RandomSignerRFC6979(t)))
|
||||
|
||||
var msg refs.Signature
|
||||
sig.WriteToV2(&msg)
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
)
|
||||
|
||||
func TestContainer_NetworkConfig(t *testing.T) {
|
||||
c := containertest.Container()
|
||||
c := containertest.Container(t)
|
||||
nc := netmaptest.NetworkInfo()
|
||||
|
||||
t.Run("default", func(t *testing.T) {
|
||||
|
|
|
@ -2,6 +2,7 @@ package containertest
|
|||
|
||||
import (
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neofs-sdk-go/container"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/container/acl"
|
||||
|
@ -11,8 +12,8 @@ import (
|
|||
)
|
||||
|
||||
// Container returns random container.Container.
|
||||
func Container() (x container.Container) {
|
||||
owner := usertest.ID()
|
||||
func Container(t *testing.T) (x container.Container) {
|
||||
owner := usertest.ID(t)
|
||||
|
||||
x.Init()
|
||||
x.SetAttribute("some attribute", "value")
|
||||
|
|
|
@ -1,11 +1,18 @@
|
|||
package neofscrypto
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
||||
)
|
||||
|
||||
// ErrIncorrectSigner is returned from function when the signer passed to it
|
||||
// is incompatible with the function requirements. This variable is intended
|
||||
// to be used as documentation and for [errors.Is] purposes and MUST NOT be
|
||||
// changed.
|
||||
var ErrIncorrectSigner = errors.New("incorrect signer")
|
||||
|
||||
// Scheme represents digital signature algorithm with fixed cryptographic hash function.
|
||||
//
|
||||
// Negative values are reserved and depend on context (e.g. unsupported scheme).
|
||||
|
@ -92,3 +99,38 @@ type PublicKey interface {
|
|||
// Verify checks signature of the given data. True means correct signature.
|
||||
Verify(data, signature []byte) bool
|
||||
}
|
||||
|
||||
// StaticSigner emulates real sign and contains already precalculated hash.
|
||||
// Provides neofscrypto.Signer interface.
|
||||
type StaticSigner struct {
|
||||
scheme Scheme
|
||||
sig []byte
|
||||
pubKey PublicKey
|
||||
}
|
||||
|
||||
// NewStaticSigner creates new StaticSigner.
|
||||
func NewStaticSigner(scheme Scheme, sig []byte, pubKey PublicKey) *StaticSigner {
|
||||
return &StaticSigner{
|
||||
scheme: scheme,
|
||||
sig: sig,
|
||||
pubKey: pubKey,
|
||||
}
|
||||
}
|
||||
|
||||
// Scheme returns neofscrypto.ECDSA_DETERMINISTIC_SHA256.
|
||||
// Implements neofscrypto.Signer.
|
||||
func (s *StaticSigner) Scheme() Scheme {
|
||||
return s.scheme
|
||||
}
|
||||
|
||||
// Sign returns precalculated hash.
|
||||
// Implements neofscrypto.Signer.
|
||||
func (s *StaticSigner) Sign(_ []byte) ([]byte, error) {
|
||||
return s.sig, nil
|
||||
}
|
||||
|
||||
// Public returns neofscrypto.PublicKey.
|
||||
// Implements neofscrypto.Signer.
|
||||
func (s *StaticSigner) Public() PublicKey {
|
||||
return s.pubKey
|
||||
}
|
||||
|
|
33
crypto/test/tests.go
Normal file
33
crypto/test/tests.go
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
Package tests provides special help functions for testing NeoFS API and its environment.
|
||||
|
||||
All functions accepting `t *testing.T` that emphasize there are only for tests purposes.
|
||||
*/
|
||||
package test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
|
||||
neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// RandomSigner return neofscrypto.Signer ONLY for TESTs purposes.
|
||||
// It may be used like helper to get new neofscrypto.Signer if you need it in yours tests.
|
||||
func RandomSigner(tb testing.TB) neofscrypto.Signer {
|
||||
p, err := keys.NewPrivateKey()
|
||||
require.NoError(tb, err)
|
||||
|
||||
return neofsecdsa.Signer(p.PrivateKey)
|
||||
}
|
||||
|
||||
// RandomSignerRFC6979 return neofscrypto.Signer ONLY for TESTs purposes.
|
||||
// It may be used like helper to get new neofscrypto.Signer if you need it in yours tests.
|
||||
func RandomSignerRFC6979(tb testing.TB) neofscrypto.Signer {
|
||||
p, err := keys.NewPrivateKey()
|
||||
require.NoError(tb, err)
|
||||
|
||||
return neofsecdsa.SignerRFC6979(p.PrivateKey)
|
||||
}
|
|
@ -127,21 +127,30 @@ func ActionFromV2(action v2acl.Action) (a Action) {
|
|||
return a
|
||||
}
|
||||
|
||||
// String returns string representation of Action.
|
||||
// EncodeToString returns string representation of Action.
|
||||
//
|
||||
// String mapping:
|
||||
// - ActionAllow: ALLOW;
|
||||
// - ActionDeny: DENY;
|
||||
// - ActionUnknown, default: ACTION_UNSPECIFIED.
|
||||
func (a Action) String() string {
|
||||
func (a Action) EncodeToString() string {
|
||||
return a.ToV2().String()
|
||||
}
|
||||
|
||||
// FromString parses Action from a string representation.
|
||||
// It is a reverse action to String().
|
||||
// String implements fmt.Stringer.
|
||||
//
|
||||
// String is designed to be human-readable, and its format MAY differ between
|
||||
// SDK versions. String MAY return same result as EncodeToString. String MUST NOT
|
||||
// be used to encode ID into NeoFS protocol string.
|
||||
func (a Action) String() string {
|
||||
return a.EncodeToString()
|
||||
}
|
||||
|
||||
// DecodeString parses Action from a string representation.
|
||||
// It is a reverse action to EncodeToString().
|
||||
//
|
||||
// Returns true if s was parsed successfully.
|
||||
func (a *Action) FromString(s string) bool {
|
||||
func (a *Action) DecodeString(s string) bool {
|
||||
var g v2acl.Action
|
||||
|
||||
ok := g.FromString(s)
|
||||
|
@ -199,7 +208,7 @@ func OperationFromV2(operation v2acl.Operation) (o Operation) {
|
|||
return o
|
||||
}
|
||||
|
||||
// String returns string representation of Operation.
|
||||
// EncodeToString returns string representation of Operation.
|
||||
//
|
||||
// String mapping:
|
||||
// - OperationGet: GET;
|
||||
|
@ -210,15 +219,24 @@ func OperationFromV2(operation v2acl.Operation) (o Operation) {
|
|||
// - OperationRange: GETRANGE;
|
||||
// - OperationRangeHash: GETRANGEHASH;
|
||||
// - OperationUnknown, default: OPERATION_UNSPECIFIED.
|
||||
func (o Operation) String() string {
|
||||
func (o Operation) EncodeToString() string {
|
||||
return o.ToV2().String()
|
||||
}
|
||||
|
||||
// FromString parses Operation from a string representation.
|
||||
// It is a reverse action to String().
|
||||
// String implements fmt.Stringer.
|
||||
//
|
||||
// String is designed to be human-readable, and its format MAY differ between
|
||||
// SDK versions. String MAY return same result as EncodeToString. String MUST NOT
|
||||
// be used to encode ID into NeoFS protocol string.
|
||||
func (o Operation) String() string {
|
||||
return o.EncodeToString()
|
||||
}
|
||||
|
||||
// DecodeString parses Operation from a string representation.
|
||||
// It is a reverse action to EncodeToString().
|
||||
//
|
||||
// Returns true if s was parsed successfully.
|
||||
func (o *Operation) FromString(s string) bool {
|
||||
func (o *Operation) DecodeString(s string) bool {
|
||||
var g v2acl.Operation
|
||||
|
||||
ok := g.FromString(s)
|
||||
|
@ -260,22 +278,31 @@ func RoleFromV2(role v2acl.Role) (r Role) {
|
|||
return r
|
||||
}
|
||||
|
||||
// String returns string representation of Role.
|
||||
// EncodeToString returns string representation of Role.
|
||||
//
|
||||
// String mapping:
|
||||
// - RoleUser: USER;
|
||||
// - RoleSystem: SYSTEM;
|
||||
// - RoleOthers: OTHERS;
|
||||
// - RoleUnknown, default: ROLE_UNKNOWN.
|
||||
func (r Role) String() string {
|
||||
func (r Role) EncodeToString() string {
|
||||
return r.ToV2().String()
|
||||
}
|
||||
|
||||
// FromString parses Role from a string representation.
|
||||
// It is a reverse action to String().
|
||||
// String implements fmt.Stringer.
|
||||
//
|
||||
// String is designed to be human-readable, and its format MAY differ between
|
||||
// SDK versions. String MAY return same result as EncodeToString. String MUST NOT
|
||||
// be used to encode ID into NeoFS protocol string.
|
||||
func (r Role) String() string {
|
||||
return r.EncodeToString()
|
||||
}
|
||||
|
||||
// DecodeString parses Role from a string representation.
|
||||
// It is a reverse action to EncodeToString().
|
||||
//
|
||||
// Returns true if s was parsed successfully.
|
||||
func (r *Role) FromString(s string) bool {
|
||||
func (r *Role) DecodeString(s string) bool {
|
||||
var g v2acl.Role
|
||||
|
||||
ok := g.FromString(s)
|
||||
|
@ -313,21 +340,30 @@ func MatchFromV2(match v2acl.MatchType) (m Match) {
|
|||
return m
|
||||
}
|
||||
|
||||
// String returns string representation of Match.
|
||||
// EncodeToString returns string representation of Match.
|
||||
//
|
||||
// String mapping:
|
||||
// - MatchStringEqual: STRING_EQUAL;
|
||||
// - MatchStringNotEqual: STRING_NOT_EQUAL;
|
||||
// - MatchUnknown, default: MATCH_TYPE_UNSPECIFIED.
|
||||
func (m Match) String() string {
|
||||
func (m Match) EncodeToString() string {
|
||||
return m.ToV2().String()
|
||||
}
|
||||
|
||||
// FromString parses Match from a string representation.
|
||||
// It is a reverse action to String().
|
||||
// String implements fmt.Stringer.
|
||||
//
|
||||
// String is designed to be human-readable, and its format MAY differ between
|
||||
// SDK versions. String MAY return same result as EncodeToString. String MUST NOT
|
||||
// be used to encode ID into NeoFS protocol string.
|
||||
func (m Match) String() string {
|
||||
return m.EncodeToString()
|
||||
}
|
||||
|
||||
// DecodeString parses Match from a string representation.
|
||||
// It is a reverse action to EncodeToString().
|
||||
//
|
||||
// Returns true if s was parsed successfully.
|
||||
func (m *Match) FromString(s string) bool {
|
||||
func (m *Match) DecodeString(s string) bool {
|
||||
var g v2acl.MatchType
|
||||
|
||||
ok := g.FromString(s)
|
||||
|
@ -369,21 +405,30 @@ func FilterHeaderTypeFromV2(header v2acl.HeaderType) (h FilterHeaderType) {
|
|||
return h
|
||||
}
|
||||
|
||||
// String returns string representation of FilterHeaderType.
|
||||
// EncodeToString returns string representation of FilterHeaderType.
|
||||
//
|
||||
// String mapping:
|
||||
// - HeaderFromRequest: REQUEST;
|
||||
// - HeaderFromObject: OBJECT;
|
||||
// - HeaderTypeUnknown, default: HEADER_UNSPECIFIED.
|
||||
func (h FilterHeaderType) String() string {
|
||||
func (h FilterHeaderType) EncodeToString() string {
|
||||
return h.ToV2().String()
|
||||
}
|
||||
|
||||
// FromString parses FilterHeaderType from a string representation.
|
||||
// It is a reverse action to String().
|
||||
// String implements fmt.Stringer.
|
||||
//
|
||||
// String is designed to be human-readable, and its format MAY differ between
|
||||
// SDK versions. String MAY return same result as EncodeToString. String MUST NOT
|
||||
// be used to encode ID into NeoFS protocol string.
|
||||
func (h FilterHeaderType) String() string {
|
||||
return h.EncodeToString()
|
||||
}
|
||||
|
||||
// DecodeString parses FilterHeaderType from a string representation.
|
||||
// It is a reverse action to EncodeToString().
|
||||
//
|
||||
// Returns true if s was parsed successfully.
|
||||
func (h *FilterHeaderType) FromString(s string) bool {
|
||||
func (h *FilterHeaderType) DecodeString(s string) bool {
|
||||
var g v2acl.HeaderType
|
||||
|
||||
ok := g.FromString(s)
|
||||
|
|
|
@ -118,8 +118,8 @@ func TestFilterHeaderType(t *testing.T) {
|
|||
}
|
||||
|
||||
type enumIface interface {
|
||||
FromString(string) bool
|
||||
String() string
|
||||
DecodeString(string) bool
|
||||
EncodeToString() string
|
||||
}
|
||||
|
||||
type enumStringItem struct {
|
||||
|
@ -129,11 +129,11 @@ type enumStringItem struct {
|
|||
|
||||
func testEnumStrings(t *testing.T, e enumIface, items []enumStringItem) {
|
||||
for _, item := range items {
|
||||
require.Equal(t, item.str, item.val.String())
|
||||
require.Equal(t, item.str, item.val.EncodeToString())
|
||||
|
||||
s := item.val.String()
|
||||
s := item.val.EncodeToString()
|
||||
|
||||
require.True(t, e.FromString(s), s)
|
||||
require.True(t, e.DecodeString(s), s)
|
||||
|
||||
require.EqualValues(t, item.val, e, item.val)
|
||||
}
|
||||
|
@ -143,7 +143,7 @@ func testEnumStrings(t *testing.T, e enumIface, items []enumStringItem) {
|
|||
"some string",
|
||||
"UNSPECIFIED",
|
||||
} {
|
||||
require.False(t, e.FromString(str))
|
||||
require.False(t, e.DecodeString(str))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -146,7 +146,7 @@ func (r *Record) AddObjectPayloadHashFilter(m Match, h checksum.Checksum) {
|
|||
|
||||
// AddObjectTypeFilter adds filter by object type.
|
||||
func (r *Record) AddObjectTypeFilter(m Match, t object.Type) {
|
||||
r.addObjectReservedFilter(m, fKeyObjType, staticStringer(t.String()))
|
||||
r.addObjectReservedFilter(m, fKeyObjType, staticStringer(t.EncodeToString()))
|
||||
}
|
||||
|
||||
// AddObjectHomomorphicHashFilter adds filter by object payload homomorphic hash value.
|
||||
|
|
|
@ -154,7 +154,7 @@ func TestReservedRecords(t *testing.T) {
|
|||
v = versiontest.Version()
|
||||
oid = oidtest.ID()
|
||||
cid = cidtest.ID()
|
||||
ownerid = usertest.ID()
|
||||
ownerid = usertest.ID(t)
|
||||
h = checksumtest.Checksum()
|
||||
typ = new(object.Type)
|
||||
)
|
||||
|
@ -211,7 +211,7 @@ func TestReservedRecords(t *testing.T) {
|
|||
},
|
||||
{
|
||||
f: func(r *Record) {
|
||||
require.True(t, typ.FromString("REGULAR"))
|
||||
require.True(t, typ.DecodeString("REGULAR"))
|
||||
r.AddObjectTypeFilter(MatchStringEqual, *typ)
|
||||
},
|
||||
key: v2acl.FilterObjectType,
|
||||
|
@ -219,7 +219,7 @@ func TestReservedRecords(t *testing.T) {
|
|||
},
|
||||
{
|
||||
f: func(r *Record) {
|
||||
require.True(t, typ.FromString("TOMBSTONE"))
|
||||
require.True(t, typ.DecodeString("TOMBSTONE"))
|
||||
r.AddObjectTypeFilter(MatchStringEqual, *typ)
|
||||
},
|
||||
key: v2acl.FilterObjectType,
|
||||
|
@ -227,7 +227,7 @@ func TestReservedRecords(t *testing.T) {
|
|||
},
|
||||
{
|
||||
f: func(r *Record) {
|
||||
require.True(t, typ.FromString("STORAGE_GROUP"))
|
||||
require.True(t, typ.DecodeString("STORAGE_GROUP"))
|
||||
r.AddObjectTypeFilter(MatchStringEqual, *typ)
|
||||
},
|
||||
key: v2acl.FilterObjectType,
|
||||
|
|
|
@ -66,7 +66,7 @@ func TestTable_AddRecord(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTableEncoding(t *testing.T) {
|
||||
tab := eacltest.Table()
|
||||
tab := eacltest.Table(t)
|
||||
|
||||
t.Run("binary", func(t *testing.T) {
|
||||
data, err := tab.Marshal()
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package eacltest
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/eacl"
|
||||
usertest "github.com/nspcc-dev/neofs-sdk-go/user/test"
|
||||
|
@ -21,24 +23,24 @@ func Target() *eacl.Target {
|
|||
}
|
||||
|
||||
// Record returns random eacl.Record.
|
||||
func Record() *eacl.Record {
|
||||
func Record(tb testing.TB) *eacl.Record {
|
||||
x := eacl.NewRecord()
|
||||
|
||||
x.SetAction(eacl.ActionAllow)
|
||||
x.SetOperation(eacl.OperationRangeHash)
|
||||
x.SetTargets(*Target(), *Target())
|
||||
x.AddObjectContainerIDFilter(eacl.MatchStringEqual, cidtest.ID())
|
||||
x.AddObjectOwnerIDFilter(eacl.MatchStringNotEqual, usertest.ID())
|
||||
x.AddObjectOwnerIDFilter(eacl.MatchStringNotEqual, usertest.ID(tb))
|
||||
|
||||
return x
|
||||
}
|
||||
|
||||
func Table() *eacl.Table {
|
||||
func Table(tb testing.TB) *eacl.Table {
|
||||
x := eacl.NewTable()
|
||||
|
||||
x.SetCID(cidtest.ID())
|
||||
x.AddRecord(Record())
|
||||
x.AddRecord(Record())
|
||||
x.AddRecord(Record(tb))
|
||||
x.AddRecord(Record(tb))
|
||||
x.SetVersion(versiontest.Version())
|
||||
|
||||
return x
|
||||
|
|
32
go.mod
32
go.mod
|
@ -1,40 +1,40 @@
|
|||
module github.com/nspcc-dev/neofs-sdk-go
|
||||
|
||||
go 1.17
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10
|
||||
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20221202181307-76fa05c21b12
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/hashicorp/golang-lru v0.5.4
|
||||
github.com/hashicorp/golang-lru v0.6.0
|
||||
github.com/mr-tron/base58 v1.2.0
|
||||
github.com/nspcc-dev/hrw v1.0.9
|
||||
github.com/nspcc-dev/neo-go v0.99.4
|
||||
github.com/nspcc-dev/neo-go v0.100.1
|
||||
github.com/nspcc-dev/neofs-api-go/v2 v2.14.0
|
||||
github.com/nspcc-dev/neofs-contract v0.16.0
|
||||
github.com/nspcc-dev/tzhash v1.6.1
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/nspcc-dev/tzhash v1.7.0
|
||||
github.com/stretchr/testify v1.8.1
|
||||
go.uber.org/atomic v1.10.0
|
||||
go.uber.org/zap v1.23.0
|
||||
go.uber.org/zap v1.24.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/btcsuite/btcd v0.22.0-beta // indirect
|
||||
github.com/coreos/go-semver v0.3.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/gorilla/websocket v1.4.2 // indirect
|
||||
github.com/nspcc-dev/go-ordered-json v0.0.0-20220111165707-25110be27d22 // indirect
|
||||
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20220927123257-24c107e3a262 // indirect
|
||||
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb // indirect
|
||||
github.com/nspcc-dev/neofs-crypto v0.4.0 // indirect
|
||||
github.com/nspcc-dev/rfc6979 v0.2.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||
go.uber.org/multierr v1.8.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
|
||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f // indirect
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
go.uber.org/multierr v1.9.0 // indirect
|
||||
golang.org/x/crypto v0.4.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20221227203929-1b447090c38c // indirect
|
||||
golang.org/x/net v0.3.0 // indirect
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
|
||||
golang.org/x/sys v0.8.0 // indirect
|
||||
golang.org/x/text v0.5.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 // indirect
|
||||
google.golang.org/grpc v1.48.0 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
|
|
63
go.sum
63
go.sum
|
@ -51,15 +51,14 @@ github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGn
|
|||
github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210521073959-f0d4d129b7f1/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY=
|
||||
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 h1:yL7+Jz0jTC6yykIK/Wh74gnTJnrGr5AyrNMXuA0gves=
|
||||
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY=
|
||||
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20221202181307-76fa05c21b12 h1:npHgfD4Tl2WJS3AJaMUi5ynGDPUBfkg3U3fCzDyXZ+4=
|
||||
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20221202181307-76fa05c21b12/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM=
|
||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
|
||||
github.com/btcsuite/btcd v0.22.0-beta h1:LTDpDKUM5EeOFBPM8IXpinEcmZ6FWfNZbE3lfrfdnWo=
|
||||
github.com/btcsuite/btcd v0.22.0-beta/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA=
|
||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
|
||||
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
|
||||
|
@ -86,7 +85,6 @@ github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWH
|
|||
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
|
@ -94,6 +92,9 @@ github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2
|
|||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
|
||||
github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
|
@ -194,8 +195,9 @@ github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad
|
|||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
|
||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/hashicorp/golang-lru v0.6.0 h1:uL2shRDx7RTrOrTCUZEGP/wJUFiUI8QT6E7z5o8jga4=
|
||||
github.com/hashicorp/golang-lru v0.6.0/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
|
@ -249,7 +251,6 @@ github.com/nspcc-dev/dbft v0.0.0-20200117124306-478e5cfbf03a/go.mod h1:/YFK+XOxx
|
|||
github.com/nspcc-dev/dbft v0.0.0-20200219114139-199d286ed6c1/go.mod h1:O0qtn62prQSqizzoagHmuuKoz8QMkU3SzBoKdEvm3aQ=
|
||||
github.com/nspcc-dev/dbft v0.0.0-20210721160347-1b03241391ac/go.mod h1:U8MSnEShH+o5hexfWJdze6uMFJteP0ko7J2frO7Yu1Y=
|
||||
github.com/nspcc-dev/dbft v0.0.0-20220629112714-fd49ca59d354/go.mod h1:U8MSnEShH+o5hexfWJdze6uMFJteP0ko7J2frO7Yu1Y=
|
||||
github.com/nspcc-dev/dbft v0.0.0-20220902113116-58a5e763e647/go.mod h1:g9xisXmX9NP9MjioaTe862n9SlZTrP+6PVUWLBYOr98=
|
||||
github.com/nspcc-dev/go-ordered-json v0.0.0-20210915112629-e1b6cce73d02/go.mod h1:79bEUDEviBHJMFV6Iq6in57FEOCMcRhfQnfaf0ETA5U=
|
||||
github.com/nspcc-dev/go-ordered-json v0.0.0-20220111165707-25110be27d22 h1:n4ZaFCKt1pQJd7PXoMJabZWK9ejjbLOVrkl/lOUmshg=
|
||||
github.com/nspcc-dev/go-ordered-json v0.0.0-20220111165707-25110be27d22/go.mod h1:79bEUDEviBHJMFV6Iq6in57FEOCMcRhfQnfaf0ETA5U=
|
||||
|
@ -258,11 +259,11 @@ github.com/nspcc-dev/hrw v1.0.9/go.mod h1:l/W2vx83vMQo6aStyx2AuZrJ+07lGv2JQGlVkP
|
|||
github.com/nspcc-dev/neo-go v0.73.1-pre.0.20200303142215-f5a1b928ce09/go.mod h1:pPYwPZ2ks+uMnlRLUyXOpLieaDQSEaf4NM3zHVbRjmg=
|
||||
github.com/nspcc-dev/neo-go v0.98.0/go.mod h1:E3cc1x6RXSXrJb2nDWXTXjnXk3rIqVN8YdFyWv+FrqM=
|
||||
github.com/nspcc-dev/neo-go v0.99.2/go.mod h1:9P0yWqhZX7i/ChJ+zjtiStO1uPTolPFUM+L5oNznU8E=
|
||||
github.com/nspcc-dev/neo-go v0.99.4 h1:8Y+SdRxksC72a4PNkcGCh/aaQinh9Gu+c5LilbcsXOI=
|
||||
github.com/nspcc-dev/neo-go v0.99.4/go.mod h1:mKTolfRUfKjFso5HPvGSQtUZc70n0VKBMs16eGuC5gA=
|
||||
github.com/nspcc-dev/neo-go v0.100.1 h1:yugxbQRdzM+ObVa5mtr9/n4rYjxSIrryne8MVr9NBwU=
|
||||
github.com/nspcc-dev/neo-go v0.100.1/go.mod h1:Nnp7F4e9IBccsgtCeLtUWV+0T6gk1PtP5HRtA13hUfc=
|
||||
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20220809123759-3094d3e0c14b/go.mod h1:23bBw0v6pBYcrWs8CBEEDIEDJNbcFoIh8pGGcf2Vv8s=
|
||||
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20220927123257-24c107e3a262 h1:UTmSLZw5OpD/JPE1B5Vf98GF0zu2/Hsqq1lGLtStTUE=
|
||||
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20220927123257-24c107e3a262/go.mod h1:23bBw0v6pBYcrWs8CBEEDIEDJNbcFoIh8pGGcf2Vv8s=
|
||||
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb h1:GFxfkpXEYAbMIr69JpKOsQWeLOaGrd49HNAor8uDW+A=
|
||||
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20221202075445-cb5c18dc73eb/go.mod h1:23bBw0v6pBYcrWs8CBEEDIEDJNbcFoIh8pGGcf2Vv8s=
|
||||
github.com/nspcc-dev/neofs-api-go/v2 v2.11.0-pre.0.20211201134523-3604d96f3fe1/go.mod h1:oS8dycEh8PPf2Jjp6+8dlwWyEv2Dy77h/XhhcdxYEFs=
|
||||
github.com/nspcc-dev/neofs-api-go/v2 v2.11.1/go.mod h1:oS8dycEh8PPf2Jjp6+8dlwWyEv2Dy77h/XhhcdxYEFs=
|
||||
github.com/nspcc-dev/neofs-api-go/v2 v2.14.0 h1:jhuN8Ldqz7WApvUJRFY0bjRXE1R3iCkboMX5QVZhHVk=
|
||||
|
@ -279,8 +280,8 @@ github.com/nspcc-dev/neofs-sdk-go v0.0.0-20220113123743-7f3162110659/go.mod h1:/
|
|||
github.com/nspcc-dev/rfc6979 v0.1.0/go.mod h1:exhIh1PdpDC5vQmyEsGvc4YDM/lyQp/452QxGq/UEso=
|
||||
github.com/nspcc-dev/rfc6979 v0.2.0 h1:3e1WNxrN60/6N0DW7+UYisLeZJyfqZTNOjeV/toYvOE=
|
||||
github.com/nspcc-dev/rfc6979 v0.2.0/go.mod h1:exhIh1PdpDC5vQmyEsGvc4YDM/lyQp/452QxGq/UEso=
|
||||
github.com/nspcc-dev/tzhash v1.6.1 h1:8dUrWFpjkmoHF+7GxuGUmarj9LLHWFcuyF3CTrqq9JE=
|
||||
github.com/nspcc-dev/tzhash v1.6.1/go.mod h1:BoflzCVp+DO/f1mvbcsJQWoFzidIFBhWFZMglbUW648=
|
||||
github.com/nspcc-dev/tzhash v1.7.0 h1:/+aL33NC7y5OIGnY2kYgjZt8mg7LVGFMdj/KAJLndnk=
|
||||
github.com/nspcc-dev/tzhash v1.7.0/go.mod h1:Dnx9LUlOLr5paL2Rtc96x0PPs8D9eIkUtowt1n+KQus=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
|
@ -337,14 +338,16 @@ github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2
|
|||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/syndtr/goleveldb v0.0.0-20180307113352-169b1b37be73/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954 h1:xQdMZ1WLrgkkvOZ/LDQxjVxMLdby7osSh4ZEVa5sIjs=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM=
|
||||
|
@ -357,7 +360,6 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
|||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/gopher-lua v0.0.0-20190514113301-1cd887cd7036/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
|
||||
github.com/yuin/gopher-lua v0.0.0-20191128022950-c6266f4fe8d7/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
|
||||
|
@ -377,15 +379,14 @@ go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
|||
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
|
||||
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
|
||||
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
|
||||
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
|
||||
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
||||
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
|
||||
go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY=
|
||||
go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY=
|
||||
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
|
||||
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
|
||||
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
|
@ -396,8 +397,9 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
|||
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8=
|
||||
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
|
@ -408,6 +410,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
|
|||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp v0.0.0-20221227203929-1b447090c38c h1:Govq2W3bnHJimHT2ium65kXcI7ZzTniZHcFATnLJM0Q=
|
||||
golang.org/x/exp v0.0.0-20221227203929-1b447090c38c/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
|
@ -428,7 +432,6 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
|
|||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
@ -464,12 +467,12 @@ golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81R
|
|||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.3.0 h1:VWL6FNY2bEEmsGVKabSlHu5Irp34xmMRoqb/9lF9lxk=
|
||||
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
@ -488,8 +491,9 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
|
|||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8=
|
||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
@ -538,16 +542,15 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210429154555-c04ba851c2a4/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
|
@ -557,8 +560,9 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3
|
|||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
|
||||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
@ -605,7 +609,6 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc
|
|||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
@ -694,7 +697,6 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
|
|||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/abiosoft/ishell.v2 v2.0.0/go.mod h1:sFp+cGtH6o4s1FtpVPTMcHq2yue+c4DGOVohJCPUzwY=
|
||||
|
@ -713,7 +715,6 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
@ -6,6 +6,8 @@ import (
|
|||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||
oid "github.com/nspcc-dev/neofs-sdk-go/object/id"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -37,6 +39,20 @@ func compareNodes(t testing.TB, expected [][]int, nodes nodes, actual [][]NodeIn
|
|||
}
|
||||
}
|
||||
|
||||
func compareNodesIgnoreOrder(t testing.TB, expected [][]int, nodes nodes, actual [][]NodeInfo) {
|
||||
require.Equal(t, len(expected), len(actual))
|
||||
for i := range expected {
|
||||
require.Equal(t, len(expected[i]), len(actual[i]))
|
||||
|
||||
var expectedNodes []NodeInfo
|
||||
for _, index := range expected[i] {
|
||||
expectedNodes = append(expectedNodes, nodes[index])
|
||||
}
|
||||
|
||||
require.ElementsMatch(t, expectedNodes, actual[i])
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlacementPolicy_Interopability(t *testing.T) {
|
||||
const testsDir = "./json_tests"
|
||||
|
||||
|
@ -62,7 +78,10 @@ func TestPlacementPolicy_Interopability(t *testing.T) {
|
|||
|
||||
for name, tt := range tc.Tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
v, err := nm.ContainerNodes(tt.Policy, tt.Pivot)
|
||||
var pivot cid.ID
|
||||
copy(pivot[:], tt.Pivot)
|
||||
|
||||
v, err := nm.ContainerNodes(tt.Policy, pivot)
|
||||
if tt.Result == nil {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), tt.Error)
|
||||
|
@ -70,10 +89,13 @@ func TestPlacementPolicy_Interopability(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
require.Equal(t, srcNodes, tc.Nodes)
|
||||
|
||||
compareNodes(t, tt.Result, tc.Nodes, v)
|
||||
compareNodesIgnoreOrder(t, tt.Result, tc.Nodes, v)
|
||||
|
||||
if tt.Placement.Result != nil {
|
||||
res, err := nm.PlacementVectors(v, tt.Placement.Pivot)
|
||||
var placementPivot oid.ID
|
||||
copy(placementPivot[:], tt.Placement.Pivot)
|
||||
|
||||
res, err := nm.PlacementVectors(v, placementPivot)
|
||||
require.NoError(t, err)
|
||||
compareNodes(t, tt.Placement.Result, tc.Nodes, res)
|
||||
require.Equal(t, srcNodes, tc.Nodes)
|
||||
|
@ -108,11 +130,14 @@ func BenchmarkPlacementPolicyInteropability(b *testing.B) {
|
|||
|
||||
for name, tt := range tc.Tests {
|
||||
b.Run(name, func(b *testing.B) {
|
||||
var pivot cid.ID
|
||||
copy(pivot[:], tt.Pivot)
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
b.StartTimer()
|
||||
v, err := nm.ContainerNodes(tt.Policy, tt.Pivot)
|
||||
v, err := nm.ContainerNodes(tt.Policy, pivot)
|
||||
b.StopTimer()
|
||||
if tt.Result == nil {
|
||||
require.Error(b, err)
|
||||
|
@ -120,11 +145,14 @@ func BenchmarkPlacementPolicyInteropability(b *testing.B) {
|
|||
} else {
|
||||
require.NoError(b, err)
|
||||
|
||||
compareNodes(b, tt.Result, tc.Nodes, v)
|
||||
compareNodesIgnoreOrder(b, tt.Result, tc.Nodes, v)
|
||||
|
||||
if tt.Placement.Result != nil {
|
||||
var placementPivot oid.ID
|
||||
copy(placementPivot[:], tt.Placement.Pivot)
|
||||
|
||||
b.StartTimer()
|
||||
res, err := nm.PlacementVectors(v, tt.Placement.Pivot)
|
||||
res, err := nm.PlacementVectors(v, placementPivot)
|
||||
b.StopTimer()
|
||||
require.NoError(b, err)
|
||||
compareNodes(b, tt.Placement.Result, tc.Nodes, res)
|
||||
|
@ -150,11 +178,14 @@ func BenchmarkManySelects(b *testing.B) {
|
|||
var nm NetMap
|
||||
nm.SetNodes(tc.Nodes)
|
||||
|
||||
var pivot cid.ID
|
||||
copy(pivot[:], tt.Pivot)
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err = nm.ContainerNodes(tt.Policy, tt.Pivot)
|
||||
_, err = nm.ContainerNodes(tt.Policy, pivot)
|
||||
if err != nil {
|
||||
b.FailNow()
|
||||
}
|
||||
|
|
|
@ -183,9 +183,9 @@
|
|||
"policy": {"replicas":[{"count":1,"selector":"SameRU"},{"count":1,"selector":"DistinctRU"},{"count":1,"selector":"Good"},{"count":1,"selector":"Main"}],"containerBackupFactor":2,"selectors":[{"name":"SameRU","count":2,"clause":"SAME","attribute":"City","filter":"FromRU"},{"name":"DistinctRU","count":2,"clause":"DISTINCT","attribute":"City","filter":"FromRU"},{"name":"Good","count":2,"clause":"DISTINCT","attribute":"Country","filter":"Good"},{"name":"Main","count":3,"clause":"DISTINCT","attribute":"Country","filter":"*"}],"filters":[{"name":"FromRU","key":"Country","op":"EQ","value":"Russia"},{"name":"Good","key":"Rating","op":"GE","value":"4"}]},
|
||||
"result": [
|
||||
[0, 5, 9, 10],
|
||||
[2, 6, 0, 5],
|
||||
[0, 5, 2, 6],
|
||||
[1, 8, 2, 5],
|
||||
[3, 4, 1, 7, 0, 2]
|
||||
[0, 2, 1, 7, 3, 4]
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -321,10 +321,10 @@
|
|||
4
|
||||
],
|
||||
[
|
||||
8,
|
||||
12,
|
||||
5,
|
||||
10
|
||||
10,
|
||||
8,
|
||||
12
|
||||
]
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,254 +0,0 @@
|
|||
{
|
||||
"name": "subnet tests",
|
||||
"nodes": [
|
||||
{
|
||||
"attributes": [
|
||||
{
|
||||
"key": "ID",
|
||||
"value": "0"
|
||||
},
|
||||
{
|
||||
"key": "City",
|
||||
"value": "Paris"
|
||||
},
|
||||
{
|
||||
"key": "__NEOFS__SUBNET_0",
|
||||
"value": "False"
|
||||
}
|
||||
],
|
||||
"state": "UNSPECIFIED"
|
||||
},
|
||||
{
|
||||
"attributes": [
|
||||
{
|
||||
"key": "ID",
|
||||
"value": "1"
|
||||
},
|
||||
{
|
||||
"key": "City",
|
||||
"value": "Paris"
|
||||
}
|
||||
],
|
||||
"state": "UNSPECIFIED"
|
||||
},
|
||||
{
|
||||
"attributes": [
|
||||
{
|
||||
"key": "ID",
|
||||
"value": "2"
|
||||
},
|
||||
{
|
||||
"key": "City",
|
||||
"value": "London"
|
||||
},
|
||||
{
|
||||
"key": "__NEOFS__SUBNET_1",
|
||||
"value": "True"
|
||||
}
|
||||
],
|
||||
"state": "UNSPECIFIED"
|
||||
},
|
||||
{
|
||||
"attributes": [
|
||||
{
|
||||
"key": "ID",
|
||||
"value": "3"
|
||||
},
|
||||
{
|
||||
"key": "City",
|
||||
"value": "London"
|
||||
}
|
||||
],
|
||||
"state": "UNSPECIFIED"
|
||||
},
|
||||
{
|
||||
"attributes": [
|
||||
{
|
||||
"key": "ID",
|
||||
"value": "4"
|
||||
},
|
||||
{
|
||||
"key": "City",
|
||||
"value": "Toronto"
|
||||
},
|
||||
{
|
||||
"key": "__NEOFS__SUBNET_1",
|
||||
"value": "True"
|
||||
}
|
||||
],
|
||||
"state": "UNSPECIFIED"
|
||||
},
|
||||
{
|
||||
"attributes": [
|
||||
{
|
||||
"key": "ID",
|
||||
"value": "5"
|
||||
},
|
||||
{
|
||||
"key": "City",
|
||||
"value": "Toronto"
|
||||
},
|
||||
{
|
||||
"key": "__NEOFS__SUBNET_2",
|
||||
"value": "True"
|
||||
}
|
||||
],
|
||||
"state": "UNSPECIFIED"
|
||||
},
|
||||
{
|
||||
"attributes": [
|
||||
{
|
||||
"key": "ID",
|
||||
"value": "6"
|
||||
},
|
||||
{
|
||||
"key": "City",
|
||||
"value": "Tokyo"
|
||||
},
|
||||
{
|
||||
"key": "__NEOFS__SUBNET_2",
|
||||
"value": "True"
|
||||
}
|
||||
],
|
||||
"state": "UNSPECIFIED"
|
||||
},
|
||||
{
|
||||
"attributes": [
|
||||
{
|
||||
"key": "ID",
|
||||
"value": "7"
|
||||
},
|
||||
{
|
||||
"key": "City",
|
||||
"value": "Tokyo"
|
||||
},
|
||||
{
|
||||
"key": "__NEOFS__SUBNET_2",
|
||||
"value": "True"
|
||||
}
|
||||
],
|
||||
"state": "UNSPECIFIED"
|
||||
}
|
||||
],
|
||||
"tests": {
|
||||
"select from default subnet, fail": {
|
||||
"policy": {
|
||||
"replicas": [
|
||||
{
|
||||
"count": 1,
|
||||
"selector": "S"
|
||||
}
|
||||
],
|
||||
"containerBackupFactor": 0,
|
||||
"selectors": [
|
||||
{
|
||||
"name": "S",
|
||||
"count": 2,
|
||||
"clause": "SAME",
|
||||
"attribute": "City",
|
||||
"filter": "F"
|
||||
}
|
||||
],
|
||||
"filters": [
|
||||
{
|
||||
"name": "F",
|
||||
"key": "City",
|
||||
"op": "EQ",
|
||||
"value": "Paris",
|
||||
"filters": []
|
||||
}
|
||||
],
|
||||
"subnetId": null
|
||||
},
|
||||
"error": "not enough nodes"
|
||||
},
|
||||
"select from default subnet, success": {
|
||||
"policy": {
|
||||
"replicas": [
|
||||
{
|
||||
"count": 1,
|
||||
"selector": "S"
|
||||
}
|
||||
],
|
||||
"containerBackupFactor": 0,
|
||||
"selectors": [
|
||||
{
|
||||
"name": "S",
|
||||
"count": 2,
|
||||
"clause": "SAME",
|
||||
"attribute": "City",
|
||||
"filter": "F"
|
||||
}
|
||||
],
|
||||
"filters": [
|
||||
{
|
||||
"name": "F",
|
||||
"key": "City",
|
||||
"op": "EQ",
|
||||
"value": "Toronto",
|
||||
"filters": []
|
||||
}
|
||||
],
|
||||
"subnetId": null
|
||||
},
|
||||
"result": [
|
||||
[
|
||||
4,
|
||||
5
|
||||
]
|
||||
]
|
||||
},
|
||||
"select from non-default subnet, success": {
|
||||
"policy": {
|
||||
"replicas": [
|
||||
{
|
||||
"count": 3,
|
||||
"selector": ""
|
||||
}
|
||||
],
|
||||
"containerBackupFactor": 0,
|
||||
"selectors": [],
|
||||
"filters": [],
|
||||
"subnetId": {
|
||||
"value": 2
|
||||
}
|
||||
},
|
||||
"result": [
|
||||
[
|
||||
5,
|
||||
6,
|
||||
7
|
||||
]
|
||||
]
|
||||
},
|
||||
"select subnet via filters": {
|
||||
"policy": {
|
||||
"replicas": [
|
||||
{
|
||||
"count": 1,
|
||||
"selector": "S"
|
||||
}
|
||||
],
|
||||
"containerBackupFactor": 1,
|
||||
"selectors": [
|
||||
{
|
||||
"name": "S",
|
||||
"count": 1,
|
||||
"clause": "SAME",
|
||||
"attribute": "City",
|
||||
"filter": "F"
|
||||
}
|
||||
],
|
||||
"filters": [
|
||||
{
|
||||
"name": "F",
|
||||
"key": "__NEOFS_SUBNET.2.ENABLED",
|
||||
"op": "EQ",
|
||||
"value": "True"
|
||||
}
|
||||
]
|
||||
},
|
||||
"error": "not enough nodes"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +1,13 @@
|
|||
package netmap
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
|
||||
"github.com/nspcc-dev/hrw"
|
||||
"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"
|
||||
)
|
||||
|
||||
// NetMap represents NeoFS network map. It includes information about all
|
||||
|
@ -140,11 +143,14 @@ func flattenNodes(ns []nodes) nodes {
|
|||
}
|
||||
|
||||
// PlacementVectors sorts container nodes returned by ContainerNodes method
|
||||
// and returns placement vectors for the entity identified by the given pivot.
|
||||
// and returns placement vectors for the entity identified by the given object id.
|
||||
// For example, in order to build node list to store the object, binary-encoded
|
||||
// object identifier can be used as pivot. Result is deterministic for
|
||||
// the fixed NetMap and parameters.
|
||||
func (m NetMap) PlacementVectors(vectors [][]NodeInfo, pivot []byte) ([][]NodeInfo, error) {
|
||||
func (m NetMap) PlacementVectors(vectors [][]NodeInfo, objectID oid.ID) ([][]NodeInfo, error) {
|
||||
pivot := make([]byte, sha256.Size)
|
||||
objectID.Encode(pivot)
|
||||
|
||||
h := hrw.Hash(pivot)
|
||||
wf := defaultWeightFunc(m.nodes)
|
||||
result := make([][]NodeInfo, len(vectors))
|
||||
|
@ -162,15 +168,18 @@ func (m NetMap) PlacementVectors(vectors [][]NodeInfo, pivot []byte) ([][]NodeIn
|
|||
// given PlacementPolicy to the NetMap. Each line of the list corresponds to a
|
||||
// replica descriptor. Line order corresponds to order of ReplicaDescriptor list
|
||||
// in the policy. Nodes are pre-filtered according to the Filter list from
|
||||
// the policy, and then selected by Selector list. Result is deterministic for
|
||||
// the fixed NetMap and parameters.
|
||||
// the policy, and then selected by Selector list. Result is not deterministic and
|
||||
// node order in each vector may vary for call.
|
||||
//
|
||||
// Result can be used in PlacementVectors.
|
||||
func (m NetMap) ContainerNodes(p PlacementPolicy, pivot []byte) ([][]NodeInfo, error) {
|
||||
func (m NetMap) ContainerNodes(p PlacementPolicy, containerID cid.ID) ([][]NodeInfo, error) {
|
||||
c := newContext(m)
|
||||
c.setPivot(pivot)
|
||||
c.setCBF(p.backupFactor)
|
||||
|
||||
pivot := make([]byte, sha256.Size)
|
||||
containerID.Encode(pivot)
|
||||
c.setPivot(pivot)
|
||||
|
||||
if err := c.processFilters(p); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -112,6 +112,33 @@ func (x NetworkInfo) WriteToV2(m *netmap.NetworkInfo) {
|
|||
*m = x.m
|
||||
}
|
||||
|
||||
// Marshal encodes NetworkInfo into a binary format of the NeoFS API protocol
|
||||
// (Protocol Buffers with direct field order).
|
||||
//
|
||||
// See also Unmarshal.
|
||||
func (x NetworkInfo) Marshal() []byte {
|
||||
var m netmap.NetworkInfo
|
||||
x.WriteToV2(&m)
|
||||
|
||||
return m.StableMarshal(nil)
|
||||
}
|
||||
|
||||
// Unmarshal decodes NeoFS API protocol binary format into the NetworkInfo
|
||||
// (Protocol Buffers with direct field order). Returns an error describing
|
||||
// a format violation.
|
||||
//
|
||||
// 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)
|
||||
}
|
||||
|
||||
// CurrentEpoch returns epoch set using SetCurrentEpoch.
|
||||
//
|
||||
// Zero NetworkInfo has zero current epoch.
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
||||
. "github.com/nspcc-dev/neofs-sdk-go/netmap"
|
||||
netmaptest "github.com/nspcc-dev/neofs-sdk-go/netmap/test"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -62,16 +63,16 @@ func TestNetworkInfo_MsPerBlock(t *testing.T) {
|
|||
}
|
||||
|
||||
func testConfigValue(t *testing.T,
|
||||
getter func(x NetworkInfo) interface{},
|
||||
setter func(x *NetworkInfo, val interface{}),
|
||||
val1, val2 interface{},
|
||||
v2Key string, v2Val func(val interface{}) []byte,
|
||||
getter func(x NetworkInfo) any,
|
||||
setter func(x *NetworkInfo, val any),
|
||||
val1, val2 any,
|
||||
v2Key string, v2Val func(val any) []byte,
|
||||
) {
|
||||
var x NetworkInfo
|
||||
|
||||
require.Zero(t, getter(x))
|
||||
|
||||
checkVal := func(exp interface{}) {
|
||||
checkVal := func(exp any) {
|
||||
require.EqualValues(t, exp, getter(x))
|
||||
|
||||
var m netmap.NetworkInfo
|
||||
|
@ -98,10 +99,10 @@ func testConfigValue(t *testing.T,
|
|||
|
||||
func TestNetworkInfo_AuditFee(t *testing.T) {
|
||||
testConfigValue(t,
|
||||
func(x NetworkInfo) interface{} { return x.AuditFee() },
|
||||
func(info *NetworkInfo, val interface{}) { info.SetAuditFee(val.(uint64)) },
|
||||
func(x NetworkInfo) any { return x.AuditFee() },
|
||||
func(info *NetworkInfo, val any) { info.SetAuditFee(val.(uint64)) },
|
||||
uint64(1), uint64(2),
|
||||
"AuditFee", func(val interface{}) []byte {
|
||||
"AuditFee", func(val any) []byte {
|
||||
data := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(data, val.(uint64))
|
||||
return data
|
||||
|
@ -111,10 +112,10 @@ func TestNetworkInfo_AuditFee(t *testing.T) {
|
|||
|
||||
func TestNetworkInfo_StoragePrice(t *testing.T) {
|
||||
testConfigValue(t,
|
||||
func(x NetworkInfo) interface{} { return x.StoragePrice() },
|
||||
func(info *NetworkInfo, val interface{}) { info.SetStoragePrice(val.(uint64)) },
|
||||
func(x NetworkInfo) any { return x.StoragePrice() },
|
||||
func(info *NetworkInfo, val any) { info.SetStoragePrice(val.(uint64)) },
|
||||
uint64(1), uint64(2),
|
||||
"BasicIncomeRate", func(val interface{}) []byte {
|
||||
"BasicIncomeRate", func(val any) []byte {
|
||||
data := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(data, val.(uint64))
|
||||
return data
|
||||
|
@ -124,10 +125,10 @@ func TestNetworkInfo_StoragePrice(t *testing.T) {
|
|||
|
||||
func TestNetworkInfo_ContainerFee(t *testing.T) {
|
||||
testConfigValue(t,
|
||||
func(x NetworkInfo) interface{} { return x.ContainerFee() },
|
||||
func(info *NetworkInfo, val interface{}) { info.SetContainerFee(val.(uint64)) },
|
||||
func(x NetworkInfo) any { return x.ContainerFee() },
|
||||
func(info *NetworkInfo, val any) { info.SetContainerFee(val.(uint64)) },
|
||||
uint64(1), uint64(2),
|
||||
"ContainerFee", func(val interface{}) []byte {
|
||||
"ContainerFee", func(val any) []byte {
|
||||
data := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(data, val.(uint64))
|
||||
return data
|
||||
|
@ -137,10 +138,10 @@ func TestNetworkInfo_ContainerFee(t *testing.T) {
|
|||
|
||||
func TestNetworkInfo_NamedContainerFee(t *testing.T) {
|
||||
testConfigValue(t,
|
||||
func(x NetworkInfo) interface{} { return x.NamedContainerFee() },
|
||||
func(info *NetworkInfo, val interface{}) { info.SetNamedContainerFee(val.(uint64)) },
|
||||
func(x NetworkInfo) any { return x.NamedContainerFee() },
|
||||
func(info *NetworkInfo, val any) { info.SetNamedContainerFee(val.(uint64)) },
|
||||
uint64(1), uint64(2),
|
||||
"ContainerAliasFee", func(val interface{}) []byte {
|
||||
"ContainerAliasFee", func(val any) []byte {
|
||||
data := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(data, val.(uint64))
|
||||
return data
|
||||
|
@ -150,10 +151,10 @@ func TestNetworkInfo_NamedContainerFee(t *testing.T) {
|
|||
|
||||
func TestNetworkInfo_EigenTrustAlpha(t *testing.T) {
|
||||
testConfigValue(t,
|
||||
func(x NetworkInfo) interface{} { return x.EigenTrustAlpha() },
|
||||
func(info *NetworkInfo, val interface{}) { info.SetEigenTrustAlpha(val.(float64)) },
|
||||
func(x NetworkInfo) any { return x.EigenTrustAlpha() },
|
||||
func(info *NetworkInfo, val any) { info.SetEigenTrustAlpha(val.(float64)) },
|
||||
0.1, 0.2,
|
||||
"EigenTrustAlpha", func(val interface{}) []byte {
|
||||
"EigenTrustAlpha", func(val any) []byte {
|
||||
data := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(data, math.Float64bits(val.(float64)))
|
||||
return data
|
||||
|
@ -163,10 +164,10 @@ func TestNetworkInfo_EigenTrustAlpha(t *testing.T) {
|
|||
|
||||
func TestNetworkInfo_NumberOfEigenTrustIterations(t *testing.T) {
|
||||
testConfigValue(t,
|
||||
func(x NetworkInfo) interface{} { return x.NumberOfEigenTrustIterations() },
|
||||
func(info *NetworkInfo, val interface{}) { info.SetNumberOfEigenTrustIterations(val.(uint64)) },
|
||||
func(x NetworkInfo) any { return x.NumberOfEigenTrustIterations() },
|
||||
func(info *NetworkInfo, val any) { info.SetNumberOfEigenTrustIterations(val.(uint64)) },
|
||||
uint64(1), uint64(2),
|
||||
"EigenTrustIterations", func(val interface{}) []byte {
|
||||
"EigenTrustIterations", func(val any) []byte {
|
||||
data := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(data, val.(uint64))
|
||||
return data
|
||||
|
@ -176,10 +177,10 @@ func TestNetworkInfo_NumberOfEigenTrustIterations(t *testing.T) {
|
|||
|
||||
func TestNetworkInfo_IRCandidateFee(t *testing.T) {
|
||||
testConfigValue(t,
|
||||
func(x NetworkInfo) interface{} { return x.IRCandidateFee() },
|
||||
func(info *NetworkInfo, val interface{}) { info.SetIRCandidateFee(val.(uint64)) },
|
||||
func(x NetworkInfo) any { return x.IRCandidateFee() },
|
||||
func(info *NetworkInfo, val any) { info.SetIRCandidateFee(val.(uint64)) },
|
||||
uint64(1), uint64(2),
|
||||
"InnerRingCandidateFee", func(val interface{}) []byte {
|
||||
"InnerRingCandidateFee", func(val any) []byte {
|
||||
data := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(data, val.(uint64))
|
||||
return data
|
||||
|
@ -189,10 +190,10 @@ func TestNetworkInfo_IRCandidateFee(t *testing.T) {
|
|||
|
||||
func TestNetworkInfo_MaxObjectSize(t *testing.T) {
|
||||
testConfigValue(t,
|
||||
func(x NetworkInfo) interface{} { return x.MaxObjectSize() },
|
||||
func(info *NetworkInfo, val interface{}) { info.SetMaxObjectSize(val.(uint64)) },
|
||||
func(x NetworkInfo) any { return x.MaxObjectSize() },
|
||||
func(info *NetworkInfo, val any) { info.SetMaxObjectSize(val.(uint64)) },
|
||||
uint64(1), uint64(2),
|
||||
"MaxObjectSize", func(val interface{}) []byte {
|
||||
"MaxObjectSize", func(val any) []byte {
|
||||
data := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(data, val.(uint64))
|
||||
return data
|
||||
|
@ -202,10 +203,10 @@ func TestNetworkInfo_MaxObjectSize(t *testing.T) {
|
|||
|
||||
func TestNetworkInfo_WithdrawalFee(t *testing.T) {
|
||||
testConfigValue(t,
|
||||
func(x NetworkInfo) interface{} { return x.WithdrawalFee() },
|
||||
func(info *NetworkInfo, val interface{}) { info.SetWithdrawalFee(val.(uint64)) },
|
||||
func(x NetworkInfo) any { return x.WithdrawalFee() },
|
||||
func(info *NetworkInfo, val any) { info.SetWithdrawalFee(val.(uint64)) },
|
||||
uint64(1), uint64(2),
|
||||
"WithdrawFee", func(val interface{}) []byte {
|
||||
"WithdrawFee", func(val any) []byte {
|
||||
data := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(data, val.(uint64))
|
||||
return data
|
||||
|
@ -215,14 +216,14 @@ func TestNetworkInfo_WithdrawalFee(t *testing.T) {
|
|||
|
||||
func TestNetworkInfo_HomomorphicHashingDisabled(t *testing.T) {
|
||||
testConfigValue(t,
|
||||
func(x NetworkInfo) interface{} { return x.HomomorphicHashingDisabled() },
|
||||
func(info *NetworkInfo, val interface{}) {
|
||||
func(x NetworkInfo) any { return x.HomomorphicHashingDisabled() },
|
||||
func(info *NetworkInfo, val any) {
|
||||
if val.(bool) {
|
||||
info.DisableHomomorphicHashing()
|
||||
}
|
||||
},
|
||||
true, true, // it is impossible to enable hashing
|
||||
"HomomorphicHashingDisabled", func(val interface{}) []byte {
|
||||
"HomomorphicHashingDisabled", func(val any) []byte {
|
||||
data := make([]byte, 1)
|
||||
|
||||
if val.(bool) {
|
||||
|
@ -236,14 +237,14 @@ func TestNetworkInfo_HomomorphicHashingDisabled(t *testing.T) {
|
|||
|
||||
func TestNetworkInfo_MaintenanceModeAllowed(t *testing.T) {
|
||||
testConfigValue(t,
|
||||
func(x NetworkInfo) interface{} { return x.MaintenanceModeAllowed() },
|
||||
func(info *NetworkInfo, val interface{}) {
|
||||
func(x NetworkInfo) any { return x.MaintenanceModeAllowed() },
|
||||
func(info *NetworkInfo, val any) {
|
||||
if val.(bool) {
|
||||
info.AllowMaintenanceMode()
|
||||
}
|
||||
},
|
||||
true, true,
|
||||
"MaintenanceModeAllowed", func(val interface{}) []byte {
|
||||
"MaintenanceModeAllowed", func(val any) []byte {
|
||||
if val.(bool) {
|
||||
return []byte{1}
|
||||
}
|
||||
|
@ -251,3 +252,12 @@ func TestNetworkInfo_MaintenanceModeAllowed(t *testing.T) {
|
|||
},
|
||||
)
|
||||
}
|
||||
|
||||
func TestNetworkInfo_Marshal(t *testing.T) {
|
||||
v := netmaptest.NetworkInfo()
|
||||
|
||||
var v2 NetworkInfo
|
||||
require.NoError(t, v2.Unmarshal(v.Marshal()))
|
||||
|
||||
require.Equal(t, v, v2)
|
||||
}
|
||||
|
|
|
@ -9,9 +9,7 @@ import (
|
|||
|
||||
"github.com/nspcc-dev/hrw"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
||||
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
|
||||
subnetid "github.com/nspcc-dev/neofs-sdk-go/subnet/id"
|
||||
)
|
||||
|
||||
// NodeInfo groups information about NeoFS storage node which is reflected
|
||||
|
@ -53,8 +51,6 @@ func (x *NodeInfo) readFromV2(m netmap.NodeInfo, checkFieldPresence bool) error
|
|||
return fmt.Errorf("duplicated attbiuted %s", key)
|
||||
}
|
||||
|
||||
const subnetPrefix = "__NEOFS__SUBNET_"
|
||||
|
||||
switch {
|
||||
case key == attrCapacity:
|
||||
_, err = strconv.ParseUint(attributes[i].GetValue(), 10, 64)
|
||||
|
@ -67,17 +63,6 @@ func (x *NodeInfo) readFromV2(m netmap.NodeInfo, checkFieldPresence bool) error
|
|||
if err != nil {
|
||||
return fmt.Errorf("invalid %s attribute: %w", attrPrice, err)
|
||||
}
|
||||
case strings.HasPrefix(key, subnetPrefix):
|
||||
var id subnetid.ID
|
||||
|
||||
err = id.DecodeString(strings.TrimPrefix(key, subnetPrefix))
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid key to the subnet attribute %s: %w", key, err)
|
||||
}
|
||||
|
||||
if val := attributes[i].GetValue(); val != "True" && val != "False" {
|
||||
return fmt.Errorf("invalid value of the subnet attribute %s: %w", val, err)
|
||||
}
|
||||
default:
|
||||
if attributes[i].GetValue() == "" {
|
||||
return fmt.Errorf("empty value of the attribute %s", key)
|
||||
|
@ -478,81 +463,6 @@ func (x *NodeInfo) SortAttributes() {
|
|||
x.m.SetAttributes(as)
|
||||
}
|
||||
|
||||
// EnterSubnet writes storage node's intention to enter the given subnet.
|
||||
//
|
||||
// Zero NodeInfo belongs to zero subnet.
|
||||
func (x *NodeInfo) EnterSubnet(id subnetid.ID) {
|
||||
x.changeSubnet(id, true)
|
||||
}
|
||||
|
||||
// ExitSubnet writes storage node's intention to exit the given subnet.
|
||||
func (x *NodeInfo) ExitSubnet(id subnetid.ID) {
|
||||
x.changeSubnet(id, false)
|
||||
}
|
||||
|
||||
func (x *NodeInfo) changeSubnet(id subnetid.ID, isMember bool) {
|
||||
var (
|
||||
idv2 refs.SubnetID
|
||||
info netmap.NodeSubnetInfo
|
||||
)
|
||||
|
||||
id.WriteToV2(&idv2)
|
||||
|
||||
info.SetID(&idv2)
|
||||
info.SetEntryFlag(isMember)
|
||||
|
||||
netmap.WriteSubnetInfo(&x.m, info)
|
||||
}
|
||||
|
||||
// ErrRemoveSubnet is returned when a node needs to leave the subnet.
|
||||
var ErrRemoveSubnet = netmap.ErrRemoveSubnet
|
||||
|
||||
// IterateSubnets iterates over all subnets the node belongs to and passes the IDs to f.
|
||||
// Handler MUST NOT be nil.
|
||||
//
|
||||
// If f returns ErrRemoveSubnet, then removes subnet entry. Note that this leads to an
|
||||
// instant mutation of NodeInfo. Breaks on any other non-nil error and returns it.
|
||||
//
|
||||
// Returns an error if subnet incorrectly enabled/disabled.
|
||||
// Returns an error if the node is not included to any subnet by the end of the loop.
|
||||
//
|
||||
// See also EnterSubnet, ExitSubnet.
|
||||
func (x NodeInfo) IterateSubnets(f func(subnetid.ID) error) error {
|
||||
var id subnetid.ID
|
||||
|
||||
return netmap.IterateSubnets(&x.m, func(idv2 refs.SubnetID) error {
|
||||
err := id.ReadFromV2(idv2)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid subnet: %w", err)
|
||||
}
|
||||
|
||||
err = f(id)
|
||||
if errors.Is(err, ErrRemoveSubnet) {
|
||||
return netmap.ErrRemoveSubnet
|
||||
}
|
||||
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
var errAbortSubnetIter = errors.New("abort subnet iterator")
|
||||
|
||||
// BelongsToSubnet is a helper function over the IterateSubnets method which
|
||||
// checks whether a node belongs to a subnet.
|
||||
//
|
||||
// Zero NodeInfo belongs to zero subnet only.
|
||||
func BelongsToSubnet(node NodeInfo, id subnetid.ID) bool {
|
||||
err := node.IterateSubnets(func(id_ subnetid.ID) error {
|
||||
if id.Equals(id_) {
|
||||
return errAbortSubnetIter
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return errors.Is(err, errAbortSubnetIter)
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
package parser
|
||||
|
||||
//go:generate antlr4 -Dlanguage=Go -visitor QueryLexer.g4 Query.g4
|
||||
// ANTLR can be downloaded from https://www.antlr.org/download/antlr-4.11.1-complete.jar
|
||||
//go:generate java -Xmx500M -cp "./antlr-4.11.1-complete.jar:$CLASSPATH" org.antlr.v4.Tool -Dlanguage=Go -visitor QueryLexer.g4 Query.g4
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
// Code generated from Query.g4 by ANTLR 4.10.1. DO NOT EDIT.
|
||||
// Code generated from java-escape by ANTLR 4.11.1. DO NOT EDIT.
|
||||
|
||||
package parser // Query
|
||||
|
||||
import "github.com/antlr/antlr4/runtime/Go/antlr"
|
||||
import "github.com/antlr/antlr4/runtime/Go/antlr/v4"
|
||||
|
||||
// BaseQueryListener is a complete listener for a parse tree produced by Query.
|
||||
type BaseQueryListener struct{}
|
||||
|
|
|
@ -1,65 +1,65 @@
|
|||
// Code generated from Query.g4 by ANTLR 4.10.1. DO NOT EDIT.
|
||||
// Code generated from java-escape by ANTLR 4.11.1. DO NOT EDIT.
|
||||
|
||||
package parser // Query
|
||||
|
||||
import "github.com/antlr/antlr4/runtime/Go/antlr"
|
||||
import "github.com/antlr/antlr4/runtime/Go/antlr/v4"
|
||||
|
||||
type BaseQueryVisitor struct {
|
||||
*antlr.BaseParseTreeVisitor
|
||||
}
|
||||
|
||||
func (v *BaseQueryVisitor) VisitPolicy(ctx *PolicyContext) interface{} {
|
||||
func (v *BaseQueryVisitor) VisitPolicy(ctx *PolicyContext) any {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseQueryVisitor) VisitRepStmt(ctx *RepStmtContext) interface{} {
|
||||
func (v *BaseQueryVisitor) VisitRepStmt(ctx *RepStmtContext) any {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseQueryVisitor) VisitCbfStmt(ctx *CbfStmtContext) interface{} {
|
||||
func (v *BaseQueryVisitor) VisitCbfStmt(ctx *CbfStmtContext) any {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseQueryVisitor) VisitSelectStmt(ctx *SelectStmtContext) interface{} {
|
||||
func (v *BaseQueryVisitor) VisitSelectStmt(ctx *SelectStmtContext) any {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseQueryVisitor) VisitClause(ctx *ClauseContext) interface{} {
|
||||
func (v *BaseQueryVisitor) VisitClause(ctx *ClauseContext) any {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseQueryVisitor) VisitFilterExpr(ctx *FilterExprContext) interface{} {
|
||||
func (v *BaseQueryVisitor) VisitFilterExpr(ctx *FilterExprContext) any {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseQueryVisitor) VisitFilterStmt(ctx *FilterStmtContext) interface{} {
|
||||
func (v *BaseQueryVisitor) VisitFilterStmt(ctx *FilterStmtContext) any {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseQueryVisitor) VisitExpr(ctx *ExprContext) interface{} {
|
||||
func (v *BaseQueryVisitor) VisitExpr(ctx *ExprContext) any {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseQueryVisitor) VisitFilterKey(ctx *FilterKeyContext) interface{} {
|
||||
func (v *BaseQueryVisitor) VisitFilterKey(ctx *FilterKeyContext) any {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseQueryVisitor) VisitFilterValue(ctx *FilterValueContext) interface{} {
|
||||
func (v *BaseQueryVisitor) VisitFilterValue(ctx *FilterValueContext) any {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseQueryVisitor) VisitNumber(ctx *NumberContext) interface{} {
|
||||
func (v *BaseQueryVisitor) VisitNumber(ctx *NumberContext) any {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseQueryVisitor) VisitKeyword(ctx *KeywordContext) interface{} {
|
||||
func (v *BaseQueryVisitor) VisitKeyword(ctx *KeywordContext) any {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseQueryVisitor) VisitIdent(ctx *IdentContext) interface{} {
|
||||
func (v *BaseQueryVisitor) VisitIdent(ctx *IdentContext) any {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseQueryVisitor) VisitIdentWC(ctx *IdentWCContext) interface{} {
|
||||
func (v *BaseQueryVisitor) VisitIdentWC(ctx *IdentWCContext) any {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Code generated from QueryLexer.g4 by ANTLR 4.10.1. DO NOT EDIT.
|
||||
// Code generated from java-escape by ANTLR 4.11.1. DO NOT EDIT.
|
||||
|
||||
package parser
|
||||
|
||||
|
@ -7,7 +7,7 @@ import (
|
|||
"sync"
|
||||
"unicode"
|
||||
|
||||
"github.com/antlr/antlr4/runtime/Go/antlr"
|
||||
"github.com/antlr/antlr4/runtime/Go/antlr/v4"
|
||||
)
|
||||
|
||||
// Suppress unused import error
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
// Code generated from Query.g4 by ANTLR 4.10.1. DO NOT EDIT.
|
||||
// Code generated from java-escape by ANTLR 4.11.1. DO NOT EDIT.
|
||||
|
||||
package parser // Query
|
||||
|
||||
import "github.com/antlr/antlr4/runtime/Go/antlr"
|
||||
import "github.com/antlr/antlr4/runtime/Go/antlr/v4"
|
||||
|
||||
// QueryListener is a complete listener for a parse tree produced by Query.
|
||||
type QueryListener interface {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Code generated from Query.g4 by ANTLR 4.10.1. DO NOT EDIT.
|
||||
// Code generated from java-escape by ANTLR 4.11.1. DO NOT EDIT.
|
||||
|
||||
package parser // Query
|
||||
|
||||
|
@ -7,7 +7,7 @@ import (
|
|||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/antlr/antlr4/runtime/Go/antlr"
|
||||
"github.com/antlr/antlr4/runtime/Go/antlr/v4"
|
||||
)
|
||||
|
||||
// Suppress unused import errors
|
||||
|
@ -133,7 +133,7 @@ func NewQuery(input antlr.TokenStream) *Query {
|
|||
this.RuleNames = staticData.ruleNames
|
||||
this.LiteralNames = staticData.literalNames
|
||||
this.SymbolicNames = staticData.symbolicNames
|
||||
this.GrammarFileName = "Query.g4"
|
||||
this.GrammarFileName = "java-escape"
|
||||
|
||||
return this
|
||||
}
|
||||
|
@ -383,7 +383,7 @@ func (s *PolicyContext) ExitRule(listener antlr.ParseTreeListener) {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *PolicyContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
|
||||
func (s *PolicyContext) Accept(visitor antlr.ParseTreeVisitor) any {
|
||||
switch t := visitor.(type) {
|
||||
case QueryVisitor:
|
||||
return t.VisitPolicy(s)
|
||||
|
@ -587,7 +587,7 @@ func (s *RepStmtContext) ExitRule(listener antlr.ParseTreeListener) {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *RepStmtContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
|
||||
func (s *RepStmtContext) Accept(visitor antlr.ParseTreeVisitor) any {
|
||||
switch t := visitor.(type) {
|
||||
case QueryVisitor:
|
||||
return t.VisitRepStmt(s)
|
||||
|
@ -732,7 +732,7 @@ func (s *CbfStmtContext) ExitRule(listener antlr.ParseTreeListener) {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *CbfStmtContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
|
||||
func (s *CbfStmtContext) Accept(visitor antlr.ParseTreeVisitor) any {
|
||||
switch t := visitor.(type) {
|
||||
case QueryVisitor:
|
||||
return t.VisitCbfStmt(s)
|
||||
|
@ -976,7 +976,7 @@ func (s *SelectStmtContext) ExitRule(listener antlr.ParseTreeListener) {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *SelectStmtContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
|
||||
func (s *SelectStmtContext) Accept(visitor antlr.ParseTreeVisitor) any {
|
||||
switch t := visitor.(type) {
|
||||
case QueryVisitor:
|
||||
return t.VisitSelectStmt(s)
|
||||
|
@ -1150,7 +1150,7 @@ func (s *ClauseContext) ExitRule(listener antlr.ParseTreeListener) {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *ClauseContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
|
||||
func (s *ClauseContext) Accept(visitor antlr.ParseTreeVisitor) any {
|
||||
switch t := visitor.(type) {
|
||||
case QueryVisitor:
|
||||
return t.VisitClause(s)
|
||||
|
@ -1375,7 +1375,7 @@ func (s *FilterExprContext) ExitRule(listener antlr.ParseTreeListener) {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *FilterExprContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
|
||||
func (s *FilterExprContext) Accept(visitor antlr.ParseTreeVisitor) any {
|
||||
switch t := visitor.(type) {
|
||||
case QueryVisitor:
|
||||
return t.VisitFilterExpr(s)
|
||||
|
@ -1643,7 +1643,7 @@ func (s *FilterStmtContext) ExitRule(listener antlr.ParseTreeListener) {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *FilterStmtContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
|
||||
func (s *FilterStmtContext) Accept(visitor antlr.ParseTreeVisitor) any {
|
||||
switch t := visitor.(type) {
|
||||
case QueryVisitor:
|
||||
return t.VisitFilterStmt(s)
|
||||
|
@ -1850,7 +1850,7 @@ func (s *ExprContext) ExitRule(listener antlr.ParseTreeListener) {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *ExprContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
|
||||
func (s *ExprContext) Accept(visitor antlr.ParseTreeVisitor) any {
|
||||
switch t := visitor.(type) {
|
||||
case QueryVisitor:
|
||||
return t.VisitExpr(s)
|
||||
|
@ -2007,7 +2007,7 @@ func (s *FilterKeyContext) ExitRule(listener antlr.ParseTreeListener) {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *FilterKeyContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
|
||||
func (s *FilterKeyContext) Accept(visitor antlr.ParseTreeVisitor) any {
|
||||
switch t := visitor.(type) {
|
||||
case QueryVisitor:
|
||||
return t.VisitFilterKey(s)
|
||||
|
@ -2159,7 +2159,7 @@ func (s *FilterValueContext) ExitRule(listener antlr.ParseTreeListener) {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *FilterValueContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
|
||||
func (s *FilterValueContext) Accept(visitor antlr.ParseTreeVisitor) any {
|
||||
switch t := visitor.(type) {
|
||||
case QueryVisitor:
|
||||
return t.VisitFilterValue(s)
|
||||
|
@ -2290,7 +2290,7 @@ func (s *NumberContext) ExitRule(listener antlr.ParseTreeListener) {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *NumberContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
|
||||
func (s *NumberContext) Accept(visitor antlr.ParseTreeVisitor) any {
|
||||
switch t := visitor.(type) {
|
||||
case QueryVisitor:
|
||||
return t.VisitNumber(s)
|
||||
|
@ -2422,7 +2422,7 @@ func (s *KeywordContext) ExitRule(listener antlr.ParseTreeListener) {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *KeywordContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
|
||||
func (s *KeywordContext) Accept(visitor antlr.ParseTreeVisitor) any {
|
||||
switch t := visitor.(type) {
|
||||
case QueryVisitor:
|
||||
return t.VisitKeyword(s)
|
||||
|
@ -2461,7 +2461,7 @@ func (p *Query) Keyword() (localctx IKeywordContext) {
|
|||
p.SetState(119)
|
||||
_la = p.GetTokenStream().LA(1)
|
||||
|
||||
if !(((_la)&-(0x1f+1)) == 0 && ((1<<uint(_la))&((1<<QueryREP)|(1<<QueryIN)|(1<<QueryAS)|(1<<QuerySELECT)|(1<<QueryFROM)|(1<<QueryFILTER))) != 0) {
|
||||
if !((int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&1904) != 0) {
|
||||
p.GetErrorHandler().RecoverInline(p)
|
||||
} else {
|
||||
p.GetErrorHandler().ReportMatch(p)
|
||||
|
@ -2550,7 +2550,7 @@ func (s *IdentContext) ExitRule(listener antlr.ParseTreeListener) {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *IdentContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
|
||||
func (s *IdentContext) Accept(visitor antlr.ParseTreeVisitor) any {
|
||||
switch t := visitor.(type) {
|
||||
case QueryVisitor:
|
||||
return t.VisitIdent(s)
|
||||
|
@ -2686,7 +2686,7 @@ func (s *IdentWCContext) ExitRule(listener antlr.ParseTreeListener) {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *IdentWCContext) Accept(visitor antlr.ParseTreeVisitor) interface{} {
|
||||
func (s *IdentWCContext) Accept(visitor antlr.ParseTreeVisitor) any {
|
||||
switch t := visitor.(type) {
|
||||
case QueryVisitor:
|
||||
return t.VisitIdentWC(s)
|
||||
|
|
|
@ -1,52 +1,52 @@
|
|||
// Code generated from Query.g4 by ANTLR 4.10.1. DO NOT EDIT.
|
||||
// Code generated from java-escape by ANTLR 4.11.1. DO NOT EDIT.
|
||||
|
||||
package parser // Query
|
||||
|
||||
import "github.com/antlr/antlr4/runtime/Go/antlr"
|
||||
import "github.com/antlr/antlr4/runtime/Go/antlr/v4"
|
||||
|
||||
// A complete Visitor for a parse tree produced by Query.
|
||||
type QueryVisitor interface {
|
||||
antlr.ParseTreeVisitor
|
||||
|
||||
// Visit a parse tree produced by Query#policy.
|
||||
VisitPolicy(ctx *PolicyContext) interface{}
|
||||
VisitPolicy(ctx *PolicyContext) any
|
||||
|
||||
// Visit a parse tree produced by Query#repStmt.
|
||||
VisitRepStmt(ctx *RepStmtContext) interface{}
|
||||
VisitRepStmt(ctx *RepStmtContext) any
|
||||
|
||||
// Visit a parse tree produced by Query#cbfStmt.
|
||||
VisitCbfStmt(ctx *CbfStmtContext) interface{}
|
||||
VisitCbfStmt(ctx *CbfStmtContext) any
|
||||
|
||||
// Visit a parse tree produced by Query#selectStmt.
|
||||
VisitSelectStmt(ctx *SelectStmtContext) interface{}
|
||||
VisitSelectStmt(ctx *SelectStmtContext) any
|
||||
|
||||
// Visit a parse tree produced by Query#clause.
|
||||
VisitClause(ctx *ClauseContext) interface{}
|
||||
VisitClause(ctx *ClauseContext) any
|
||||
|
||||
// Visit a parse tree produced by Query#filterExpr.
|
||||
VisitFilterExpr(ctx *FilterExprContext) interface{}
|
||||
VisitFilterExpr(ctx *FilterExprContext) any
|
||||
|
||||
// Visit a parse tree produced by Query#filterStmt.
|
||||
VisitFilterStmt(ctx *FilterStmtContext) interface{}
|
||||
VisitFilterStmt(ctx *FilterStmtContext) any
|
||||
|
||||
// Visit a parse tree produced by Query#expr.
|
||||
VisitExpr(ctx *ExprContext) interface{}
|
||||
VisitExpr(ctx *ExprContext) any
|
||||
|
||||
// Visit a parse tree produced by Query#filterKey.
|
||||
VisitFilterKey(ctx *FilterKeyContext) interface{}
|
||||
VisitFilterKey(ctx *FilterKeyContext) any
|
||||
|
||||
// Visit a parse tree produced by Query#filterValue.
|
||||
VisitFilterValue(ctx *FilterValueContext) interface{}
|
||||
VisitFilterValue(ctx *FilterValueContext) any
|
||||
|
||||
// Visit a parse tree produced by Query#number.
|
||||
VisitNumber(ctx *NumberContext) interface{}
|
||||
VisitNumber(ctx *NumberContext) any
|
||||
|
||||
// Visit a parse tree produced by Query#keyword.
|
||||
VisitKeyword(ctx *KeywordContext) interface{}
|
||||
VisitKeyword(ctx *KeywordContext) any
|
||||
|
||||
// Visit a parse tree produced by Query#ident.
|
||||
VisitIdent(ctx *IdentContext) interface{}
|
||||
VisitIdent(ctx *IdentContext) any
|
||||
|
||||
// Visit a parse tree produced by Query#identWC.
|
||||
VisitIdentWC(ctx *IdentWCContext) interface{}
|
||||
VisitIdentWC(ctx *IdentWCContext) any
|
||||
}
|
||||
|
|
|
@ -7,11 +7,9 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/antlr/antlr4/runtime/Go/antlr"
|
||||
"github.com/antlr/antlr4/runtime/Go/antlr/v4"
|
||||
"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/netmap/parser"
|
||||
subnetid "github.com/nspcc-dev/neofs-sdk-go/subnet/id"
|
||||
)
|
||||
|
||||
// PlacementPolicy declares policy to store objects in the NeoFS container.
|
||||
|
@ -25,8 +23,6 @@ import (
|
|||
type PlacementPolicy struct {
|
||||
backupFactor uint32
|
||||
|
||||
subnet subnetid.ID
|
||||
|
||||
filters []netmap.Filter
|
||||
|
||||
selectors []netmap.Selector
|
||||
|
@ -40,16 +36,6 @@ func (p *PlacementPolicy) readFromV2(m netmap.PlacementPolicy, checkFieldPresenc
|
|||
return errors.New("missing replicas")
|
||||
}
|
||||
|
||||
subnetV2 := m.GetSubnetID()
|
||||
if subnetV2 != nil {
|
||||
err := p.subnet.ReadFromV2(*subnetV2)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid subnet: %w", err)
|
||||
}
|
||||
} else {
|
||||
p.subnet = subnetid.ID{}
|
||||
}
|
||||
|
||||
p.backupFactor = m.GetContainerBackupFactor()
|
||||
p.selectors = m.GetSelectors()
|
||||
p.filters = m.GetFilters()
|
||||
|
@ -123,29 +109,12 @@ func (p *PlacementPolicy) ReadFromV2(m netmap.PlacementPolicy) error {
|
|||
//
|
||||
// See also ReadFromV2.
|
||||
func (p PlacementPolicy) WriteToV2(m *netmap.PlacementPolicy) {
|
||||
var subnetV2 refs.SubnetID
|
||||
p.subnet.WriteToV2(&subnetV2)
|
||||
|
||||
m.SetContainerBackupFactor(p.backupFactor)
|
||||
m.SetSubnetID(&subnetV2)
|
||||
m.SetFilters(p.filters)
|
||||
m.SetSelectors(p.selectors)
|
||||
m.SetReplicas(p.replicas)
|
||||
}
|
||||
|
||||
// RestrictSubnet sets a rule to select nodes from the given subnet only.
|
||||
// By default, nodes from zero subnet are selected (whole network map).
|
||||
func (p *PlacementPolicy) RestrictSubnet(subnet subnetid.ID) {
|
||||
p.subnet = subnet
|
||||
}
|
||||
|
||||
// Subnet returns subnet set using RestrictSubnet.
|
||||
//
|
||||
// Zero PlacementPolicy returns zero subnet meaning unlimited.
|
||||
func (p PlacementPolicy) Subnet() subnetid.ID {
|
||||
return p.subnet
|
||||
}
|
||||
|
||||
// ReplicaDescriptor replica descriptor characterizes replicas of objects from
|
||||
// the subset selected by a particular Selector.
|
||||
type ReplicaDescriptor struct {
|
||||
|
@ -601,17 +570,17 @@ type policyVisitor struct {
|
|||
antlr.DefaultErrorListener
|
||||
}
|
||||
|
||||
func (p *policyVisitor) SyntaxError(_ antlr.Recognizer, _ interface{}, line, column int, msg string, _ antlr.RecognitionException) {
|
||||
func (p *policyVisitor) SyntaxError(_ antlr.Recognizer, _ any, line, column int, msg string, _ antlr.RecognitionException) {
|
||||
p.reportError(fmt.Errorf("%w: line %d:%d %s", errSyntaxError, line, column, msg))
|
||||
}
|
||||
|
||||
func (p *policyVisitor) reportError(err error) interface{} {
|
||||
func (p *policyVisitor) reportError(err error) any {
|
||||
p.errors = append(p.errors, err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// VisitPolicy implements parser.QueryVisitor interface.
|
||||
func (p *policyVisitor) VisitPolicy(ctx *parser.PolicyContext) interface{} {
|
||||
func (p *policyVisitor) VisitPolicy(ctx *parser.PolicyContext) any {
|
||||
if len(p.errors) != 0 {
|
||||
return nil
|
||||
}
|
||||
|
@ -659,7 +628,7 @@ func (p *policyVisitor) VisitPolicy(ctx *parser.PolicyContext) interface{} {
|
|||
return pl
|
||||
}
|
||||
|
||||
func (p *policyVisitor) VisitCbfStmt(ctx *parser.CbfStmtContext) interface{} {
|
||||
func (p *policyVisitor) VisitCbfStmt(ctx *parser.CbfStmtContext) any {
|
||||
cbf, err := strconv.ParseUint(ctx.GetBackupFactor().GetText(), 10, 32)
|
||||
if err != nil {
|
||||
return p.reportError(errInvalidNumber)
|
||||
|
@ -669,7 +638,7 @@ func (p *policyVisitor) VisitCbfStmt(ctx *parser.CbfStmtContext) interface{} {
|
|||
}
|
||||
|
||||
// VisitRepStmt implements parser.QueryVisitor interface.
|
||||
func (p *policyVisitor) VisitRepStmt(ctx *parser.RepStmtContext) interface{} {
|
||||
func (p *policyVisitor) VisitRepStmt(ctx *parser.RepStmtContext) any {
|
||||
num, err := strconv.ParseUint(ctx.GetCount().GetText(), 10, 32)
|
||||
if err != nil {
|
||||
return p.reportError(errInvalidNumber)
|
||||
|
@ -686,7 +655,7 @@ func (p *policyVisitor) VisitRepStmt(ctx *parser.RepStmtContext) interface{} {
|
|||
}
|
||||
|
||||
// VisitSelectStmt implements parser.QueryVisitor interface.
|
||||
func (p *policyVisitor) VisitSelectStmt(ctx *parser.SelectStmtContext) interface{} {
|
||||
func (p *policyVisitor) VisitSelectStmt(ctx *parser.SelectStmtContext) any {
|
||||
res, err := strconv.ParseUint(ctx.GetCount().GetText(), 10, 32)
|
||||
if err != nil {
|
||||
return p.reportError(errInvalidNumber)
|
||||
|
@ -712,13 +681,13 @@ func (p *policyVisitor) VisitSelectStmt(ctx *parser.SelectStmtContext) interface
|
|||
}
|
||||
|
||||
// VisitFilterStmt implements parser.QueryVisitor interface.
|
||||
func (p *policyVisitor) VisitFilterStmt(ctx *parser.FilterStmtContext) interface{} {
|
||||
func (p *policyVisitor) VisitFilterStmt(ctx *parser.FilterStmtContext) any {
|
||||
f := p.VisitFilterExpr(ctx.GetExpr().(*parser.FilterExprContext)).(*netmap.Filter)
|
||||
f.SetName(ctx.GetName().GetText())
|
||||
return f
|
||||
}
|
||||
|
||||
func (p *policyVisitor) VisitFilterExpr(ctx *parser.FilterExprContext) interface{} {
|
||||
func (p *policyVisitor) VisitFilterExpr(ctx *parser.FilterExprContext) any {
|
||||
if eCtx := ctx.Expr(); eCtx != nil {
|
||||
return eCtx.Accept(p)
|
||||
}
|
||||
|
@ -747,7 +716,7 @@ func (p *policyVisitor) VisitFilterExpr(ctx *parser.FilterExprContext) interface
|
|||
}
|
||||
|
||||
// VisitFilterKey implements parser.QueryVisitor interface.
|
||||
func (p *policyVisitor) VisitFilterKey(ctx *parser.FilterKeyContext) interface{} {
|
||||
func (p *policyVisitor) VisitFilterKey(ctx *parser.FilterKeyContext) any {
|
||||
if id := ctx.Ident(); id != nil {
|
||||
return id.GetText()
|
||||
}
|
||||
|
@ -756,7 +725,7 @@ func (p *policyVisitor) VisitFilterKey(ctx *parser.FilterKeyContext) interface{}
|
|||
return str[1 : len(str)-1]
|
||||
}
|
||||
|
||||
func (p *policyVisitor) VisitFilterValue(ctx *parser.FilterValueContext) interface{} {
|
||||
func (p *policyVisitor) VisitFilterValue(ctx *parser.FilterValueContext) any {
|
||||
if id := ctx.Ident(); id != nil {
|
||||
return id.GetText()
|
||||
}
|
||||
|
@ -770,7 +739,7 @@ func (p *policyVisitor) VisitFilterValue(ctx *parser.FilterValueContext) interfa
|
|||
}
|
||||
|
||||
// VisitExpr implements parser.QueryVisitor interface.
|
||||
func (p *policyVisitor) VisitExpr(ctx *parser.ExprContext) interface{} {
|
||||
func (p *policyVisitor) VisitExpr(ctx *parser.ExprContext) any {
|
||||
f := new(netmap.Filter)
|
||||
if flt := ctx.GetFilter(); flt != nil {
|
||||
f.SetName(flt.GetText())
|
||||
|
|
|
@ -28,12 +28,10 @@ FILTER City EQ SPB AND SSD EQ true OR City EQ SPB AND Rating GE 5 AS SPBSSD`,
|
|||
var p PlacementPolicy
|
||||
|
||||
for _, testCase := range testCases {
|
||||
|
||||
require.NoError(t, p.DecodeString(testCase))
|
||||
|
||||
var b strings.Builder
|
||||
require.NoError(t, p.WriteStringTo(&b))
|
||||
|
||||
require.Equal(t, testCase, b.String())
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
|
||||
"github.com/nspcc-dev/hrw"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
||||
subnetid "github.com/nspcc-dev/neofs-sdk-go/subnet/id"
|
||||
)
|
||||
|
||||
// processSelectors processes selectors and returns error is any of them is invalid.
|
||||
|
@ -59,7 +58,7 @@ func calcBucketWeight(ns nodes, a aggregator, wf weightFunc) float64 {
|
|||
// Last argument specifies if more buckets can be used to fulfill CBF.
|
||||
func (c *context) getSelection(p PlacementPolicy, s netmap.Selector) ([]nodes, error) {
|
||||
bucketCount, nodesInBucket := calcNodesCount(s)
|
||||
buckets := c.getSelectionBase(p.subnet, s)
|
||||
buckets := c.getSelectionBase(s)
|
||||
|
||||
if len(buckets) < bucketCount {
|
||||
return nil, fmt.Errorf("%w: '%s'", errNotEnoughNodes, s.GetName())
|
||||
|
@ -132,7 +131,7 @@ 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(subnetID subnetid.ID, s netmap.Selector) []nodeAttrPair {
|
||||
func (c *context) getSelectionBase(s netmap.Selector) []nodeAttrPair {
|
||||
fName := s.GetFilter()
|
||||
f := c.processedFilters[fName]
|
||||
isMain := fName == mainFilterName
|
||||
|
@ -141,10 +140,6 @@ func (c *context) getSelectionBase(subnetID subnetid.ID, s netmap.Selector) []no
|
|||
attr := s.GetAttribute()
|
||||
|
||||
for i := range c.netMap.nodes {
|
||||
// TODO(fyrchik): make `BelongsToSubnet` to accept pointer
|
||||
if !BelongsToSubnet(c.netMap.nodes[i], subnetID) {
|
||||
continue
|
||||
}
|
||||
if isMain || c.match(f, c.netMap.nodes[i]) {
|
||||
if attr == "" {
|
||||
// Default attribute is transparent identifier which is different for every node.
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
|
||||
"github.com/nspcc-dev/hrw"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -129,7 +130,7 @@ func BenchmarkPolicyHRWType(b *testing.B) {
|
|||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := nm.ContainerNodes(p, []byte{1})
|
||||
_, err := nm.ContainerNodes(p, cid.ID{1})
|
||||
if err != nil {
|
||||
b.Fatal()
|
||||
}
|
||||
|
@ -173,7 +174,7 @@ func TestPlacementPolicy_DeterministicOrder(t *testing.T) {
|
|||
nm.SetNodes(nodeList)
|
||||
|
||||
getIndices := func(t *testing.T) (uint64, uint64) {
|
||||
v, err := nm.ContainerNodes(p, []byte{1})
|
||||
v, err := nm.ContainerNodes(p, cid.ID{1})
|
||||
require.NoError(t, err)
|
||||
|
||||
nss := make([]nodes, len(v))
|
||||
|
|
|
@ -1,126 +0,0 @@
|
|||
package netmap_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neofs-sdk-go/netmap"
|
||||
subnetid "github.com/nspcc-dev/neofs-sdk-go/subnet/id"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNodeInfoSubnets(t *testing.T) {
|
||||
t.Run("enter subnet", func(t *testing.T) {
|
||||
var id subnetid.ID
|
||||
|
||||
id.SetNumeric(13)
|
||||
|
||||
var node netmap.NodeInfo
|
||||
|
||||
node.EnterSubnet(id)
|
||||
|
||||
mIDs := make(map[string]struct{})
|
||||
|
||||
err := node.IterateSubnets(func(id subnetid.ID) error {
|
||||
mIDs[id.String()] = struct{}{}
|
||||
return nil
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
_, ok := mIDs[id.String()]
|
||||
require.True(t, ok)
|
||||
})
|
||||
|
||||
t.Run("iterate with removal", func(t *testing.T) {
|
||||
t.Run("not last", func(t *testing.T) {
|
||||
var id, idrm subnetid.ID
|
||||
|
||||
id.SetNumeric(13)
|
||||
idrm.SetNumeric(23)
|
||||
|
||||
var node netmap.NodeInfo
|
||||
|
||||
node.EnterSubnet(id)
|
||||
node.EnterSubnet(idrm)
|
||||
|
||||
err := node.IterateSubnets(func(id subnetid.ID) error {
|
||||
if subnetid.IsZero(id) || id.Equals(idrm) {
|
||||
return netmap.ErrRemoveSubnet
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
mIDs := make(map[string]struct{})
|
||||
|
||||
err = node.IterateSubnets(func(id subnetid.ID) error {
|
||||
mIDs[id.String()] = struct{}{}
|
||||
return nil
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
var zeroID subnetid.ID
|
||||
|
||||
_, ok := mIDs[zeroID.String()]
|
||||
require.False(t, ok)
|
||||
|
||||
_, ok = mIDs[idrm.String()]
|
||||
require.False(t, ok)
|
||||
|
||||
_, ok = mIDs[id.String()]
|
||||
require.True(t, ok)
|
||||
})
|
||||
|
||||
t.Run("last", func(t *testing.T) {
|
||||
var node netmap.NodeInfo
|
||||
|
||||
err := node.IterateSubnets(func(id subnetid.ID) error {
|
||||
return netmap.ErrRemoveSubnet
|
||||
})
|
||||
|
||||
require.Error(t, err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestEnterSubnet(t *testing.T) {
|
||||
var (
|
||||
id subnetid.ID
|
||||
node netmap.NodeInfo
|
||||
)
|
||||
|
||||
require.True(t, netmap.BelongsToSubnet(node, id))
|
||||
|
||||
node.EnterSubnet(id)
|
||||
require.True(t, netmap.BelongsToSubnet(node, id))
|
||||
|
||||
node.ExitSubnet(id)
|
||||
require.False(t, netmap.BelongsToSubnet(node, id))
|
||||
|
||||
id.SetNumeric(10)
|
||||
node.EnterSubnet(id)
|
||||
require.True(t, netmap.BelongsToSubnet(node, id))
|
||||
require.False(t, netmap.BelongsToSubnet(node, subnetid.ID{}))
|
||||
|
||||
node.ExitSubnet(id)
|
||||
require.False(t, netmap.BelongsToSubnet(node, id))
|
||||
require.False(t, netmap.BelongsToSubnet(node, subnetid.ID{}))
|
||||
}
|
||||
|
||||
func TestBelongsToSubnet(t *testing.T) {
|
||||
var id, idMiss, idZero subnetid.ID
|
||||
|
||||
id.SetNumeric(13)
|
||||
idMiss.SetNumeric(23)
|
||||
|
||||
var node netmap.NodeInfo
|
||||
|
||||
node.EnterSubnet(id)
|
||||
|
||||
require.True(t, netmap.BelongsToSubnet(node, idZero))
|
||||
require.True(t, netmap.BelongsToSubnet(node, id))
|
||||
require.False(t, netmap.BelongsToSubnet(node, idMiss))
|
||||
}
|
|
@ -4,7 +4,6 @@ import (
|
|||
"math/rand"
|
||||
|
||||
"github.com/nspcc-dev/neofs-sdk-go/netmap"
|
||||
subnetidtest "github.com/nspcc-dev/neofs-sdk-go/subnet/id/test"
|
||||
)
|
||||
|
||||
func filter(withInner bool) (x netmap.Filter) {
|
||||
|
@ -48,7 +47,6 @@ func PlacementPolicy() (p netmap.PlacementPolicy) {
|
|||
p.AddFilters(Filter(), Filter())
|
||||
p.AddReplicas(Replica(), Replica())
|
||||
p.AddSelectors(Selector(), Selector())
|
||||
p.RestrictSubnet(subnetidtest.ID())
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ type NNS struct {
|
|||
nnsContract util.Uint160
|
||||
|
||||
invoker interface {
|
||||
Call(contract util.Uint160, operation string, params ...interface{}) (*result.Invoke, error)
|
||||
Call(contract util.Uint160, operation string, params ...any) (*result.Invoke, error)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ type testNeoClient struct {
|
|||
err error
|
||||
}
|
||||
|
||||
func (x *testNeoClient) Call(contract util.Uint160, operation string, params ...interface{}) (*result.Invoke, error) {
|
||||
func (x *testNeoClient) Call(contract util.Uint160, operation string, params ...any) (*result.Invoke, error) {
|
||||
var domain string
|
||||
|
||||
require.Equal(x.t, x.expectedContract, contract)
|
||||
|
@ -49,7 +49,7 @@ type brokenArrayStackItem struct {
|
|||
stackitem.Item
|
||||
}
|
||||
|
||||
func (x brokenArrayStackItem) Value() interface{} {
|
||||
func (x brokenArrayStackItem) Value() any {
|
||||
return 1
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ package object
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
@ -96,13 +95,13 @@ func VerifyID(obj *Object) error {
|
|||
|
||||
// CalculateAndSetSignature signs id with provided key and sets that signature to
|
||||
// the object.
|
||||
func CalculateAndSetSignature(key ecdsa.PrivateKey, obj *Object) error {
|
||||
func CalculateAndSetSignature(signer neofscrypto.Signer, obj *Object) error {
|
||||
oID, set := obj.ID()
|
||||
if !set {
|
||||
return errOIDNotSet
|
||||
}
|
||||
|
||||
sig, err := oID.CalculateIDSignature(key)
|
||||
sig, err := oID.CalculateIDSignature(signer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -132,12 +131,12 @@ func (o *Object) VerifyIDSignature() bool {
|
|||
}
|
||||
|
||||
// SetIDWithSignature sets object identifier and signature.
|
||||
func SetIDWithSignature(key ecdsa.PrivateKey, obj *Object) error {
|
||||
func SetIDWithSignature(signer neofscrypto.Signer, obj *Object) error {
|
||||
if err := CalculateAndSetID(obj); err != nil {
|
||||
return fmt.Errorf("could not set identifier: %w", err)
|
||||
}
|
||||
|
||||
if err := CalculateAndSetSignature(key, obj); err != nil {
|
||||
if err := CalculateAndSetSignature(signer, obj); err != nil {
|
||||
return fmt.Errorf("could not set signature: %w", err)
|
||||
}
|
||||
|
||||
|
@ -145,10 +144,10 @@ func SetIDWithSignature(key ecdsa.PrivateKey, obj *Object) error {
|
|||
}
|
||||
|
||||
// SetVerificationFields calculates and sets all verification fields of the object.
|
||||
func SetVerificationFields(key ecdsa.PrivateKey, obj *Object) error {
|
||||
func SetVerificationFields(signer neofscrypto.Signer, obj *Object) error {
|
||||
CalculateAndSetPayloadChecksum(obj)
|
||||
|
||||
return SetIDWithSignature(key, obj)
|
||||
return SetIDWithSignature(signer, obj)
|
||||
}
|
||||
|
||||
// CheckVerificationFields checks all verification fields of the object.
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"crypto/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/crypto/test"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -17,9 +17,7 @@ func TestVerificationFields(t *testing.T) {
|
|||
obj.SetPayload(payload)
|
||||
obj.SetPayloadSize(uint64(len(payload)))
|
||||
|
||||
p, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, SetVerificationFields(p.PrivateKey, obj))
|
||||
require.NoError(t, SetVerificationFields(test.RandomSigner(t), obj))
|
||||
|
||||
require.NoError(t, CheckVerificationFields(obj))
|
||||
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
package oid
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
|
||||
"github.com/mr-tron/base58"
|
||||
"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"
|
||||
)
|
||||
|
||||
// ID represents NeoFS object identifier in a container.
|
||||
|
@ -117,7 +115,7 @@ func (id ID) String() string {
|
|||
}
|
||||
|
||||
// CalculateIDSignature signs object id with provided key.
|
||||
func (id ID) CalculateIDSignature(key ecdsa.PrivateKey) (neofscrypto.Signature, error) {
|
||||
func (id ID) CalculateIDSignature(signer neofscrypto.Signer) (neofscrypto.Signature, error) {
|
||||
data, err := id.Marshal()
|
||||
if err != nil {
|
||||
return neofscrypto.Signature{}, fmt.Errorf("marshal ID: %w", err)
|
||||
|
@ -125,7 +123,7 @@ func (id ID) CalculateIDSignature(key ecdsa.PrivateKey) (neofscrypto.Signature,
|
|||
|
||||
var sig neofscrypto.Signature
|
||||
|
||||
return sig, sig.Calculate(neofsecdsa.Signer(key), data)
|
||||
return sig, sig.Calculate(signer, data)
|
||||
}
|
||||
|
||||
// Marshal marshals ID into a protobuf binary form.
|
||||
|
|
|
@ -338,6 +338,13 @@ func (o *Object) PreviousID() (v oid.ID, isSet bool) {
|
|||
return
|
||||
}
|
||||
|
||||
// ResetPreviousID resets identifier of the previous sibling object.
|
||||
func (o *Object) ResetPreviousID() {
|
||||
o.setSplitFields(func(split *object.SplitHeader) {
|
||||
split.SetPrevious(nil)
|
||||
})
|
||||
}
|
||||
|
||||
// SetPreviousID sets identifier of the previous sibling object.
|
||||
func (o *Object) SetPreviousID(v oid.ID) {
|
||||
var v2 refs.ObjectID
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
func TestInitCreation(t *testing.T) {
|
||||
var o object.Object
|
||||
cnr := cidtest.ID()
|
||||
own := *usertest.ID()
|
||||
own := *usertest.ID(t)
|
||||
|
||||
object.InitCreation(&o, object.RequiredFields{
|
||||
Container: cnr,
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
package object
|
||||
|
||||
import (
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/object"
|
||||
)
|
||||
|
||||
// RawObject represents v2-compatible NeoFS object that provides
|
||||
// a convenient interface to fill in the fields of
|
||||
// an object in isolation from its internal structure.
|
||||
//
|
||||
// Deprecated: use Object type instead.
|
||||
type RawObject = Object
|
||||
|
||||
// NewRawFromV2 wraps v2 Object message to Object.
|
||||
//
|
||||
// Deprecated: (v1.0.0) use NewFromV2 function instead.
|
||||
func NewRawFromV2(oV2 *object.Object) *Object {
|
||||
return NewFromV2(oV2)
|
||||
}
|
||||
|
||||
// NewRawFrom wraps Object instance to Object.
|
||||
//
|
||||
// Deprecated: (v1.0.0) function is no-op.
|
||||
func NewRawFrom(obj *Object) *Object {
|
||||
return obj
|
||||
}
|
||||
|
||||
// NewRaw creates and initializes blank Object.
|
||||
//
|
||||
// Deprecated: (v1.0.0) use New instead.
|
||||
func NewRaw() *Object {
|
||||
return New()
|
||||
}
|
||||
|
||||
// Object returns object instance.
|
||||
//
|
||||
// Deprecated: (v1.0.0) method is no-op, use arg directly.
|
||||
func (o *Object) Object() *Object {
|
||||
return o
|
||||
}
|
|
@ -1,318 +0,0 @@
|
|||
package object
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/object"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/checksum"
|
||||
cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test"
|
||||
oid "github.com/nspcc-dev/neofs-sdk-go/object/id"
|
||||
sessiontest "github.com/nspcc-dev/neofs-sdk-go/session/test"
|
||||
usertest "github.com/nspcc-dev/neofs-sdk-go/user/test"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/version"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func randID(t *testing.T) oid.ID {
|
||||
var id oid.ID
|
||||
id.SetSHA256(randSHA256Checksum(t))
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
func randSHA256Checksum(t *testing.T) (cs [sha256.Size]byte) {
|
||||
_, err := rand.Read(cs[:])
|
||||
require.NoError(t, err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func randTZChecksum(t *testing.T) (cs [64]byte) {
|
||||
_, err := rand.Read(cs[:])
|
||||
require.NoError(t, err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func TestObject_SetID(t *testing.T) {
|
||||
obj := New()
|
||||
|
||||
id := randID(t)
|
||||
|
||||
obj.SetID(id)
|
||||
|
||||
oID, set := obj.ID()
|
||||
require.True(t, set)
|
||||
require.Equal(t, id, oID)
|
||||
}
|
||||
|
||||
func TestObject_SetPayload(t *testing.T) {
|
||||
obj := New()
|
||||
|
||||
payload := make([]byte, 10)
|
||||
_, _ = rand.Read(payload)
|
||||
|
||||
obj.SetPayload(payload)
|
||||
|
||||
require.Equal(t, payload, obj.Payload())
|
||||
}
|
||||
|
||||
func TestObject_SetVersion(t *testing.T) {
|
||||
obj := New()
|
||||
|
||||
var ver version.Version
|
||||
ver.SetMajor(1)
|
||||
ver.SetMinor(2)
|
||||
|
||||
obj.SetVersion(&ver)
|
||||
|
||||
require.Equal(t, ver, *obj.Version())
|
||||
}
|
||||
|
||||
func TestObject_SetPayloadSize(t *testing.T) {
|
||||
obj := New()
|
||||
|
||||
sz := uint64(133)
|
||||
obj.SetPayloadSize(sz)
|
||||
|
||||
require.Equal(t, sz, obj.PayloadSize())
|
||||
}
|
||||
|
||||
func TestObject_SetContainerID(t *testing.T) {
|
||||
obj := New()
|
||||
|
||||
cid := cidtest.ID()
|
||||
|
||||
obj.SetContainerID(cid)
|
||||
|
||||
cID, set := obj.ContainerID()
|
||||
require.True(t, set)
|
||||
require.Equal(t, cid, cID)
|
||||
}
|
||||
|
||||
func TestObject_SetOwnerID(t *testing.T) {
|
||||
obj := New()
|
||||
|
||||
ownerID := usertest.ID()
|
||||
|
||||
obj.SetOwnerID(ownerID)
|
||||
|
||||
require.Equal(t, ownerID, obj.OwnerID())
|
||||
}
|
||||
|
||||
func TestObject_SetCreationEpoch(t *testing.T) {
|
||||
obj := New()
|
||||
|
||||
creat := uint64(228)
|
||||
obj.SetCreationEpoch(creat)
|
||||
|
||||
require.Equal(t, creat, obj.CreationEpoch())
|
||||
}
|
||||
|
||||
func TestObject_SetPayloadChecksum(t *testing.T) {
|
||||
obj := New()
|
||||
var cs checksum.Checksum
|
||||
cs.SetSHA256(randSHA256Checksum(t))
|
||||
|
||||
obj.SetPayloadChecksum(cs)
|
||||
cs2, set := obj.PayloadChecksum()
|
||||
|
||||
require.True(t, set)
|
||||
require.Equal(t, cs, cs2)
|
||||
}
|
||||
|
||||
func TestObject_SetPayloadHomomorphicHash(t *testing.T) {
|
||||
obj := New()
|
||||
|
||||
var cs checksum.Checksum
|
||||
cs.SetTillichZemor(randTZChecksum(t))
|
||||
|
||||
obj.SetPayloadHomomorphicHash(cs)
|
||||
cs2, set := obj.PayloadHomomorphicHash()
|
||||
|
||||
require.True(t, set)
|
||||
require.Equal(t, cs, cs2)
|
||||
}
|
||||
|
||||
func TestObject_SetAttributes(t *testing.T) {
|
||||
obj := New()
|
||||
|
||||
a1 := NewAttribute()
|
||||
a1.SetKey("key1")
|
||||
a1.SetValue("val1")
|
||||
|
||||
a2 := NewAttribute()
|
||||
a2.SetKey("key2")
|
||||
a2.SetValue("val2")
|
||||
|
||||
obj.SetAttributes(*a1, *a2)
|
||||
|
||||
require.Equal(t, []Attribute{*a1, *a2}, obj.Attributes())
|
||||
}
|
||||
|
||||
func TestObject_SetPreviousID(t *testing.T) {
|
||||
obj := New()
|
||||
|
||||
prev := randID(t)
|
||||
|
||||
obj.SetPreviousID(prev)
|
||||
|
||||
oID, set := obj.PreviousID()
|
||||
|
||||
require.True(t, set)
|
||||
require.Equal(t, prev, oID)
|
||||
}
|
||||
|
||||
func TestObject_SetChildren(t *testing.T) {
|
||||
obj := New()
|
||||
|
||||
id1 := randID(t)
|
||||
id2 := randID(t)
|
||||
|
||||
obj.SetChildren(id1, id2)
|
||||
|
||||
require.Equal(t, []oid.ID{id1, id2}, obj.Children())
|
||||
}
|
||||
|
||||
func TestObject_SetSplitID(t *testing.T) {
|
||||
obj := New()
|
||||
|
||||
require.Nil(t, obj.SplitID())
|
||||
|
||||
splitID := NewSplitID()
|
||||
obj.SetSplitID(splitID)
|
||||
|
||||
require.Equal(t, obj.SplitID(), splitID)
|
||||
}
|
||||
|
||||
func TestObject_SetParent(t *testing.T) {
|
||||
obj := New()
|
||||
|
||||
require.Nil(t, obj.Parent())
|
||||
|
||||
par := New()
|
||||
par.SetID(randID(t))
|
||||
par.SetContainerID(cidtest.ID())
|
||||
|
||||
obj.SetParent(par)
|
||||
|
||||
require.Equal(t, par, obj.Parent())
|
||||
}
|
||||
|
||||
func TestObject_ToV2(t *testing.T) {
|
||||
objV2 := new(object.Object)
|
||||
objV2.SetPayload([]byte{1, 2, 3})
|
||||
|
||||
obj := NewFromV2(objV2)
|
||||
|
||||
require.Equal(t, objV2, obj.ToV2())
|
||||
}
|
||||
|
||||
func TestObject_SetSessionToken(t *testing.T) {
|
||||
obj := New()
|
||||
|
||||
tok := sessiontest.ObjectSigned()
|
||||
|
||||
obj.SetSessionToken(tok)
|
||||
|
||||
require.Equal(t, tok, obj.SessionToken())
|
||||
}
|
||||
|
||||
func TestObject_SetType(t *testing.T) {
|
||||
obj := New()
|
||||
|
||||
typ := TypeStorageGroup
|
||||
|
||||
obj.SetType(typ)
|
||||
|
||||
require.Equal(t, typ, obj.Type())
|
||||
}
|
||||
|
||||
func TestObject_CutPayload(t *testing.T) {
|
||||
o1 := New()
|
||||
|
||||
p1 := []byte{12, 3}
|
||||
o1.SetPayload(p1)
|
||||
|
||||
sz := uint64(13)
|
||||
o1.SetPayloadSize(sz)
|
||||
|
||||
o2 := o1.CutPayload()
|
||||
|
||||
require.Equal(t, sz, o2.PayloadSize())
|
||||
require.Empty(t, o2.Payload())
|
||||
|
||||
sz++
|
||||
o1.SetPayloadSize(sz)
|
||||
|
||||
require.Equal(t, sz, o1.PayloadSize())
|
||||
require.Equal(t, sz, o2.PayloadSize())
|
||||
|
||||
p2 := []byte{4, 5, 6}
|
||||
o2.SetPayload(p2)
|
||||
|
||||
require.Equal(t, p2, o2.Payload())
|
||||
require.Equal(t, p1, o1.Payload())
|
||||
}
|
||||
|
||||
func TestObject_SetParentID(t *testing.T) {
|
||||
obj := New()
|
||||
|
||||
id := randID(t)
|
||||
obj.SetParentID(id)
|
||||
|
||||
oID, set := obj.ParentID()
|
||||
require.True(t, set)
|
||||
require.Equal(t, id, oID)
|
||||
}
|
||||
|
||||
func TestObject_ResetRelations(t *testing.T) {
|
||||
obj := New()
|
||||
|
||||
obj.SetPreviousID(randID(t))
|
||||
|
||||
obj.ResetRelations()
|
||||
|
||||
_, set := obj.PreviousID()
|
||||
require.False(t, set)
|
||||
}
|
||||
|
||||
func TestObject_HasParent(t *testing.T) {
|
||||
obj := New()
|
||||
|
||||
obj.InitRelations()
|
||||
|
||||
require.True(t, obj.HasParent())
|
||||
|
||||
obj.ResetRelations()
|
||||
|
||||
require.False(t, obj.HasParent())
|
||||
}
|
||||
|
||||
func TestObjectEncoding(t *testing.T) {
|
||||
o := New()
|
||||
o.SetID(randID(t))
|
||||
o.SetContainerID(cidtest.ID())
|
||||
|
||||
t.Run("binary", func(t *testing.T) {
|
||||
data, err := o.Marshal()
|
||||
require.NoError(t, err)
|
||||
|
||||
o2 := New()
|
||||
require.NoError(t, o2.Unmarshal(data))
|
||||
|
||||
require.Equal(t, o, o2)
|
||||
})
|
||||
|
||||
t.Run("json", func(t *testing.T) {
|
||||
data, err := o.MarshalJSON()
|
||||
require.NoError(t, err)
|
||||
|
||||
o2 := New()
|
||||
require.NoError(t, o2.UnmarshalJSON(data))
|
||||
|
||||
require.Equal(t, o, o2)
|
||||
})
|
||||
}
|
|
@ -19,8 +19,9 @@ type Tokens struct {
|
|||
}
|
||||
|
||||
type Relations interface {
|
||||
// GetSplitInfo tries to get split info by root object id.
|
||||
// If object isn't virtual it returns ErrNoSplitInfo.
|
||||
// GetSplitInfo tries to get split info by some object id.
|
||||
// This method must return split info on any object from split chain as well as on parent/linking object.
|
||||
// If object doesn't have any split information returns ErrNoSplitInfo.
|
||||
GetSplitInfo(ctx context.Context, cnrID cid.ID, rootID oid.ID, tokens Tokens) (*object.SplitInfo, error)
|
||||
|
||||
// ListChildrenByLinker returns list of children for link object.
|
||||
|
@ -31,9 +32,6 @@ type Relations interface {
|
|||
// If no previous object it returns ErrNoLeftSibling.
|
||||
GetLeftSibling(ctx context.Context, cnrID cid.ID, objID oid.ID, tokens Tokens) (oid.ID, error)
|
||||
|
||||
// FindSiblingBySplitID returns all objects that relates to the provided split id.
|
||||
FindSiblingBySplitID(ctx context.Context, cnrID cid.ID, splitID *object.SplitID, tokens Tokens) ([]oid.ID, error)
|
||||
|
||||
// FindSiblingByParentID returns all object that relates to the provided parent id.
|
||||
FindSiblingByParentID(ctx context.Context, cnrID cid.ID, parentID oid.ID, tokens Tokens) ([]oid.ID, error)
|
||||
}
|
||||
|
@ -46,9 +44,15 @@ var (
|
|||
ErrNoSplitInfo = errors.New("no split info")
|
||||
)
|
||||
|
||||
// ListAllRelations return all related phy objects for provided root object ID.
|
||||
// Result doesn't include root object ID itself.
|
||||
// ListAllRelations return all related phy objects for provided root object ID in split-chain order.
|
||||
// Result doesn't include root object ID itself. If linking object is found its id will be the last one.
|
||||
func ListAllRelations(ctx context.Context, rels Relations, cnrID cid.ID, rootObjID oid.ID, tokens Tokens) ([]oid.ID, error) {
|
||||
return ListRelations(ctx, rels, cnrID, rootObjID, tokens, true)
|
||||
}
|
||||
|
||||
// ListRelations return all related phy objects for provided root object ID in split-chain order.
|
||||
// Result doesn't include root object ID itself.
|
||||
func ListRelations(ctx context.Context, rels Relations, cnrID cid.ID, rootObjID oid.ID, tokens Tokens, includeLinking bool) ([]oid.ID, error) {
|
||||
splitInfo, err := rels.GetSplitInfo(ctx, cnrID, rootObjID, tokens)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrNoSplitInfo) {
|
||||
|
@ -59,22 +63,40 @@ func ListAllRelations(ctx context.Context, rels Relations, cnrID cid.ID, rootObj
|
|||
|
||||
// collect split chain by the descending ease of operations (ease is evaluated heuristically).
|
||||
// If any approach fails, we don't try the next since we assume that it will fail too.
|
||||
if _, ok := splitInfo.Link(); !ok {
|
||||
// the list is expected to contain last part and (probably) split info
|
||||
list, err := rels.FindSiblingByParentID(ctx, cnrID, rootObjID, tokens)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to find object children: %w", err)
|
||||
}
|
||||
|
||||
for _, id := range list {
|
||||
split, err := rels.GetSplitInfo(ctx, cnrID, id, tokens)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrNoSplitInfo) {
|
||||
continue
|
||||
}
|
||||
return nil, fmt.Errorf("failed to get split info: %w", err)
|
||||
}
|
||||
if link, ok := split.Link(); ok {
|
||||
splitInfo.SetLink(link)
|
||||
}
|
||||
if last, ok := split.LastPart(); ok {
|
||||
splitInfo.SetLastPart(last)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if idLinking, ok := splitInfo.Link(); ok {
|
||||
children, err := rels.ListChildrenByLinker(ctx, cnrID, idLinking, tokens)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get linking object's header: %w", err)
|
||||
}
|
||||
|
||||
// include linking object
|
||||
return append(children, idLinking), nil
|
||||
if includeLinking {
|
||||
children = append(children, idLinking)
|
||||
}
|
||||
|
||||
if idSplit := splitInfo.SplitID(); idSplit != nil {
|
||||
members, err := rels.FindSiblingBySplitID(ctx, cnrID, idSplit, tokens)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to search objects by split ID: %w", err)
|
||||
}
|
||||
return members, nil
|
||||
return children, nil
|
||||
}
|
||||
|
||||
idMember, ok := splitInfo.LastPart()
|
||||
|
@ -85,9 +107,6 @@ func ListAllRelations(ctx context.Context, rels Relations, cnrID cid.ID, rootObj
|
|||
chain := []oid.ID{idMember}
|
||||
chainSet := map[oid.ID]struct{}{idMember: {}}
|
||||
|
||||
// prmHead.SetRawFlag(false)
|
||||
// split members are almost definitely singular, but don't get hung up on it
|
||||
|
||||
for {
|
||||
idMember, err = rels.GetLeftSibling(ctx, cnrID, idMember, tokens)
|
||||
if err != nil {
|
||||
|
@ -101,20 +120,9 @@ func ListAllRelations(ctx context.Context, rels Relations, cnrID cid.ID, rootObj
|
|||
return nil, fmt.Errorf("duplicated member in the split chain %s", idMember)
|
||||
}
|
||||
|
||||
chain = append(chain, idMember)
|
||||
chain = append([]oid.ID{idMember}, chain...)
|
||||
chainSet[idMember] = struct{}{}
|
||||
}
|
||||
|
||||
list, err := rels.FindSiblingByParentID(ctx, cnrID, rootObjID, tokens)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to find object children: %w", err)
|
||||
}
|
||||
|
||||
for i := range list {
|
||||
if _, ok = chainSet[list[i]]; !ok {
|
||||
chain = append(chain, list[i])
|
||||
}
|
||||
}
|
||||
|
||||
return chain, nil
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package object
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
|
||||
|
@ -9,6 +11,7 @@ import (
|
|||
oid "github.com/nspcc-dev/neofs-sdk-go/object/id"
|
||||
"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.
|
||||
|
@ -54,7 +57,7 @@ func SearchMatchFromV2(t v2object.MatchType) (m SearchMatchType) {
|
|||
return m
|
||||
}
|
||||
|
||||
// String returns string representation of SearchMatchType.
|
||||
// EncodeToString returns string representation of SearchMatchType.
|
||||
//
|
||||
// String mapping:
|
||||
// - MatchStringEqual: STRING_EQUAL;
|
||||
|
@ -62,15 +65,24 @@ func SearchMatchFromV2(t v2object.MatchType) (m SearchMatchType) {
|
|||
// - MatchNotPresent: NOT_PRESENT;
|
||||
// - MatchCommonPrefix: COMMON_PREFIX;
|
||||
// - MatchUnknown, default: MATCH_TYPE_UNSPECIFIED.
|
||||
func (m SearchMatchType) String() string {
|
||||
func (m SearchMatchType) EncodeToString() string {
|
||||
return m.ToV2().String()
|
||||
}
|
||||
|
||||
// FromString parses SearchMatchType from a string representation.
|
||||
// It is a reverse action to String().
|
||||
// String implements fmt.Stringer.
|
||||
//
|
||||
// String is designed to be human-readable, and its format MAY differ between
|
||||
// SDK versions. String MAY return same result as EncodeToString. String MUST NOT
|
||||
// be used to encode ID into NeoFS protocol string.
|
||||
func (m SearchMatchType) String() string {
|
||||
return m.EncodeToString()
|
||||
}
|
||||
|
||||
// DecodeString parses SearchMatchType from a string representation.
|
||||
// It is a reverse action to EncodeToString().
|
||||
//
|
||||
// Returns true if s was parsed successfully.
|
||||
func (m *SearchMatchType) FromString(s string) bool {
|
||||
func (m *SearchMatchType) DecodeString(s string) bool {
|
||||
var g v2object.MatchType
|
||||
|
||||
ok := g.FromString(s)
|
||||
|
@ -279,7 +291,7 @@ func (f *SearchFilters) AddSplitIDFilter(m SearchMatchType, id *SplitID) {
|
|||
|
||||
// AddTypeFilter adds filter by object type.
|
||||
func (f *SearchFilters) AddTypeFilter(m SearchMatchType, typ Type) {
|
||||
f.addReservedFilter(m, fKeyType, staticStringer(typ.String()))
|
||||
f.addReservedFilter(m, fKeyType, staticStringer(typ.EncodeToString()))
|
||||
}
|
||||
|
||||
// MarshalJSON encodes SearchFilters to protobuf JSON format.
|
||||
|
@ -299,3 +311,13 @@ func (f *SearchFilters) UnmarshalJSON(data []byte) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddPayloadHashFilter adds filter by payload hash.
|
||||
func (f *SearchFilters) AddPayloadHashFilter(m SearchMatchType, sum [sha256.Size]byte) {
|
||||
f.addReservedFilter(m, fKeyPayloadHash, staticStringer(hex.EncodeToString(sum[:])))
|
||||
}
|
||||
|
||||
// AddHomomorphicHashFilter adds filter by homomorphic hash.
|
||||
func (f *SearchFilters) AddHomomorphicHashFilter(m SearchMatchType, sum [tz.Size]byte) {
|
||||
f.addReservedFilter(m, fKeyHomomorphicHash, staticStringer(hex.EncodeToString(sum[:])))
|
||||
}
|
||||
|
|
|
@ -2,12 +2,16 @@ package object_test
|
|||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"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"
|
||||
oid "github.com/nspcc-dev/neofs-sdk-go/object/id"
|
||||
"github.com/nspcc-dev/tzhash/tz"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -172,7 +176,7 @@ func TestSearchFilters_AddTypeFilter(t *testing.T) {
|
|||
require.Len(t, fsV2, 1)
|
||||
|
||||
require.Equal(t, v2object.FilterHeaderObjectType, fsV2[0].GetKey())
|
||||
require.Equal(t, typ.String(), fsV2[0].GetValue())
|
||||
require.Equal(t, typ.EncodeToString(), fsV2[0].GetValue())
|
||||
require.Equal(t, v2object.MatchStringEqual, fsV2[0].GetMatchType())
|
||||
})
|
||||
}
|
||||
|
@ -207,3 +211,77 @@ func TestSearchMatchType_String(t *testing.T) {
|
|||
{val: toPtr(object.MatchUnknown), str: "MATCH_TYPE_UNSPECIFIED"},
|
||||
})
|
||||
}
|
||||
|
||||
func testChecksumSha256() [sha256.Size]byte {
|
||||
cs := [sha256.Size]byte{}
|
||||
rand.Read(cs[:])
|
||||
|
||||
return cs
|
||||
}
|
||||
|
||||
func testChecksumTZ() [tz.Size]byte {
|
||||
cs := [tz.Size]byte{}
|
||||
rand.Read(cs[:])
|
||||
|
||||
return cs
|
||||
}
|
||||
|
||||
func TestSearchFilters_AddPayloadHashFilter(t *testing.T) {
|
||||
cs := testChecksumSha256()
|
||||
|
||||
fs := new(object.SearchFilters)
|
||||
fs.AddPayloadHashFilter(object.MatchStringEqual, cs)
|
||||
|
||||
t.Run("v2", func(t *testing.T) {
|
||||
fsV2 := fs.ToV2()
|
||||
|
||||
require.Len(t, fsV2, 1)
|
||||
|
||||
require.Equal(t, v2object.FilterHeaderPayloadHash, fsV2[0].GetKey())
|
||||
require.Equal(t, hex.EncodeToString(cs[:]), fsV2[0].GetValue())
|
||||
require.Equal(t, v2object.MatchStringEqual, fsV2[0].GetMatchType())
|
||||
})
|
||||
}
|
||||
|
||||
func ExampleSearchFilters_AddPayloadHashFilter() {
|
||||
hash, _ := hex.DecodeString("66842cfea090b1d906b52400fae49d86df078c0670f2bdd059ba289ebe24a498")
|
||||
|
||||
var v [sha256.Size]byte
|
||||
copy(v[:], hash[:sha256.Size])
|
||||
|
||||
var cs checksum.Checksum
|
||||
cs.SetSHA256(v)
|
||||
|
||||
fmt.Println(hex.EncodeToString(cs.Value()))
|
||||
// Output: 66842cfea090b1d906b52400fae49d86df078c0670f2bdd059ba289ebe24a498
|
||||
}
|
||||
|
||||
func TestSearchFilters_AddHomomorphicHashFilter(t *testing.T) {
|
||||
cs := testChecksumTZ()
|
||||
|
||||
fs := new(object.SearchFilters)
|
||||
fs.AddHomomorphicHashFilter(object.MatchStringEqual, cs)
|
||||
|
||||
t.Run("v2", func(t *testing.T) {
|
||||
fsV2 := fs.ToV2()
|
||||
|
||||
require.Len(t, fsV2, 1)
|
||||
|
||||
require.Equal(t, v2object.FilterHeaderHomomorphicHash, fsV2[0].GetKey())
|
||||
require.Equal(t, hex.EncodeToString(cs[:]), fsV2[0].GetValue())
|
||||
require.Equal(t, v2object.MatchStringEqual, fsV2[0].GetMatchType())
|
||||
})
|
||||
}
|
||||
|
||||
func ExampleSearchFilters_AddHomomorphicHashFilter() {
|
||||
hash, _ := hex.DecodeString("7e302ebb3937e810feb501965580c746048db99cebd095c3ce27022407408bf904dde8d9aa8085d2cf7202345341cc947fa9d722c6b6699760d307f653815d0c")
|
||||
|
||||
var v [tz.Size]byte
|
||||
copy(v[:], hash[:tz.Size])
|
||||
|
||||
var cs checksum.Checksum
|
||||
cs.SetTillichZemor(v)
|
||||
|
||||
fmt.Println(hex.EncodeToString(cs.Value()))
|
||||
// Output: 7e302ebb3937e810feb501965580c746048db99cebd095c3ce27022407408bf904dde8d9aa8085d2cf7202345341cc947fa9d722c6b6699760d307f653815d0c
|
||||
}
|
||||
|
|
4
object/slicer/doc.go
Normal file
4
object/slicer/doc.go
Normal file
|
@ -0,0 +1,4 @@
|
|||
/*
|
||||
Package slicer provides raw data slicing into NeoFS objects.
|
||||
*/
|
||||
package slicer
|
27
object/slicer/options.go
Normal file
27
object/slicer/options.go
Normal file
|
@ -0,0 +1,27 @@
|
|||
package slicer
|
||||
|
||||
// Options groups Slicer options.
|
||||
type Options struct {
|
||||
objectPayloadLimit uint64
|
||||
|
||||
currentNeoFSEpoch uint64
|
||||
|
||||
withHomoChecksum bool
|
||||
}
|
||||
|
||||
// SetObjectPayloadLimit specifies data size limit for produced physically
|
||||
// stored objects.
|
||||
func (x *Options) SetObjectPayloadLimit(l uint64) {
|
||||
x.objectPayloadLimit = l
|
||||
}
|
||||
|
||||
// SetCurrentNeoFSEpoch sets current NeoFS epoch.
|
||||
func (x *Options) SetCurrentNeoFSEpoch(e uint64) {
|
||||
x.currentNeoFSEpoch = e
|
||||
}
|
||||
|
||||
// CalculateHomomorphicChecksum makes Slicer to calculate and set homomorphic
|
||||
// checksum of the processed objects.
|
||||
func (x *Options) CalculateHomomorphicChecksum() {
|
||||
x.withHomoChecksum = true
|
||||
}
|
648
object/slicer/slicer.go
Normal file
648
object/slicer/slicer.go
Normal file
|
@ -0,0 +1,648 @@
|
|||
package slicer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
|
||||
"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/session"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/user"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/version"
|
||||
"github.com/nspcc-dev/tzhash/tz"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrInvalidAttributeAmount indicates wrong number of arguments. Amount of arguments MUST be even number.
|
||||
ErrInvalidAttributeAmount = errors.New("attributes must be even number of strings")
|
||||
)
|
||||
|
||||
// ObjectWriter represents a virtual object recorder.
|
||||
type ObjectWriter interface {
|
||||
// InitDataStream initializes and returns a stream of writable data associated
|
||||
// with the object according to its header. Provided header includes at least
|
||||
// container, owner and object ID fields.
|
||||
InitDataStream(header object.Object) (dataStream io.Writer, err error)
|
||||
}
|
||||
|
||||
// Slicer converts input raw data streams into NeoFS objects. Working Slicer
|
||||
// must be constructed via New.
|
||||
type Slicer struct {
|
||||
signer neofscrypto.Signer
|
||||
|
||||
cnr cid.ID
|
||||
|
||||
owner user.ID
|
||||
|
||||
w ObjectWriter
|
||||
|
||||
opts Options
|
||||
|
||||
sessionToken *session.Object
|
||||
}
|
||||
|
||||
// New constructs Slicer which writes sliced ready-to-go objects owned by
|
||||
// particular user into the specified container using provided ObjectWriter.
|
||||
// All objects are signed using provided neofscrypto.Signer.
|
||||
//
|
||||
// If ObjectWriter returns data streams which provide io.Closer, they are closed
|
||||
// in Slicer.Slice after the payload of any object has been written. In this
|
||||
// case, Slicer.Slice fails immediately on Close error.
|
||||
//
|
||||
// Options parameter allows you to provide optional parameters which tune
|
||||
// the default Slicer behavior. They are detailed below.
|
||||
//
|
||||
// If payload size limit is specified via Options.SetObjectPayloadLimit,
|
||||
// outgoing objects has payload not bigger than the limit. NeoFS stores the
|
||||
// corresponding value in the network configuration. Ignore this option if you
|
||||
// don't (want to) have access to it. By default, single object is limited by
|
||||
// 1MB. Slicer uses this value to enforce the maximum object payload size limit
|
||||
// described in the NeoFS Specification. If the total amount of data exceeds the
|
||||
// specified limit, Slicer applies the slicing algorithm described within the
|
||||
// same specification. The outcome will be a group of "small" objects containing
|
||||
// a chunk of data, as well as an auxiliary linking object. All derived objects
|
||||
// are written to the parameterized ObjectWriter. If the amount of data is
|
||||
// within the limit, one object is produced. Note that Slicer can write multiple
|
||||
// objects, but returns the root object ID only.
|
||||
//
|
||||
// If current NeoFS epoch is specified via Options.SetCurrentNeoFSEpoch, it is
|
||||
// written to the metadata of all resulting objects as a creation epoch.
|
||||
//
|
||||
// See also NewSession.
|
||||
func New(signer neofscrypto.Signer, cnr cid.ID, owner user.ID, w ObjectWriter, opts Options) *Slicer {
|
||||
return &Slicer{
|
||||
signer: signer,
|
||||
cnr: cnr,
|
||||
owner: owner,
|
||||
w: w,
|
||||
opts: opts,
|
||||
}
|
||||
}
|
||||
|
||||
// NewSession creates Slicer which generates objects within provided session.
|
||||
// NewSession work similar to New with the detail that the session issuer owns
|
||||
// the produced objects. Specified session token is written to the metadata of
|
||||
// all resulting objects. In this case, the object is considered to be created
|
||||
// by a proxy on behalf of the session issuer.
|
||||
func NewSession(signer neofscrypto.Signer, cnr cid.ID, token session.Object, w ObjectWriter, opts Options) *Slicer {
|
||||
return &Slicer{
|
||||
signer: signer,
|
||||
cnr: cnr,
|
||||
owner: token.Issuer(),
|
||||
w: w,
|
||||
opts: opts,
|
||||
sessionToken: &token,
|
||||
}
|
||||
}
|
||||
|
||||
// fillCommonMetadata writes to the object metadata common to all objects of the
|
||||
// same stream.
|
||||
func (x *Slicer) fillCommonMetadata(obj *object.Object) {
|
||||
currentVersion := version.Current()
|
||||
obj.SetVersion(¤tVersion)
|
||||
obj.SetContainerID(x.cnr)
|
||||
obj.SetCreationEpoch(x.opts.currentNeoFSEpoch)
|
||||
obj.SetType(object.TypeRegular)
|
||||
obj.SetOwnerID(&x.owner)
|
||||
obj.SetSessionToken(x.sessionToken)
|
||||
}
|
||||
|
||||
const defaultPayloadSizeLimit = 1 << 20
|
||||
|
||||
// childPayloadSizeLimit returns configured size limit of the child object's
|
||||
// payload which defaults to 1MB.
|
||||
func (x *Slicer) childPayloadSizeLimit() uint64 {
|
||||
if x.opts.objectPayloadLimit > 0 {
|
||||
return x.opts.objectPayloadLimit
|
||||
}
|
||||
return defaultPayloadSizeLimit
|
||||
}
|
||||
|
||||
// Slice creates new NeoFS object from the input data stream, associates the
|
||||
// object with the configured container and writes the object via underlying
|
||||
// ObjectWriter. After a successful write, Slice returns an oid.ID which is a
|
||||
// unique reference to the object in the container. Slice sets all required
|
||||
// calculated fields like payload length, checksum, etc.
|
||||
//
|
||||
// Slice allows you to specify string key-value pairs to be written to the
|
||||
// resulting object's metadata as object attributes. Corresponding argument MUST
|
||||
// NOT be empty or have odd length. Keys SHOULD NOT start with system-reserved
|
||||
// '__NEOFS__' prefix.
|
||||
//
|
||||
// See New for details.
|
||||
func (x *Slicer) Slice(data io.Reader, attributes ...string) (oid.ID, error) {
|
||||
var rootID oid.ID
|
||||
|
||||
if len(attributes)%2 != 0 {
|
||||
return rootID, ErrInvalidAttributeAmount
|
||||
}
|
||||
|
||||
if x.opts.objectPayloadLimit == 0 {
|
||||
x.opts.objectPayloadLimit = 1 << 20
|
||||
}
|
||||
|
||||
var rootHeader object.Object
|
||||
var offset uint64
|
||||
var isSplit bool
|
||||
var childMeta dynamicObjectMetadata
|
||||
var writtenChildren []oid.ID
|
||||
var childHeader object.Object
|
||||
rootMeta := newDynamicObjectMetadata(x.opts.withHomoChecksum)
|
||||
bChunk := make([]byte, x.opts.objectPayloadLimit+1)
|
||||
|
||||
x.fillCommonMetadata(&rootHeader)
|
||||
|
||||
for {
|
||||
n, err := data.Read(bChunk[offset:])
|
||||
if err == nil {
|
||||
if last := offset + uint64(n); last <= x.opts.objectPayloadLimit {
|
||||
rootMeta.accumulateNextPayloadChunk(bChunk[offset:last])
|
||||
if isSplit {
|
||||
childMeta.accumulateNextPayloadChunk(bChunk[offset:last])
|
||||
}
|
||||
offset = last
|
||||
// data is not over, and we expect more bytes to form next object
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
if !errors.Is(err, io.EOF) {
|
||||
return rootID, fmt.Errorf("read payload chunk: %w", err)
|
||||
}
|
||||
|
||||
// there will be no more data
|
||||
|
||||
toSend := offset + uint64(n)
|
||||
if toSend <= x.opts.objectPayloadLimit {
|
||||
// we can finalize the root object and send last part
|
||||
|
||||
if len(attributes) > 0 {
|
||||
attrs := make([]object.Attribute, len(attributes)/2)
|
||||
|
||||
for i := 0; i < len(attrs); i++ {
|
||||
attrs[i].SetKey(attributes[2*i])
|
||||
attrs[i].SetValue(attributes[2*i+1])
|
||||
}
|
||||
|
||||
rootHeader.SetAttributes(attrs...)
|
||||
}
|
||||
|
||||
rootID, err = flushObjectMetadata(x.signer, rootMeta, &rootHeader)
|
||||
if err != nil {
|
||||
return rootID, fmt.Errorf("form root object: %w", err)
|
||||
}
|
||||
|
||||
if isSplit {
|
||||
// when splitting, root object's header is written into its last child
|
||||
childHeader.SetParent(&rootHeader)
|
||||
childHeader.SetPreviousID(writtenChildren[len(writtenChildren)-1])
|
||||
|
||||
childID, err := writeInMemObject(x.signer, x.w, childHeader, bChunk[:toSend], childMeta)
|
||||
if err != nil {
|
||||
return rootID, fmt.Errorf("write child object: %w", err)
|
||||
}
|
||||
|
||||
writtenChildren = append(writtenChildren, childID)
|
||||
} else {
|
||||
// root object is single (full < limit), so send it directly
|
||||
rootID, err = writeInMemObject(x.signer, x.w, rootHeader, bChunk[:toSend], rootMeta)
|
||||
if err != nil {
|
||||
return rootID, fmt.Errorf("write single root object: %w", err)
|
||||
}
|
||||
|
||||
return rootID, nil
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
// otherwise, form penultimate object, then do one more iteration for
|
||||
// simplicity: according to io.Reader, we'll get io.EOF again, but the overflow
|
||||
// will no longer occur, so we'll finish the loop
|
||||
}
|
||||
|
||||
// according to buffer size, here we can overflow the object payload limit, e.g.
|
||||
// 1. full=11B,limit=10B,read=11B (no objects created yet)
|
||||
// 2. full=21B,limit=10B,read=11B (one object has been already sent with size=10B)
|
||||
|
||||
toSend := offset + uint64(n)
|
||||
overflow := toSend > x.opts.objectPayloadLimit
|
||||
if overflow {
|
||||
toSend = x.opts.objectPayloadLimit
|
||||
}
|
||||
|
||||
// we could read some data even in case of io.EOF, so don't forget pick up the tail
|
||||
if n > 0 {
|
||||
rootMeta.accumulateNextPayloadChunk(bChunk[offset:toSend])
|
||||
if isSplit {
|
||||
childMeta.accumulateNextPayloadChunk(bChunk[offset:toSend])
|
||||
}
|
||||
}
|
||||
|
||||
if overflow {
|
||||
isSplitCp := isSplit // we modify it in next condition below but need after it
|
||||
if !isSplit {
|
||||
// we send only child object below, but we can get here at the beginning (see
|
||||
// option 1 described above), so we need to pre-init child resources
|
||||
isSplit = true
|
||||
x.fillCommonMetadata(&childHeader)
|
||||
childHeader.SetSplitID(object.NewSplitID())
|
||||
childMeta = rootMeta
|
||||
// we do shallow copy of rootMeta because below we take this into account and do
|
||||
// not corrupt it
|
||||
} else {
|
||||
childHeader.SetPreviousID(writtenChildren[len(writtenChildren)-1])
|
||||
}
|
||||
|
||||
childID, err := writeInMemObject(x.signer, x.w, childHeader, bChunk[:toSend], childMeta)
|
||||
if err != nil {
|
||||
return rootID, fmt.Errorf("write child object: %w", err)
|
||||
}
|
||||
|
||||
writtenChildren = append(writtenChildren, childID)
|
||||
|
||||
// shift overflow bytes to the beginning
|
||||
if !isSplitCp {
|
||||
childMeta = newDynamicObjectMetadata(x.opts.withHomoChecksum) // to avoid rootMeta corruption
|
||||
}
|
||||
childMeta.reset()
|
||||
childMeta.accumulateNextPayloadChunk(bChunk[toSend:])
|
||||
rootMeta.accumulateNextPayloadChunk(bChunk[toSend:])
|
||||
offset = uint64(copy(bChunk, bChunk[toSend:]))
|
||||
}
|
||||
}
|
||||
|
||||
// linking object
|
||||
childMeta.reset()
|
||||
childHeader.ResetPreviousID()
|
||||
childHeader.SetChildren(writtenChildren...)
|
||||
|
||||
_, err := writeInMemObject(x.signer, x.w, childHeader, nil, childMeta)
|
||||
if err != nil {
|
||||
return rootID, fmt.Errorf("write linking object: %w", err)
|
||||
}
|
||||
|
||||
return rootID, nil
|
||||
}
|
||||
|
||||
// InitPayloadStream works similar to Slice but provides PayloadWriter allowing
|
||||
// the caller to write data himself.
|
||||
func (x *Slicer) InitPayloadStream(attributes ...string) (*PayloadWriter, error) {
|
||||
res := &PayloadWriter{
|
||||
stream: x.w,
|
||||
signer: x.signer,
|
||||
container: x.cnr,
|
||||
owner: x.owner,
|
||||
currentEpoch: x.opts.currentNeoFSEpoch,
|
||||
sessionToken: x.sessionToken,
|
||||
attributes: attributes,
|
||||
rootMeta: newDynamicObjectMetadata(x.opts.withHomoChecksum),
|
||||
childMeta: newDynamicObjectMetadata(x.opts.withHomoChecksum),
|
||||
}
|
||||
|
||||
res.buf.Grow(int(x.childPayloadSizeLimit()))
|
||||
res.rootMeta.reset()
|
||||
res.currentWriter = newLimitedWriter(io.MultiWriter(&res.buf, &res.rootMeta), x.childPayloadSizeLimit())
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// PayloadWriter is a single-object payload stream provided by Slicer.
|
||||
type PayloadWriter struct {
|
||||
stream ObjectWriter
|
||||
|
||||
rootID oid.ID
|
||||
|
||||
signer neofscrypto.Signer
|
||||
container cid.ID
|
||||
owner user.ID
|
||||
currentEpoch uint64
|
||||
sessionToken *session.Object
|
||||
attributes []string
|
||||
|
||||
buf bytes.Buffer
|
||||
|
||||
rootMeta dynamicObjectMetadata
|
||||
childMeta dynamicObjectMetadata
|
||||
|
||||
currentWriter limitedWriter
|
||||
|
||||
withSplit bool
|
||||
|
||||
writtenChildren []oid.ID
|
||||
}
|
||||
|
||||
// Write writes next chunk of the object data. Concatenation of all chunks forms
|
||||
// the payload of the final object. When the data is over, the PayloadWriter
|
||||
// should be closed.
|
||||
func (x *PayloadWriter) Write(chunk []byte) (int, error) {
|
||||
if len(chunk) == 0 {
|
||||
// not explicitly prohibited in the io.Writer documentation
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
n, err := x.currentWriter.Write(chunk)
|
||||
if err == nil || !errors.Is(err, errOverflow) {
|
||||
return n, err
|
||||
}
|
||||
|
||||
if !x.withSplit {
|
||||
err = x.writeIntermediateChild(x.rootMeta)
|
||||
if err != nil {
|
||||
return n, fmt.Errorf("write 1st child: %w", err)
|
||||
}
|
||||
|
||||
x.currentWriter.reset(io.MultiWriter(&x.buf, &x.rootMeta, &x.childMeta))
|
||||
x.withSplit = true
|
||||
} else {
|
||||
err = x.writeIntermediateChild(x.childMeta)
|
||||
if err != nil {
|
||||
return n, fmt.Errorf("write next child: %w", err)
|
||||
}
|
||||
|
||||
x.currentWriter.resetProgress()
|
||||
}
|
||||
|
||||
x.buf.Reset()
|
||||
x.childMeta.reset()
|
||||
|
||||
n2, err := x.Write(chunk[n:]) // here n > 0 so infinite recursion shouldn't occur
|
||||
|
||||
return n + n2, err
|
||||
}
|
||||
|
||||
// Close finalizes object with written payload data, saves the object and closes
|
||||
// the stream. Reference to the stored object can be obtained by ID method.
|
||||
func (x *PayloadWriter) Close() error {
|
||||
if x.withSplit {
|
||||
return x.writeLastChild(x.childMeta, x.setID)
|
||||
}
|
||||
return x.writeLastChild(x.rootMeta, x.setID)
|
||||
}
|
||||
|
||||
func (x *PayloadWriter) setID(id oid.ID) {
|
||||
x.rootID = id
|
||||
}
|
||||
|
||||
// ID returns unique identifier of the stored object representing its reference
|
||||
// in the configured container.
|
||||
//
|
||||
// ID MUST NOT be called before successful Close (undefined behavior otherwise).
|
||||
func (x *PayloadWriter) ID() oid.ID {
|
||||
return x.rootID
|
||||
}
|
||||
|
||||
// writeIntermediateChild writes intermediate split-chain element with specified
|
||||
// dynamicObjectMetadata to the configured ObjectWriter.
|
||||
func (x *PayloadWriter) writeIntermediateChild(meta dynamicObjectMetadata) error {
|
||||
return x._writeChild(meta, false, nil)
|
||||
}
|
||||
|
||||
// writeIntermediateChild writes last split-chain element with specified
|
||||
// dynamicObjectMetadata to the configured ObjectWriter. If rootIDHandler is
|
||||
// specified, ID of the resulting root object is passed into it.
|
||||
func (x *PayloadWriter) writeLastChild(meta dynamicObjectMetadata, rootIDHandler func(id oid.ID)) error {
|
||||
return x._writeChild(meta, true, rootIDHandler)
|
||||
}
|
||||
|
||||
func (x *PayloadWriter) _writeChild(meta dynamicObjectMetadata, last bool, rootIDHandler func(id oid.ID)) error {
|
||||
currentVersion := version.Current()
|
||||
|
||||
fCommon := func(obj *object.Object) {
|
||||
obj.SetVersion(¤tVersion)
|
||||
obj.SetContainerID(x.container)
|
||||
obj.SetCreationEpoch(x.currentEpoch)
|
||||
obj.SetType(object.TypeRegular)
|
||||
obj.SetOwnerID(&x.owner)
|
||||
obj.SetSessionToken(x.sessionToken)
|
||||
}
|
||||
|
||||
var obj object.Object
|
||||
fCommon(&obj)
|
||||
if len(x.writtenChildren) > 0 {
|
||||
obj.SetPreviousID(x.writtenChildren[len(x.writtenChildren)-1])
|
||||
}
|
||||
if last {
|
||||
var rootObj *object.Object
|
||||
if x.withSplit {
|
||||
rootObj = new(object.Object)
|
||||
} else {
|
||||
rootObj = &obj
|
||||
}
|
||||
|
||||
fCommon(rootObj)
|
||||
|
||||
if len(x.attributes) > 0 {
|
||||
attrs := make([]object.Attribute, len(x.attributes)/2)
|
||||
|
||||
for i := 0; i < len(attrs); i++ {
|
||||
attrs[i].SetKey(x.attributes[2*i])
|
||||
attrs[i].SetValue(x.attributes[2*i+1])
|
||||
}
|
||||
|
||||
rootObj.SetAttributes(attrs...)
|
||||
}
|
||||
|
||||
rootID, err := flushObjectMetadata(x.signer, x.rootMeta, rootObj)
|
||||
if err != nil {
|
||||
return fmt.Errorf("form root object: %w", err)
|
||||
}
|
||||
|
||||
if rootIDHandler != nil {
|
||||
rootIDHandler(rootID)
|
||||
}
|
||||
|
||||
if x.withSplit {
|
||||
obj.SetParentID(rootID)
|
||||
obj.SetParent(rootObj)
|
||||
}
|
||||
}
|
||||
|
||||
id, err := writeInMemObject(x.signer, x.stream, obj, x.buf.Bytes(), meta)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write formed object: %w", err)
|
||||
}
|
||||
|
||||
x.writtenChildren = append(x.writtenChildren, id)
|
||||
|
||||
if x.withSplit && last {
|
||||
obj.ResetPreviousID()
|
||||
obj.SetChildren(x.writtenChildren...)
|
||||
|
||||
_, err = writeInMemObject(x.signer, x.stream, obj, nil, meta)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write linking object: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func flushObjectMetadata(signer neofscrypto.Signer, meta dynamicObjectMetadata, header *object.Object) (oid.ID, error) {
|
||||
var cs checksum.Checksum
|
||||
|
||||
var csBytes [sha256.Size]byte
|
||||
copy(csBytes[:], meta.checksum.Sum(nil))
|
||||
|
||||
cs.SetSHA256(csBytes)
|
||||
header.SetPayloadChecksum(cs)
|
||||
|
||||
if meta.homomorphicChecksum != nil {
|
||||
var csHomoBytes [tz.Size]byte
|
||||
copy(csHomoBytes[:], meta.homomorphicChecksum.Sum(nil))
|
||||
|
||||
cs.SetTillichZemor(csHomoBytes)
|
||||
header.SetPayloadHomomorphicHash(cs)
|
||||
}
|
||||
|
||||
header.SetPayloadSize(meta.length)
|
||||
|
||||
id, err := object.CalculateID(header)
|
||||
if err != nil {
|
||||
return id, fmt.Errorf("calculate ID: %w", err)
|
||||
}
|
||||
|
||||
header.SetID(id)
|
||||
|
||||
bID, err := id.Marshal()
|
||||
if err != nil {
|
||||
return id, fmt.Errorf("marshal object ID: %w", err)
|
||||
}
|
||||
|
||||
var sig neofscrypto.Signature
|
||||
|
||||
err = sig.Calculate(signer, bID)
|
||||
if err != nil {
|
||||
return id, fmt.Errorf("sign object ID: %w", err)
|
||||
}
|
||||
|
||||
header.SetSignature(&sig)
|
||||
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func writeInMemObject(signer neofscrypto.Signer, w ObjectWriter, header object.Object, payload []byte, meta dynamicObjectMetadata) (oid.ID, error) {
|
||||
id, err := flushObjectMetadata(signer, meta, &header)
|
||||
if err != nil {
|
||||
return id, err
|
||||
}
|
||||
|
||||
stream, err := w.InitDataStream(header)
|
||||
if err != nil {
|
||||
return id, fmt.Errorf("init data stream for next object: %w", err)
|
||||
}
|
||||
|
||||
_, err = stream.Write(payload)
|
||||
if err != nil {
|
||||
return id, fmt.Errorf("write object payload: %w", err)
|
||||
}
|
||||
|
||||
if c, ok := stream.(io.Closer); ok {
|
||||
err = c.Close()
|
||||
if err != nil {
|
||||
return id, fmt.Errorf("finish object stream: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// dynamicObjectMetadata groups accumulated object metadata which depends on
|
||||
// payload.
|
||||
type dynamicObjectMetadata struct {
|
||||
length uint64
|
||||
checksum hash.Hash
|
||||
homomorphicChecksum hash.Hash
|
||||
}
|
||||
|
||||
func newDynamicObjectMetadata(withHomoChecksum bool) dynamicObjectMetadata {
|
||||
m := dynamicObjectMetadata{
|
||||
checksum: sha256.New(),
|
||||
}
|
||||
|
||||
if withHomoChecksum {
|
||||
m.homomorphicChecksum = tz.New()
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func (x *dynamicObjectMetadata) Write(chunk []byte) (int, error) {
|
||||
x.accumulateNextPayloadChunk(chunk)
|
||||
return len(chunk), nil
|
||||
}
|
||||
|
||||
// accumulateNextPayloadChunk handles the next payload chunk and updates the
|
||||
// accumulated metadata.
|
||||
func (x *dynamicObjectMetadata) accumulateNextPayloadChunk(chunk []byte) {
|
||||
x.length += uint64(len(chunk))
|
||||
x.checksum.Write(chunk)
|
||||
if x.homomorphicChecksum != nil {
|
||||
x.homomorphicChecksum.Write(chunk)
|
||||
}
|
||||
}
|
||||
|
||||
// reset resets all accumulated metadata.
|
||||
func (x *dynamicObjectMetadata) reset() {
|
||||
x.length = 0
|
||||
x.checksum.Reset()
|
||||
if x.homomorphicChecksum != nil {
|
||||
x.homomorphicChecksum.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
var errOverflow = errors.New("overflow")
|
||||
|
||||
// limitedWriter provides io.Writer limiting data volume.
|
||||
type limitedWriter struct {
|
||||
base io.Writer
|
||||
|
||||
limit, written uint64
|
||||
}
|
||||
|
||||
// newLimitedWriter initializes limiterWriter which writes data to the base
|
||||
// writer before the specified limit.
|
||||
func newLimitedWriter(base io.Writer, limit uint64) limitedWriter {
|
||||
return limitedWriter{
|
||||
base: base,
|
||||
limit: limit,
|
||||
}
|
||||
}
|
||||
|
||||
// reset resets progress to zero and sets the base target for writing subsequent
|
||||
// data.
|
||||
func (x *limitedWriter) reset(base io.Writer) {
|
||||
x.base = base
|
||||
x.resetProgress()
|
||||
}
|
||||
|
||||
// resetProgress resets progress to zero.
|
||||
func (x *limitedWriter) resetProgress() {
|
||||
x.written = 0
|
||||
}
|
||||
|
||||
// Write writes next chunk of the data to the base writer. If chunk along with
|
||||
// already written data overflows configured limit, Write returns errOverflow.
|
||||
func (x *limitedWriter) Write(p []byte) (n int, err error) {
|
||||
overflow := uint64(len(p)) > x.limit-x.written
|
||||
|
||||
if overflow {
|
||||
n, err = x.base.Write(p[:x.limit-x.written])
|
||||
} else {
|
||||
n, err = x.base.Write(p)
|
||||
}
|
||||
|
||||
x.written += uint64(n)
|
||||
|
||||
if overflow && err == nil {
|
||||
return n, errOverflow
|
||||
}
|
||||
|
||||
return n, err
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue