Compare commits

..

110 commits

Author SHA1 Message Date
Pavel Karpy
72baada0bf client: Fix panic in SessionCreate
Do not panic if a signer was provided as a default. Also, return
`ErrMissingSigner` if no signer was provided at all.

Signed-off-by: Pavel Karpy <carpawell@morphbits.io>
2023-05-24 21:08:50 +03:00
LeL
5f07bcfec2
Drop subnet support (#420) 2023-05-24 18:47:22 +04:00
Roman Khimov
4d4c5fc70c
Revise apistatus funcs (#419)
close #417
2023-05-24 14:55:28 +03:00
Evgenii Baidakov
e7a5728d6b
client: Rename FromStatusV2 to ErrorFromV2, ToStatusV2 to ErrorToV2
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-05-24 13:41:58 +04:00
Roman Khimov
74cd2b12d1
Check signer type where appropriate (#418) 2023-05-23 18:46:59 +03:00
Evgenii Baidakov
26c1b26eec
client: Use error in FromStatusV2, ToStatusV2 functions
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-05-23 14:56:40 +04:00
Evgenii Baidakov
22ff96ad44
client: Remove status.Status type
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-05-23 14:49:46 +04:00
Evgenii Baidakov
277b8f7de4
client: Remove ErrFromStatus function
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-05-23 14:49:46 +04:00
Evgenii Baidakov
e2a88bc258
client: Remove status returning from processResponse function
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-05-23 14:49:36 +04:00
Evgenii Baidakov
b9ec85e5e3
status: Remove IsSuccessful and ErrToStatus functions
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-05-23 07:36:32 +04:00
Roman Khimov
de560d7424 *: drop subnet support
We don't need them and while there are reason to have some remnants in the
protocol itself we can remove them from the SDK immediately to not include
them into the 1.0.0 release.

Refs. nspcc-dev/neofs-api#261.

Signed-off-by: Roman Khimov <roman@nspcc.ru>
2023-05-22 22:46:41 +03:00
Roman Khimov
e99e9537a2 *: demand RFC6979 signer where appropriate
It's needed for container operations, therefore default ones for client and
pool have to be of this type as well. And it's easier to check for it before
usage.

Fixes #209.

Signed-off-by: Roman Khimov <roman@nspcc.ru>
2023-05-22 17:30:52 +03:00
Roman Khimov
2e18c3c16d client: replace client.Init() with New()
We'll need to check initialization parameters for consistency and Init doesn't
return any error, therefore it's easier to use smth.New() pattern and simplify
things a bit because uninited Client is not very useful anyway (we require
Init and there can be additional reasons for that in the future).

Refs. #164.

Signed-off-by: Roman Khimov <roman@nspcc.ru>
2023-05-22 17:03:10 +03:00
Roman Khimov
4e1390763a *: specify RFC6979 signer as mandatory where appropriate
Refs. #209.

Signed-off-by: Roman Khimov <roman@nspcc.ru>
2023-05-22 16:50:53 +03:00
Roman Khimov
7002b3b0df
Return errors instead of panic (#414)
close #390
2023-05-19 14:58:58 +03:00
Evgenii Baidakov
d1bcce5f79
client: Make MissingField error exported
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-05-19 14:45:50 +04:00
Evgenii Baidakov
4b0c67ea7a
*: Update documentation
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-05-19 14:45:50 +04:00
Evgenii Baidakov
4fe5e6022d
slicer: Replace panics with errors
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-05-19 14:45:49 +04:00
Evgenii Baidakov
5dec2b49b0
client: Replace panics with errors in sign logic
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-05-19 14:45:49 +04:00
Evgenii Baidakov
ef887b3ab1
client: Replace panics with errors in dial
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-05-19 14:45:49 +04:00
Evgenii Baidakov
f9d740487a
client: Replace panics with errors
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-05-19 14:45:48 +04:00
Evgenii Baidakov
8094342b1c
user: Add exported error for user extracting from key
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-05-19 14:30:02 +04:00
Evgenii Baidakov
cb4acec6a2
client: Remove MissingContext panic
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-05-19 14:30:01 +04:00
Roman Khimov
cfdd870755
Drop ResolveNeoFSFailures (#413)
close #406 
fixes #405
2023-05-19 12:13:24 +03:00
Evgenii Baidakov
49bc3b7202
pool: Refactor handleError to updateErrorRate
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-05-19 12:21:16 +04:00
Evgenii Baidakov
e377b3b4f6
*: Remove statusRes as unused
Fixes #405

Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-05-19 12:21:15 +04:00
Evgenii Baidakov
e0afe0807c
client: Fix error checking for go 1.18-19
Should be reverted/updated when minimum version of Go will be set to 1.20

Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-05-19 12:19:20 +04:00
Evgenii Baidakov
a4e14ab35b
client: Fix linter - unused parameter
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-05-19 12:19:19 +04:00
Evgenii Baidakov
9e1079723e
client: Fix linter - unnecessary leading newline
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-05-19 12:19:19 +04:00
Evgenii Baidakov
2f45caf8a5
client: Remove ResolveNeoFSFailures
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-05-19 12:19:19 +04:00
Evgenii Baidakov
548f911195
client: Update documentation
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-05-19 12:19:18 +04:00
Evgenii Baidakov
149b145073
client: Extend tests for apistatuses
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-05-19 12:19:18 +04:00
Evgenii Baidakov
1d952ced4e
client: Replace apistatus type check with error check
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-05-19 10:33:09 +04:00
Evgenii Baidakov
483aff30c0
client: Remove duplicate test function
TestFromStatusV2 and TestToStatusV2 have the identical testcases and code to check them

Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-05-19 10:33:09 +04:00
Evgenii Baidakov
9533a778a8
client: Move and extend status errors tests
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-05-19 10:33:08 +04:00
Evgenii Baidakov
fc0bd12101
client: Add separate error for each apistatus
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-05-19 10:33:08 +04:00
LeL
9d25bc7519
go.mod: update tzhash (#415) 2023-05-18 17:55:26 +04:00
Roman Khimov
d17066dfea go.mod: update tzhash
Signed-off-by: Roman Khimov <roman@nspcc.ru>
2023-05-18 16:21:39 +03:00
Roman Khimov
fec2f065e8
Set contextCall.err if missed session params (#410)
close #221
2023-05-16 12:29:28 +03:00
Evgenii Baidakov
162a15ae4b
client: Add tests for contextCall.err in session creation
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-05-15 07:45:14 +04:00
Evgenii Baidakov
0c7bfc2afe
client: Set contextCall.err if missed session params
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-05-15 07:45:06 +04:00
LeL
669c9ce9bc
.github: extend CODEOWNERS list (#411) 2023-05-12 10:14:04 +04:00
Roman Khimov
9e893fe3e9 .github: extend CODEOWNERS list
Signed-off-by: Roman Khimov <roman@nspcc.ru>
2023-05-11 18:26:42 +03:00
Roman Khimov
c97f834c6b
Accept oid.Address into client.PrmObjectGet, PrmObjectHash, PrmObjectDelete (#408) 2023-05-05 12:04:07 +03:00
Evgenii Baidakov
04ea0e8f6a
client: Accept rng object.Range into PrmObjectRange
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-05-05 12:17:21 +04:00
Evgenii Baidakov
626532d7dd
client: Use ByAddress instead of two calls FromContainer + ByID
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-05-05 12:17:12 +04:00
Evgenii Baidakov
3f603dc8eb
client: Accept oid.Address into PrmObjectHash, PrmObjectDelete, PrmObjectGet
close #404

Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-05-05 12:17:12 +04:00
Roman Khimov
d7a12a4846
Replace IsErrXXX functions with regular Go errors (#407)
close #220
2023-05-05 10:47:42 +03:00
Evgenii Baidakov
f34c99d538
docs: Actualize interfaces in status structs
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-05-05 10:17:16 +04:00
Evgenii Baidakov
ca0f19c453
client: Remove IsErrXXX functions
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-05-05 10:17:16 +04:00
Evgenii Baidakov
f1b438a2ac
pool: Replace IsErrXXX functions with errors
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-05-05 10:17:15 +04:00
Evgenii Baidakov
7d2cfff825
client: Define separate errors instead of IsErrXXX functions
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-05-05 10:17:15 +04:00
Roman Khimov
8ed98d6dec
Release 1.0.0-rc.8 (#403) 2023-04-27 14:25:13 +03:00
Roman Khimov
c0aaa66fe5 gitignore: ignore text editor backup files
Signed-off-by: Roman Khimov <roman@nspcc.ru>
2023-04-27 12:58:11 +03:00
Roman Khimov
24527b7880 CHANGELOG: add it, release 1.0.0-rc.8
Signed-off-by: Roman Khimov <roman@nspcc.ru>
2023-04-27 12:58:06 +03:00
Roman Khimov
e0d06dd444
Provide static object slicer (#382)
* closes #342
2023-04-27 12:03:40 +03:00
Leonard Lyubich
153695a03d client: Provide the ability to create slicer.Slicer instance
`CreateObject` function uses `slicer.Slicer` to read data encapsulated
behind `io.Reader` and store it in the NeoFS network. Sometimes there is
a need to init data writer (`io.Writer`)` instead of providing reader.
To cover such use-cases, it's worth to expose slicer's constructor based
on client.

Add `NewDataSlicer` constructor. Use the function in `CreateObject`.

Refs #342.

Signed-off-by: Leonard Lyubich <leonard@morphbits.io>
2023-04-26 21:40:01 +04:00
Leonard Lyubich
e2011832eb slicer: Allow to toggle homomorphic hashing
Homomorphic hashing of object payload is not always necessary. There is
a need to provide ability to skip calculation. It's also worth to not
calculate it by default since current implementation of Tillich-Zemor
algorithm has large resource cost.

Do not calculate homomorphic checksum in `Slicer` methods by default.
Provide option to enable the calculation. Make tests to randomize
calculation flag and assert results according to it.

Refs #342.

Signed-off-by: Leonard Lyubich <leonard@morphbits.io>
2023-04-26 21:40:01 +04:00
Leonard Lyubich
ab5ae28fdb slicer: Implement alternative slicing through user writing
Provide method to initialize payload stream as `io.WriteCloser`. This
approach will be useful for applications which need to control data
writing by themselves.

Refs #342.

Signed-off-by: Leonard Lyubich <leonard@morphbits.io>
2023-04-26 21:39:59 +04:00
Leonard Lyubich
0d7d03d56f client: Support static data slicing
Add `CreateObject` function which slices data stream into NeoFS objects
and saves the produced objects in the NeoFS network using `Client`.

Closes #342.

Signed-off-by: Leonard Lyubich <leonard@morphbits.io>
2023-04-26 21:38:08 +04:00
Leonard Lyubich
8eded316de Initial commit of static object slicer
There is a need to provide convenient function which allows to slice
user data into objects to be stored in NeoFS. The main challenge is to
produce objects compliant with the format described in the NeoFS
Specification.

Add `object/slicer` package. Export `Slicer` type which performs data
slicing.

Refs #342.

Signed-off-by: Leonard Lyubich <leonard@morphbits.io>
2023-04-26 21:38:05 +04:00
Roman Khimov
28a3708b4e
Replace signature package from neofs-api-go (#401)
close #219
2023-04-26 17:17:12 +03:00
Evgenii Baidakov
36b1e8442c
tests: Use dedicated function to generate signers in tests
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-04-26 15:03:55 +04:00
Evgenii Baidakov
25c0fd9b8e
client: Update coverage for signing
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-04-26 15:01:44 +04:00
Evgenii Baidakov
64c0612bdc
:* Replace ecdsa.PrivateKey with neofscrypto.Signer
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-04-26 14:59:21 +04:00
Evgenii Baidakov
570a628462
:* Replace signature package from neofs-api-go
close #219, #155

Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-04-26 14:54:12 +04:00
Evgenii Baidakov
77c2e227b9
pool: Use PublicKey like cache key instead of PrivateKey
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-04-25 14:08:47 +04:00
Roman Khimov
dbbb22ca28
Use CID to prevent incorrect usage in netmap (#396)
close #216
2023-04-21 15:25:53 +03:00
Evgenii Baidakov
83ca99ae92
doc: Actualize docs for ContainerNodes func
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-04-21 10:24:37 +04:00
Evgenii Baidakov
4b6965f209
netmap: Use CID to prevent incorrect usage in netmap
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-04-21 10:24:10 +04:00
Roman Khimov
16daed8140
Set external signer for requests payload (#398)
close #373
2023-04-20 14:48:09 +03:00
Evgenii Baidakov
70df422866
neofsecdsa: Add StaticSigner for externally calculated hashes
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-04-20 14:29:59 +04:00
Evgenii Baidakov
6ac961d41c
client: Set external signer for requests payload
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-04-20 14:29:58 +04:00
Roman Khimov
0bc98456e3
Remove FromString method of enumerations (#393)
close #98
2023-04-19 21:09:43 +03:00
Evgenii Baidakov
6757c0a706
linter: Fix fmt.Errorf format for type variable
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-04-19 16:14:22 +04:00
Evgenii Baidakov
5d3fcd6f55
enums: Remove FromString method of enumerations
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-04-19 14:33:35 +04:00
LeL
34213c6275
Update go version to 1.20 (#394) 2023-04-19 14:23:22 +04:00
LeL
bde3b2f4b6
Actualize CODEOWNERS (#395) 2023-04-19 14:19:40 +04:00
Roman Khimov
dcdd75b751
documentation: Container create and get example (#392)
Close #385
2023-04-18 22:49:39 +03:00
Roman Khimov
32fc84a67c .github: hey, I'm the code owner too!
Let me have this green button.

Signed-off-by: Roman Khimov <roman@nspcc.ru>
2023-04-18 21:58:12 +03:00
Evgenii Baidakov
38a613fce5
*: replace interface{} with any keyword
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-04-18 12:28:25 +04:00
Evgenii Baidakov
54faed8384
.github: Remove support 1.17, add support 1.20
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-04-18 12:21:34 +04:00
Evgenii Baidakov
c5f949e314
go.mod: Set minimum version to 1.18
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-04-18 12:19:34 +04:00
Evgenii Baidakov
d1ae4ef576
documentation: Container create and get example
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-04-17 17:39:33 +04:00
Roman Khimov
47af8441eb
[#362] pool: Don't use default session token for read (#363)
close #362
2023-04-17 15:43:06 +03:00
Roman Khimov
8678b36ed9
Check CID is set to eacl table in ContainerSetEACL command (#389)
close #384
2023-04-17 15:25:58 +03:00
Roman Khimov
c6bda422fc
filter: Add payload and homomorphic hash filter methods (#386)
close #383
2023-04-17 14:44:21 +03:00
Evgenii Baidakov
dfb799783e
client: Check CID is set to eacl table in ContainerSetEACL command
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-04-17 09:40:43 +04:00
Roman Khimov
493fa50915
Feature/308 add forming phy storage group (#348)
Move forming storage group (with phy objects) from
[neofs-cli](c2918fce3a/pkg/services/object_manager/storagegroup/collect.go (L17-L67))
to sdk

close #308
2023-04-15 20:53:46 +03:00
Roman Khimov
08a152b0bf
Update deps (#372) 2023-04-15 20:39:17 +03:00
Roman Khimov
3d42ae62d6
Method to take raw client (#388)
close #379
2023-04-15 14:29:38 +03:00
Evgenii Baidakov
7d66148d1f
filter: Add payload and homomorphic hash filter methods
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-04-14 16:37:48 +04:00
Evgenii Baidakov
77f557530e
pool: Method to take raw client
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-04-14 09:56:58 +04:00
Roman Khimov
bd429c9f37
object: Remove deprecated object.RawObject type (#387)
close #139
2023-04-13 23:33:37 +03:00
Evgenii Baidakov
71350c1f42
object: Remove deprecated object.RawObject type
Signed-off-by: Evgenii Baidakov <evgenii@nspcc.io>
2023-04-13 19:26:22 +04:00
Roman Khimov
9a543b6f64
Add some encoding methods (#378)
_Inspired by https://github.com/AxLabs/neofs-shared-lib project._
2023-04-13 13:00:45 +03:00
Leonard Lyubich
651c17f9b3 [#378] version: Add JSON encoding for Version type
Define `MarshalJSON` / `UnmarshalJSON` methods of the `Version` type.

Signed-off-by: Leonard Lyubich <ctulhurider@gmail.com>
2023-01-31 12:51:51 +04:00
Leonard Lyubich
8fdbf6950a [#378] netmap: Add binary encoding for NetworkInfo type
Define `Marshal` / `Unmarshal` methods of the `NetworkInfo` type.

Signed-off-by: Leonard Lyubich <ctulhurider@gmail.com>
2023-01-31 12:51:51 +04:00
Leonard Lyubich
4191e5f13e [#378] accounting: Add binary encoding for Decimal type
Define `Marshal` / `Unmarshal` methods of the `Decimal` type.

Signed-off-by: Leonard Lyubich <ctulhurider@gmail.com>
2023-01-31 12:51:47 +04:00
ae44191e8c [#374] pool: Fix handling SplitInfoError
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2023-01-23 11:06:41 +03:00
72df51cfe9 [#372] go.mod: Update ANTLR
Current `go:generate` command was, probably, executed only on my laptop.
Replace it with explicit version, because package in the generated code
depends on in.

Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2022-12-29 13:55:59 +03:00
9e18d50978 [#372] go.mod: Update dependencies
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2022-12-29 13:55:59 +03:00
5fc9865577 [#372] go.mod: Update neo-go to v0.100.1
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2022-12-29 13:55:59 +03:00
Denis Kirillov
170f31b7c4 [#365] pool: Add request callback
Signed-off-by: Denis Kirillov <denis@nspcc.ru>
2022-12-29 13:29:14 +03:00
Evgenii Stratonikov
9efc4ecd70 [#367] client: Allow to override key in Object.Hash RPC
Signed-off-by: Evgenii Stratonikov <evgeniy@nspcc.ru>
2022-12-29 13:28:38 +03:00
Pavel Karpy
1bf41e9bc1 [#370] bearer, session: Clarify expiration epoch
The expiration epoch is the _last_ valid epoch for an entity. Also, clarify
the expiration epoch meaning for tombstones and regular objects.

Signed-off-by: Pavel Karpy <p.karpy@yadro.com>
2022-12-29 13:28:11 +03:00
Pavel Karpy
a690dcb159 [#369] status: Make errors return default messages
Use default messages in `Error` methods like in `ToStatusV2`.

Signed-off-by: Pavel Karpy <p.karpy@yadro.com>
2022-12-12 11:36:58 +03:00
Denis Kirillov
4fbd53ba73 [#308] audit: Add CollectMembers function
Add new function to build storage group using OIDs of virtual objects.

Signed-off-by: Denis Kirillov <denis@nspcc.ru>
2022-11-17 16:31:45 +03:00
Denis Kirillov
9964a83083 [#308] object/relations: List relations in split order
Signed-off-by: Denis Kirillov <denis@nspcc.ru>
2022-11-17 16:31:26 +03:00
Denis Kirillov
f5e1c4c31c [#362] pool: Don't use default session token for read
Signed-off-by: Denis Kirillov <denis@nspcc.ru>
2022-11-15 11:41:28 +03:00
243 changed files with 5893 additions and 5394 deletions

2
.github/CODEOWNERS vendored
View file

@ -1 +1 @@
* @TrueCloudLab/storage-core @TrueCloudLab/storage-services
* @roman-khimov @cthulhu-rider @smallhive @notimetoname

View file

@ -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`):

View file

@ -1 +0,0 @@
blank_issues_enabled: false

View file

@ -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
View file

@ -17,6 +17,7 @@ vendor/
# IDE
.idea
.vscode
*~
# coverage
coverage.txt

View file

@ -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

View file

@ -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

View file

@ -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
View 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
View 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

View file

@ -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.

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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)

View file

@ -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.

View file

@ -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
View 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
}

View file

@ -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:

View file

@ -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.
//

View file

@ -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"
)

View file

@ -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

View file

@ -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.

View file

@ -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{}
}
}

View file

@ -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))
}

View file

@ -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)

View file

@ -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
}

View file

@ -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 {

View file

@ -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"
)

View file

@ -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() {

View file

@ -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

View file

@ -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.

View file

@ -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))
}

View file

@ -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
}

View file

@ -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) {

View file

@ -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) {

View file

@ -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)
}

View file

@ -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

View file

@ -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
// ...

View file

@ -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
}
}

View file

@ -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)
}

View 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}}}
}

View file

@ -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()

View file

@ -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())
}

View file

@ -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()

View 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()))
})
}

View file

@ -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
View 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())
})
}

View file

@ -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")

View 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()))
})
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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++

View file

@ -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
}

View file

@ -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
}

View file

@ -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
View 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
View 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
View 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)
}

View file

@ -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)

View file

@ -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())
})

View file

@ -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

View 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)
}
}
}

View file

@ -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

View file

@ -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)
}

View file

@ -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

View file

@ -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
}

View file

@ -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)
})
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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"

View file

@ -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))
}
}
}

View file

@ -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

View file

@ -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.
*/

View file

@ -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 {

View file

@ -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))

View file

@ -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)

View file

@ -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.

View file

@ -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()
}

View file

@ -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"
)

View file

@ -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

View file

@ -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.

View file

@ -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

View file

@ -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) {

View file

@ -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 {

View file

@ -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"
)

View file

@ -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")

View file

@ -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()

View file

@ -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

View file

@ -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

View file

@ -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)
})
}

View file

@ -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

View file

@ -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)
}

View file

@ -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

View file

@ -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 {

View file

@ -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
View 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)
}

View file

@ -1,4 +1,4 @@
package frostfscrypto
package neofscrypto
import "encoding/hex"

View file

@ -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)

View file

@ -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))
}
}

View file

@ -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