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 |
243 changed files with 5893 additions and 5394 deletions
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
|
@ -1 +1 @@
|
|||
* @TrueCloudLab/storage-core @TrueCloudLab/storage-services
|
||||
* @roman-khimov @cthulhu-rider @smallhive @notimetoname
|
||||
|
|
45
.github/ISSUE_TEMPLATE/bug_report.md
vendored
45
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
@ -1,45 +0,0 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: community, triage, bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--- Provide a general summary of the issue in the Title above -->
|
||||
|
||||
## Expected Behavior
|
||||
<!--- If you're describing a bug, tell us what should happen -->
|
||||
<!--- If you're suggesting a change/improvement, tell us how it should work -->
|
||||
|
||||
## Current Behavior
|
||||
<!--- If describing a bug, tell us what happens instead of the expected behavior -->
|
||||
<!--- If suggesting a change/improvement, explain the difference from current behavior -->
|
||||
|
||||
## Possible Solution
|
||||
<!--- Not obligatory -->
|
||||
<!--- If no reason/fix/additions for the bug can be suggested, -->
|
||||
<!--- uncomment the following phrase: -->
|
||||
|
||||
<!--- No fix can be suggested by a QA engineer. Further solutions shall be up to developers. -->
|
||||
|
||||
## Steps to Reproduce (for bugs)
|
||||
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
|
||||
<!--- reproduce this bug. -->
|
||||
|
||||
1.
|
||||
|
||||
## Context
|
||||
<!--- How has this issue affected you? What are you trying to accomplish? -->
|
||||
<!--- Providing context helps us come up with a solution that is most useful in the real world -->
|
||||
|
||||
## Regression
|
||||
<!-- Is this issue a regression? (Yes / No) -->
|
||||
<!-- If Yes, optionally please include version or commit id or PR# that caused this regression, if you have these details. -->
|
||||
|
||||
## Your Environment
|
||||
<!--- Include as many relevant details about the environment you experienced the bug in -->
|
||||
* Version used:
|
||||
* Server setup and configuration:
|
||||
* Operating System and version (`uname -a`):
|
1
.github/ISSUE_TEMPLATE/config.yml
vendored
1
.github/ISSUE_TEMPLATE/config.yml
vendored
|
@ -1 +0,0 @@
|
|||
blank_issues_enabled: false
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
|
@ -1,20 +0,0 @@
|
|||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: community, triage
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## Is your feature request related to a problem? Please describe.
|
||||
<!--- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
|
||||
|
||||
## Describe the solution you'd like
|
||||
<!--- A clear and concise description of what you want to happen. -->
|
||||
|
||||
## Describe alternatives you've considered
|
||||
<!--- A clear and concise description of any alternative solutions or features you've considered. -->
|
||||
|
||||
## Additional context
|
||||
<!--- Add any other context or screenshots about the feature request here. -->
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -17,6 +17,7 @@ vendor/
|
|||
# IDE
|
||||
.idea
|
||||
.vscode
|
||||
*~
|
||||
|
||||
# coverage
|
||||
coverage.txt
|
||||
|
|
10
.gitlint
10
.gitlint
|
@ -1,10 +0,0 @@
|
|||
[general]
|
||||
fail-without-commits=true
|
||||
contrib=CC1
|
||||
|
||||
[title-match-regex]
|
||||
regex=^\[\#[0-9Xx]+\]\s
|
||||
|
||||
[ignore-by-title]
|
||||
regex=^Release(.*)
|
||||
ignore=title-match-regex
|
|
@ -24,9 +24,6 @@ linters-settings:
|
|||
govet:
|
||||
# report about shadowed variables
|
||||
check-shadowing: false
|
||||
staticcheck:
|
||||
checks: ["all", "-SA1019"] # TODO Enable SA1019 after deprecated warning are fixed.
|
||||
|
||||
|
||||
linters:
|
||||
enable:
|
||||
|
@ -35,12 +32,15 @@ linters:
|
|||
- revive
|
||||
|
||||
# some default golangci-lint linters
|
||||
- deadcode
|
||||
- errcheck
|
||||
- gosimple
|
||||
- ineffassign
|
||||
- staticcheck
|
||||
- structcheck
|
||||
- typecheck
|
||||
- unused
|
||||
- varcheck
|
||||
|
||||
# extra linters
|
||||
- exhaustive
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
ci:
|
||||
autofix_prs: false
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.4.0
|
||||
hooks:
|
||||
- id: check-added-large-files
|
||||
- id: check-case-conflict
|
||||
- id: check-executables-have-shebangs
|
||||
- id: check-shebang-scripts-are-executable
|
||||
- id: check-merge-conflict
|
||||
- id: check-json
|
||||
- id: check-xml
|
||||
- id: check-yaml
|
||||
- id: trailing-whitespace
|
||||
args: [--markdown-linebreak-ext=md]
|
||||
- id: end-of-file-fixer
|
||||
exclude: ".key$"
|
||||
|
||||
- repo: https://github.com/golangci/golangci-lint
|
||||
rev: v1.51.2
|
||||
hooks:
|
||||
- id: golangci-lint
|
||||
|
||||
- repo: https://github.com/jorisroovers/gitlint
|
||||
rev: v0.18.0
|
||||
hooks:
|
||||
- id: gitlint
|
||||
stages: [commit-msg]
|
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
|
2
Makefile
Executable file → Normal file
2
Makefile
Executable file → Normal file
|
@ -37,4 +37,4 @@ help:
|
|||
@echo ''
|
||||
@echo ' Targets:'
|
||||
@echo ''
|
||||
@awk '/^#/{ comment = substr($$0,3) } comment && /^[a-zA-Z][a-zA-Z0-9_-]+ ?:/{ print " ", $$1, comment }' $(MAKEFILE_LIST) | column -t -s ':' | grep -v 'IGNORE' | sort -u
|
||||
@awk '/^#/{ comment = substr($$0,3) } comment && /^[a-zA-Z][a-zA-Z0-9_-]+ ?:/{ print " ", $$1, comment }' $(MAKEFILE_LIST) | column -t -s ':' | grep -v 'IGNORE' | sort -u
|
52
README.md
52
README.md
|
@ -1,6 +1,6 @@
|
|||
# frostfs-sdk-go
|
||||
Go implementation of FrostFS SDK. It contains high-level version-independent wrappers
|
||||
for structures from [frostfs-api-go](https://git.frostfs.info/TrueCloudLab/frostfs-api-go) as well as
|
||||
# neofs-sdk-go
|
||||
Go implementation of NeoFS SDK. It contains high-level version-independent wrappers
|
||||
for structures from [neofs-api-go](https://github.com/nspcc-dev/neofs-api-go) as well as
|
||||
helper functions for simplifying node/dApp implementations.
|
||||
|
||||
## Repository structure
|
||||
|
@ -10,42 +10,43 @@ Contains fixed-point `Decimal` type for performing balance calculations.
|
|||
|
||||
### eacl
|
||||
Contains Extended ACL types for fine-grained access control.
|
||||
There is also a reference implementation of checking algorithm which is used in FrostFS node.
|
||||
There is also a reference implementation of checking algorithm which is used in NeoFS node.
|
||||
|
||||
### checksum
|
||||
Contains `Checksum` type encapsulating checksum as well as it's kind.
|
||||
Currently Sha256 and [Tillich-Zemor hashsum](https://git.frostfs.info/TrueCloudLab/tzhash) are in use.
|
||||
Currently Sha256 and [Tillich-Zemor hashsum](https://github.com/nspcc-dev/tzhash) are in use.
|
||||
|
||||
### owner
|
||||
`owner.ID` type represents single account interacting with FrostFS. In v2 version of protocol
|
||||
`owner.ID` type represents single account interacting with NeoFS. In v2 version of protocol
|
||||
it is just raw bytes behing [base58-encoded address](https://docs.neo.org/docs/en-us/basic/concept/wallets.html#address)
|
||||
in Neo blockchain. Note that for historical reasons it contains
|
||||
version prefix and checksum in addition to script-hash.
|
||||
|
||||
### token
|
||||
Contains Bearer token type with several FrostFS-specific methods.
|
||||
Contains Bearer token type with several NeoFS-specific methods.
|
||||
|
||||
### ns
|
||||
In FrostFS there are 2 types of name resolution: DNS and NNS. NNS stands for Neo Name Service
|
||||
is just a [contract](https://git.frostfs.info/TrueCloudLab/frostfs-contract) deployed on a Neo blockchain.
|
||||
In NeoFS there are 2 types of name resolution: DNS and NNS. NNS stands for Neo Name Service
|
||||
is just a [contract](https://github.com/nspcc-dev/neofs-contract/) deployed on a Neo blockchain.
|
||||
Basically, NNS is just a DNS-on-chain which can be used for resolving container nice-names as well
|
||||
as any other name in dApps. See our [CoreDNS plugin](https://github.com/nspcc-dev/coredns/tree/master/plugin/nns)
|
||||
for the example of how NNS can be integrated in DNS.
|
||||
|
||||
### session
|
||||
To help lightweight clients interact with FrostFS without sacrificing trust, FrostFS has a concept
|
||||
To help lightweight clients interact with NeoFS without sacrificing trust, NeoFS has a concept
|
||||
of session token. It is signed by client and allows any node with which a session is established
|
||||
to perform certain actions on behalf of the user.
|
||||
|
||||
### client
|
||||
Contains client for working with FrostFS.
|
||||
Contains client for working with NeoFS.
|
||||
```go
|
||||
var prmInit client.PrmInit
|
||||
prmInit.SetDefaultPrivateKey(key) // private key for request signing
|
||||
prmInit.ResolveFrostFSFailures() // 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
|
||||
|
@ -54,7 +55,7 @@ err := c.Dial(prmDial)
|
|||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5 * time.Second)
|
||||
defer cancel()
|
||||
|
||||
|
@ -70,15 +71,14 @@ fmt.Printf("Balance for %s: %v\n", acc, res.Amount())
|
|||
```
|
||||
|
||||
#### Response status
|
||||
In FrostFS every operation can fail on multiple levels, so a single `error` doesn't suffice,
|
||||
In NeoFS every operation can fail on multiple levels, so a single `error` doesn't suffice,
|
||||
e.g. consider a case when object was put on 4 out of 5 replicas. Thus, all request execution
|
||||
details are contained in `Status` returned from every RPC call. dApp can inspect them
|
||||
if needed and perform any desired action. In the case above we may want to report
|
||||
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
|
||||
[FrostFS API](https://git.frostfs.info/TrueCloudLab/frostfs-api/src/branch/master/status/types.proto). There is also
|
||||
a `client.PrmInit.ResolveFrostFSFailures()` 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
|
||||
|
@ -98,19 +98,19 @@ Contains CRUSH-like implementation of container node selection algorithm. Releva
|
|||
are described in this paper http://ceur-ws.org/Vol-2344/short10.pdf . Note that it can be
|
||||
outdated in some details.
|
||||
|
||||
`netmap/json_tests` subfolder contains language-agnostic tests for selection algorithm.
|
||||
`netmap/json_tests` subfolder contains language-agnostic tests for selection algorithm.
|
||||
|
||||
```go
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/netmap"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/object"
|
||||
)
|
||||
|
||||
func placementNodes(addr *object.Address, p *netmap.PlacementPolicy, frostfsNodes []netmap.NodeInfo) {
|
||||
// Convert list of nodes in FrostFS API format to the intermediate representation.
|
||||
func placementNodes(addr *object.Address, p *netmap.PlacementPolicy, neofsNodes []netmap.NodeInfo) {
|
||||
// Convert list of nodes in NeoFS API format to the intermediate representation.
|
||||
nodes := netmap.NodesFromInfo(nodes)
|
||||
|
||||
// Create new netmap (errors are skipped for the sake of clarity).
|
||||
// Create new netmap (errors are skipped for the sake of clarity).
|
||||
nm, _ := NewNetmap(nodes)
|
||||
|
||||
// Calculate nodes of container.
|
||||
|
@ -122,13 +122,13 @@ func placementNodes(addr *object.Address, p *netmap.PlacementPolicy, frostfsNode
|
|||
```
|
||||
|
||||
### pool
|
||||
Simple pool for managing connections to FrostFS nodes.
|
||||
Simple pool for managing connections to NeoFS nodes.
|
||||
|
||||
### acl, checksum, version, signature
|
||||
Contain simple API wrappers.
|
||||
|
||||
### logger
|
||||
Wrapper over `zap.Logger` which is used across FrostFS codebase.
|
||||
Wrapper over `zap.Logger` which is used across NeoFS codebase.
|
||||
|
||||
### util
|
||||
Utilities for working with signature-related code.
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
package accounting
|
||||
|
||||
import "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/accounting"
|
||||
import (
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/accounting"
|
||||
)
|
||||
|
||||
// Decimal represents decimal number for accounting operations.
|
||||
//
|
||||
// Decimal is mutually compatible with git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/accounting.Decimal
|
||||
// Decimal is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/accounting.Decimal
|
||||
// message. See ReadFromV2 / WriteToV2 methods.
|
||||
//
|
||||
// Instances can be created using built-in var declaration.
|
||||
|
@ -15,7 +17,7 @@ import "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/accounting"
|
|||
type Decimal accounting.Decimal
|
||||
|
||||
// ReadFromV2 reads Decimal from the accounting.Decimal message. Checks if the
|
||||
// message conforms to FrostFS API V2 protocol.
|
||||
// message conforms to NeoFS API V2 protocol.
|
||||
//
|
||||
// See also WriteToV2.
|
||||
func (d *Decimal) ReadFromV2(m accounting.Decimal) error {
|
||||
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -3,8 +3,9 @@ package accounting_test
|
|||
import (
|
||||
"testing"
|
||||
|
||||
v2accounting "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/accounting"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/accounting"
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Package accounting provides primitives to perform accounting operations in FrostFS.
|
||||
Package accounting provides primitives to perform accounting operations in NeoFS.
|
||||
|
||||
Decimal type provides functionality to process user balances. For example, when
|
||||
working with Fixed8 balance precision:
|
||||
|
@ -8,12 +8,12 @@ working with Fixed8 balance precision:
|
|||
dec.SetValue(val)
|
||||
dec.SetPrecision(8)
|
||||
|
||||
Instances can be also used to process FrostFS API V2 protocol messages
|
||||
(see neo.fs.v2.accounting package in https://git.frostfs.info/TrueCloudLab/frostfs-api).
|
||||
Instances can be also used to process NeoFS API V2 protocol messages
|
||||
(see neo.fs.v2.accounting package in https://github.com/nspcc-dev/neofs-api).
|
||||
|
||||
On client side:
|
||||
|
||||
import "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/accounting"
|
||||
import "github.com/nspcc-dev/neofs-api-go/v2/accounting"
|
||||
|
||||
var msg accounting.Decimal
|
||||
dec.WriteToV2(&msg)
|
||||
|
|
|
@ -3,7 +3,7 @@ package accountingtest
|
|||
import (
|
||||
"math/rand"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/accounting"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/accounting"
|
||||
)
|
||||
|
||||
// Decimal returns random accounting.Decimal.
|
||||
|
|
|
@ -5,7 +5,7 @@ Note that importing the package into source files is highly discouraged.
|
|||
|
||||
Random instance generation functions can be useful when testing expects any value, e.g.:
|
||||
|
||||
import accountingtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/accounting/test"
|
||||
import accountingtest "github.com/nspcc-dev/neofs-sdk-go/accounting/test"
|
||||
|
||||
dec := accountingtest.Decimal()
|
||||
// test the value
|
||||
|
|
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,5 +1,5 @@
|
|||
/*
|
||||
Package audit provides features to process data audit in FrostFS system.
|
||||
Package audit provides features to process data audit in NeoFS system.
|
||||
|
||||
Result type groups values which can be gathered during data audit process:
|
||||
|
||||
|
|
|
@ -4,16 +4,16 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/audit"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/version"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/audit"
|
||||
"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/nspcc-dev/neofs-sdk-go/version"
|
||||
)
|
||||
|
||||
// Result represents report on the results of the data audit in FrostFS system.
|
||||
// Result represents report on the results of the data audit in NeoFS system.
|
||||
//
|
||||
// Result is mutually binary-compatible with git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/audit.DataAuditResult
|
||||
// Result is mutually binary-compatible with github.com/nspcc-dev/neofs-api-go/v2/audit.DataAuditResult
|
||||
// message. See Marshal / Unmarshal methods.
|
||||
//
|
||||
// Instances can be created using built-in var declaration.
|
||||
|
@ -23,7 +23,7 @@ type Result struct {
|
|||
v2 audit.DataAuditResult
|
||||
}
|
||||
|
||||
// Marshal encodes Result into a canonical FrostFS binary format (Protocol Buffers
|
||||
// Marshal encodes Result into a canonical NeoFS binary format (Protocol Buffers
|
||||
// with direct field order).
|
||||
//
|
||||
// Writes version.Current() protocol version into the resulting message if Result
|
||||
|
@ -43,7 +43,7 @@ func (r *Result) Marshal() []byte {
|
|||
|
||||
var errCIDNotSet = errors.New("container ID is not set")
|
||||
|
||||
// Unmarshal decodes Result from its canonical FrostFS binary format (Protocol Buffers
|
||||
// Unmarshal decodes Result from its canonical NeoFS binary format (Protocol Buffers
|
||||
// with direct field order). Returns an error describing a format violation.
|
||||
//
|
||||
// See also Marshal.
|
||||
|
@ -91,7 +91,7 @@ func (r *Result) Unmarshal(data []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Epoch returns FrostFS epoch when the data associated with the Result was audited.
|
||||
// Epoch returns NeoFS epoch when the data associated with the Result was audited.
|
||||
//
|
||||
// Zero Result has zero epoch.
|
||||
//
|
||||
|
@ -100,7 +100,7 @@ func (r Result) Epoch() uint64 {
|
|||
return r.v2.GetAuditEpoch()
|
||||
}
|
||||
|
||||
// ForEpoch specifies FrostFS epoch when the data associated with the Result was audited.
|
||||
// ForEpoch specifies NeoFS epoch when the data associated with the Result was audited.
|
||||
//
|
||||
// See also Epoch.
|
||||
func (r *Result) ForEpoch(epoch uint64) {
|
||||
|
@ -136,8 +136,8 @@ func (r *Result) ForContainer(cnr cid.ID) {
|
|||
r.v2.SetContainerID(&cidV2)
|
||||
}
|
||||
|
||||
// AuditorKey returns public key of the auditing FrostFS Inner Ring node in
|
||||
// a FrostFS binary key format.
|
||||
// AuditorKey returns public key of the auditing NeoFS Inner Ring node in
|
||||
// a NeoFS binary key format.
|
||||
//
|
||||
// Zero Result has nil key. Return value MUST NOT be mutated: to do this,
|
||||
// first make a copy.
|
||||
|
@ -147,8 +147,8 @@ func (r Result) AuditorKey() []byte {
|
|||
return r.v2.GetPublicKey()
|
||||
}
|
||||
|
||||
// SetAuditorKey specifies public key of the auditing FrostFS Inner Ring node in
|
||||
// a FrostFS binary key format.
|
||||
// SetAuditorKey specifies public key of the auditing NeoFS Inner Ring node in
|
||||
// a NeoFS binary key format.
|
||||
//
|
||||
// Argument MUST NOT be mutated at least until the end of using the Result.
|
||||
//
|
||||
|
|
|
@ -4,11 +4,11 @@ import (
|
|||
"bytes"
|
||||
"testing"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/audit"
|
||||
audittest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/audit/test"
|
||||
cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
oidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id/test"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/audit"
|
||||
audittest "github.com/nspcc-dev/neofs-sdk-go/audit/test"
|
||||
cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/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"
|
||||
)
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ Note that importing the package into source files is highly discouraged.
|
|||
|
||||
Random instance generation functions can be useful when testing expects any value, e.g.:
|
||||
|
||||
import audittest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/audit/test"
|
||||
import audittest "github.com/nspcc-dev/neofs-sdk-go/audit/test"
|
||||
|
||||
dec := audittest.Result()
|
||||
// test the value
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package audittest
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/audit"
|
||||
cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test"
|
||||
oidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id/test"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/audit"
|
||||
cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test"
|
||||
oidtest "github.com/nspcc-dev/neofs-sdk-go/object/id/test"
|
||||
)
|
||||
|
||||
// Result returns random audit.Result.
|
||||
|
|
|
@ -1,22 +1,20 @@
|
|||
package bearer
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
frostfscrypto "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto"
|
||||
frostfsecdsa "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto/ecdsa"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/acl"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/eacl"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/user"
|
||||
)
|
||||
|
||||
// Token represents bearer token for object service operations.
|
||||
//
|
||||
// Token is mutually compatible with git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl.BearerToken
|
||||
// Token is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/acl.BearerToken
|
||||
// message. See ReadFromV2 / WriteToV2 methods.
|
||||
//
|
||||
// Instances can be created using built-in var declaration.
|
||||
|
@ -136,7 +134,7 @@ func (b Token) WriteToV2(m *acl.BearerToken) {
|
|||
}
|
||||
|
||||
// SetExp sets "exp" (expiration time) claim which identifies the
|
||||
// expiration time (in FrostFS epochs) after which the Token MUST NOT be
|
||||
// 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.
|
||||
|
@ -150,7 +148,7 @@ func (b *Token) SetExp(exp uint64) {
|
|||
}
|
||||
|
||||
// SetNbf sets "nbf" (not before) claim which identifies the time (in
|
||||
// FrostFS epochs) before which the Token MUST NOT be accepted for processing. The
|
||||
// NeoFS epochs) before which the Token MUST NOT be accepted for processing. The
|
||||
// processing of the "nbf" claim requires that the current epoch MUST be
|
||||
// after or equal to the not-before epoch listed in the "nbf" claim.
|
||||
//
|
||||
|
@ -162,7 +160,7 @@ func (b *Token) SetNbf(nbf uint64) {
|
|||
b.lifetimeSet = true
|
||||
}
|
||||
|
||||
// SetIat sets "iat" (issued at) claim which identifies the time (in FrostFS
|
||||
// SetIat sets "iat" (issued at) claim which identifies the time (in NeoFS
|
||||
// epochs) at which the Token was issued. This claim can be used to determine
|
||||
// the age of the Token.
|
||||
//
|
||||
|
@ -189,7 +187,7 @@ func (b Token) InvalidAt(epoch uint64) bool {
|
|||
// within any issuer's container.
|
||||
//
|
||||
// SetEACLTable MUST be called if Token is going to be transmitted over
|
||||
// FrostFS API V2 protocol.
|
||||
// NeoFS API V2 protocol.
|
||||
//
|
||||
// See also EACLTable, AssertContainer.
|
||||
func (b *Token) SetEACLTable(table eacl.Table) {
|
||||
|
@ -245,20 +243,20 @@ 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
|
||||
// FrostFS API V2 protocol.
|
||||
// NeoFS API V2 protocol.
|
||||
//
|
||||
// Note that any Token mutation is likely to break the signature, so it is
|
||||
// expected to be calculated as a final stage of Token formation.
|
||||
//
|
||||
// See also VerifySignature, Issuer.
|
||||
func (b *Token) Sign(key ecdsa.PrivateKey) error {
|
||||
var sig frostfscrypto.Signature
|
||||
func (b *Token) Sign(signer neofscrypto.Signer) error {
|
||||
var sig neofscrypto.Signature
|
||||
|
||||
err := sig.Calculate(frostfsecdsa.Signer(key), b.signedData())
|
||||
err := sig.Calculate(signer, b.signedData())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -279,13 +277,13 @@ func (b Token) VerifySignature() bool {
|
|||
return false
|
||||
}
|
||||
|
||||
var sig frostfscrypto.Signature
|
||||
var sig neofscrypto.Signature
|
||||
|
||||
// TODO: (#233) check owner<->key relation
|
||||
return sig.ReadFromV2(b.sig) == nil && sig.Verify(b.signedData())
|
||||
}
|
||||
|
||||
// Marshal encodes Token into a binary format of the FrostFS API protocol
|
||||
// Marshal encodes Token into a binary format of the NeoFS API protocol
|
||||
// (Protocol Buffers V3 with direct field order).
|
||||
//
|
||||
// See also Unmarshal.
|
||||
|
@ -296,7 +294,7 @@ func (b Token) Marshal() []byte {
|
|||
return m.StableMarshal(nil)
|
||||
}
|
||||
|
||||
// Unmarshal decodes FrostFS API protocol binary data into the Token
|
||||
// Unmarshal decodes NeoFS API protocol binary data into the Token
|
||||
// (Protocol Buffers V3 with direct field order). Returns an error describing
|
||||
// a format violation.
|
||||
//
|
||||
|
@ -312,7 +310,7 @@ func (b *Token) Unmarshal(data []byte) error {
|
|||
return b.readFromV2(m, false)
|
||||
}
|
||||
|
||||
// MarshalJSON encodes Token into a JSON format of the FrostFS API protocol
|
||||
// MarshalJSON encodes Token into a JSON format of the NeoFS API protocol
|
||||
// (Protocol Buffers V3 JSON).
|
||||
//
|
||||
// See also UnmarshalJSON.
|
||||
|
@ -323,7 +321,7 @@ func (b Token) MarshalJSON() ([]byte, error) {
|
|||
return m.MarshalJSON()
|
||||
}
|
||||
|
||||
// UnmarshalJSON decodes FrostFS API protocol JSON data into the Token
|
||||
// UnmarshalJSON decodes NeoFS API protocol JSON data into the Token
|
||||
// (Protocol Buffers V3 JSON). Returns an error describing a format violation.
|
||||
//
|
||||
// See also MarshalJSON.
|
||||
|
@ -339,7 +337,7 @@ func (b *Token) UnmarshalJSON(data []byte) error {
|
|||
}
|
||||
|
||||
// SigningKeyBytes returns issuer's public key in a binary format of
|
||||
// FrostFS API protocol.
|
||||
// NeoFS API protocol.
|
||||
//
|
||||
// Unsigned Token has empty key.
|
||||
//
|
||||
|
@ -360,9 +358,8 @@ func ResolveIssuer(b Token) (usr user.ID) {
|
|||
binKey := b.SigningKeyBytes()
|
||||
|
||||
if len(binKey) != 0 {
|
||||
var key frostfsecdsa.PublicKey
|
||||
if key.Decode(binKey) == nil {
|
||||
user.IDFromKey(&usr, ecdsa.PublicKey(key))
|
||||
if err := user.IDFromKey(&usr, binKey); err != nil {
|
||||
usr = user.ID{}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,18 +5,17 @@ import (
|
|||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
||||
bearertest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer/test"
|
||||
cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test"
|
||||
frostfscrypto "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto"
|
||||
frostfsecdsa "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto/ecdsa"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
|
||||
eacltest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl/test"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||
usertest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user/test"
|
||||
"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"
|
||||
"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"
|
||||
usertest "github.com/nspcc-dev/neofs-sdk-go/user/test"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -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())
|
||||
|
@ -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,12 +343,9 @@ 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 := test.RandomSigner(t)
|
||||
|
||||
signer := frostfsecdsa.Signer(k.PrivateKey)
|
||||
|
||||
var s frostfscrypto.Signature
|
||||
var s neofscrypto.Signature
|
||||
|
||||
require.NoError(t, s.Calculate(signer, body.StableMarshal(nil)))
|
||||
|
||||
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ Bearer token must be signed by owner of the container.
|
|||
Provide signed token in JSON or binary format to the request sender. Request
|
||||
sender can attach this bearer token to the object service requests:
|
||||
|
||||
import sdkClient "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
||||
import sdkClient "github.com/nspcc-dev/neofs-sdk-go/client"
|
||||
|
||||
var headParams sdkClient.PrmObjectHead
|
||||
headParams.WithBearerToken(bearerToken)
|
||||
|
|
|
@ -1,20 +1,22 @@
|
|||
package bearertest
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
||||
eacltest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl/test"
|
||||
usertest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user/test"
|
||||
"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"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
|
|
@ -6,13 +6,13 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
|
||||
"git.frostfs.info/TrueCloudLab/tzhash/tz"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
||||
"github.com/nspcc-dev/tzhash/tz"
|
||||
)
|
||||
|
||||
// Checksum represents checksum of some digital data.
|
||||
//
|
||||
// Checksum is mutually compatible with git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs.Checksum
|
||||
// Checksum is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/refs.Checksum
|
||||
// message. See ReadFromV2 / WriteToV2 methods.
|
||||
//
|
||||
// Instances can be created using built-in var declaration.
|
||||
|
@ -38,7 +38,7 @@ const (
|
|||
)
|
||||
|
||||
// ReadFromV2 reads Checksum from the refs.Checksum message. Checks if the
|
||||
// message conforms to FrostFS API V2 protocol.
|
||||
// message conforms to NeoFS API V2 protocol.
|
||||
//
|
||||
// See also WriteToV2.
|
||||
func (c *Checksum) ReadFromV2(m refs.Checksum) error {
|
||||
|
|
|
@ -5,8 +5,8 @@ import (
|
|||
"crypto/sha256"
|
||||
"testing"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
|
||||
"git.frostfs.info/TrueCloudLab/tzhash/tz"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
||||
"github.com/nspcc-dev/tzhash/tz"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
"fmt"
|
||||
"math/rand"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
||||
)
|
||||
|
||||
func ExampleCalculate() {
|
||||
|
|
|
@ -5,7 +5,7 @@ Note that importing the package into source files is highly discouraged.
|
|||
|
||||
Random instance generation functions can be useful when testing expects any value, e.g.:
|
||||
|
||||
import checksumtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/checksum/test"
|
||||
import checksumtest "github.com/nspcc-dev/neofs-sdk-go/checksum/test"
|
||||
|
||||
cs := checksumtest.Checksum()
|
||||
// test the value
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"crypto/sha256"
|
||||
"math/rand"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/checksum"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/checksum"
|
||||
)
|
||||
|
||||
// Checksum returns random checksum.Checksum.
|
||||
|
|
|
@ -3,12 +3,12 @@ package client
|
|||
import (
|
||||
"context"
|
||||
|
||||
v2accounting "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/accounting"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
|
||||
rpcapi "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/accounting"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||
v2accounting "github.com/nspcc-dev/neofs-api-go/v2/accounting"
|
||||
"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"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/accounting"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/user"
|
||||
)
|
||||
|
||||
// PrmBalanceGet groups parameters of BalanceGet operation.
|
||||
|
@ -19,7 +19,7 @@ type PrmBalanceGet struct {
|
|||
account user.ID
|
||||
}
|
||||
|
||||
// SetAccount sets identifier of the FrostFS account for which the balance is requested.
|
||||
// SetAccount sets identifier of the NeoFS account for which the balance is requested.
|
||||
// Required parameter.
|
||||
func (x *PrmBalanceGet) SetAccount(id user.ID) {
|
||||
x.account = id
|
||||
|
@ -28,35 +28,27 @@ func (x *PrmBalanceGet) SetAccount(id user.ID) {
|
|||
|
||||
// ResBalanceGet groups resulting values of BalanceGet operation.
|
||||
type ResBalanceGet struct {
|
||||
statusRes
|
||||
|
||||
amount accounting.Decimal
|
||||
}
|
||||
|
||||
// Amount returns current amount of funds on the FrostFS account as decimal number.
|
||||
// Amount returns current amount of funds on the NeoFS account as decimal number.
|
||||
func (x ResBalanceGet) Amount() accounting.Decimal {
|
||||
return x.amount
|
||||
}
|
||||
|
||||
// BalanceGet requests current balance of the FrostFS account.
|
||||
// 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.ResolveFrostFSFailures has been called, unsuccessful
|
||||
// FrostFS 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.
|
||||
//
|
||||
// Returns an error 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:
|
||||
return nil, errorMissingContext
|
||||
case !prm.accountSet:
|
||||
return nil, errorAccountNotSet
|
||||
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))
|
||||
}
|
||||
|
|
|
@ -4,18 +4,21 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
|
||||
v2netmap "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/netmap"
|
||||
rpcapi "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
|
||||
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 FrostFS API server. Exists for test purposes only.
|
||||
type frostFSAPIServer interface {
|
||||
// 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)
|
||||
}
|
||||
|
||||
// wrapper over real client connection which communicates over FrostFS API protocol.
|
||||
// Provides frostFSAPIServer for Client instances used in real applications.
|
||||
// wrapper over real client connection which communicates over NeoFS API protocol.
|
||||
// Provides neoFSAPIServer for Client instances used in real applications.
|
||||
type coreServer client.Client
|
||||
|
||||
// unifies errors of all RPC.
|
||||
|
@ -23,7 +26,7 @@ func rpcErr(e error) error {
|
|||
return fmt.Errorf("rpc failure: %w", e)
|
||||
}
|
||||
|
||||
// executes NetmapService.NetmapSnapshot RPC declared in FrostFS API protocol
|
||||
// executes NetmapService.NetmapSnapshot RPC declared in NeoFS API protocol
|
||||
// using underlying client.Client.
|
||||
func (x *coreServer) netMapSnapshot(ctx context.Context, req v2netmap.SnapshotRequest) (*v2netmap.SnapshotResponse, error) {
|
||||
resp, err := rpcapi.NetMapSnapshot((*client.Client)(x), &req, client.WithContext(ctx))
|
||||
|
@ -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
|
||||
}
|
||||
|
|
119
client/client.go
119
client/client.go
|
@ -2,43 +2,42 @@ package client
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
v2accounting "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/accounting"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
|
||||
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 FrostFS network to communicate
|
||||
// with FrostFS server using FrostFS API protocol. It is designed to provide
|
||||
// Client represents virtual connection to the NeoFS network to communicate
|
||||
// with NeoFS server using NeoFS API protocol. It is designed to provide
|
||||
// an abstraction interface from the protocol details of data transfer over
|
||||
// a network in FrostFS.
|
||||
// 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).
|
||||
// Before executing the FrostFS operations using the Client, connection to the
|
||||
// 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 FrostFS API call may return a server response.
|
||||
// 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.
|
||||
//
|
||||
|
@ -48,20 +47,27 @@ type Client struct {
|
|||
|
||||
c client.Client
|
||||
|
||||
server frostFSAPIServer
|
||||
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 FrostFS network.
|
||||
// Dial establishes a connection to the server from the NeoFS network.
|
||||
// Returns an error describing failure reason. If failed, the Client
|
||||
// SHOULD NOT be used.
|
||||
//
|
||||
|
@ -69,21 +75,25 @@ func (c *Client) Init(prm PrmInit) {
|
|||
// argument, otherwise context.Background() is used. Dial returns context
|
||||
// errors, see context package docs for details.
|
||||
//
|
||||
// Returns an error if required parameters are set incorrectly, look carefully
|
||||
// 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 == "" {
|
||||
return errorServerAddrUnset
|
||||
return ErrMissingServer
|
||||
}
|
||||
|
||||
if prm.timeoutDialSet {
|
||||
if prm.timeoutDial <= 0 {
|
||||
return errorNonPositiveTimeout
|
||||
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 {
|
||||
return errorNonPositiveTimeout
|
||||
return ErrNonPositiveTimeout
|
||||
}
|
||||
} else {
|
||||
prm.streamTimeout = 10 * time.Second
|
||||
|
@ -103,7 +113,7 @@ func (c *Client) Dial(prm PrmDial) error {
|
|||
client.WithRWTimeout(prm.streamTimeout),
|
||||
)...)
|
||||
|
||||
c.setFrostFSAPIServer((*coreServer)(&c.c))
|
||||
c.setNeoFSAPIServer((*coreServer)(&c.c))
|
||||
|
||||
if prm.parentCtx == nil {
|
||||
prm.parentCtx = context.Background()
|
||||
|
@ -121,58 +131,51 @@ func (c *Client) Dial(prm PrmDial) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// sets underlying provider of frostFSAPIServer. The method is used for testing as an approach
|
||||
// to skip Dial stage and override FrostFS API server. MUST NOT be used outside test code.
|
||||
// In real applications wrapper over git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client
|
||||
// sets underlying provider of neoFSAPIServer. The method is used for testing as an approach
|
||||
// to skip Dial stage and override NeoFS API server. MUST NOT be used outside test code.
|
||||
// In real applications wrapper over github.com/nspcc-dev/neofs-api-go/v2/rpc/client
|
||||
// is statically used.
|
||||
func (c *Client) setFrostFSAPIServer(server frostFSAPIServer) {
|
||||
func (c *Client) setNeoFSAPIServer(server neoFSAPIServer) {
|
||||
c.server = server
|
||||
}
|
||||
|
||||
// Close closes underlying connection to the FrostFS server. Implements io.Closer.
|
||||
// Close closes underlying connection to the NeoFS server. Implements io.Closer.
|
||||
// MUST NOT be called before successful Dial. Can be called concurrently
|
||||
// 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 {
|
||||
resolveFrostFSErrors 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
|
||||
}
|
||||
|
||||
// ResolveFrostFSFailures makes the Client to resolve failure statuses of the
|
||||
// FrostFS 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) ResolveFrostFSFailures() {
|
||||
x.resolveFrostFSErrors = 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
|
||||
// FrostFS server response to f. Nil (default) means ignore response meta info.
|
||||
// NeoFS server response to f. Nil (default) means ignore response meta info.
|
||||
func (x *PrmInit) SetResponseInfoCallback(f func(ResponseMetaInfo) error) {
|
||||
x.cbRespInfo = f
|
||||
}
|
||||
|
@ -194,7 +197,7 @@ type PrmDial struct {
|
|||
parentCtx context.Context
|
||||
}
|
||||
|
||||
// SetServerURI sets server URI in the FrostFS network.
|
||||
// SetServerURI sets server URI in the NeoFS network.
|
||||
// Required parameter.
|
||||
//
|
||||
// Format of the URI:
|
||||
|
@ -212,7 +215,7 @@ func (x *PrmDial) SetServerURI(endpoint string) {
|
|||
}
|
||||
|
||||
// SetTLSConfig sets tls.Config to open TLS client connection
|
||||
// to the FrostFS server. Nil (default) means insecure connection.
|
||||
// to the NeoFS server. Nil (default) means insecure connection.
|
||||
//
|
||||
// See also SetServerURI.
|
||||
func (x *PrmDial) SetTLSConfig(tlsConfig *tls.Config) {
|
||||
|
|
|
@ -2,12 +2,10 @@ package client
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
|
||||
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
||||
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 frostFSAPIServer) *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.setFrostFSAPIServer(server)
|
||||
c, err := New(prm)
|
||||
require.NoError(t, err)
|
||||
c.setNeoFSAPIServer(server)
|
||||
|
||||
return &c
|
||||
return c
|
||||
}
|
||||
|
||||
func TestClient_DialContext(t *testing.T) {
|
||||
|
|
114
client/common.go
114
client/common.go
|
@ -1,43 +1,19 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
|
||||
v2session "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/signature"
|
||||
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/version"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/rpc/client"
|
||||
v2session "github.com/nspcc-dev/neofs-api-go/v2/session"
|
||||
apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status"
|
||||
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/version"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
// FrostFS request X-Headers
|
||||
// NeoFS request X-Headers
|
||||
xHeaders []string
|
||||
}
|
||||
|
||||
|
@ -71,23 +47,6 @@ func writeXHeadersToMeta(xHeaders []string, h *v2session.RequestMetaHeader) {
|
|||
h.SetXHeaders(hs)
|
||||
}
|
||||
|
||||
// error messages.
|
||||
var (
|
||||
errorMissingContext = errors.New("missing context")
|
||||
errorMissingContainer = errors.New("missing container")
|
||||
errorMissingObject = errors.New("missing object")
|
||||
errorAccountNotSet = errors.New("account not set")
|
||||
errorServerAddrUnset = errors.New("server address is unset or empty")
|
||||
errorNonPositiveTimeout = errors.New("non-positive timeout")
|
||||
errorEACLTableNotSet = errors.New("eACL table not set")
|
||||
errorMissingAnnouncements = errors.New("missing announcements")
|
||||
errorZeroRangeLength = errors.New("zero range length")
|
||||
errorMissingRanges = errors.New("missing ranges")
|
||||
errorZeroEpoch = errors.New("zero epoch")
|
||||
errorMissingTrusts = errors.New("missing trusts")
|
||||
errorTrustNotSet = errors.New("current trust value not set")
|
||||
)
|
||||
|
||||
// groups all the details required to send a single request and process a response to it.
|
||||
type contextCall struct {
|
||||
// ==================================================
|
||||
|
@ -102,16 +61,13 @@ 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
|
||||
|
||||
// FrostFS network magic
|
||||
// NeoFS network magic
|
||||
netMagic uint64
|
||||
|
||||
// Meta parameters
|
||||
|
@ -120,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
|
||||
|
@ -198,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
|
||||
|
@ -237,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)
|
||||
}
|
||||
|
||||
return successfulStatus
|
||||
x.err = apistatus.ErrorFromV2(x.resp.GetMetaHeader().GetStatus())
|
||||
return x.err == nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
// 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)
|
||||
}
|
||||
|
||||
st := apistatus.FromStatusV2(resp.GetMetaHeader().GetStatus())
|
||||
if c.prm.resolveFrostFSErrors {
|
||||
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()
|
||||
|
@ -339,15 +276,14 @@ 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.resolveFrostFSErrors
|
||||
ctx.signer = c.prm.signer
|
||||
ctx.callbackResp = c.prm.cbRespInfo
|
||||
ctx.netMagic = c.prm.netMagic
|
||||
}
|
||||
|
||||
// ExecRaw executes f with underlying git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client.Client
|
||||
// ExecRaw executes f with underlying github.com/nspcc-dev/neofs-api-go/v2/rpc/client.Client
|
||||
// instance. Communicate over the Protocol Buffers protocol in a more flexible way:
|
||||
// most often used to transmit data over a fixed version of the FrostFS protocol, as well
|
||||
// most often used to transmit data over a fixed version of the NeoFS protocol, as well
|
||||
// as to support custom services.
|
||||
//
|
||||
// The f must not manipulate the client connection passed into it.
|
||||
|
@ -356,7 +292,7 @@ func (c *Client) initCallContext(ctx *contextCall) {
|
|||
// before closing the connection.
|
||||
//
|
||||
// See also Dial and Close.
|
||||
// See also git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client package docs.
|
||||
// See also github.com/nspcc-dev/neofs-api-go/v2/rpc/client package docs.
|
||||
func (c *Client) ExecRaw(f func(client *client.Client) error) error {
|
||||
return f(&c.c)
|
||||
}
|
||||
|
|
|
@ -5,18 +5,17 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
|
||||
v2container "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
|
||||
rpcapi "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
|
||||
v2session "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
frostfscrypto "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto"
|
||||
frostfsecdsa "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto/ecdsa"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||
v2container "github.com/nspcc-dev/neofs-api-go/v2/container"
|
||||
"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"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/container"
|
||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/eacl"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/session"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/user"
|
||||
)
|
||||
|
||||
// PrmContainerPut groups parameters of ContainerPut operation.
|
||||
|
@ -28,15 +27,24 @@ type PrmContainerPut struct {
|
|||
|
||||
sessionSet bool
|
||||
session session.Container
|
||||
|
||||
signer neofscrypto.Signer
|
||||
}
|
||||
|
||||
// SetContainer sets structured information about new FrostFS container.
|
||||
// SetContainer sets structured information about new NeoFS container.
|
||||
// Required parameter.
|
||||
func (x *PrmContainerPut) SetContainer(cnr container.Container) {
|
||||
x.cnr = cnr
|
||||
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
|
||||
}
|
||||
|
||||
// ContainerPut sends request to save container in FrostFS.
|
||||
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.ResolveFrostFSFailures has been called, unsuccessful
|
||||
// FrostFS 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).
|
||||
//
|
||||
// Returns an error 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:
|
||||
return nil, errorMissingContext
|
||||
case !prm.cnrSet:
|
||||
return nil, errorMissingContainer
|
||||
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 frostfscrypto.Signature
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -196,26 +201,19 @@ func (x ResContainerGet) Container() container.Container {
|
|||
return x.cnr
|
||||
}
|
||||
|
||||
// ContainerGet reads FrostFS container by ID.
|
||||
// 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.ResolveFrostFSFailures has been called, unsuccessful
|
||||
// FrostFS 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.
|
||||
//
|
||||
// Returns an error 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:
|
||||
return nil, errorMissingContext
|
||||
case !prm.idSet:
|
||||
return nil, errorMissingContainer
|
||||
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))
|
||||
}
|
||||
|
@ -275,7 +272,7 @@ type PrmContainerList struct {
|
|||
ownerID user.ID
|
||||
}
|
||||
|
||||
// SetAccount sets identifier of the FrostFS account to list the containers.
|
||||
// SetAccount sets identifier of the NeoFS account to list the containers.
|
||||
// Required parameter.
|
||||
func (x *PrmContainerList) SetAccount(id user.ID) {
|
||||
x.ownerID = id
|
||||
|
@ -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.ResolveFrostFSFailures has been called, unsuccessful
|
||||
// FrostFS 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.
|
||||
//
|
||||
// Returns an error 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:
|
||||
return nil, errorMissingContext
|
||||
case !prm.ownerSet:
|
||||
return nil, errorAccountNotSet
|
||||
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,15 +363,24 @@ type PrmContainerDelete struct {
|
|||
|
||||
tokSet bool
|
||||
tok session.Container
|
||||
|
||||
signer neofscrypto.Signer
|
||||
}
|
||||
|
||||
// SetContainer sets identifier of the FrostFS container to be removed.
|
||||
// SetContainer sets identifier of the NeoFS container to be removed.
|
||||
// Required parameter.
|
||||
func (x *PrmContainerDelete) SetContainer(id cid.ID) {
|
||||
x.id = 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 FrostFS container.
|
||||
// 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.ResolveFrostFSFailures has been called, unsuccessful
|
||||
// FrostFS 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).
|
||||
//
|
||||
// Returns an error 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:
|
||||
return nil, errorMissingContext
|
||||
case !prm.idSet:
|
||||
return nil, errorMissingContainer
|
||||
return ErrMissingContainer
|
||||
}
|
||||
|
||||
// sign container ID
|
||||
|
@ -438,11 +424,18 @@ func (c *Client) ContainerDelete(ctx context.Context, prm PrmContainerDelete) (*
|
|||
// don't get confused with stable marshaled protobuf container.ID structure
|
||||
data := cidV2.GetValue()
|
||||
|
||||
var sig frostfscrypto.Signature
|
||||
var sig neofscrypto.Signature
|
||||
signer := prm.signer
|
||||
if signer == nil {
|
||||
signer = c.defaultSigner()
|
||||
}
|
||||
|
||||
err := sig.Calculate(frostfsecdsa.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
|
||||
|
@ -474,23 +467,21 @@ func (c *Client) ContainerDelete(ctx context.Context, prm PrmContainerDelete) (*
|
|||
// init call context
|
||||
|
||||
var (
|
||||
cc contextCall
|
||||
res ResContainerDelete
|
||||
cc contextCall
|
||||
)
|
||||
|
||||
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.
|
||||
|
@ -501,7 +492,7 @@ type PrmContainerEACL struct {
|
|||
id cid.ID
|
||||
}
|
||||
|
||||
// SetContainer sets identifier of the FrostFS container to read the eACL table.
|
||||
// SetContainer sets identifier of the NeoFS container to read the eACL table.
|
||||
// Required parameter.
|
||||
func (x *PrmContainerEACL) SetContainer(id cid.ID) {
|
||||
x.id = id
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -520,28 +509,20 @@ func (x ResContainerEACL) Table() eacl.Table {
|
|||
return x.table
|
||||
}
|
||||
|
||||
// ContainerEACL reads eACL table of the FrostFS container.
|
||||
// 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.ResolveFrostFSFailures has been called, unsuccessful
|
||||
// FrostFS 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.
|
||||
//
|
||||
// Returns an error 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:
|
||||
return nil, errorMissingContext
|
||||
case !prm.idSet:
|
||||
return nil, errorMissingContainer
|
||||
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 FrostFS container.
|
||||
// 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.ResolveFrostFSFailures has been called, unsuccessful
|
||||
// FrostFS 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).
|
||||
//
|
||||
// Returns an error 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:
|
||||
return nil, errorMissingContext
|
||||
case !prm.tableSet:
|
||||
return nil, errorEACLTableNotSet
|
||||
return ErrMissingEACL
|
||||
}
|
||||
|
||||
_, isCIDSet := prm.table.CID()
|
||||
if !isCIDSet {
|
||||
return ErrMissingEACLContainer
|
||||
}
|
||||
|
||||
// sign the eACL table
|
||||
eaclV2 := prm.table.ToV2()
|
||||
|
||||
var sig frostfscrypto.Signature
|
||||
var sig neofscrypto.Signature
|
||||
signer := prm.signer
|
||||
if signer == nil {
|
||||
signer = c.defaultSigner()
|
||||
}
|
||||
|
||||
err := sig.Calculate(frostfsecdsa.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
|
||||
|
@ -695,23 +688,21 @@ func (c *Client) ContainerSetEACL(ctx context.Context, prm PrmContainerSetEACL)
|
|||
// init call context
|
||||
|
||||
var (
|
||||
cc contextCall
|
||||
res ResContainerSetEACL
|
||||
cc contextCall
|
||||
)
|
||||
|
||||
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,45 +720,34 @@ 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.ResolveFrostFSFailures has been called, unsuccessful
|
||||
// FrostFS 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.
|
||||
//
|
||||
// Returns an error 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:
|
||||
return nil, errorMissingContext
|
||||
case len(prm.announcements) == 0:
|
||||
return nil, errorMissingAnnouncements
|
||||
return ErrMissingAnnouncements
|
||||
}
|
||||
|
||||
// convert list of SDK announcement structures into FrostFS-API v2 list
|
||||
// convert list of SDK announcement structures into NeoFS-API v2 list
|
||||
v2announce := make([]v2container.UsedSpaceAnnouncement, len(prm.announcements))
|
||||
for i := range prm.announcements {
|
||||
prm.announcements[i].WriteToV2(&v2announce[i])
|
||||
}
|
||||
|
||||
// prepare body of the FrostFS-API v2 request and request itself
|
||||
// prepare body of the NeoFS-API v2 request and request itself
|
||||
reqBody := new(v2container.AnnounceUsedSpaceRequestBody)
|
||||
reqBody.SetAnnouncements(v2announce)
|
||||
|
||||
|
@ -779,24 +759,22 @@ func (c *Client) ContainerAnnounceUsedSpace(ctx context.Context, prm PrmAnnounce
|
|||
// init call context
|
||||
|
||||
var (
|
||||
cc contextCall
|
||||
res ResAnnounceSpace
|
||||
cc contextCall
|
||||
)
|
||||
|
||||
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
|
||||
|
|
|
@ -1,32 +1,28 @@
|
|||
/*
|
||||
Package client provides FrostFS API client implementation.
|
||||
Package client provides NeoFS API client implementation.
|
||||
|
||||
The main component is Client type. It is a virtual connection to the network
|
||||
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 FrostFS server:
|
||||
Connect to the NeoFS server:
|
||||
|
||||
var prm client.PrmDial
|
||||
prm.SetServerURI("localhost:8080")
|
||||
prm.SetDefaultPrivateKey(key)
|
||||
prm.SetDefaultSigner(signer)
|
||||
// ...
|
||||
|
||||
err := c.Dial(prm)
|
||||
// ...
|
||||
|
||||
Execute FrostFS operation on the server:
|
||||
Execute NeoFS operation on the server:
|
||||
|
||||
var prm client.PrmContainerPut
|
||||
prm.SetContainer(cnr)
|
||||
|
@ -47,8 +43,8 @@ Consume custom service of the server:
|
|||
rpc CustomRPC(CustomRPCRequest) returns (CustomRPCResponse);
|
||||
}
|
||||
|
||||
import "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
|
||||
import "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/common"
|
||||
import "github.com/nspcc-dev/neofs-api-go/v2/rpc/client"
|
||||
import "github.com/nspcc-dev/neofs-api-go/v2/rpc/common"
|
||||
|
||||
req := new(CustomRPCRequest)
|
||||
// ...
|
||||
|
@ -72,9 +68,9 @@ for the all operations are write-only and the results of the all operations are
|
|||
read-only. To be able to override client behavior (e.g. for tests), abstract it
|
||||
with an interface:
|
||||
|
||||
import "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
||||
import "github.com/nspcc-dev/neofs-sdk-go/client"
|
||||
|
||||
type FrostFSClient interface {
|
||||
type NeoFSClient interface {
|
||||
// Operations according to the application needs
|
||||
CreateContainer(context.Context, container.Container) error
|
||||
// ...
|
||||
|
|
162
client/errors.go
162
client/errors.go
|
@ -3,104 +3,106 @@ package client
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
apistatus "git.frostfs.info/TrueCloudLab/frostfs-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")
|
||||
|
||||
return err
|
||||
// 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
|
||||
}
|
||||
|
||||
// IsErrContainerNotFound checks if err corresponds to FrostFS status
|
||||
// return corresponding to missing container. Supports wrapped errors.
|
||||
func IsErrContainerNotFound(err error) bool {
|
||||
switch unwrapErr(err).(type) {
|
||||
default:
|
||||
return false
|
||||
case
|
||||
apistatus.ContainerNotFound,
|
||||
*apistatus.ContainerNotFound:
|
||||
return true
|
||||
}
|
||||
// Error implements the error interface.
|
||||
func (e MissingResponseFieldErr) Error() string {
|
||||
return fmt.Sprintf("missing %s field in the response", e.name)
|
||||
}
|
||||
|
||||
// IsErrEACLNotFound checks if err corresponds to FrostFS status
|
||||
// return corresponding to missing eACL table. Supports wrapped errors.
|
||||
func IsErrEACLNotFound(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.EACLNotFound,
|
||||
*apistatus.EACLNotFound:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// IsErrObjectNotFound checks if err corresponds to FrostFS 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 FrostFS 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 FrostFS 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 FrostFS 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 FrostFS protocol)
|
||||
// returns error describing invalid field (according to the NeoFS protocol)
|
||||
// with the given name and format violation err.
|
||||
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"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
||||
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/client"
|
||||
"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}}}
|
||||
}
|
|
@ -4,14 +4,12 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
|
||||
v2netmap "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/netmap"
|
||||
rpcapi "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
|
||||
v2session "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/signature"
|
||||
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/version"
|
||||
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"
|
||||
v2session "github.com/nspcc-dev/neofs-api-go/v2/session"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/netmap"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/version"
|
||||
)
|
||||
|
||||
// PrmEndpointInfo groups parameters of EndpointInfo operation.
|
||||
|
@ -21,19 +19,17 @@ type PrmEndpointInfo struct {
|
|||
|
||||
// ResEndpointInfo group resulting values of EndpointInfo operation.
|
||||
type ResEndpointInfo struct {
|
||||
statusRes
|
||||
|
||||
version version.Version
|
||||
|
||||
ni netmap.NodeInfo
|
||||
}
|
||||
|
||||
// LatestVersion returns latest FrostFS API protocol's version in use.
|
||||
// LatestVersion returns latest NeoFS API protocol's version in use.
|
||||
func (x ResEndpointInfo) LatestVersion() version.Version {
|
||||
return x.version
|
||||
}
|
||||
|
||||
// NodeInfo returns information about the FrostFS node served on the remote endpoint.
|
||||
// NodeInfo returns information about the NeoFS node served on the remote endpoint.
|
||||
func (x ResEndpointInfo) NodeInfo() netmap.NodeInfo {
|
||||
return x.ni
|
||||
}
|
||||
|
@ -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.ResolveFrostFSFailures has been called, unsuccessful
|
||||
// FrostFS 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.
|
||||
//
|
||||
// Returns an error 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 {
|
||||
return nil, errorMissingContext
|
||||
}
|
||||
|
||||
// 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,37 +111,24 @@ type PrmNetworkInfo struct {
|
|||
|
||||
// ResNetworkInfo groups resulting values of NetworkInfo operation.
|
||||
type ResNetworkInfo struct {
|
||||
statusRes
|
||||
|
||||
info netmap.NetworkInfo
|
||||
}
|
||||
|
||||
// Info returns structured information about the FrostFS network.
|
||||
// Info returns structured information about the NeoFS network.
|
||||
func (x ResNetworkInfo) Info() netmap.NetworkInfo {
|
||||
return x.info
|
||||
}
|
||||
|
||||
// NetworkInfo requests information about the FrostFS network of which the remote server is a part.
|
||||
// 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.ResolveFrostFSFailures has been called, unsuccessful
|
||||
// FrostFS 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.
|
||||
//
|
||||
// Returns an error 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 {
|
||||
return nil, errorMissingContext
|
||||
}
|
||||
|
||||
// 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,25 +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.ResolveFrostFSFailures has been called, unsuccessful
|
||||
// FrostFS 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.
|
||||
//
|
||||
// Returns an error if parameters are set incorrectly.
|
||||
// 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 {
|
||||
return nil, errorMissingContext
|
||||
}
|
||||
|
||||
// form request body
|
||||
var body v2netmap.SnapshotRequestBody
|
||||
|
||||
|
@ -249,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)
|
||||
}
|
||||
|
@ -260,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()
|
||||
|
|
|
@ -6,11 +6,13 @@ import (
|
|||
"fmt"
|
||||
"testing"
|
||||
|
||||
v2netmap "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/netmap"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/signature"
|
||||
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
||||
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"
|
||||
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,13 +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
|
||||
//nolint:staticcheck
|
||||
_, err = c.NetMapSnapshot(nil, prm)
|
||||
require.ErrorIs(t, err, errorMissingContext, "")
|
||||
signer := test.RandomSignerRFC6979(t)
|
||||
|
||||
srv.signer = signer
|
||||
|
||||
c := newClient(t, signer, &srv)
|
||||
ctx := context.Background()
|
||||
|
||||
// request signature
|
||||
srv.errTransport = errors.New("any error")
|
||||
|
@ -88,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
|
||||
|
||||
|
@ -131,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,21 +2,19 @@ package client
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl"
|
||||
v2object "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
|
||||
v2refs "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
|
||||
rpcapi "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
|
||||
v2session "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/signature"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
||||
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/acl"
|
||||
v2object "github.com/nspcc-dev/neofs-api-go/v2/object"
|
||||
v2refs "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"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/bearer"
|
||||
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"
|
||||
)
|
||||
|
||||
// PrmObjectDelete groups parameters of ObjectDelete operation.
|
||||
|
@ -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.
|
||||
|
@ -55,8 +53,8 @@ func (x *PrmObjectDelete) WithBearerToken(t bearer.Token) {
|
|||
x.meta.SetBearerToken(&v2token)
|
||||
}
|
||||
|
||||
// FromContainer specifies FrostFS container of the object.
|
||||
// Required parameter.
|
||||
// FromContainer specifies NeoFS container of the object.
|
||||
// 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
|
||||
}
|
||||
|
||||
|
@ -100,7 +102,7 @@ func (x ResObjectDelete) Tombstone() oid.ID {
|
|||
return x.tomb
|
||||
}
|
||||
|
||||
// ObjectDelete marks an object for deletion from the container using FrostFS API protocol.
|
||||
// ObjectDelete marks an object for deletion from the container using NeoFS API protocol.
|
||||
// As a marker, a special unit called a tombstone is placed in the container.
|
||||
// It confirms the user's intent to delete the object, and is itself a container object.
|
||||
// Explicit deletion is done asynchronously, and is generally not guaranteed.
|
||||
|
@ -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.ResolveFrostFSFailures has been called, unsuccessful
|
||||
// FrostFS status codes are returned as `error`, otherwise, are included
|
||||
// in the returned result structure.
|
||||
// see [apistatus] package for NeoFS-specific error types.
|
||||
//
|
||||
// Returns an error 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:
|
||||
return nil, errorMissingContext
|
||||
case prm.addr.GetContainerID() == nil:
|
||||
return nil, errorMissingContainer
|
||||
return nil, ErrMissingContainer
|
||||
case prm.addr.GetObjectID() == nil:
|
||||
return nil, errorMissingObject
|
||||
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,24 +2,22 @@ package client
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl"
|
||||
v2object "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
|
||||
v2refs "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
|
||||
rpcapi "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
|
||||
v2session "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/signature"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
||||
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/acl"
|
||||
v2object "github.com/nspcc-dev/neofs-api-go/v2/object"
|
||||
v2refs "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"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/bearer"
|
||||
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"
|
||||
)
|
||||
|
||||
// shared parameters of GET/HEAD/RANGE.
|
||||
|
@ -72,8 +70,8 @@ func (x *prmObjectRead) WithBearerToken(t bearer.Token) {
|
|||
x.meta.SetBearerToken(&v2token)
|
||||
}
|
||||
|
||||
// FromContainer specifies FrostFS container of the object.
|
||||
// Required parameter.
|
||||
// FromContainer specifies NeoFS container of the object.
|
||||
// Required parameter. It is an alternative to ByAddress.
|
||||
func (x *prmObjectRead) FromContainer(id cid.ID) {
|
||||
var cnrV2 v2refs.ContainerID
|
||||
id.WriteToV2(&cnrV2)
|
||||
|
@ -81,26 +79,27 @@ 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
|
||||
signer neofscrypto.Signer
|
||||
}
|
||||
|
||||
// ResObjectGet groups the final result values of ObjectGetInit operation.
|
||||
type ResObjectGet struct {
|
||||
statusRes
|
||||
}
|
||||
|
||||
// ObjectReader is designed to read one object from FrostFS system.
|
||||
// ObjectReader is designed to read one object from NeoFS system.
|
||||
//
|
||||
// Must be initialized using Client.ObjectGetInit, any other
|
||||
// usage is unsafe.
|
||||
|
@ -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 FrostFS API statuses, then FrostFS failures
|
||||
// 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 {
|
||||
|
@ -291,22 +284,23 @@ func (x *ObjectReader) Read(p []byte) (int, error) {
|
|||
return n, nil
|
||||
}
|
||||
|
||||
// ObjectGetInit initiates reading an object through a remote server using FrostFS API protocol.
|
||||
// ObjectGetInit initiates reading an object through a remote server using NeoFS API protocol.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// Returns an error 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:
|
||||
return nil, errorMissingContext
|
||||
case prm.addr.GetContainerID() == nil:
|
||||
return nil, errorMissingContainer
|
||||
return nil, ErrMissingContainer
|
||||
case prm.addr.GetObjectID() == nil:
|
||||
return nil, errorMissingObject
|
||||
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
|
||||
|
||||
|
@ -392,36 +382,30 @@ func (x *ResObjectHead) ReadHeader(dst *object.Object) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
// ObjectHead reads object header through a remote server using FrostFS API protocol.
|
||||
// ObjectHead reads object header through a remote server using NeoFS API protocol.
|
||||
//
|
||||
// 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.ResolveFrostFSFailures has been called, unsuccessful
|
||||
// FrostFS status codes are returned as `error`, otherwise, are included
|
||||
// in the returned result structure.
|
||||
// see [apistatus] package for NeoFS-specific error types.
|
||||
//
|
||||
// Returns an error 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:
|
||||
return nil, errorMissingContext
|
||||
case prm.addr.GetContainerID() == nil:
|
||||
return nil, errorMissingContainer
|
||||
return nil, ErrMissingContainer
|
||||
case prm.addr.GetObjectID() == nil:
|
||||
return nil, errorMissingObject
|
||||
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,36 +455,37 @@ 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
|
||||
// from FrostFS system.
|
||||
// from NeoFS system.
|
||||
//
|
||||
// Must be initialized using Client.ObjectRangeInit, any other
|
||||
// usage is unsafe.
|
||||
|
@ -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 FrostFS API statuses, then FrostFS failures
|
||||
// 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 {
|
||||
|
@ -658,24 +633,26 @@ func (x *ObjectRangeReader) Read(p []byte) (int, error) {
|
|||
}
|
||||
|
||||
// ObjectRangeInit initiates reading an object's payload range through a remote
|
||||
// server using FrostFS API protocol.
|
||||
// server using NeoFS API protocol.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// Returns an error 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:
|
||||
return nil, errorMissingContext
|
||||
case prm.addr.GetContainerID() == nil:
|
||||
return nil, errorMissingContainer
|
||||
return nil, ErrMissingContainer
|
||||
case prm.addr.GetObjectID() == nil:
|
||||
return nil, errorMissingObject
|
||||
return nil, ErrMissingObject
|
||||
case prm.rng.GetLength() == 0:
|
||||
return nil, errorZeroRangeLength
|
||||
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())
|
||||
})
|
||||
}
|
|
@ -2,21 +2,19 @@ package client
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl"
|
||||
v2object "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
|
||||
v2refs "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
|
||||
rpcapi "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
|
||||
v2session "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/signature"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
||||
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/acl"
|
||||
v2object "github.com/nspcc-dev/neofs-api-go/v2/object"
|
||||
v2refs "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"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/bearer"
|
||||
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"
|
||||
)
|
||||
|
||||
// PrmObjectHash groups parameters of ObjectHash operation.
|
||||
|
@ -29,15 +27,13 @@ type PrmObjectHash struct {
|
|||
|
||||
addr v2refs.Address
|
||||
|
||||
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 *PrmObjectHash) 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 *PrmObjectHash) UseSigner(signer neofscrypto.Signer) {
|
||||
x.signer = signer
|
||||
}
|
||||
|
||||
// MarkLocal tells the server to execute the operation locally.
|
||||
|
@ -69,8 +65,8 @@ func (x *PrmObjectHash) WithBearerToken(t bearer.Token) {
|
|||
x.meta.SetBearerToken(&v2token)
|
||||
}
|
||||
|
||||
// FromContainer specifies FrostFS container of the object.
|
||||
// Required parameter.
|
||||
// FromContainer specifies NeoFS container of the object.
|
||||
// Required parameter. It is an alternative to [PrmObjectHash.ByAddress].
|
||||
func (x *PrmObjectHash) FromContainer(id cid.ID) {
|
||||
var cidV2 v2refs.ContainerID
|
||||
id.WriteToV2(&cidV2)
|
||||
|
@ -79,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)
|
||||
|
@ -87,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.
|
||||
//
|
||||
|
@ -132,8 +134,6 @@ func (x *PrmObjectHash) WithXHeaders(hs ...string) {
|
|||
|
||||
// ResObjectHash groups resulting values of ObjectHash operation.
|
||||
type ResObjectHash struct {
|
||||
statusRes
|
||||
|
||||
checksums [][]byte
|
||||
}
|
||||
|
||||
|
@ -143,37 +143,29 @@ func (x ResObjectHash) Checksums() [][]byte {
|
|||
}
|
||||
|
||||
// ObjectHash requests checksum of the range list of the object payload using
|
||||
// FrostFS API protocol.
|
||||
// NeoFS API protocol.
|
||||
//
|
||||
// Returns a list of checksums in raw form: the format of hashes and their number
|
||||
// is left for the caller to check. Client preserves the order of the server's response.
|
||||
//
|
||||
// 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.ResolveFrostFSFailures has been called, unsuccessful
|
||||
// FrostFS status codes are returned as `error`, otherwise, are included
|
||||
// in the returned result structure.
|
||||
// see [apistatus] package for NeoFS-specific error types.
|
||||
//
|
||||
// Returns an error 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:
|
||||
return nil, errorMissingContext
|
||||
case prm.addr.GetContainerID() == nil:
|
||||
return nil, errorMissingContainer
|
||||
return nil, ErrMissingContainer
|
||||
case prm.addr.GetObjectID() == nil:
|
||||
return nil, errorMissingObject
|
||||
return nil, ErrMissingObject
|
||||
case len(prm.body.GetRanges()) == 0:
|
||||
return nil, errorMissingRanges
|
||||
return nil, ErrMissingRanges
|
||||
}
|
||||
|
||||
prm.body.SetAddress(&prm.addr)
|
||||
|
@ -187,12 +179,12 @@ func (c *Client) ObjectHash(ctx context.Context, prm PrmObjectHash) (*ResObjectH
|
|||
c.prepareRequest(&req, &prm.meta)
|
||||
req.SetBody(&prm.body)
|
||||
|
||||
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)
|
||||
}
|
||||
|
@ -203,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,28 +2,29 @@ package client
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl"
|
||||
v2object "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
|
||||
rpcapi "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
|
||||
v2session "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/signature"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
||||
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/acl"
|
||||
v2object "github.com/nspcc-dev/neofs-api-go/v2/object"
|
||||
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-sdk-go/bearer"
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -44,7 +43,7 @@ func (x ResObjectPut) StoredObjectID() oid.ID {
|
|||
return x.obj
|
||||
}
|
||||
|
||||
// ObjectWriter is designed to write one object to FrostFS system.
|
||||
// ObjectWriter is designed to write one object to NeoFS system.
|
||||
//
|
||||
// Must be initialized using Client.ObjectPutInit, any other
|
||||
// usage is unsafe.
|
||||
|
@ -57,9 +56,9 @@ type ObjectWriter struct {
|
|||
Close() error
|
||||
}
|
||||
|
||||
key *ecdsa.PrivateKey
|
||||
res ResObjectPut
|
||||
err error
|
||||
signer neofscrypto.Signer
|
||||
res ResObjectPut
|
||||
err error
|
||||
|
||||
chunkCalled bool
|
||||
|
||||
|
@ -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
|
||||
|
@ -181,17 +180,17 @@ func (x *ObjectWriter) WritePayloadChunk(chunk []byte) 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 Go built-in error.
|
||||
// If Client is tuned to resolve FrostFS API statuses, then FrostFS failures
|
||||
// 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()
|
||||
|
@ -230,19 +224,13 @@ func (x *ObjectWriter) Close() (*ResObjectPut, error) {
|
|||
return &x.res, nil
|
||||
}
|
||||
|
||||
// ObjectPutInit initiates writing an object through a remote server using FrostFS API protocol.
|
||||
// ObjectPutInit initiates writing an object through a remote server using NeoFS API protocol.
|
||||
//
|
||||
// The call only opens the transmission channel, explicit recording is done using the ObjectWriter.
|
||||
// Exactly one return value is non-nil. Resulting writer must be finally closed.
|
||||
//
|
||||
// Returns an error if parameters are set incorrectly.
|
||||
// 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 {
|
||||
return nil, errorMissingContext
|
||||
}
|
||||
|
||||
var w ObjectWriter
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
|
@ -252,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
|
||||
|
@ -265,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,31 +2,29 @@ package client
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl"
|
||||
v2object "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
|
||||
v2refs "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
|
||||
rpcapi "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
|
||||
v2session "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/signature"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
||||
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/acl"
|
||||
v2object "github.com/nspcc-dev/neofs-api-go/v2/object"
|
||||
v2refs "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"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/bearer"
|
||||
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"
|
||||
)
|
||||
|
||||
// PrmObjectSearch groups parameters of ObjectSearch operation.
|
||||
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,19 +87,13 @@ 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 FrostFS system.
|
||||
// ObjectListReader is designed to read list of object identifiers from NeoFS system.
|
||||
//
|
||||
// Must be initialized using Client.ObjectSearch, any other usage is unsafe.
|
||||
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,41 +180,40 @@ 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 FrostFS API statuses, then FrostFS failures
|
||||
// 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 FrostFS API protocol.
|
||||
// ObjectSearchInit initiates object selection through a remote server using NeoFS API protocol.
|
||||
//
|
||||
// The call only opens the transmission channel, explicit fetching of matched objects
|
||||
// is done using the ObjectListReader. Exactly one return value is non-nil.
|
||||
// Resulting reader must be finally closed.
|
||||
//
|
||||
// Returns an error 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:
|
||||
return nil, errorMissingContext
|
||||
case !prm.cnrSet:
|
||||
return nil, errorMissingContainer
|
||||
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,18 +1,16 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
v2object "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
|
||||
signatureV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/signature"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
oidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id/test"
|
||||
"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"
|
||||
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++
|
||||
|
|
|
@ -3,10 +3,10 @@ package client
|
|||
import (
|
||||
"context"
|
||||
|
||||
v2reputation "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/reputation"
|
||||
rpcapi "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/reputation"
|
||||
v2reputation "github.com/nspcc-dev/neofs-api-go/v2/reputation"
|
||||
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-sdk-go/reputation"
|
||||
)
|
||||
|
||||
// PrmAnnounceLocalTrust groups parameters of AnnounceLocalTrust operation.
|
||||
|
@ -18,13 +18,13 @@ type PrmAnnounceLocalTrust struct {
|
|||
trusts []reputation.Trust
|
||||
}
|
||||
|
||||
// SetEpoch sets number of FrostFS epoch in which the trust was assessed.
|
||||
// SetEpoch sets number of NeoFS epoch in which the trust was assessed.
|
||||
// Required parameter, must not be zero.
|
||||
func (x *PrmAnnounceLocalTrust) SetEpoch(epoch uint64) {
|
||||
x.epoch = epoch
|
||||
}
|
||||
|
||||
// SetValues sets values describing trust of the client to the FrostFS network participants.
|
||||
// SetValues sets values describing trust of the client to the NeoFS network participants.
|
||||
// Required parameter. Must not be empty.
|
||||
//
|
||||
// Must not be mutated before the end of the operation.
|
||||
|
@ -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 FrostFS network participants.
|
||||
// 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.ResolveFrostFSFailures has been called, unsuccessful
|
||||
// FrostFS 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.
|
||||
//
|
||||
// Returns an error 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:
|
||||
return nil, errorMissingContext
|
||||
case prm.epoch == 0:
|
||||
return nil, errorZeroEpoch
|
||||
return ErrZeroEpoch
|
||||
case len(prm.trusts) == 0:
|
||||
return nil, errorMissingTrusts
|
||||
return ErrMissingTrusts
|
||||
}
|
||||
|
||||
// form request body
|
||||
|
@ -81,24 +71,22 @@ func (c *Client) AnnounceLocalTrust(ctx context.Context, prm PrmAnnounceLocalTru
|
|||
// init call context
|
||||
|
||||
var (
|
||||
cc contextCall
|
||||
res ResAnnounceLocalTrust
|
||||
cc contextCall
|
||||
)
|
||||
|
||||
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.
|
||||
|
@ -113,7 +101,7 @@ type PrmAnnounceIntermediateTrust struct {
|
|||
trust reputation.PeerToPeerTrust
|
||||
}
|
||||
|
||||
// SetEpoch sets number of FrostFS epoch with which client's calculation algorithm is initialized.
|
||||
// SetEpoch sets number of NeoFS epoch with which client's calculation algorithm is initialized.
|
||||
// Required parameter, must not be zero.
|
||||
func (x *PrmAnnounceIntermediateTrust) SetEpoch(epoch uint64) {
|
||||
x.epoch = epoch
|
||||
|
@ -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 FrostFS network participants
|
||||
// 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.ResolveFrostFSFailures has been called, unsuccessful
|
||||
// FrostFS 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.
|
||||
//
|
||||
// Returns an error 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:
|
||||
return nil, errorMissingContext
|
||||
case prm.epoch == 0:
|
||||
return nil, errorZeroEpoch
|
||||
return ErrZeroEpoch
|
||||
case !prm.trustSet:
|
||||
return nil, errorTrustNotSet
|
||||
return ErrMissingTrust
|
||||
}
|
||||
|
||||
var trust v2reputation.PeerToPeerTrust
|
||||
|
@ -179,22 +157,20 @@ func (c *Client) AnnounceIntermediateTrust(ctx context.Context, prm PrmAnnounceI
|
|||
// init call context
|
||||
|
||||
var (
|
||||
cc contextCall
|
||||
res ResAnnounceIntermediateTrust
|
||||
cc contextCall
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package client
|
||||
|
||||
import "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session"
|
||||
import "github.com/nspcc-dev/neofs-api-go/v2/session"
|
||||
|
||||
// ResponseMetaInfo groups meta information about any FrostFS API response.
|
||||
// ResponseMetaInfo groups meta information about any NeoFS API response.
|
||||
type ResponseMetaInfo struct {
|
||||
key []byte
|
||||
|
||||
|
@ -21,7 +21,7 @@ func (x ResponseMetaInfo) ResponderKey() []byte {
|
|||
return x.key
|
||||
}
|
||||
|
||||
// Epoch returns local FrostFS epoch of the server.
|
||||
// Epoch returns local NeoFS epoch of the server.
|
||||
func (x ResponseMetaInfo) Epoch() uint64 {
|
||||
return x.epoch
|
||||
}
|
||||
|
|
|
@ -2,13 +2,13 @@ package client
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
|
||||
rpcapi "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
|
||||
v2session "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||
"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"
|
||||
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/user"
|
||||
)
|
||||
|
||||
// PrmSessionCreate groups parameters of SessionCreate operation.
|
||||
|
@ -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
|
||||
|
@ -46,7 +42,7 @@ func (x *ResSessionCreate) setID(id []byte) {
|
|||
x.id = id
|
||||
}
|
||||
|
||||
// ID returns identifier of the opened session in a binary FrostFS API protocol format.
|
||||
// ID returns identifier of the opened session in a binary NeoFS API protocol format.
|
||||
//
|
||||
// Client doesn't retain value so modification is safe.
|
||||
func (x ResSessionCreate) ID() []byte {
|
||||
|
@ -57,7 +53,7 @@ func (x *ResSessionCreate) setSessionKey(key []byte) {
|
|||
x.sessionKey = key
|
||||
}
|
||||
|
||||
// PublicKey returns public key of the opened session in a binary FrostFS API protocol format.
|
||||
// PublicKey returns public key of the opened session in a binary NeoFS API protocol format.
|
||||
func (x ResSessionCreate) PublicKey() []byte {
|
||||
return x.sessionKey
|
||||
}
|
||||
|
@ -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.ResolveFrostFSFailures has been called, unsuccessful
|
||||
// FrostFS 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.
|
||||
//
|
||||
// Returns an error 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 {
|
||||
return nil, errorMissingContext
|
||||
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"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/status"
|
||||
"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,7 +165,7 @@ 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
|
||||
}
|
||||
|
@ -144,19 +184,29 @@ func (x SignatureVerification) Error() string {
|
|||
)
|
||||
}
|
||||
|
||||
// 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() == "" {
|
||||
|
@ -183,7 +233,7 @@ 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
|
||||
}
|
||||
|
@ -203,18 +253,28 @@ 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 or
|
||||
// - 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)
|
||||
|
|
|
@ -3,8 +3,8 @@ package apistatus_test
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/status"
|
||||
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/status"
|
||||
apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -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,12 +1,23 @@
|
|||
package apistatus
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/status"
|
||||
"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
|
||||
}
|
||||
|
@ -25,18 +36,28 @@ func (x ContainerNotFound) Error() string {
|
|||
)
|
||||
}
|
||||
|
||||
// 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(defaultContainerNotFoundMsg)
|
||||
return &x.v2
|
||||
|
@ -44,7 +65,7 @@ func (x ContainerNotFound) ToStatusV2() *status.Status {
|
|||
|
||||
// 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
|
||||
}
|
||||
|
@ -63,18 +84,28 @@ func (x EACLNotFound) Error() string {
|
|||
)
|
||||
}
|
||||
|
||||
// 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(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,12 +1,35 @@
|
|||
package apistatus
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/status"
|
||||
"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
|
||||
}
|
||||
|
@ -25,25 +48,35 @@ func (x ObjectLocked) Error() string {
|
|||
)
|
||||
}
|
||||
|
||||
// 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(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
|
||||
}
|
||||
|
@ -62,25 +95,35 @@ func (x LockNonRegularObject) Error() string {
|
|||
)
|
||||
}
|
||||
|
||||
// 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(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
|
||||
}
|
||||
|
@ -99,18 +142,28 @@ func (x ObjectAccessDenied) Error() string {
|
|||
)
|
||||
}
|
||||
|
||||
// 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(defaultObjectAccessDeniedMsg)
|
||||
return &x.v2
|
||||
|
@ -128,7 +181,7 @@ 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
|
||||
}
|
||||
|
@ -147,18 +200,28 @@ func (x ObjectNotFound) Error() string {
|
|||
)
|
||||
}
|
||||
|
||||
// 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(defaultObjectNotFoundMsg)
|
||||
return &x.v2
|
||||
|
@ -184,18 +247,28 @@ func (x ObjectAlreadyRemoved) Error() string {
|
|||
)
|
||||
}
|
||||
|
||||
// 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(defaultObjectAlreadyRemovedMsg)
|
||||
return &x.v2
|
||||
|
@ -203,7 +276,7 @@ func (x ObjectAlreadyRemoved) ToStatusV2() *status.Status {
|
|||
|
||||
// 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
|
||||
}
|
||||
|
@ -222,18 +295,28 @@ func (x ObjectOutOfRange) Error() string {
|
|||
)
|
||||
}
|
||||
|
||||
// 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(defaultObjectOutOfRangeMsg)
|
||||
return &x.v2
|
||||
|
|
|
@ -3,7 +3,7 @@ package apistatus_test
|
|||
import (
|
||||
"testing"
|
||||
|
||||
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
||||
apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -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,12 +1,23 @@
|
|||
package apistatus
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/status"
|
||||
"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
|
||||
}
|
||||
|
@ -25,25 +36,35 @@ func (x SessionTokenNotFound) Error() string {
|
|||
)
|
||||
}
|
||||
|
||||
// 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(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
|
||||
}
|
||||
|
@ -62,18 +83,28 @@ func (x SessionTokenExpired) Error() string {
|
|||
)
|
||||
}
|
||||
|
||||
// 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(defaultSessionTokenExpiredMsg)
|
||||
return &x.v2
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
package apistatus
|
||||
|
||||
// Status defines a variety of FrostFS 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 FrostFS 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 "git.frostfs.info/TrueCloudLab/frostfs-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 (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-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)
|
||||
}
|
|
@ -1,18 +1,34 @@
|
|||
package apistatus
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/status"
|
||||
"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,46 +1,59 @@
|
|||
package apistatus
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/status"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/container"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/object"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/session"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/status"
|
||||
)
|
||||
|
||||
// StatusV2 defines a variety of Status instances compatible with FrostFS 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 git.frostfs.info/TrueCloudLab/frostfs-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"
|
||||
|
|
|
@ -4,296 +4,193 @@ import (
|
|||
"errors"
|
||||
"testing"
|
||||
|
||||
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
||||
apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status"
|
||||
"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
|
||||
codeV2 uint64
|
||||
messageV2 string
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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
|
||||
}
|
||||
st = cons()
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,9 +6,9 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
// Basic represents basic part of the FrostFS container's ACL. It includes
|
||||
// Basic represents basic part of the NeoFS container's ACL. It includes
|
||||
// common (pretty simple) access rules for operations inside the container.
|
||||
// See FrostFS Specification for details.
|
||||
// See NeoFS Specification for details.
|
||||
//
|
||||
// One can find some similarities with the traditional Unix permission, such as
|
||||
//
|
||||
|
@ -236,7 +236,7 @@ const (
|
|||
NamePublicAppendExtended = "eacl-public-append"
|
||||
)
|
||||
|
||||
// Frequently used Basic values. Bitmasks are taken from the FrostFS Specification.
|
||||
// Frequently used Basic values. Bitmasks are taken from the NeoFS Specification.
|
||||
const (
|
||||
Private = Basic(0x1C8C8CCC) // private
|
||||
PrivateExtended = Basic(0x0C8C8CCC) // eacl-private
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
Package acl provides functionality to control access to data and operations on them in FrostFS containers.
|
||||
Package acl provides functionality to control access to data and operations on them in NeoFS containers.
|
||||
|
||||
Type Basic represents basic ACL of the FrostFS container which specifies the general order of data access.
|
||||
Type Basic represents basic ACL of the NeoFS container which specifies the general order of data access.
|
||||
Basic provides interface of rule composition. Package acl also exports some frequently used settings like
|
||||
private or public.
|
||||
*/
|
||||
|
|
|
@ -1,43 +1,40 @@
|
|||
package container
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container"
|
||||
v2netmap "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/netmap"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
frostfscrypto "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto"
|
||||
frostfsecdsa "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto/ecdsa"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
||||
subnetid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/subnet/id"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/version"
|
||||
"github.com/google/uuid"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/container"
|
||||
v2netmap "github.com/nspcc-dev/neofs-api-go/v2/netmap"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/container/acl"
|
||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/netmap"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/user"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/version"
|
||||
)
|
||||
|
||||
// Container represents descriptor of the FrostFS container. Container logically
|
||||
// stores FrostFS objects. Container is one of the basic and at the same time
|
||||
// necessary data storage units in the FrostFS. Container includes data about the
|
||||
// Container represents descriptor of the NeoFS container. Container logically
|
||||
// stores NeoFS objects. Container is one of the basic and at the same time
|
||||
// necessary data storage units in the NeoFS. Container includes data about the
|
||||
// owner, rules for placing objects and other information necessary for the
|
||||
// system functioning.
|
||||
//
|
||||
// Container type instances can represent different container states in the
|
||||
// system, depending on the context. To create new container in FrostFS zero
|
||||
// system, depending on the context. To create new container in NeoFS zero
|
||||
// instance SHOULD be declared, initialized using Init method and filled using
|
||||
// dedicated methods. Once container is saved in the FrostFS network, it can't be
|
||||
// changed: containers stored in the system are immutable, and FrostFS is a CAS
|
||||
// dedicated methods. Once container is saved in the NeoFS network, it can't be
|
||||
// changed: containers stored in the system are immutable, and NeoFS is a CAS
|
||||
// of containers that are identified by a fixed length value (see cid.ID type).
|
||||
// Instances for existing containers can be initialized using decoding methods
|
||||
// (e.g Unmarshal).
|
||||
//
|
||||
// Container is mutually compatible with git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container.Container
|
||||
// Container is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/container.Container
|
||||
// message. See ReadFromV2 / WriteToV2 methods.
|
||||
type Container struct {
|
||||
v2 container.Container
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -137,7 +132,7 @@ func (x *Container) readFromV2(m container.Container, checkFieldPresence bool) e
|
|||
}
|
||||
|
||||
// ReadFromV2 reads Container from the container.Container message. Checks if the
|
||||
// message conforms to FrostFS API V2 protocol.
|
||||
// message conforms to NeoFS API V2 protocol.
|
||||
//
|
||||
// See also WriteToV2.
|
||||
func (x *Container) ReadFromV2(m container.Container) error {
|
||||
|
@ -152,7 +147,7 @@ func (x Container) WriteToV2(m *container.Container) {
|
|||
*m = x.v2
|
||||
}
|
||||
|
||||
// Marshal encodes Container into a binary format of the FrostFS API protocol
|
||||
// Marshal encodes Container into a binary format of the NeoFS API protocol
|
||||
// (Protocol Buffers with direct field order).
|
||||
//
|
||||
// See also Unmarshal.
|
||||
|
@ -160,7 +155,7 @@ func (x Container) Marshal() []byte {
|
|||
return x.v2.StableMarshal(nil)
|
||||
}
|
||||
|
||||
// Unmarshal decodes FrostFS API protocol binary format into the Container
|
||||
// Unmarshal decodes NeoFS API protocol binary format into the Container
|
||||
// (Protocol Buffers with direct field order). Returns an error describing
|
||||
// a format violation.
|
||||
//
|
||||
|
@ -176,7 +171,7 @@ func (x *Container) Unmarshal(data []byte) error {
|
|||
return x.readFromV2(m, false)
|
||||
}
|
||||
|
||||
// MarshalJSON encodes Container into a JSON format of the FrostFS API protocol
|
||||
// MarshalJSON encodes Container into a JSON format of the NeoFS API protocol
|
||||
// (Protocol Buffers JSON).
|
||||
//
|
||||
// See also UnmarshalJSON.
|
||||
|
@ -184,7 +179,7 @@ func (x Container) MarshalJSON() ([]byte, error) {
|
|||
return x.v2.MarshalJSON()
|
||||
}
|
||||
|
||||
// UnmarshalJSON decodes FrostFS API protocol JSON format into the Container
|
||||
// UnmarshalJSON decodes NeoFS API protocol JSON format into the Container
|
||||
// (Protocol Buffers JSON). Returns an error describing a format violation.
|
||||
//
|
||||
// See also MarshalJSON.
|
||||
|
@ -192,7 +187,7 @@ func (x *Container) UnmarshalJSON(data []byte) error {
|
|||
return x.v2.UnmarshalJSON(data)
|
||||
}
|
||||
|
||||
// Init initializes all internal data of the Container required by FrostFS API
|
||||
// Init initializes all internal data of the Container required by NeoFS API
|
||||
// protocol. Init MUST be called when creating a new container. Init SHOULD NOT
|
||||
// be called multiple times. Init SHOULD NOT be called if the Container instance
|
||||
// is used for decoding only.
|
||||
|
@ -212,7 +207,7 @@ func (x *Container) Init() {
|
|||
|
||||
// SetOwner specifies the owner of the Container. Each Container has exactly
|
||||
// one owner, so SetOwner MUST be called for instances to be saved in the
|
||||
// FrostFS.
|
||||
// NeoFS.
|
||||
//
|
||||
// See also Owner.
|
||||
func (x *Container) SetOwner(owner user.ID) {
|
||||
|
@ -224,7 +219,7 @@ func (x *Container) SetOwner(owner user.ID) {
|
|||
|
||||
// Owner returns owner of the Container set using SetOwner.
|
||||
//
|
||||
// Zero Container has no owner which is incorrect according to FrostFS API
|
||||
// Zero Container has no owner which is incorrect according to NeoFS API
|
||||
// protocol.
|
||||
func (x Container) Owner() (res user.ID) {
|
||||
m := x.v2.GetOwnerID()
|
||||
|
@ -256,7 +251,7 @@ func (x Container) BasicACL() (res acl.Basic) {
|
|||
}
|
||||
|
||||
// SetPlacementPolicy sets placement policy for the objects within the Container.
|
||||
// FrostFS storage layer strives to follow the specified policy.
|
||||
// NeoFS storage layer strives to follow the specified policy.
|
||||
//
|
||||
// See also PlacementPolicy.
|
||||
func (x *Container) SetPlacementPolicy(policy netmap.PlacementPolicy) {
|
||||
|
@ -269,7 +264,7 @@ func (x *Container) SetPlacementPolicy(policy netmap.PlacementPolicy) {
|
|||
// PlacementPolicy returns placement policy set using SetPlacementPolicy.
|
||||
//
|
||||
// Zero Container has no placement policy which is incorrect according to
|
||||
// FrostFS API protocol.
|
||||
// NeoFS API protocol.
|
||||
func (x Container) PlacementPolicy() (res netmap.PlacementPolicy) {
|
||||
m := x.v2.GetPlacementPolicy()
|
||||
if m != nil {
|
||||
|
@ -284,7 +279,7 @@ func (x Container) PlacementPolicy() (res netmap.PlacementPolicy) {
|
|||
|
||||
// SetAttribute sets Container attribute value by key. Both key and value
|
||||
// MUST NOT be empty. Attributes set by the creator (owner) are most commonly
|
||||
// ignored by the FrostFS system and used for application layer. Some attributes
|
||||
// ignored by the NeoFS system and used for application layer. Some attributes
|
||||
// are so-called system or well-known attributes: they are reserved for system
|
||||
// needs. System attributes SHOULD NOT be modified using SetAttribute, use
|
||||
// corresponding methods/functions. List of the reserved keys is documented
|
||||
|
@ -383,28 +378,6 @@ func CreatedAt(cnr Container) time.Time {
|
|||
return time.Unix(sec, 0)
|
||||
}
|
||||
|
||||
// SetSubnet places the Container on the specified FrostFS 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
|
||||
|
@ -419,12 +392,11 @@ func DisableHomomorphicHashing(cnr *Container) {
|
|||
//
|
||||
// Zero Container has enabled hashing.
|
||||
func IsHomomorphicHashingDisabled(cnr Container) bool {
|
||||
return cnr.Attribute(container.SysAttributeHomomorphicHashing) == attributeHomoHashEnabled ||
|
||||
cnr.Attribute(container.SysAttributeHomomorphicHashingNeoFS) == attributeHomoHashEnabled
|
||||
return cnr.Attribute(container.SysAttributeHomomorphicHashing) == attributeHomoHashEnabled
|
||||
}
|
||||
|
||||
// Domain represents information about container domain registered in the NNS
|
||||
// contract deployed in the FrostFS network.
|
||||
// contract deployed in the NeoFS network.
|
||||
type Domain struct {
|
||||
name, zone string
|
||||
}
|
||||
|
@ -466,12 +438,10 @@ func WriteDomain(cnr *Container, domain Domain) {
|
|||
// ReadDomain reads Domain from the Container. Returns value with empty name
|
||||
// if domain is not specified.
|
||||
func ReadDomain(cnr Container) (res Domain) {
|
||||
if name := cnr.Attribute(container.SysAttributeName); name != "" {
|
||||
name := cnr.Attribute(container.SysAttributeName)
|
||||
if name != "" {
|
||||
res.SetName(name)
|
||||
res.SetZone(cnr.Attribute(container.SysAttributeZone))
|
||||
} else if name = cnr.Attribute(container.SysAttributeNameNeoFS); name != "" {
|
||||
res.SetName(name)
|
||||
res.SetZone(cnr.Attribute(container.SysAttributeZoneNeoFS))
|
||||
}
|
||||
|
||||
return
|
||||
|
@ -480,22 +450,30 @@ func ReadDomain(cnr Container) (res Domain) {
|
|||
// CalculateSignature calculates signature of the Container using provided signer
|
||||
// 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 FrostFS network. Note that мany subsequent change
|
||||
// will most likely break the signature.
|
||||
// saving the Container in the NeoFS network. Note that мany subsequent change
|
||||
// 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 *frostfscrypto.Signature, cnr Container, signer ecdsa.PrivateKey) error {
|
||||
return dst.Calculate(frostfsecdsa.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.
|
||||
// Result means signature correctness.
|
||||
func VerifySignature(sig frostfscrypto.Signature, cnr Container) bool {
|
||||
func VerifySignature(sig neofscrypto.Signature, cnr Container) bool {
|
||||
return sig.Verify(cnr.Marshal())
|
||||
}
|
||||
|
||||
// CalculateIDFromBinary calculates identifier of the binary-encoded container
|
||||
// in CAS of the FrostFS containers and writes it into dst. ID instance MUST NOT
|
||||
// in CAS of the NeoFS containers and writes it into dst. ID instance MUST NOT
|
||||
// be nil.
|
||||
//
|
||||
// See also CalculateID, AssertID.
|
||||
|
@ -512,7 +490,7 @@ func CalculateID(dst *cid.ID, cnr Container) {
|
|||
}
|
||||
|
||||
// AssertID checks if the given Container matches its identifier in CAS of the
|
||||
// FrostFS containers.
|
||||
// NeoFS containers.
|
||||
//
|
||||
// See also CalculateID.
|
||||
func AssertID(id cid.ID, cnr Container) bool {
|
||||
|
|
|
@ -6,26 +6,24 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
v2container "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container"
|
||||
v2netmap "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/netmap"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test"
|
||||
containertest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/test"
|
||||
frostfscrypto "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto"
|
||||
netmaptest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap/test"
|
||||
subnetid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/subnet/id"
|
||||
subnetidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/subnet/id/test"
|
||||
usertest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user/test"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/version"
|
||||
"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"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/container"
|
||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||
cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test"
|
||||
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"
|
||||
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,19 +308,17 @@ func TestCalculateID(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCalculateSignature(t *testing.T) {
|
||||
key, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
val := containertest.Container(t)
|
||||
|
||||
val := containertest.Container()
|
||||
var sig neofscrypto.Signature
|
||||
|
||||
var sig frostfscrypto.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)
|
||||
|
||||
var sig2 frostfscrypto.Signature
|
||||
var sig2 neofscrypto.Signature
|
||||
require.NoError(t, sig2.ReadFromV2(msg))
|
||||
|
||||
require.True(t, container.VerifySignature(sig2, val))
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
Package container provides functionality related to the FrostFS containers.
|
||||
Package container provides functionality related to the NeoFS containers.
|
||||
|
||||
The base type is Container. To create new container in the FrostFS network
|
||||
The base type is Container. To create new container in the NeoFS network
|
||||
Container instance should be initialized
|
||||
|
||||
var cnr Container
|
||||
|
@ -10,7 +10,7 @@ Container instance should be initialized
|
|||
|
||||
// encode cnr and send
|
||||
|
||||
After the container is persisted in the FrostFS network, applications can process
|
||||
After the container is persisted in the NeoFS network, applications can process
|
||||
it using the instance of Container types
|
||||
|
||||
// recv binary container
|
||||
|
@ -22,12 +22,12 @@ it using the instance of Container types
|
|||
|
||||
// process the container data
|
||||
|
||||
Instances can be also used to process FrostFS API V2 protocol messages
|
||||
(see neo.fs.v2.container package in https://git.frostfs.info/TrueCloudLab/frostfs-api).
|
||||
Instances can be also used to process NeoFS API V2 protocol messages
|
||||
(see neo.fs.v2.container package in https://github.com/nspcc-dev/neofs-api).
|
||||
|
||||
On client side:
|
||||
|
||||
import "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container"
|
||||
import "github.com/nspcc-dev/neofs-api-go/v2/container"
|
||||
|
||||
var msg container.Container
|
||||
cnr.WriteToV2(&msg)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Package cid provides primitives to work with container identification in FrostFS.
|
||||
Package cid provides primitives to work with container identification in NeoFS.
|
||||
|
||||
Using package types in an application is recommended to potentially work with
|
||||
different protocol versions with which these types are compatible.
|
||||
|
|
|
@ -4,13 +4,13 @@ import (
|
|||
"crypto/sha256"
|
||||
"fmt"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
|
||||
"github.com/mr-tron/base58"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
||||
)
|
||||
|
||||
// ID represents FrostFS container identifier.
|
||||
// ID represents NeoFS container identifier.
|
||||
//
|
||||
// ID is mutually compatible with git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs.ContainerID
|
||||
// ID is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/refs.ContainerID
|
||||
// message. See ReadFromV2 / WriteToV2 methods.
|
||||
//
|
||||
// Instances can be created using built-in var declaration.
|
||||
|
@ -22,7 +22,7 @@ type ID [sha256.Size]byte
|
|||
|
||||
// ReadFromV2 reads ID from the refs.ContainerID message.
|
||||
// Returns an error if the message is malformed according
|
||||
// to the FrostFS API V2 protocol.
|
||||
// to the NeoFS API V2 protocol.
|
||||
//
|
||||
// See also WriteToV2.
|
||||
func (id *ID) ReadFromV2(m refs.ContainerID) error {
|
||||
|
@ -83,7 +83,7 @@ func (id ID) Equals(id2 ID) bool {
|
|||
return id == id2
|
||||
}
|
||||
|
||||
// EncodeToString encodes ID into FrostFS API protocol string.
|
||||
// EncodeToString encodes ID into NeoFS API protocol string.
|
||||
//
|
||||
// Zero ID is base58 encoding of 32 zeros.
|
||||
//
|
||||
|
@ -92,7 +92,7 @@ func (id ID) EncodeToString() string {
|
|||
return base58.Encode(id[:])
|
||||
}
|
||||
|
||||
// DecodeString decodes string into ID according to FrostFS API protocol. Returns
|
||||
// DecodeString decodes string into ID according to NeoFS API protocol. Returns
|
||||
// an error if s is malformed.
|
||||
//
|
||||
// See also DecodeString.
|
||||
|
@ -109,7 +109,7 @@ func (id *ID) DecodeString(s string) error {
|
|||
//
|
||||
// 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 FrostFS protocol string.
|
||||
// be used to encode ID into NeoFS protocol string.
|
||||
func (id ID) String() string {
|
||||
return id.EncodeToString()
|
||||
}
|
||||
|
|
|
@ -5,10 +5,10 @@ import (
|
|||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test"
|
||||
"github.com/mr-tron/base58"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||
cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ Note that importing the package into source files is highly discouraged.
|
|||
|
||||
Random instance generation functions can be useful when testing expects any value, e.g.:
|
||||
|
||||
import cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test"
|
||||
import cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test"
|
||||
|
||||
cid := cidtest.ID()
|
||||
// test the value
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"crypto/sha256"
|
||||
"math/rand"
|
||||
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||
)
|
||||
|
||||
// ID returns random cid.ID.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package container
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/netmap"
|
||||
)
|
||||
|
||||
// ApplyNetworkConfig applies network configuration to the
|
||||
|
|
|
@ -3,14 +3,14 @@ package container_test
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
|
||||
containertest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/test"
|
||||
netmaptest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap/test"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/container"
|
||||
containertest "github.com/nspcc-dev/neofs-sdk-go/container/test"
|
||||
netmaptest "github.com/nspcc-dev/neofs-sdk-go/netmap/test"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestContainer_NetworkConfig(t *testing.T) {
|
||||
c := containertest.Container()
|
||||
c := containertest.Container(t)
|
||||
nc := netmaptest.NetworkInfo()
|
||||
|
||||
t.Run("default", func(t *testing.T) {
|
||||
|
|
|
@ -4,22 +4,22 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/container"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||
)
|
||||
|
||||
// SizeEstimation groups information about estimation of the size of the data
|
||||
// stored in the FrostFS container.
|
||||
// stored in the NeoFS container.
|
||||
//
|
||||
// SizeEstimation is mutually compatible with git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container.UsedSpaceAnnouncement
|
||||
// SizeEstimation is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/container.UsedSpaceAnnouncement
|
||||
// message. See ReadFromV2 / WriteToV2 methods.
|
||||
type SizeEstimation struct {
|
||||
m container.UsedSpaceAnnouncement
|
||||
}
|
||||
|
||||
// ReadFromV2 reads SizeEstimation from the container.UsedSpaceAnnouncement message.
|
||||
// Checks if the message conforms to FrostFS API V2 protocol.
|
||||
// Checks if the message conforms to NeoFS API V2 protocol.
|
||||
//
|
||||
// See also WriteToV2.
|
||||
func (x *SizeEstimation) ReadFromV2(m container.UsedSpaceAnnouncement) error {
|
||||
|
@ -63,7 +63,7 @@ func (x SizeEstimation) Epoch() uint64 {
|
|||
}
|
||||
|
||||
// SetContainer specifies the container for which the amount of data is estimated.
|
||||
// Required by the FrostFS API protocol.
|
||||
// Required by the NeoFS API protocol.
|
||||
//
|
||||
// See also Container.
|
||||
func (x *SizeEstimation) SetContainer(cnr cid.ID) {
|
||||
|
@ -76,7 +76,7 @@ func (x *SizeEstimation) SetContainer(cnr cid.ID) {
|
|||
// Container returns container set using SetContainer.
|
||||
//
|
||||
// Zero SizeEstimation is not bound to any container (returns zero) which is
|
||||
// incorrect according to FrostFS API protocol.
|
||||
// incorrect according to NeoFS API protocol.
|
||||
func (x SizeEstimation) Container() (res cid.ID) {
|
||||
m := x.m.GetContainerID()
|
||||
if m != nil {
|
||||
|
|
|
@ -4,11 +4,11 @@ import (
|
|||
"crypto/sha256"
|
||||
"testing"
|
||||
|
||||
v2container "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test"
|
||||
v2container "github.com/nspcc-dev/neofs-api-go/v2/container"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/container"
|
||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||
cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
|
|
@ -2,17 +2,18 @@ package containertest
|
|||
|
||||
import (
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
|
||||
cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test"
|
||||
netmaptest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap/test"
|
||||
usertest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user/test"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/container"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/container/acl"
|
||||
cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test"
|
||||
netmaptest "github.com/nspcc-dev/neofs-sdk-go/netmap/test"
|
||||
usertest "github.com/nspcc-dev/neofs-sdk-go/user/test"
|
||||
)
|
||||
|
||||
// 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,13 +1,13 @@
|
|||
package frostfscrypto_test
|
||||
package neofscrypto_test
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
|
||||
frostfscrypto "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto"
|
||||
frostfsecdsa "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto/ecdsa"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
||||
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
|
||||
neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -18,18 +18,18 @@ func TestSignature(t *testing.T) {
|
|||
k, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
var s frostfscrypto.Signature
|
||||
var s neofscrypto.Signature
|
||||
var m refs.Signature
|
||||
|
||||
for _, f := range []func() frostfscrypto.Signer{
|
||||
func() frostfscrypto.Signer {
|
||||
return frostfsecdsa.Signer(k.PrivateKey)
|
||||
for _, f := range []func() neofscrypto.Signer{
|
||||
func() neofscrypto.Signer {
|
||||
return neofsecdsa.Signer(k.PrivateKey)
|
||||
},
|
||||
func() frostfscrypto.Signer {
|
||||
return frostfsecdsa.SignerRFC6979(k.PrivateKey)
|
||||
func() neofscrypto.Signer {
|
||||
return neofsecdsa.SignerRFC6979(k.PrivateKey)
|
||||
},
|
||||
func() frostfscrypto.Signer {
|
||||
return frostfsecdsa.SignerWalletConnect(k.PrivateKey)
|
||||
func() neofscrypto.Signer {
|
||||
return neofsecdsa.SignerWalletConnect(k.PrivateKey)
|
||||
},
|
||||
} {
|
||||
signer := f()
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
Package frostfscrypto collects FrostFS cryptographic primitives.
|
||||
Package neofscrypto collects NeoFS cryptographic primitives.
|
||||
|
||||
Signer type unifies entities for signing FrostFS data.
|
||||
Signer type unifies entities for signing NeoFS data.
|
||||
|
||||
// instantiate Signer
|
||||
// select data to be signed
|
||||
|
@ -24,12 +24,12 @@ PublicKey allows to verify signatures.
|
|||
isValid := sig.Verify(data)
|
||||
// ...
|
||||
|
||||
Signature can be also used to process FrostFS API V2 protocol messages
|
||||
(see neo.fs.v2.refs package in https://git.frostfs.info/TrueCloudLab/frostfs-api).
|
||||
Signature can be also used to process NeoFS API V2 protocol messages
|
||||
(see neo.fs.v2.refs package in https://github.com/nspcc-dev/neofs-api).
|
||||
|
||||
On client side:
|
||||
|
||||
import "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
|
||||
import "github.com/nspcc-dev/neofs-api-go/v2/refs"
|
||||
|
||||
var msg refs.Signature
|
||||
sig.WriteToV2(&msg)
|
||||
|
@ -40,7 +40,7 @@ On server side:
|
|||
|
||||
// recv msg
|
||||
|
||||
var sig frostfscrypto.Signature
|
||||
var sig neofscrypto.Signature
|
||||
sig.ReadFromV2(msg)
|
||||
|
||||
// process sig
|
||||
|
@ -48,4 +48,4 @@ On server side:
|
|||
Using package types in an application is recommended to potentially work with
|
||||
different protocol versions with which these types are compatible.
|
||||
*/
|
||||
package frostfscrypto
|
||||
package neofscrypto
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
/*
|
||||
Package frostfsecdsa collects ECDSA primitives for FrostFS cryptography.
|
||||
Package neofsecdsa collects ECDSA primitives for NeoFS cryptography.
|
||||
|
||||
Signer and PublicKey support ECDSA signature algorithm with SHA-512 hashing.
|
||||
SignerRFC6979 and PublicKeyRFC6979 implement signature algorithm described in RFC 6979.
|
||||
All these types provide corresponding interfaces from frostfscrypto package.
|
||||
All these types provide corresponding interfaces from neofscrypto package.
|
||||
|
||||
Package import causes registration of next signature schemes via frostfscrypto.RegisterScheme:
|
||||
- frostfscrypto.ECDSA_SHA512
|
||||
- frostfscrypto.ECDSA_DETERMINISTIC_SHA256
|
||||
Package import causes registration of next signature schemes via neofscrypto.RegisterScheme:
|
||||
- neofscrypto.ECDSA_SHA512
|
||||
- neofscrypto.ECDSA_DETERMINISTIC_SHA256
|
||||
*/
|
||||
package frostfsecdsa
|
||||
package neofsecdsa
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
package frostfsecdsa
|
||||
package neofsecdsa
|
||||
|
||||
import frostfscrypto "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto"
|
||||
import neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
|
||||
|
||||
func init() {
|
||||
frostfscrypto.RegisterScheme(frostfscrypto.ECDSA_SHA512, func() frostfscrypto.PublicKey {
|
||||
neofscrypto.RegisterScheme(neofscrypto.ECDSA_SHA512, func() neofscrypto.PublicKey {
|
||||
return new(PublicKey)
|
||||
})
|
||||
|
||||
frostfscrypto.RegisterScheme(frostfscrypto.ECDSA_DETERMINISTIC_SHA256, func() frostfscrypto.PublicKey {
|
||||
neofscrypto.RegisterScheme(neofscrypto.ECDSA_DETERMINISTIC_SHA256, func() neofscrypto.PublicKey {
|
||||
return new(PublicKeyRFC6979)
|
||||
})
|
||||
|
||||
frostfscrypto.RegisterScheme(frostfscrypto.ECDSA_WALLETCONNECT, func() frostfscrypto.PublicKey {
|
||||
neofscrypto.RegisterScheme(neofscrypto.ECDSA_WALLETCONNECT, func() neofscrypto.PublicKey {
|
||||
return new(PublicKeyWalletConnect)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package frostfsecdsa
|
||||
package neofsecdsa
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
|
@ -11,8 +11,8 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
)
|
||||
|
||||
// PublicKey is a wrapper over ecdsa.PublicKey used for FrostFS needs.
|
||||
// Provides frostfscrypto.PublicKey interface.
|
||||
// PublicKey is a wrapper over ecdsa.PublicKey used for NeoFS needs.
|
||||
// Provides neofscrypto.PublicKey interface.
|
||||
//
|
||||
// Instances MUST be initialized from ecdsa.PublicKey using type conversion.
|
||||
type PublicKey ecdsa.PublicKey
|
||||
|
@ -77,8 +77,8 @@ func (x PublicKey) Verify(data, signature []byte) bool {
|
|||
return r != nil && s != nil && ecdsa.Verify((*ecdsa.PublicKey)(&x), h[:], r, s)
|
||||
}
|
||||
|
||||
// PublicKeyRFC6979 is a wrapper over ecdsa.PublicKey used for FrostFS needs.
|
||||
// Provides frostfscrypto.PublicKey interface.
|
||||
// PublicKeyRFC6979 is a wrapper over ecdsa.PublicKey used for NeoFS needs.
|
||||
// Provides neofscrypto.PublicKey interface.
|
||||
//
|
||||
// Instances MUST be initialized from ecdsa.PublicKey using type conversion.
|
||||
type PublicKeyRFC6979 ecdsa.PublicKey
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package frostfsecdsa
|
||||
package neofsecdsa
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
|
@ -6,24 +6,24 @@ import (
|
|||
"crypto/rand"
|
||||
"crypto/sha512"
|
||||
|
||||
frostfscrypto "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
|
||||
)
|
||||
|
||||
// Signer wraps ecdsa.PrivateKey and represents signer based on ECDSA with
|
||||
// SHA-512 hashing. Provides frostfscrypto.Signer interface.
|
||||
// SHA-512 hashing. Provides neofscrypto.Signer interface.
|
||||
//
|
||||
// Instances MUST be initialized from ecdsa.PrivateKey using type conversion.
|
||||
type Signer ecdsa.PrivateKey
|
||||
|
||||
// Scheme returns frostfscrypto.ECDSA_SHA512.
|
||||
// Implements frostfscrypto.Signer.
|
||||
func (x Signer) Scheme() frostfscrypto.Scheme {
|
||||
return frostfscrypto.ECDSA_SHA512
|
||||
// Scheme returns neofscrypto.ECDSA_SHA512.
|
||||
// Implements neofscrypto.Signer.
|
||||
func (x Signer) Scheme() neofscrypto.Scheme {
|
||||
return neofscrypto.ECDSA_SHA512
|
||||
}
|
||||
|
||||
// Sign signs data using ECDSA algorithm with SHA-512 hashing.
|
||||
// Implements frostfscrypto.Signer.
|
||||
// Implements neofscrypto.Signer.
|
||||
func (x Signer) Sign(data []byte) ([]byte, error) {
|
||||
h := sha512.Sum512(data)
|
||||
r, s, err := ecdsa.Sign(rand.Reader, (*ecdsa.PrivateKey)(&x), h[:])
|
||||
|
@ -43,26 +43,26 @@ func (x Signer) Sign(data []byte) ([]byte, error) {
|
|||
return buf, nil
|
||||
}
|
||||
|
||||
// Public initializes PublicKey and returns it as frostfscrypto.PublicKey.
|
||||
// Implements frostfscrypto.Signer.
|
||||
func (x Signer) Public() frostfscrypto.PublicKey {
|
||||
// Public initializes PublicKey and returns it as neofscrypto.PublicKey.
|
||||
// Implements neofscrypto.Signer.
|
||||
func (x Signer) Public() neofscrypto.PublicKey {
|
||||
return (*PublicKey)(&x.PublicKey)
|
||||
}
|
||||
|
||||
// SignerRFC6979 wraps ecdsa.PrivateKey and represents signer based on deterministic
|
||||
// ECDSA with SHA-256 hashing (RFC 6979). Provides frostfscrypto.Signer interface.
|
||||
// ECDSA with SHA-256 hashing (RFC 6979). Provides neofscrypto.Signer interface.
|
||||
//
|
||||
// Instances SHOULD be initialized from ecdsa.PrivateKey using type conversion.
|
||||
type SignerRFC6979 ecdsa.PrivateKey
|
||||
|
||||
// Scheme returns frostfscrypto.ECDSA_DETERMINISTIC_SHA256.
|
||||
// Implements frostfscrypto.Signer.
|
||||
func (x SignerRFC6979) Scheme() frostfscrypto.Scheme {
|
||||
return frostfscrypto.ECDSA_DETERMINISTIC_SHA256
|
||||
// Scheme returns neofscrypto.ECDSA_DETERMINISTIC_SHA256.
|
||||
// Implements neofscrypto.Signer.
|
||||
func (x SignerRFC6979) Scheme() neofscrypto.Scheme {
|
||||
return neofscrypto.ECDSA_DETERMINISTIC_SHA256
|
||||
}
|
||||
|
||||
// Sign signs data using deterministic ECDSA algorithm with SHA-256 hashing.
|
||||
// Implements frostfscrypto.Signer.
|
||||
// Implements neofscrypto.Signer.
|
||||
//
|
||||
// See also RFC 6979.
|
||||
func (x SignerRFC6979) Sign(data []byte) ([]byte, error) {
|
||||
|
@ -70,8 +70,8 @@ func (x SignerRFC6979) Sign(data []byte) ([]byte, error) {
|
|||
return p.Sign(data), nil
|
||||
}
|
||||
|
||||
// Public initializes PublicKeyRFC6979 and returns it as frostfscrypto.PublicKey.
|
||||
// Implements frostfscrypto.Signer.
|
||||
func (x SignerRFC6979) Public() frostfscrypto.PublicKey {
|
||||
// Public initializes PublicKeyRFC6979 and returns it as neofscrypto.PublicKey.
|
||||
// Implements neofscrypto.Signer.
|
||||
func (x SignerRFC6979) Public() neofscrypto.PublicKey {
|
||||
return (*PublicKeyRFC6979)(&x.PublicKey)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package frostfsecdsa
|
||||
package neofsecdsa
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
|
@ -6,9 +6,9 @@ import (
|
|||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/util/signature/walletconnect"
|
||||
frostfscrypto "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/util/signature/walletconnect"
|
||||
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
|
||||
)
|
||||
|
||||
// SignerWalletConnect is similar to SignerRFC6979 with 2 changes:
|
||||
|
@ -18,28 +18,28 @@ import (
|
|||
// Instances MUST be initialized from ecdsa.PrivateKey using type conversion.
|
||||
type SignerWalletConnect ecdsa.PrivateKey
|
||||
|
||||
// Scheme returns frostfscrypto.ECDSA_WALLETCONNECT.
|
||||
// Implements frostfscrypto.Signer.
|
||||
func (x SignerWalletConnect) Scheme() frostfscrypto.Scheme {
|
||||
return frostfscrypto.ECDSA_WALLETCONNECT
|
||||
// Scheme returns neofscrypto.ECDSA_WALLETCONNECT.
|
||||
// Implements neofscrypto.Signer.
|
||||
func (x SignerWalletConnect) Scheme() neofscrypto.Scheme {
|
||||
return neofscrypto.ECDSA_WALLETCONNECT
|
||||
}
|
||||
|
||||
// Sign signs data using ECDSA algorithm with SHA-512 hashing.
|
||||
// Implements frostfscrypto.Signer.
|
||||
// Implements neofscrypto.Signer.
|
||||
func (x SignerWalletConnect) Sign(data []byte) ([]byte, error) {
|
||||
b64 := make([]byte, base64.StdEncoding.EncodedLen(len(data)))
|
||||
base64.StdEncoding.Encode(b64, data)
|
||||
return walletconnect.Sign((*ecdsa.PrivateKey)(&x), b64)
|
||||
}
|
||||
|
||||
// Public initializes PublicKey and returns it as frostfscrypto.PublicKey.
|
||||
// Implements frostfscrypto.Signer.
|
||||
func (x SignerWalletConnect) Public() frostfscrypto.PublicKey {
|
||||
// Public initializes PublicKey and returns it as neofscrypto.PublicKey.
|
||||
// Implements neofscrypto.Signer.
|
||||
func (x SignerWalletConnect) Public() neofscrypto.PublicKey {
|
||||
return (*PublicKeyWalletConnect)(&x.PublicKey)
|
||||
}
|
||||
|
||||
// PublicKeyWalletConnect is a wrapper over ecdsa.PublicKey used for FrostFS needs.
|
||||
// Provides frostfscrypto.PublicKey interface.
|
||||
// PublicKeyWalletConnect is a wrapper over ecdsa.PublicKey used for NeoFS needs.
|
||||
// Provides neofscrypto.PublicKey interface.
|
||||
//
|
||||
// Instances MUST be initialized from ecdsa.PublicKey using type conversion.
|
||||
type PublicKeyWalletConnect ecdsa.PublicKey
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
package frostfscrypto
|
||||
package neofscrypto
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
|
||||
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
||||
)
|
||||
|
||||
// Signature represents a confirmation of data integrity received by the
|
||||
// digital signature mechanism.
|
||||
//
|
||||
// Signature is mutually compatible with git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs.Signature
|
||||
// Signature is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/refs.Signature
|
||||
// message. See ReadFromV2 / WriteToV2 methods.
|
||||
//
|
||||
// Note that direct typecast is not safe and may result in loss of compatibility:
|
||||
|
@ -19,7 +19,7 @@ import (
|
|||
type Signature refs.Signature
|
||||
|
||||
// ReadFromV2 reads Signature from the refs.Signature message. Checks if the
|
||||
// message conforms to FrostFS API V2 protocol.
|
||||
// message conforms to NeoFS API V2 protocol.
|
||||
//
|
||||
// See also WriteToV2.
|
||||
func (x *Signature) ReadFromV2(m refs.Signature) error {
|
||||
|
|
|
@ -1,11 +1,18 @@
|
|||
package frostfscrypto
|
||||
package neofscrypto
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
|
||||
"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).
|
||||
|
@ -45,7 +52,7 @@ func RegisterScheme(scheme Scheme, f func() PublicKey) {
|
|||
}
|
||||
|
||||
// Signer is an interface of entities that can be used for signing operations
|
||||
// in FrostFS. Unites secret and public parts. For example, an ECDSA private key
|
||||
// in NeoFS. Unites secret and public parts. For example, an ECDSA private key
|
||||
// or external auth service.
|
||||
//
|
||||
// See also PublicKey.
|
||||
|
@ -63,7 +70,7 @@ type Signer interface {
|
|||
}
|
||||
|
||||
// PublicKey represents a public key using fixed signature scheme supported by
|
||||
// FrostFS.
|
||||
// NeoFS.
|
||||
//
|
||||
// See also Signer.
|
||||
type PublicKey interface {
|
||||
|
@ -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)
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package frostfscrypto
|
||||
package neofscrypto
|
||||
|
||||
import "encoding/hex"
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package eacl
|
||||
|
||||
import (
|
||||
v2acl "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl"
|
||||
v2acl "github.com/nspcc-dev/neofs-api-go/v2/acl"
|
||||
)
|
||||
|
||||
// Action taken if ContainerEACL record matched request.
|
||||
|
@ -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)
|
||||
|
|
|
@ -3,8 +3,8 @@ package eacl_test
|
|||
import (
|
||||
"testing"
|
||||
|
||||
v2acl "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
|
||||
v2acl "github.com/nspcc-dev/neofs-api-go/v2/acl"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/eacl"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ package eacl
|
|||
import (
|
||||
"strconv"
|
||||
|
||||
v2acl "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl"
|
||||
v2acl "github.com/nspcc-dev/neofs-api-go/v2/acl"
|
||||
)
|
||||
|
||||
// Filter defines check conditions if request header is matched or not. Matched
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue