Remove v1 code
This commit is contained in:
parent
ed7879a89e
commit
0a5d0ff1a2
140 changed files with 0 additions and 12952 deletions
|
@ -1,8 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
CHEQUE=7849b02d01cc7f7734295fa815ea64ec4d2012e45b8781eb891723ba2703c53263e8d6e522dc32203339dcd8eee9c6b7439a00ea56fa00000000611e000000000000060003012d47e76210aec73be39ab3d186e0a40fe8d86bfa3d4fabfda57ba13b88f96a8ebe4360627a1326f13fb9516c0dbc4af90f116e44bd33f4d04a0d1633afa243ad4f2fa9cd933e7631a619b5132cec6983906aba757af5590434124b232a43e302f7528ce97c9a93558efe7d4f62577aabdf771c931f54a71be6ad21e7d9cc177744b4b9781cf0c29adb503f33d2df9f810ebf33a774849848984cf7e2bbebd48ef0cd8592fbf9b6aee1dc74803e31c95a02dbbd5fd9783f9ecbcbf444b5942f830368a6f5829fb2a34fa03d0308ae6b05f433f2904d9a852fed1f5d2eb598ca794770adb1ece9dccd1c7ad98f709cfb890e3bdd5973dcdd838111fae2efa4c3c09ea2133e5d7c6eac6ae24afcce46db7c9f4dc154f123c835adff4e0b7e19bcffda0340750b92789821683283bcb98e32b7e032b94f267b6964613fc31a7ce5813fddad8298f71dfdc4f9b3e353f969727af476d43b12a25727cf6b9c73ae7152266d995bec807068ad2156288c4d946aeb17ebca787d498a1b87b9dae1bcd935763403fef27f744e829131d0ec980829fafa51db1714c2761d9f78762c008c323e9d66db9b5086d355897e52fe065e14f1cc70334248349aa4c7a3e6e3dc8f8693b1511c73dc88e6d6e8b6c6c68de922f351b5b1543917af2f2a3588aebfbd1ff3fac6023e00f03a16e8707ce045eb42ee80d392451541ee510dc18e1c8befbac54d742648b58f379b5337d9b74c5a61afb8ef3db7f3eb0454d6823777b613a3ee22cd6ce47e4fa72170d49267b773cc09c123654e0bcd7278aa2ae1e7c85d049b557a3c
|
|
||||||
DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
|
|
||||||
|
|
||||||
echo $CHEQUE | xxd -p -r > $DIR/cheque_data
|
|
||||||
|
|
||||||
exit 0
|
|
Binary file not shown.
|
@ -1,46 +0,0 @@
|
||||||
package accounting
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/decimal"
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/internal"
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/refs"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
// OwnerID type alias.
|
|
||||||
OwnerID = refs.OwnerID
|
|
||||||
|
|
||||||
// Decimal type alias.
|
|
||||||
Decimal = decimal.Decimal
|
|
||||||
|
|
||||||
// Filter is used to filter accounts by criteria.
|
|
||||||
Filter func(acc *Account) bool
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// ErrEmptyAddress is raised when passed Address is empty.
|
|
||||||
ErrEmptyAddress = internal.Error("empty address")
|
|
||||||
|
|
||||||
// ErrEmptyLockTarget is raised when passed LockTarget is empty.
|
|
||||||
ErrEmptyLockTarget = internal.Error("empty lock target")
|
|
||||||
|
|
||||||
// ErrEmptyContainerID is raised when passed CID is empty.
|
|
||||||
ErrEmptyContainerID = internal.Error("empty container ID")
|
|
||||||
|
|
||||||
// ErrEmptyParentAddress is raised when passed ParentAddress is empty.
|
|
||||||
ErrEmptyParentAddress = internal.Error("empty parent address")
|
|
||||||
)
|
|
||||||
|
|
||||||
// SumFunds goes through all accounts and sums up active funds.
|
|
||||||
func SumFunds(accounts []*Account) (res *decimal.Decimal) {
|
|
||||||
res = decimal.Zero.Copy()
|
|
||||||
|
|
||||||
for i := range accounts {
|
|
||||||
if accounts[i] == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
res = res.Add(accounts[i].ActiveFunds)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
Binary file not shown.
|
@ -1,36 +0,0 @@
|
||||||
syntax = "proto3";
|
|
||||||
package accounting;
|
|
||||||
option go_package = "github.com/nspcc-dev/neofs-api-go/accounting";
|
|
||||||
option csharp_namespace = "NeoFS.API.Accounting";
|
|
||||||
|
|
||||||
import "service/meta.proto";
|
|
||||||
import "service/verify.proto";
|
|
||||||
import "decimal/decimal.proto";
|
|
||||||
import "accounting/types.proto";
|
|
||||||
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
|
|
||||||
|
|
||||||
option (gogoproto.stable_marshaler_all) = true;
|
|
||||||
|
|
||||||
// Accounting is a service that provides access for accounting balance
|
|
||||||
// information
|
|
||||||
service Accounting {
|
|
||||||
// Balance returns current balance status of the NeoFS user
|
|
||||||
rpc Balance(BalanceRequest) returns (BalanceResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
message BalanceRequest {
|
|
||||||
// OwnerID is a wallet address
|
|
||||||
bytes OwnerID = 1 [(gogoproto.customtype) = "OwnerID", (gogoproto.nullable) = false];
|
|
||||||
// RequestMetaHeader contains information about request meta headers (should be embedded into message)
|
|
||||||
service.RequestMetaHeader Meta = 98 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
// RequestVerificationHeader is a set of signatures of every NeoFS Node that processed request (should be embedded into message)
|
|
||||||
service.RequestVerificationHeader Verify = 99 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
}
|
|
||||||
|
|
||||||
message BalanceResponse {
|
|
||||||
// Balance contains current account balance state
|
|
||||||
decimal.Decimal Balance = 1;
|
|
||||||
// LockAccounts contains information about locked funds. Locked funds appear
|
|
||||||
// when user pays for storage or withdraw assets.
|
|
||||||
repeated Account LockAccounts = 2;
|
|
||||||
}
|
|
|
@ -1,167 +0,0 @@
|
||||||
package accounting
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/service"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SignedData returns payload bytes of the request.
|
|
||||||
func (m BalanceRequest) SignedData() ([]byte, error) {
|
|
||||||
return service.SignedDataFromReader(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedDataSize returns payload size of the request.
|
|
||||||
func (m BalanceRequest) SignedDataSize() int {
|
|
||||||
return m.GetOwnerID().Size()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadSignedData copies payload bytes to passed buffer.
|
|
||||||
//
|
|
||||||
// If the buffer size is insufficient, io.ErrUnexpectedEOF returns.
|
|
||||||
func (m BalanceRequest) ReadSignedData(p []byte) (int, error) {
|
|
||||||
sz := m.SignedDataSize()
|
|
||||||
if len(p) < sz {
|
|
||||||
return 0, io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
|
|
||||||
copy(p, m.GetOwnerID().Bytes())
|
|
||||||
|
|
||||||
return sz, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedData returns payload bytes of the request.
|
|
||||||
func (m GetRequest) SignedData() ([]byte, error) {
|
|
||||||
return service.SignedDataFromReader(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedDataSize returns payload size of the request.
|
|
||||||
func (m GetRequest) SignedDataSize() int {
|
|
||||||
return m.GetID().Size() + m.GetOwnerID().Size()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadSignedData copies payload bytes to passed buffer.
|
|
||||||
//
|
|
||||||
// If the buffer size is insufficient, io.ErrUnexpectedEOF returns.
|
|
||||||
func (m GetRequest) ReadSignedData(p []byte) (int, error) {
|
|
||||||
sz := m.SignedDataSize()
|
|
||||||
if len(p) < sz {
|
|
||||||
return 0, io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
|
|
||||||
var off int
|
|
||||||
|
|
||||||
off += copy(p[off:], m.GetID().Bytes())
|
|
||||||
|
|
||||||
copy(p[off:], m.GetOwnerID().Bytes())
|
|
||||||
|
|
||||||
return sz, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedData returns payload bytes of the request.
|
|
||||||
func (m PutRequest) SignedData() ([]byte, error) {
|
|
||||||
return service.SignedDataFromReader(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedDataSize returns payload size of the request.
|
|
||||||
func (m PutRequest) SignedDataSize() (sz int) {
|
|
||||||
sz += m.GetOwnerID().Size()
|
|
||||||
|
|
||||||
sz += m.GetMessageID().Size()
|
|
||||||
|
|
||||||
sz += 8
|
|
||||||
|
|
||||||
if amount := m.GetAmount(); amount != nil {
|
|
||||||
sz += amount.Size()
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadSignedData copies payload bytes to passed buffer.
|
|
||||||
//
|
|
||||||
// If the buffer size is insufficient, io.ErrUnexpectedEOF returns.
|
|
||||||
func (m PutRequest) ReadSignedData(p []byte) (int, error) {
|
|
||||||
if len(p) < m.SignedDataSize() {
|
|
||||||
return 0, io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
|
|
||||||
var off int
|
|
||||||
|
|
||||||
off += copy(p[off:], m.GetOwnerID().Bytes())
|
|
||||||
|
|
||||||
off += copy(p[off:], m.GetMessageID().Bytes())
|
|
||||||
|
|
||||||
binary.BigEndian.PutUint64(p[off:], m.GetHeight())
|
|
||||||
off += 8
|
|
||||||
|
|
||||||
if amount := m.GetAmount(); amount != nil {
|
|
||||||
n, err := amount.MarshalTo(p[off:])
|
|
||||||
off += n
|
|
||||||
if err != nil {
|
|
||||||
return off + n, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return off, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedData returns payload bytes of the request.
|
|
||||||
func (m ListRequest) SignedData() ([]byte, error) {
|
|
||||||
return service.SignedDataFromReader(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedDataSize returns payload size of the request.
|
|
||||||
func (m ListRequest) SignedDataSize() int {
|
|
||||||
return m.GetOwnerID().Size()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadSignedData copies payload bytes to passed buffer.
|
|
||||||
//
|
|
||||||
// If the buffer size is insufficient, io.ErrUnexpectedEOF returns.
|
|
||||||
func (m ListRequest) ReadSignedData(p []byte) (int, error) {
|
|
||||||
sz := m.SignedDataSize()
|
|
||||||
if len(p) < sz {
|
|
||||||
return 0, io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
|
|
||||||
copy(p, m.GetOwnerID().Bytes())
|
|
||||||
|
|
||||||
return sz, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedData returns payload bytes of the request.
|
|
||||||
func (m DeleteRequest) SignedData() ([]byte, error) {
|
|
||||||
return service.SignedDataFromReader(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedDataSize returns payload size of the request.
|
|
||||||
func (m DeleteRequest) SignedDataSize() (sz int) {
|
|
||||||
sz += m.GetID().Size()
|
|
||||||
|
|
||||||
sz += m.GetOwnerID().Size()
|
|
||||||
|
|
||||||
sz += m.GetMessageID().Size()
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadSignedData copies payload bytes to passed buffer.
|
|
||||||
//
|
|
||||||
// If the buffer size is insufficient, io.ErrUnexpectedEOF returns.
|
|
||||||
func (m DeleteRequest) ReadSignedData(p []byte) (int, error) {
|
|
||||||
if len(p) < m.SignedDataSize() {
|
|
||||||
return 0, io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
|
|
||||||
var off int
|
|
||||||
|
|
||||||
off += copy(p[off:], m.GetID().Bytes())
|
|
||||||
|
|
||||||
off += copy(p[off:], m.GetOwnerID().Bytes())
|
|
||||||
|
|
||||||
off += copy(p[off:], m.GetMessageID().Bytes())
|
|
||||||
|
|
||||||
return off, nil
|
|
||||||
}
|
|
|
@ -1,185 +0,0 @@
|
||||||
package accounting
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/decimal"
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/service"
|
|
||||||
"github.com/nspcc-dev/neofs-crypto/test"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSignBalanceRequest(t *testing.T) {
|
|
||||||
sk := test.DecodeKey(0)
|
|
||||||
|
|
||||||
type sigType interface {
|
|
||||||
service.RequestData
|
|
||||||
service.SignKeyPairAccumulator
|
|
||||||
service.SignKeyPairSource
|
|
||||||
SetToken(*service.Token)
|
|
||||||
}
|
|
||||||
|
|
||||||
items := []struct {
|
|
||||||
constructor func() sigType
|
|
||||||
payloadCorrupt []func(sigType)
|
|
||||||
}{
|
|
||||||
{ // BalanceRequest
|
|
||||||
constructor: func() sigType {
|
|
||||||
return new(BalanceRequest)
|
|
||||||
},
|
|
||||||
payloadCorrupt: []func(sigType){
|
|
||||||
func(s sigType) {
|
|
||||||
req := s.(*BalanceRequest)
|
|
||||||
|
|
||||||
owner := req.GetOwnerID()
|
|
||||||
owner[0]++
|
|
||||||
|
|
||||||
req.SetOwnerID(owner)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ // GetRequest
|
|
||||||
constructor: func() sigType {
|
|
||||||
return new(GetRequest)
|
|
||||||
},
|
|
||||||
payloadCorrupt: []func(sigType){
|
|
||||||
func(s sigType) {
|
|
||||||
req := s.(*GetRequest)
|
|
||||||
|
|
||||||
id, err := NewChequeID()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
req.SetID(id)
|
|
||||||
},
|
|
||||||
func(s sigType) {
|
|
||||||
req := s.(*GetRequest)
|
|
||||||
|
|
||||||
id := req.GetOwnerID()
|
|
||||||
id[0]++
|
|
||||||
|
|
||||||
req.SetOwnerID(id)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ // PutRequest
|
|
||||||
constructor: func() sigType {
|
|
||||||
req := new(PutRequest)
|
|
||||||
|
|
||||||
amount := decimal.New(1)
|
|
||||||
req.SetAmount(amount)
|
|
||||||
|
|
||||||
return req
|
|
||||||
},
|
|
||||||
payloadCorrupt: []func(sigType){
|
|
||||||
func(s sigType) {
|
|
||||||
req := s.(*PutRequest)
|
|
||||||
|
|
||||||
owner := req.GetOwnerID()
|
|
||||||
owner[0]++
|
|
||||||
|
|
||||||
req.SetOwnerID(owner)
|
|
||||||
},
|
|
||||||
func(s sigType) {
|
|
||||||
req := s.(*PutRequest)
|
|
||||||
|
|
||||||
mid := req.GetMessageID()
|
|
||||||
mid[0]++
|
|
||||||
|
|
||||||
req.SetMessageID(mid)
|
|
||||||
},
|
|
||||||
func(s sigType) {
|
|
||||||
req := s.(*PutRequest)
|
|
||||||
|
|
||||||
req.SetHeight(req.GetHeight() + 1)
|
|
||||||
},
|
|
||||||
func(s sigType) {
|
|
||||||
req := s.(*PutRequest)
|
|
||||||
|
|
||||||
amount := req.GetAmount()
|
|
||||||
if amount == nil {
|
|
||||||
req.SetAmount(decimal.New(0))
|
|
||||||
} else {
|
|
||||||
req.SetAmount(amount.Add(decimal.New(amount.GetValue())))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ // ListRequest
|
|
||||||
constructor: func() sigType {
|
|
||||||
return new(ListRequest)
|
|
||||||
},
|
|
||||||
payloadCorrupt: []func(sigType){
|
|
||||||
func(s sigType) {
|
|
||||||
req := s.(*ListRequest)
|
|
||||||
|
|
||||||
owner := req.GetOwnerID()
|
|
||||||
owner[0]++
|
|
||||||
|
|
||||||
req.SetOwnerID(owner)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
constructor: func() sigType {
|
|
||||||
return new(DeleteRequest)
|
|
||||||
},
|
|
||||||
payloadCorrupt: []func(sigType){
|
|
||||||
func(s sigType) {
|
|
||||||
req := s.(*DeleteRequest)
|
|
||||||
|
|
||||||
id, err := NewChequeID()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
req.SetID(id)
|
|
||||||
},
|
|
||||||
func(s sigType) {
|
|
||||||
req := s.(*DeleteRequest)
|
|
||||||
|
|
||||||
owner := req.GetOwnerID()
|
|
||||||
owner[0]++
|
|
||||||
|
|
||||||
req.SetOwnerID(owner)
|
|
||||||
},
|
|
||||||
func(s sigType) {
|
|
||||||
req := s.(*DeleteRequest)
|
|
||||||
|
|
||||||
mid := req.GetMessageID()
|
|
||||||
mid[0]++
|
|
||||||
|
|
||||||
req.SetMessageID(mid)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, item := range items {
|
|
||||||
{ // token corruptions
|
|
||||||
v := item.constructor()
|
|
||||||
|
|
||||||
token := new(service.Token)
|
|
||||||
v.SetToken(token)
|
|
||||||
|
|
||||||
require.NoError(t, service.SignRequestData(sk, v))
|
|
||||||
|
|
||||||
require.NoError(t, service.VerifyRequestData(v))
|
|
||||||
|
|
||||||
token.SetSessionKey(append(token.GetSessionKey(), 1))
|
|
||||||
|
|
||||||
require.Error(t, service.VerifyRequestData(v))
|
|
||||||
}
|
|
||||||
|
|
||||||
{ // payload corruptions
|
|
||||||
for _, corruption := range item.payloadCorrupt {
|
|
||||||
v := item.constructor()
|
|
||||||
|
|
||||||
require.NoError(t, service.SignRequestData(sk, v))
|
|
||||||
|
|
||||||
require.NoError(t, service.VerifyRequestData(v))
|
|
||||||
|
|
||||||
corruption(v)
|
|
||||||
|
|
||||||
require.Error(t, service.VerifyRequestData(v))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,453 +0,0 @@
|
||||||
package accounting
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/binary"
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/mr-tron/base58"
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/chain"
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/decimal"
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/internal"
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/refs"
|
|
||||||
crypto "github.com/nspcc-dev/neofs-crypto"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
// Cheque structure that describes a user request for withdrawal of funds.
|
|
||||||
Cheque struct {
|
|
||||||
ID ChequeID
|
|
||||||
Owner refs.OwnerID
|
|
||||||
Amount *decimal.Decimal
|
|
||||||
Height uint64
|
|
||||||
Signatures []ChequeSignature
|
|
||||||
}
|
|
||||||
|
|
||||||
// BalanceReceiver interface that is used to retrieve user balance by address.
|
|
||||||
BalanceReceiver interface {
|
|
||||||
Balance(accountAddress string) (*Account, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ChequeID is identifier of user request for withdrawal of funds.
|
|
||||||
ChequeID string
|
|
||||||
|
|
||||||
// CID type alias.
|
|
||||||
CID = refs.CID
|
|
||||||
|
|
||||||
// SGID type alias.
|
|
||||||
SGID = refs.SGID
|
|
||||||
|
|
||||||
// ChequeSignature contains public key and hash, and is used to verify signatures.
|
|
||||||
ChequeSignature struct {
|
|
||||||
Key *ecdsa.PublicKey
|
|
||||||
Hash []byte
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// ErrWrongSignature is raised when wrong signature is passed.
|
|
||||||
ErrWrongSignature = internal.Error("wrong signature")
|
|
||||||
|
|
||||||
// ErrWrongPublicKey is raised when wrong public key is passed.
|
|
||||||
ErrWrongPublicKey = internal.Error("wrong public key")
|
|
||||||
|
|
||||||
// ErrWrongChequeData is raised when passed bytes cannot not be parsed as valid Cheque.
|
|
||||||
ErrWrongChequeData = internal.Error("wrong cheque data")
|
|
||||||
|
|
||||||
// ErrInvalidLength is raised when passed bytes cannot not be parsed as valid ChequeID.
|
|
||||||
ErrInvalidLength = internal.Error("invalid length")
|
|
||||||
|
|
||||||
u16size = 2
|
|
||||||
u64size = 8
|
|
||||||
|
|
||||||
signaturesOffset = chain.AddressLength + refs.OwnerIDSize + u64size + u64size
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewChequeID generates valid random ChequeID using crypto/rand.Reader.
|
|
||||||
func NewChequeID() (ChequeID, error) {
|
|
||||||
d := make([]byte, chain.AddressLength)
|
|
||||||
if _, err := rand.Read(d); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
id := base58.Encode(d)
|
|
||||||
|
|
||||||
return ChequeID(id), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns string representation of ChequeID.
|
|
||||||
func (b ChequeID) String() string { return string(b) }
|
|
||||||
|
|
||||||
// Empty returns true, if ChequeID is empty.
|
|
||||||
func (b ChequeID) Empty() bool { return len(b) == 0 }
|
|
||||||
|
|
||||||
// Valid validates ChequeID.
|
|
||||||
func (b ChequeID) Valid() bool {
|
|
||||||
d, err := base58.Decode(string(b))
|
|
||||||
return err == nil && len(d) == chain.AddressLength
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bytes returns bytes representation of ChequeID.
|
|
||||||
func (b ChequeID) Bytes() []byte {
|
|
||||||
d, err := base58.Decode(string(b))
|
|
||||||
if err != nil {
|
|
||||||
return make([]byte, chain.AddressLength)
|
|
||||||
}
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equal checks that current ChequeID is equal to passed ChequeID.
|
|
||||||
func (b ChequeID) Equal(b2 ChequeID) bool {
|
|
||||||
return b.Valid() && b2.Valid() && string(b) == string(b2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unmarshal tries to parse []byte into valid ChequeID.
|
|
||||||
func (b *ChequeID) Unmarshal(data []byte) error {
|
|
||||||
*b = ChequeID(base58.Encode(data))
|
|
||||||
if !b.Valid() {
|
|
||||||
return ErrInvalidLength
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Size returns size (chain.AddressLength).
|
|
||||||
func (b ChequeID) Size() int {
|
|
||||||
return chain.AddressLength
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalTo tries to marshal ChequeID into passed bytes and returns
|
|
||||||
// count of copied bytes or error, if bytes len is not enough to contain ChequeID.
|
|
||||||
func (b ChequeID) MarshalTo(data []byte) (int, error) {
|
|
||||||
if len(data) < chain.AddressLength {
|
|
||||||
return 0, ErrInvalidLength
|
|
||||||
}
|
|
||||||
return copy(data, b.Bytes()), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equals checks that m and tx are valid and equal Tx values.
|
|
||||||
func (m Tx) Equals(tx Tx) bool {
|
|
||||||
return m.From == tx.From &&
|
|
||||||
m.To == tx.To &&
|
|
||||||
m.Type == tx.Type &&
|
|
||||||
m.Amount == tx.Amount
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify validates current Cheque and Signatures that are generated for current Cheque.
|
|
||||||
func (b Cheque) Verify() error {
|
|
||||||
data := b.marshalBody()
|
|
||||||
for i, sign := range b.Signatures {
|
|
||||||
if err := crypto.VerifyRFC6979(sign.Key, data, sign.Hash); err != nil {
|
|
||||||
return errors.Wrapf(ErrWrongSignature, "item #%d: %s", i, err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign is used to sign current Cheque and stores result inside b.Signatures.
|
|
||||||
func (b *Cheque) Sign(key *ecdsa.PrivateKey) error {
|
|
||||||
hash, err := crypto.SignRFC6979(key, b.marshalBody())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Signatures = append(b.Signatures, ChequeSignature{
|
|
||||||
Key: &key.PublicKey,
|
|
||||||
Hash: hash,
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Cheque) marshalBody() []byte {
|
|
||||||
buf := make([]byte, signaturesOffset)
|
|
||||||
|
|
||||||
var offset int
|
|
||||||
|
|
||||||
offset += copy(buf, b.ID.Bytes())
|
|
||||||
offset += copy(buf[offset:], b.Owner.Bytes())
|
|
||||||
|
|
||||||
binary.LittleEndian.PutUint64(buf[offset:], uint64(b.Amount.Value))
|
|
||||||
offset += u64size
|
|
||||||
|
|
||||||
binary.LittleEndian.PutUint64(buf[offset:], b.Height)
|
|
||||||
|
|
||||||
return buf
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Cheque) unmarshalBody(buf []byte) error {
|
|
||||||
var offset int
|
|
||||||
|
|
||||||
if len(buf) < signaturesOffset {
|
|
||||||
return ErrWrongChequeData
|
|
||||||
}
|
|
||||||
|
|
||||||
{ // unmarshal UUID
|
|
||||||
if err := b.ID.Unmarshal(buf[offset : offset+chain.AddressLength]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
offset += chain.AddressLength
|
|
||||||
}
|
|
||||||
|
|
||||||
{ // unmarshal OwnerID
|
|
||||||
if err := b.Owner.Unmarshal(buf[offset : offset+refs.OwnerIDSize]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
offset += refs.OwnerIDSize
|
|
||||||
}
|
|
||||||
|
|
||||||
{ // unmarshal amount
|
|
||||||
amount := int64(binary.LittleEndian.Uint64(buf[offset:]))
|
|
||||||
b.Amount = decimal.New(amount)
|
|
||||||
offset += u64size
|
|
||||||
}
|
|
||||||
|
|
||||||
{ // unmarshal height
|
|
||||||
b.Height = binary.LittleEndian.Uint64(buf[offset:])
|
|
||||||
offset += u64size
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalBinary is used to marshal Cheque into bytes.
|
|
||||||
func (b Cheque) MarshalBinary() ([]byte, error) {
|
|
||||||
var (
|
|
||||||
count = len(b.Signatures)
|
|
||||||
buf = make([]byte, b.Size())
|
|
||||||
offset = copy(buf, b.marshalBody())
|
|
||||||
)
|
|
||||||
|
|
||||||
binary.LittleEndian.PutUint16(buf[offset:], uint16(count))
|
|
||||||
offset += u16size
|
|
||||||
|
|
||||||
for _, sign := range b.Signatures {
|
|
||||||
key := crypto.MarshalPublicKey(sign.Key)
|
|
||||||
offset += copy(buf[offset:], key)
|
|
||||||
offset += copy(buf[offset:], sign.Hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Size returns size of Cheque (count of bytes needs to store it).
|
|
||||||
func (b Cheque) Size() int {
|
|
||||||
return signaturesOffset + u16size +
|
|
||||||
len(b.Signatures)*(crypto.PublicKeyCompressedSize+crypto.RFC6979SignatureSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalBinary tries to parse []byte into valid Cheque.
|
|
||||||
func (b *Cheque) UnmarshalBinary(buf []byte) error {
|
|
||||||
if err := b.unmarshalBody(buf); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
body := buf[:signaturesOffset]
|
|
||||||
|
|
||||||
count := int64(binary.LittleEndian.Uint16(buf[signaturesOffset:]))
|
|
||||||
offset := signaturesOffset + u16size
|
|
||||||
|
|
||||||
if ln := count * int64(crypto.PublicKeyCompressedSize+crypto.RFC6979SignatureSize); ln > int64(len(buf[offset:])) {
|
|
||||||
return ErrWrongChequeData
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := int64(0); i < count; i++ {
|
|
||||||
sign := ChequeSignature{
|
|
||||||
Key: crypto.UnmarshalPublicKey(buf[offset : offset+crypto.PublicKeyCompressedSize]),
|
|
||||||
Hash: make([]byte, crypto.RFC6979SignatureSize),
|
|
||||||
}
|
|
||||||
|
|
||||||
offset += crypto.PublicKeyCompressedSize
|
|
||||||
if sign.Key == nil {
|
|
||||||
return errors.Wrapf(ErrWrongPublicKey, "item #%d", i)
|
|
||||||
}
|
|
||||||
|
|
||||||
offset += copy(sign.Hash, buf[offset:offset+crypto.RFC6979SignatureSize])
|
|
||||||
if err := crypto.VerifyRFC6979(sign.Key, body, sign.Hash); err != nil {
|
|
||||||
return errors.Wrapf(ErrWrongSignature, "item #%d: %s (offset=%d, len=%d)", i, err.Error(), offset, len(sign.Hash))
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Signatures = append(b.Signatures, sign)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrNotEnoughFunds generates error using address and amounts.
|
|
||||||
func ErrNotEnoughFunds(addr string, needed, residue *decimal.Decimal) error {
|
|
||||||
return errors.Errorf("not enough funds (requested=%s, residue=%s, addr=%s", needed, residue, addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Account) hasLockAcc(addr string) bool {
|
|
||||||
for i := range m.LockAccounts {
|
|
||||||
if m.LockAccounts[i].Address == addr {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidateLock checks that account can be locked.
|
|
||||||
func (m *Account) ValidateLock() error {
|
|
||||||
switch {
|
|
||||||
case m.Address == "":
|
|
||||||
return ErrEmptyAddress
|
|
||||||
case m.ParentAddress == "":
|
|
||||||
return ErrEmptyParentAddress
|
|
||||||
case m.LockTarget == nil:
|
|
||||||
return ErrEmptyLockTarget
|
|
||||||
}
|
|
||||||
|
|
||||||
switch v := m.LockTarget.Target.(type) {
|
|
||||||
case *LockTarget_WithdrawTarget:
|
|
||||||
if v.WithdrawTarget.Cheque != m.Address {
|
|
||||||
return errors.Errorf("wrong cheque ID: expected %s, has %s", m.Address, v.WithdrawTarget.Cheque)
|
|
||||||
}
|
|
||||||
case *LockTarget_ContainerCreateTarget:
|
|
||||||
switch {
|
|
||||||
case v.ContainerCreateTarget.CID.Empty():
|
|
||||||
return ErrEmptyContainerID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CanLock checks possibility to lock funds.
|
|
||||||
func (m *Account) CanLock(lockAcc *Account) error {
|
|
||||||
switch {
|
|
||||||
case m.ActiveFunds.LT(lockAcc.ActiveFunds):
|
|
||||||
return ErrNotEnoughFunds(lockAcc.ParentAddress, lockAcc.ActiveFunds, m.ActiveFunds)
|
|
||||||
case m.hasLockAcc(lockAcc.Address):
|
|
||||||
return errors.Errorf("could not lock account(%s) funds: duplicating lock(%s)", m.Address, lockAcc.Address)
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LockForWithdraw checks that account contains locked funds by passed ChequeID.
|
|
||||||
func (m *Account) LockForWithdraw(chequeID string) bool {
|
|
||||||
switch v := m.LockTarget.Target.(type) {
|
|
||||||
case *LockTarget_WithdrawTarget:
|
|
||||||
return v.WithdrawTarget.Cheque == chequeID
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// LockForContainerCreate checks that account contains locked funds for container creation.
|
|
||||||
func (m *Account) LockForContainerCreate(cid refs.CID) bool {
|
|
||||||
switch v := m.LockTarget.Target.(type) {
|
|
||||||
case *LockTarget_ContainerCreateTarget:
|
|
||||||
return v.ContainerCreateTarget.CID.Equal(cid)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equal checks that current Settlement is equal to passed Settlement.
|
|
||||||
func (m *Settlement) Equal(s *Settlement) bool {
|
|
||||||
if s == nil || m.Epoch != s.Epoch || len(m.Transactions) != len(s.Transactions) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return len(m.Transactions) == 0 || reflect.DeepEqual(m.Transactions, s.Transactions)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetOwnerID is an OwnerID field getter.
|
|
||||||
func (m BalanceRequest) GetOwnerID() OwnerID {
|
|
||||||
return m.OwnerID
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetOwnerID is an OwnerID field setter.
|
|
||||||
func (m *BalanceRequest) SetOwnerID(owner OwnerID) {
|
|
||||||
m.OwnerID = owner
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetID is an ID field getter.
|
|
||||||
func (m GetRequest) GetID() ChequeID {
|
|
||||||
return m.ID
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetID is an ID field setter.
|
|
||||||
func (m *GetRequest) SetID(id ChequeID) {
|
|
||||||
m.ID = id
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetOwnerID is an OwnerID field getter.
|
|
||||||
func (m GetRequest) GetOwnerID() OwnerID {
|
|
||||||
return m.OwnerID
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetOwnerID is an OwnerID field setter.
|
|
||||||
func (m *GetRequest) SetOwnerID(id OwnerID) {
|
|
||||||
m.OwnerID = id
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetOwnerID is an OwnerID field getter.
|
|
||||||
func (m PutRequest) GetOwnerID() OwnerID {
|
|
||||||
return m.OwnerID
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetOwnerID is an OwnerID field setter.
|
|
||||||
func (m *PutRequest) SetOwnerID(id OwnerID) {
|
|
||||||
m.OwnerID = id
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMessageID is a MessageID field getter.
|
|
||||||
func (m PutRequest) GetMessageID() MessageID {
|
|
||||||
return m.MessageID
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetMessageID is a MessageID field setter.
|
|
||||||
func (m *PutRequest) SetMessageID(id MessageID) {
|
|
||||||
m.MessageID = id
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetAmount is an Amount field setter.
|
|
||||||
func (m *PutRequest) SetAmount(amount *decimal.Decimal) {
|
|
||||||
m.Amount = amount
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetHeight is a Height field setter.
|
|
||||||
func (m *PutRequest) SetHeight(h uint64) {
|
|
||||||
m.Height = h
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetOwnerID is an OwnerID field getter.
|
|
||||||
func (m ListRequest) GetOwnerID() OwnerID {
|
|
||||||
return m.OwnerID
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetOwnerID is an OwnerID field setter.
|
|
||||||
func (m *ListRequest) SetOwnerID(id OwnerID) {
|
|
||||||
m.OwnerID = id
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetID is an ID field getter.
|
|
||||||
func (m DeleteRequest) GetID() ChequeID {
|
|
||||||
return m.ID
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetID is an ID field setter.
|
|
||||||
func (m *DeleteRequest) SetID(id ChequeID) {
|
|
||||||
m.ID = id
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetOwnerID is an OwnerID field getter.
|
|
||||||
func (m DeleteRequest) GetOwnerID() OwnerID {
|
|
||||||
return m.OwnerID
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetOwnerID is an OwnerID field setter.
|
|
||||||
func (m *DeleteRequest) SetOwnerID(id OwnerID) {
|
|
||||||
m.OwnerID = id
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMessageID is a MessageID field getter.
|
|
||||||
func (m DeleteRequest) GetMessageID() MessageID {
|
|
||||||
return m.MessageID
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetMessageID is a MessageID field setter.
|
|
||||||
func (m *DeleteRequest) SetMessageID(id MessageID) {
|
|
||||||
m.MessageID = id
|
|
||||||
}
|
|
Binary file not shown.
|
@ -1,125 +0,0 @@
|
||||||
syntax = "proto3";
|
|
||||||
package accounting;
|
|
||||||
option go_package = "github.com/nspcc-dev/neofs-api-go/accounting";
|
|
||||||
option csharp_namespace = "NeoFS.API.Accounting";
|
|
||||||
|
|
||||||
import "decimal/decimal.proto";
|
|
||||||
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
|
|
||||||
|
|
||||||
option (gogoproto.stable_marshaler_all) = true;
|
|
||||||
|
|
||||||
message Account {
|
|
||||||
// OwnerID is a wallet address
|
|
||||||
bytes OwnerID = 1 [(gogoproto.customtype) = "OwnerID", (gogoproto.nullable) = false];
|
|
||||||
// Address is identifier of accounting record
|
|
||||||
string Address = 2;
|
|
||||||
// ParentAddress is identifier of parent accounting record
|
|
||||||
string ParentAddress = 3;
|
|
||||||
// ActiveFunds is amount of active (non locked) funds for account
|
|
||||||
decimal.Decimal ActiveFunds = 4;
|
|
||||||
// Lifetime is time until account is valid (used for lock accounts)
|
|
||||||
Lifetime Lifetime = 5 [(gogoproto.nullable) = false];
|
|
||||||
// LockTarget is the purpose of lock funds (it might be withdraw or payment for storage)
|
|
||||||
LockTarget LockTarget = 6;
|
|
||||||
// LockAccounts contains child accounts with locked funds
|
|
||||||
repeated Account LockAccounts = 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
// LockTarget must be one of two options
|
|
||||||
message LockTarget {
|
|
||||||
oneof Target {
|
|
||||||
// WithdrawTarget used when user requested withdraw
|
|
||||||
WithdrawTarget WithdrawTarget = 1;
|
|
||||||
// ContainerCreateTarget used when user requested creation of container
|
|
||||||
ContainerCreateTarget ContainerCreateTarget = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message Balances {
|
|
||||||
// Accounts contains multiple account snapshots
|
|
||||||
repeated Account Accounts = 1 [(gogoproto.nullable) = false];
|
|
||||||
}
|
|
||||||
|
|
||||||
message PayIO {
|
|
||||||
// BlockID contains id of the NEO block where withdraw or deposit
|
|
||||||
// call was invoked
|
|
||||||
uint64 BlockID = 1;
|
|
||||||
// Transactions contains all transactions that founded in block
|
|
||||||
// and used for PayIO
|
|
||||||
repeated Tx Transactions = 2 [(gogoproto.nullable) = false];
|
|
||||||
}
|
|
||||||
|
|
||||||
message Lifetime {
|
|
||||||
// Unit can be Unlimited, based on NeoFS epoch or Neo block
|
|
||||||
enum Unit {
|
|
||||||
Unlimited = 0;
|
|
||||||
NeoFSEpoch = 1;
|
|
||||||
NeoBlock = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unit describes how lifetime is measured in account
|
|
||||||
Unit unit = 1 [(gogoproto.customname) = "Unit"];
|
|
||||||
// Value describes how long lifetime will be valid
|
|
||||||
int64 Value = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Tx {
|
|
||||||
// Type can be withdrawal, payIO or inner
|
|
||||||
enum Type {
|
|
||||||
Unknown = 0;
|
|
||||||
Withdraw = 1;
|
|
||||||
PayIO = 2;
|
|
||||||
Inner = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type describes target of transaction
|
|
||||||
Type type = 1 [(gogoproto.customname) = "Type"];
|
|
||||||
// From describes sender of funds
|
|
||||||
string From = 2;
|
|
||||||
// To describes receiver of funds
|
|
||||||
string To = 3;
|
|
||||||
// Amount describes amount of funds
|
|
||||||
decimal.Decimal Amount = 4;
|
|
||||||
// PublicKeys contains public key of sender
|
|
||||||
bytes PublicKeys = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Settlement {
|
|
||||||
message Receiver {
|
|
||||||
// To is the address of funds recipient
|
|
||||||
string To = 1;
|
|
||||||
// Amount is the amount of funds that will be sent
|
|
||||||
decimal.Decimal Amount = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Container {
|
|
||||||
// CID is container identifier
|
|
||||||
bytes CID = 1 [(gogoproto.customtype) = "CID", (gogoproto.nullable) = false];
|
|
||||||
// SGIDs is a set of storage groups that successfully passed the audit
|
|
||||||
repeated bytes SGIDs = 2 [(gogoproto.customtype) = "SGID", (gogoproto.nullable) = false];
|
|
||||||
}
|
|
||||||
|
|
||||||
message Tx {
|
|
||||||
// From is the address of the sender of funds
|
|
||||||
string From = 1;
|
|
||||||
// Container that successfully had passed the audit
|
|
||||||
Container Container = 2 [(gogoproto.nullable) = false];
|
|
||||||
// Receivers is a set of addresses of funds recipients
|
|
||||||
repeated Receiver Receivers = 3 [(gogoproto.nullable) = false];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Epoch contains an epoch when settlement was accepted
|
|
||||||
uint64 Epoch = 1;
|
|
||||||
// Transactions is a set of transactions
|
|
||||||
repeated Tx Transactions = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ContainerCreateTarget {
|
|
||||||
// CID is container identifier
|
|
||||||
bytes CID = 1 [(gogoproto.customtype) = "CID", (gogoproto.nullable) = false];
|
|
||||||
}
|
|
||||||
|
|
||||||
message WithdrawTarget {
|
|
||||||
// Cheque is a string representation of cheque id
|
|
||||||
string Cheque = 1;
|
|
||||||
}
|
|
|
@ -1,193 +0,0 @@
|
||||||
package accounting
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/mr-tron/base58"
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/chain"
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/decimal"
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/refs"
|
|
||||||
"github.com/nspcc-dev/neofs-crypto/test"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCheque(t *testing.T) {
|
|
||||||
t.Run("new/valid", func(t *testing.T) {
|
|
||||||
id, err := NewChequeID()
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.True(t, id.Valid())
|
|
||||||
|
|
||||||
d := make([]byte, chain.AddressLength+1)
|
|
||||||
|
|
||||||
// expected size + 1 byte
|
|
||||||
str := base58.Encode(d)
|
|
||||||
require.False(t, ChequeID(str).Valid())
|
|
||||||
|
|
||||||
// expected size - 1 byte
|
|
||||||
str = base58.Encode(d[:len(d)-2])
|
|
||||||
require.False(t, ChequeID(str).Valid())
|
|
||||||
|
|
||||||
// wrong encoding
|
|
||||||
d = d[:len(d)-1] // normal size
|
|
||||||
require.False(t, ChequeID(string(d)).Valid())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("marshal/unmarshal", func(t *testing.T) {
|
|
||||||
b2 := new(Cheque)
|
|
||||||
|
|
||||||
key1 := test.DecodeKey(0)
|
|
||||||
key2 := test.DecodeKey(1)
|
|
||||||
|
|
||||||
id, err := NewChequeID()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
owner, err := refs.NewOwnerID(&key1.PublicKey)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
b1 := &Cheque{
|
|
||||||
ID: id,
|
|
||||||
Owner: owner,
|
|
||||||
Height: 100,
|
|
||||||
Amount: decimal.NewGAS(100),
|
|
||||||
}
|
|
||||||
|
|
||||||
require.NoError(t, b1.Sign(key1))
|
|
||||||
require.NoError(t, b1.Sign(key2))
|
|
||||||
|
|
||||||
data, err := b1.MarshalBinary()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Len(t, data, b1.Size())
|
|
||||||
require.NoError(t, b2.UnmarshalBinary(data))
|
|
||||||
require.Equal(t, b1, b2)
|
|
||||||
|
|
||||||
require.NoError(t, b1.Verify())
|
|
||||||
require.NoError(t, b2.Verify())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("example from SC", func(t *testing.T) {
|
|
||||||
pathToCheque := "fixtures/cheque_data"
|
|
||||||
expect, err := ioutil.ReadFile(pathToCheque)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
var cheque Cheque
|
|
||||||
require.NoError(t, cheque.UnmarshalBinary(expect))
|
|
||||||
|
|
||||||
actual, err := cheque.MarshalBinary()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, expect, actual)
|
|
||||||
|
|
||||||
require.NoError(t, cheque.Verify())
|
|
||||||
require.Equal(t, cheque.Height, uint64(7777))
|
|
||||||
require.Equal(t, cheque.Amount, decimal.NewGAS(42))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBalanceRequest_SetOwnerID(t *testing.T) {
|
|
||||||
ownerID := OwnerID{1, 2, 3}
|
|
||||||
m := new(BalanceRequest)
|
|
||||||
|
|
||||||
m.SetOwnerID(ownerID)
|
|
||||||
|
|
||||||
require.Equal(t, ownerID, m.GetOwnerID())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetRequestGettersSetters(t *testing.T) {
|
|
||||||
t.Run("id", func(t *testing.T) {
|
|
||||||
id := ChequeID("test id")
|
|
||||||
m := new(GetRequest)
|
|
||||||
|
|
||||||
m.SetID(id)
|
|
||||||
|
|
||||||
require.Equal(t, id, m.GetID())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("owner", func(t *testing.T) {
|
|
||||||
id := OwnerID{1, 2, 3}
|
|
||||||
m := new(GetRequest)
|
|
||||||
|
|
||||||
m.SetOwnerID(id)
|
|
||||||
|
|
||||||
require.Equal(t, id, m.GetOwnerID())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPutRequestGettersSetters(t *testing.T) {
|
|
||||||
t.Run("owner", func(t *testing.T) {
|
|
||||||
id := OwnerID{1, 2, 3}
|
|
||||||
m := new(PutRequest)
|
|
||||||
|
|
||||||
m.SetOwnerID(id)
|
|
||||||
|
|
||||||
require.Equal(t, id, m.GetOwnerID())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("message ID", func(t *testing.T) {
|
|
||||||
id, err := refs.NewUUID()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
m := new(PutRequest)
|
|
||||||
m.SetMessageID(id)
|
|
||||||
|
|
||||||
require.Equal(t, id, m.GetMessageID())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("amount", func(t *testing.T) {
|
|
||||||
amount := decimal.New(1)
|
|
||||||
m := new(PutRequest)
|
|
||||||
|
|
||||||
m.SetAmount(amount)
|
|
||||||
|
|
||||||
require.Equal(t, amount, m.GetAmount())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("height", func(t *testing.T) {
|
|
||||||
h := uint64(3)
|
|
||||||
m := new(PutRequest)
|
|
||||||
|
|
||||||
m.SetHeight(h)
|
|
||||||
|
|
||||||
require.Equal(t, h, m.GetHeight())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestListRequestGettersSetters(t *testing.T) {
|
|
||||||
ownerID := OwnerID{1, 2, 3}
|
|
||||||
m := new(ListRequest)
|
|
||||||
|
|
||||||
m.SetOwnerID(ownerID)
|
|
||||||
|
|
||||||
require.Equal(t, ownerID, m.GetOwnerID())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDeleteRequestGettersSetters(t *testing.T) {
|
|
||||||
t.Run("id", func(t *testing.T) {
|
|
||||||
id := ChequeID("test id")
|
|
||||||
m := new(DeleteRequest)
|
|
||||||
|
|
||||||
m.SetID(id)
|
|
||||||
|
|
||||||
require.Equal(t, id, m.GetID())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("owner", func(t *testing.T) {
|
|
||||||
id := OwnerID{1, 2, 3}
|
|
||||||
m := new(DeleteRequest)
|
|
||||||
|
|
||||||
m.SetOwnerID(id)
|
|
||||||
|
|
||||||
require.Equal(t, id, m.GetOwnerID())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("message ID", func(t *testing.T) {
|
|
||||||
id, err := refs.NewUUID()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
m := new(DeleteRequest)
|
|
||||||
m.SetMessageID(id)
|
|
||||||
|
|
||||||
require.Equal(t, id, m.GetMessageID())
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
package accounting
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/refs"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
// MessageID type alias.
|
|
||||||
MessageID = refs.MessageID
|
|
||||||
)
|
|
||||||
|
|
||||||
// PrepareData prepares bytes representation of PutRequest to satisfy SignedRequest interface.
|
|
||||||
func (m *PutRequest) PrepareData() ([]byte, error) {
|
|
||||||
var offset int
|
|
||||||
// MessageID-len + OwnerID-len + Amount + Height
|
|
||||||
buf := make([]byte, refs.UUIDSize+refs.OwnerIDSize+binary.MaxVarintLen64+binary.MaxVarintLen64)
|
|
||||||
offset += copy(buf[offset:], m.MessageID.Bytes())
|
|
||||||
offset += copy(buf[offset:], m.OwnerID.Bytes())
|
|
||||||
offset += binary.PutVarint(buf[offset:], m.Amount.Value)
|
|
||||||
binary.PutUvarint(buf[offset:], m.Height)
|
|
||||||
return buf, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrepareData prepares bytes representation of DeleteRequest to satisfy SignedRequest interface.
|
|
||||||
func (m *DeleteRequest) PrepareData() ([]byte, error) {
|
|
||||||
var offset int
|
|
||||||
// ID-len + OwnerID-len + MessageID-len
|
|
||||||
buf := make([]byte, refs.UUIDSize+refs.OwnerIDSize+refs.UUIDSize)
|
|
||||||
offset += copy(buf[offset:], m.ID.Bytes())
|
|
||||||
offset += copy(buf[offset:], m.OwnerID.Bytes())
|
|
||||||
copy(buf[offset:], m.MessageID.Bytes())
|
|
||||||
return buf, nil
|
|
||||||
}
|
|
Binary file not shown.
|
@ -1,101 +0,0 @@
|
||||||
syntax = "proto3";
|
|
||||||
package accounting;
|
|
||||||
option go_package = "github.com/nspcc-dev/neofs-api-go/accounting";
|
|
||||||
option csharp_namespace = "NeoFS.API.Accounting";
|
|
||||||
|
|
||||||
import "service/meta.proto";
|
|
||||||
import "service/verify.proto";
|
|
||||||
import "decimal/decimal.proto";
|
|
||||||
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
|
|
||||||
|
|
||||||
option (gogoproto.stable_marshaler_all) = true;
|
|
||||||
|
|
||||||
// Withdraw is a service that provides withdraw assets operations from the NeoFS
|
|
||||||
service Withdraw {
|
|
||||||
// Get returns cheque if it was signed by inner ring nodes
|
|
||||||
rpc Get(GetRequest) returns (GetResponse);
|
|
||||||
// Put ask inner ring nodes to sign a cheque for withdraw invoke
|
|
||||||
rpc Put(PutRequest) returns (PutResponse);
|
|
||||||
// List shows all user's checks
|
|
||||||
rpc List(ListRequest) returns (ListResponse);
|
|
||||||
// Delete allows user to remove unused cheque
|
|
||||||
rpc Delete(DeleteRequest) returns (DeleteResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
message Item {
|
|
||||||
// ID is a cheque identifier
|
|
||||||
bytes ID = 1 [(gogoproto.customtype) = "ChequeID", (gogoproto.nullable) = false];
|
|
||||||
// OwnerID is a wallet address
|
|
||||||
bytes OwnerID = 2 [(gogoproto.customtype) = "OwnerID", (gogoproto.nullable) = false];
|
|
||||||
// Amount of funds
|
|
||||||
decimal.Decimal Amount = 3;
|
|
||||||
// Height is the neo blockchain height until the cheque is valid
|
|
||||||
uint64 Height = 4;
|
|
||||||
// Payload contains cheque representation in bytes
|
|
||||||
bytes Payload = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
message GetRequest {
|
|
||||||
// ID is cheque identifier
|
|
||||||
bytes ID = 1 [(gogoproto.customtype) = "ChequeID", (gogoproto.nullable) = false];
|
|
||||||
// OwnerID is a wallet address
|
|
||||||
bytes OwnerID = 2 [(gogoproto.customtype) = "OwnerID", (gogoproto.nullable) = false];
|
|
||||||
// RequestMetaHeader contains information about request meta headers (should be embedded into message)
|
|
||||||
service.RequestMetaHeader Meta = 98 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
// RequestVerificationHeader is a set of signatures of every NeoFS Node that processed request (should be embedded into message)
|
|
||||||
service.RequestVerificationHeader Verify = 99 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
}
|
|
||||||
|
|
||||||
message GetResponse {
|
|
||||||
// Withdraw is cheque with meta information
|
|
||||||
Item Withdraw = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message PutRequest {
|
|
||||||
// OwnerID is a wallet address
|
|
||||||
bytes OwnerID = 1 [(gogoproto.customtype) = "OwnerID", (gogoproto.nullable) = false];
|
|
||||||
// Amount of funds
|
|
||||||
decimal.Decimal Amount = 2;
|
|
||||||
// Height is the neo blockchain height until the cheque is valid
|
|
||||||
uint64 Height = 3;
|
|
||||||
// MessageID is a nonce for uniq request (UUIDv4)
|
|
||||||
bytes MessageID = 4 [(gogoproto.customtype) = "MessageID", (gogoproto.nullable) = false];
|
|
||||||
// RequestMetaHeader contains information about request meta headers (should be embedded into message)
|
|
||||||
service.RequestMetaHeader Meta = 98 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
// RequestVerificationHeader is a set of signatures of every NeoFS Node that processed request (should be embedded into message)
|
|
||||||
service.RequestVerificationHeader Verify = 99 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
}
|
|
||||||
message PutResponse {
|
|
||||||
// ID is cheque identifier
|
|
||||||
bytes ID = 1 [(gogoproto.customtype) = "ChequeID", (gogoproto.nullable) = false];
|
|
||||||
}
|
|
||||||
|
|
||||||
message ListRequest {
|
|
||||||
// OwnerID is a wallet address
|
|
||||||
bytes OwnerID = 1 [(gogoproto.customtype) = "OwnerID", (gogoproto.nullable) = false];
|
|
||||||
// RequestMetaHeader contains information about request meta headers (should be embedded into message)
|
|
||||||
service.RequestMetaHeader Meta = 98 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
// RequestVerificationHeader is a set of signatures of every NeoFS Node that processed request (should be embedded into message)
|
|
||||||
service.RequestVerificationHeader Verify = 99 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
}
|
|
||||||
|
|
||||||
message ListResponse {
|
|
||||||
// Items is a set of cheques with meta information
|
|
||||||
repeated Item Items = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message DeleteRequest {
|
|
||||||
// ID is cheque identifier
|
|
||||||
bytes ID = 1 [(gogoproto.customtype) = "ChequeID", (gogoproto.nullable) = false];
|
|
||||||
// OwnerID is a wallet address
|
|
||||||
bytes OwnerID = 2 [(gogoproto.customtype) = "OwnerID", (gogoproto.nullable) = false];
|
|
||||||
// MessageID is a nonce for uniq request (UUIDv4)
|
|
||||||
bytes MessageID = 3 [(gogoproto.customtype) = "MessageID", (gogoproto.nullable) = false];
|
|
||||||
// RequestMetaHeader contains information about request meta headers (should be embedded into message)
|
|
||||||
service.RequestMetaHeader Meta = 98 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
// RequestVerificationHeader is a set of signatures of every NeoFS Node that processed request (should be embedded into message)
|
|
||||||
service.RequestVerificationHeader Verify = 99 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteResponse is empty
|
|
||||||
message DeleteResponse {}
|
|
|
@ -1,126 +0,0 @@
|
||||||
package eacl
|
|
||||||
|
|
||||||
const (
|
|
||||||
// MatchUnknown is a MatchType value used to mark value as undefined.
|
|
||||||
// Most of the tools consider MatchUnknown as incalculable.
|
|
||||||
// Using MatchUnknown in HeaderFilter is unsafe.
|
|
||||||
MatchUnknown MatchType = iota
|
|
||||||
|
|
||||||
// StringEqual is a MatchType of string equality.
|
|
||||||
StringEqual
|
|
||||||
|
|
||||||
// StringNotEqual is a MatchType of string inequality.
|
|
||||||
StringNotEqual
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// ActionUnknown is Action used to mark value as undefined.
|
|
||||||
// Most of the tools consider ActionUnknown as incalculable.
|
|
||||||
// Using ActionUnknown in Record is unsafe.
|
|
||||||
ActionUnknown Action = iota
|
|
||||||
|
|
||||||
// ActionAllow is Action used to mark an applicability of ACL rule.
|
|
||||||
ActionAllow
|
|
||||||
|
|
||||||
// ActionDeny is Action used to mark an inapplicability of ACL rule.
|
|
||||||
ActionDeny
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// GroupUnknown is a Group value used to mark value as undefined.
|
|
||||||
// Most of the tools consider GroupUnknown as incalculable.
|
|
||||||
// Using GroupUnknown in Target is unsafe.
|
|
||||||
GroupUnknown Group = iota
|
|
||||||
|
|
||||||
// GroupUser is a Group value for User access group.
|
|
||||||
GroupUser
|
|
||||||
|
|
||||||
// GroupSystem is a Group value for System access group.
|
|
||||||
GroupSystem
|
|
||||||
|
|
||||||
// GroupOthers is a Group value for Others access group.
|
|
||||||
GroupOthers
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// HdrTypeUnknown is a HeaderType value used to mark value as undefined.
|
|
||||||
// Most of the tools consider HdrTypeUnknown as incalculable.
|
|
||||||
// Using HdrTypeUnknown in HeaderFilter is unsafe.
|
|
||||||
HdrTypeUnknown HeaderType = iota
|
|
||||||
|
|
||||||
// HdrTypeRequest is a HeaderType for request header.
|
|
||||||
HdrTypeRequest
|
|
||||||
|
|
||||||
// HdrTypeObjSys is a HeaderType for system headers of object.
|
|
||||||
HdrTypeObjSys
|
|
||||||
|
|
||||||
// HdrTypeObjUsr is a HeaderType for user headers of object.
|
|
||||||
HdrTypeObjUsr
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// OpTypeUnknown is a OperationType value used to mark value as undefined.
|
|
||||||
// Most of the tools consider OpTypeUnknown as incalculable.
|
|
||||||
// Using OpTypeUnknown in Record is unsafe.
|
|
||||||
OpTypeUnknown OperationType = iota
|
|
||||||
|
|
||||||
// OpTypeGet is an OperationType for object.Get RPC
|
|
||||||
OpTypeGet
|
|
||||||
|
|
||||||
// OpTypePut is an OperationType for object.Put RPC
|
|
||||||
OpTypePut
|
|
||||||
|
|
||||||
// OpTypeHead is an OperationType for object.Head RPC
|
|
||||||
OpTypeHead
|
|
||||||
|
|
||||||
// OpTypeSearch is an OperationType for object.Search RPC
|
|
||||||
OpTypeSearch
|
|
||||||
|
|
||||||
// OpTypeDelete is an OperationType for object.Delete RPC
|
|
||||||
OpTypeDelete
|
|
||||||
|
|
||||||
// OpTypeRange is an OperationType for object.GetRange RPC
|
|
||||||
OpTypeRange
|
|
||||||
|
|
||||||
// OpTypeRangeHash is an OperationType for object.GetRangeHash RPC
|
|
||||||
OpTypeRangeHash
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// HdrObjSysNameID is a name of ID field in system header of object.
|
|
||||||
HdrObjSysNameID = "ID"
|
|
||||||
|
|
||||||
// HdrObjSysNameCID is a name of CID field in system header of object.
|
|
||||||
HdrObjSysNameCID = "CID"
|
|
||||||
|
|
||||||
// HdrObjSysNameOwnerID is a name of OwnerID field in system header of object.
|
|
||||||
HdrObjSysNameOwnerID = "OWNER_ID"
|
|
||||||
|
|
||||||
// HdrObjSysNameVersion is a name of Version field in system header of object.
|
|
||||||
HdrObjSysNameVersion = "VERSION"
|
|
||||||
|
|
||||||
// HdrObjSysNamePayloadLength is a name of PayloadLength field in system header of object.
|
|
||||||
HdrObjSysNamePayloadLength = "PAYLOAD_LENGTH"
|
|
||||||
|
|
||||||
// HdrObjSysNameCreatedUnix is a name of CreatedAt.UnitTime field in system header of object.
|
|
||||||
HdrObjSysNameCreatedUnix = "CREATED_UNIX"
|
|
||||||
|
|
||||||
// HdrObjSysNameCreatedEpoch is a name of CreatedAt.Epoch field in system header of object.
|
|
||||||
HdrObjSysNameCreatedEpoch = "CREATED_EPOCH"
|
|
||||||
|
|
||||||
// HdrObjSysLinkPrev is a name of previous link header in extended headers of object.
|
|
||||||
HdrObjSysLinkPrev = "LINK_PREV"
|
|
||||||
|
|
||||||
// HdrObjSysLinkNext is a name of next link header in extended headers of object.
|
|
||||||
HdrObjSysLinkNext = "LINK_NEXT"
|
|
||||||
|
|
||||||
// HdrObjSysLinkChild is a name of child link header in extended headers of object.
|
|
||||||
HdrObjSysLinkChild = "LINK_CHILD"
|
|
||||||
|
|
||||||
// HdrObjSysLinkPar is a name of parent link header in extended headers of object.
|
|
||||||
HdrObjSysLinkPar = "LINK_PAR"
|
|
||||||
|
|
||||||
// HdrObjSysLinkSG is a name of storage group link header in extended headers of object.
|
|
||||||
HdrObjSysLinkSG = "LINK_SG"
|
|
||||||
)
|
|
||||||
|
|
|
@ -1,333 +0,0 @@
|
||||||
package eacl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
sliceLenSize = 2 // uint16 for len()
|
|
||||||
actionSize = 4 // uint32
|
|
||||||
opTypeSize = 4 // uint32
|
|
||||||
hdrTypeSize = 4 // uint32
|
|
||||||
matchTypeSize = 4 // uint32
|
|
||||||
targetSize = 4 // uint32
|
|
||||||
)
|
|
||||||
|
|
||||||
// MarshalTable encodes Table into a
|
|
||||||
// binary form and returns the result.
|
|
||||||
//
|
|
||||||
// If table is nil, empty slice is returned.
|
|
||||||
func MarshalTable(table Table) []byte {
|
|
||||||
if table == nil {
|
|
||||||
return make([]byte, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// allocate buffer
|
|
||||||
buf := make([]byte, tableBinSize(table))
|
|
||||||
|
|
||||||
records := table.Records()
|
|
||||||
|
|
||||||
// write record number
|
|
||||||
binary.BigEndian.PutUint16(buf, uint16(len(records)))
|
|
||||||
off := sliceLenSize
|
|
||||||
|
|
||||||
// write all records
|
|
||||||
for _, record := range records {
|
|
||||||
// write action
|
|
||||||
binary.BigEndian.PutUint32(buf[off:], uint32(record.Action()))
|
|
||||||
off += actionSize
|
|
||||||
|
|
||||||
// write operation type
|
|
||||||
binary.BigEndian.PutUint32(buf[off:], uint32(record.OperationType()))
|
|
||||||
off += actionSize
|
|
||||||
|
|
||||||
filters := record.HeaderFilters()
|
|
||||||
|
|
||||||
// write filter number
|
|
||||||
binary.BigEndian.PutUint16(buf[off:], uint16(len(filters)))
|
|
||||||
off += sliceLenSize
|
|
||||||
|
|
||||||
// write all filters
|
|
||||||
for _, filter := range filters {
|
|
||||||
// write header type
|
|
||||||
binary.BigEndian.PutUint32(buf[off:], uint32(filter.HeaderType()))
|
|
||||||
off += hdrTypeSize
|
|
||||||
|
|
||||||
// write match type
|
|
||||||
binary.BigEndian.PutUint32(buf[off:], uint32(filter.MatchType()))
|
|
||||||
off += matchTypeSize
|
|
||||||
|
|
||||||
// write header name size
|
|
||||||
name := []byte(filter.Name())
|
|
||||||
binary.BigEndian.PutUint16(buf[off:], uint16(len(name)))
|
|
||||||
off += sliceLenSize
|
|
||||||
|
|
||||||
// write header name bytes
|
|
||||||
off += copy(buf[off:], name)
|
|
||||||
|
|
||||||
// write header value size
|
|
||||||
val := []byte(filter.Value())
|
|
||||||
binary.BigEndian.PutUint16(buf[off:], uint16(len(val)))
|
|
||||||
off += sliceLenSize
|
|
||||||
|
|
||||||
// write header value bytes
|
|
||||||
off += copy(buf[off:], val)
|
|
||||||
}
|
|
||||||
|
|
||||||
targets := record.TargetList()
|
|
||||||
|
|
||||||
// write target number
|
|
||||||
binary.BigEndian.PutUint16(buf[off:], uint16(len(targets)))
|
|
||||||
off += sliceLenSize
|
|
||||||
|
|
||||||
// write all targets
|
|
||||||
for _, target := range targets {
|
|
||||||
// write target group
|
|
||||||
binary.BigEndian.PutUint32(buf[off:], uint32(target.Group()))
|
|
||||||
off += targetSize
|
|
||||||
|
|
||||||
keys := target.KeyList()
|
|
||||||
|
|
||||||
// write key number
|
|
||||||
binary.BigEndian.PutUint16(buf[off:], uint16(len(keys)))
|
|
||||||
off += sliceLenSize
|
|
||||||
|
|
||||||
// write keys
|
|
||||||
for i := range keys {
|
|
||||||
// write key size
|
|
||||||
binary.BigEndian.PutUint16(buf[off:], uint16(len(keys[i])))
|
|
||||||
off += sliceLenSize
|
|
||||||
|
|
||||||
// write key bytes
|
|
||||||
off += copy(buf[off:], keys[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns the size of Table in a binary format.
|
|
||||||
func tableBinSize(table Table) (sz int) {
|
|
||||||
sz = sliceLenSize // number of records
|
|
||||||
|
|
||||||
records := table.Records()
|
|
||||||
ln := len(records)
|
|
||||||
|
|
||||||
sz += ln * actionSize // action type of each record
|
|
||||||
sz += ln * opTypeSize // operation type of each record
|
|
||||||
|
|
||||||
for _, record := range records {
|
|
||||||
sz += sliceLenSize // number of filters
|
|
||||||
|
|
||||||
filters := record.HeaderFilters()
|
|
||||||
ln := len(filters)
|
|
||||||
|
|
||||||
sz += ln * hdrTypeSize // header type of each filter
|
|
||||||
sz += ln * matchTypeSize // match type of each filter
|
|
||||||
|
|
||||||
for _, filter := range filters {
|
|
||||||
sz += sliceLenSize // header name size
|
|
||||||
sz += len(filter.Name()) // header name bytes
|
|
||||||
|
|
||||||
sz += sliceLenSize // header value size
|
|
||||||
sz += len(filter.Value()) // header value bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
sz += sliceLenSize // number of targets
|
|
||||||
|
|
||||||
targets := record.TargetList()
|
|
||||||
ln = len(targets)
|
|
||||||
|
|
||||||
sz += ln * targetSize // target group of each target
|
|
||||||
|
|
||||||
for _, target := range targets {
|
|
||||||
sz += sliceLenSize // number of keys
|
|
||||||
|
|
||||||
for _, key := range target.KeyList() {
|
|
||||||
sz += sliceLenSize // key size
|
|
||||||
sz += len(key) // key bytes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalTable unmarshals Table from
|
|
||||||
// a binary representation.
|
|
||||||
//
|
|
||||||
// If data is empty, table w/o records is returned.
|
|
||||||
func UnmarshalTable(data []byte) (Table, error) {
|
|
||||||
table := WrapTable(nil)
|
|
||||||
|
|
||||||
if len(data) == 0 {
|
|
||||||
return table, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// decode record number
|
|
||||||
if len(data) < sliceLenSize {
|
|
||||||
return nil, errors.New("could not decode record number")
|
|
||||||
}
|
|
||||||
|
|
||||||
recordNum := binary.BigEndian.Uint16(data)
|
|
||||||
records := make([]Record, 0, recordNum)
|
|
||||||
|
|
||||||
off := sliceLenSize
|
|
||||||
|
|
||||||
// decode all records one by one
|
|
||||||
for i := uint16(0); i < recordNum; i++ {
|
|
||||||
record := WrapRecord(nil)
|
|
||||||
|
|
||||||
// decode action
|
|
||||||
if len(data[off:]) < actionSize {
|
|
||||||
return nil, errors.Errorf("could not decode action of record #%d", i)
|
|
||||||
}
|
|
||||||
|
|
||||||
record.SetAction(Action(binary.BigEndian.Uint32(data[off:])))
|
|
||||||
off += actionSize
|
|
||||||
|
|
||||||
// decode operation type
|
|
||||||
if len(data[off:]) < opTypeSize {
|
|
||||||
return nil, errors.Errorf("could not decode operation type of record #%d", i)
|
|
||||||
}
|
|
||||||
|
|
||||||
record.SetOperationType(OperationType(binary.BigEndian.Uint32(data[off:])))
|
|
||||||
off += opTypeSize
|
|
||||||
|
|
||||||
// decode filter number
|
|
||||||
if len(data[off:]) < sliceLenSize {
|
|
||||||
return nil, errors.Errorf("could not decode filter number of record #%d", i)
|
|
||||||
}
|
|
||||||
|
|
||||||
filterNum := binary.BigEndian.Uint16(data[off:])
|
|
||||||
off += sliceLenSize
|
|
||||||
filters := make([]HeaderFilter, 0, filterNum)
|
|
||||||
|
|
||||||
// decode filters one by one
|
|
||||||
for j := uint16(0); j < filterNum; j++ {
|
|
||||||
filter := WrapFilterInfo(nil)
|
|
||||||
|
|
||||||
// decode header type
|
|
||||||
if len(data[off:]) < hdrTypeSize {
|
|
||||||
return nil, errors.Errorf("could not decode header type of filter #%d of record #%d", j, i)
|
|
||||||
}
|
|
||||||
|
|
||||||
filter.SetHeaderType(HeaderType(binary.BigEndian.Uint32(data[off:])) )
|
|
||||||
off += hdrTypeSize
|
|
||||||
|
|
||||||
// decode match type
|
|
||||||
if len(data[off:]) < matchTypeSize {
|
|
||||||
return nil, errors.Errorf("could not decode match type of filter #%d of record #%d", j, i)
|
|
||||||
}
|
|
||||||
|
|
||||||
filter.SetMatchType(MatchType(binary.BigEndian.Uint32(data[off:])) )
|
|
||||||
off += matchTypeSize
|
|
||||||
|
|
||||||
// decode header name size
|
|
||||||
if len(data[off:]) < sliceLenSize {
|
|
||||||
return nil, errors.Errorf("could not decode header name size of filter #%d of record #%d", j, i)
|
|
||||||
}
|
|
||||||
|
|
||||||
hdrNameSize := int(binary.BigEndian.Uint16(data[off:]))
|
|
||||||
off += sliceLenSize
|
|
||||||
|
|
||||||
// decode header name
|
|
||||||
if len(data[off:]) < hdrNameSize {
|
|
||||||
return nil, errors.Errorf("could not decode header name of filter #%d of record #%d", j, i)
|
|
||||||
}
|
|
||||||
|
|
||||||
filter.SetName(string(data[off : off+hdrNameSize]))
|
|
||||||
|
|
||||||
off += hdrNameSize
|
|
||||||
|
|
||||||
// decode header value size
|
|
||||||
if len(data[off:]) < sliceLenSize {
|
|
||||||
return nil, errors.Errorf("could not decode header value size of filter #%d of record #%d", j, i)
|
|
||||||
}
|
|
||||||
|
|
||||||
hdrValSize := int(binary.BigEndian.Uint16(data[off:]))
|
|
||||||
off += sliceLenSize
|
|
||||||
|
|
||||||
// decode header value
|
|
||||||
if len(data[off:]) < hdrValSize {
|
|
||||||
return nil, errors.Errorf("could not decode header value of filter #%d of record #%d", j, i)
|
|
||||||
}
|
|
||||||
|
|
||||||
filter.SetValue(string(data[off : off+hdrValSize]))
|
|
||||||
|
|
||||||
off += hdrValSize
|
|
||||||
|
|
||||||
filters = append(filters, filter)
|
|
||||||
}
|
|
||||||
|
|
||||||
record.SetHeaderFilters(filters)
|
|
||||||
|
|
||||||
// decode target number
|
|
||||||
if len(data[off:]) < sliceLenSize {
|
|
||||||
return nil, errors.Errorf("could not decode target number of record #%d", i)
|
|
||||||
}
|
|
||||||
|
|
||||||
targetNum := int(binary.BigEndian.Uint16(data[off:]))
|
|
||||||
off += sliceLenSize
|
|
||||||
|
|
||||||
targets := make([]Target, 0, targetNum)
|
|
||||||
|
|
||||||
// decode targets one by one
|
|
||||||
for j := 0; j < targetNum; j++ {
|
|
||||||
target := WrapTarget(nil)
|
|
||||||
|
|
||||||
// decode target group
|
|
||||||
if len(data[off:]) < targetSize {
|
|
||||||
return nil, errors.Errorf("could not decode target group of target #%d of record #%d", j, i)
|
|
||||||
}
|
|
||||||
|
|
||||||
target.SetGroup( Group(binary.BigEndian.Uint32(data[off:])), )
|
|
||||||
off += targetSize
|
|
||||||
|
|
||||||
// decode key number
|
|
||||||
if len(data[off:]) < sliceLenSize {
|
|
||||||
return nil, errors.Errorf("could not decode key number of target #%d of record #%d", j, i)
|
|
||||||
}
|
|
||||||
|
|
||||||
keyNum := int(binary.BigEndian.Uint16(data[off:]))
|
|
||||||
off += sliceLenSize
|
|
||||||
keys := make([][]byte, 0, keyNum)
|
|
||||||
|
|
||||||
for k := 0; k < keyNum; k++ {
|
|
||||||
// decode key size
|
|
||||||
if len(data[off:]) < sliceLenSize {
|
|
||||||
return nil, errors.Errorf("could not decode size of key #%d target #%d of record #%d", k, j, i)
|
|
||||||
}
|
|
||||||
|
|
||||||
keySz := int(binary.BigEndian.Uint16(data[off:]))
|
|
||||||
off += sliceLenSize
|
|
||||||
|
|
||||||
// decode key
|
|
||||||
if len(data[off:]) < keySz {
|
|
||||||
return nil, errors.Errorf("could not decode key #%d target #%d of record #%d", k, j, i)
|
|
||||||
}
|
|
||||||
|
|
||||||
key := make([]byte, keySz)
|
|
||||||
|
|
||||||
off += copy(key, data[off:off+keySz])
|
|
||||||
|
|
||||||
keys = append(keys, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
target.SetKeyList(keys)
|
|
||||||
|
|
||||||
targets = append(targets, target)
|
|
||||||
}
|
|
||||||
|
|
||||||
record.SetTargetList(targets)
|
|
||||||
|
|
||||||
records = append(records, record)
|
|
||||||
}
|
|
||||||
|
|
||||||
table.SetRecords(records)
|
|
||||||
|
|
||||||
return table, nil
|
|
||||||
}
|
|
|
@ -1,73 +0,0 @@
|
||||||
package eacl
|
|
||||||
|
|
||||||
// OperationType is an enumeration of operation types for extended ACL.
|
|
||||||
type OperationType uint32
|
|
||||||
|
|
||||||
// HeaderType is an enumeration of header types for extended ACL.
|
|
||||||
type HeaderType uint32
|
|
||||||
|
|
||||||
// MatchType is an enumeration of match types for extended ACL.
|
|
||||||
type MatchType uint32
|
|
||||||
|
|
||||||
// Action is an enumeration of extended ACL actions.
|
|
||||||
type Action uint32
|
|
||||||
|
|
||||||
// Group is an enumeration of access groups.
|
|
||||||
type Group uint32
|
|
||||||
|
|
||||||
// Header is an interface of string key-value pair,
|
|
||||||
type Header interface {
|
|
||||||
// Must return string identifier of header.
|
|
||||||
Name() string
|
|
||||||
|
|
||||||
// Must return string value of header.
|
|
||||||
Value() string
|
|
||||||
}
|
|
||||||
|
|
||||||
// TypedHeader is an interface of Header and HeaderType pair.
|
|
||||||
type TypedHeader interface {
|
|
||||||
Header
|
|
||||||
|
|
||||||
// Must return type of filtered header.
|
|
||||||
HeaderType() HeaderType
|
|
||||||
}
|
|
||||||
|
|
||||||
// HeaderFilter is an interface of grouped information about filtered header.
|
|
||||||
type HeaderFilter interface {
|
|
||||||
// Must return match type of filter.
|
|
||||||
MatchType() MatchType
|
|
||||||
|
|
||||||
TypedHeader
|
|
||||||
}
|
|
||||||
|
|
||||||
// Target is an interface of grouped information about extended ACL rule target.
|
|
||||||
type Target interface {
|
|
||||||
// Must return ACL target type.
|
|
||||||
Group() Group
|
|
||||||
|
|
||||||
// Must return public key list of ACL targets.
|
|
||||||
KeyList() [][]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// Record is an interface of record of extended ACL rule table.
|
|
||||||
type Record interface {
|
|
||||||
// Must return operation type of extended ACL rule.
|
|
||||||
OperationType() OperationType
|
|
||||||
|
|
||||||
// Must return list of header filters of extended ACL rule.
|
|
||||||
HeaderFilters() []HeaderFilter
|
|
||||||
|
|
||||||
// Must return target list of extended ACL rule.
|
|
||||||
TargetList() []Target
|
|
||||||
|
|
||||||
// Must return action of extended ACL rule.
|
|
||||||
Action() Action
|
|
||||||
}
|
|
||||||
|
|
||||||
// Table is an interface of extended ACL table.
|
|
||||||
type Table interface {
|
|
||||||
// Must return list of extended ACL rules.
|
|
||||||
Records() []Record
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -1,528 +0,0 @@
|
||||||
package eacl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/acl"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FilterWrapper is a wrapper over acl.EACLRecord_FilterInfo pointer.
|
|
||||||
type FilterWrapper struct {
|
|
||||||
filter *acl.EACLRecord_FilterInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
// TargetWrapper is a wrapper over acl.EACLRecord_TargetInfo pointer.
|
|
||||||
type TargetWrapper struct {
|
|
||||||
target *acl.EACLRecord_TargetInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
// RecordWrapper is a wrapper over acl.EACLRecord pointer.
|
|
||||||
type RecordWrapper struct {
|
|
||||||
record *acl.EACLRecord
|
|
||||||
}
|
|
||||||
|
|
||||||
// TableWrapper is a wrapper over acl.EACLTable pointer.
|
|
||||||
type TableWrapper struct {
|
|
||||||
table *acl.EACLTable
|
|
||||||
}
|
|
||||||
|
|
||||||
// WrapFilterInfo wraps EACLRecord_FilterInfo pointer.
|
|
||||||
//
|
|
||||||
// If argument is nil, new EACLRecord_FilterInfo is initialized.
|
|
||||||
func WrapFilterInfo(v *acl.EACLRecord_FilterInfo) FilterWrapper {
|
|
||||||
if v == nil {
|
|
||||||
v = new(acl.EACLRecord_FilterInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
return FilterWrapper{
|
|
||||||
filter: v,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WrapTarget wraps EACLRecord_TargetInfo pointer.
|
|
||||||
//
|
|
||||||
// If argument is nil, new EACLRecord_TargetInfo is initialized.
|
|
||||||
func WrapTarget(v *acl.EACLRecord_TargetInfo) TargetWrapper {
|
|
||||||
if v == nil {
|
|
||||||
v = new(acl.EACLRecord_TargetInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
return TargetWrapper{
|
|
||||||
target: v,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WrapRecord wraps EACLRecord pointer.
|
|
||||||
//
|
|
||||||
// If argument is nil, new EACLRecord is initialized.
|
|
||||||
func WrapRecord(v *acl.EACLRecord) RecordWrapper {
|
|
||||||
if v == nil {
|
|
||||||
v = new(acl.EACLRecord)
|
|
||||||
}
|
|
||||||
|
|
||||||
return RecordWrapper{
|
|
||||||
record: v,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WrapTable wraps EACLTable pointer.
|
|
||||||
//
|
|
||||||
// If argument is nil, new EACLTable is initialized.
|
|
||||||
func WrapTable(v *acl.EACLTable) TableWrapper {
|
|
||||||
if v == nil {
|
|
||||||
v = new(acl.EACLTable)
|
|
||||||
}
|
|
||||||
|
|
||||||
return TableWrapper{
|
|
||||||
table: v,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MatchType returns the match type of the filter.
|
|
||||||
//
|
|
||||||
// If filter is not initialized, 0 returns.
|
|
||||||
//
|
|
||||||
// Returns 0 if MatchType is not one of:
|
|
||||||
// - EACLRecord_FilterInfo_StringEqual;
|
|
||||||
// - EACLRecord_FilterInfo_StringNotEqual.
|
|
||||||
func (s FilterWrapper) MatchType() (res MatchType) {
|
|
||||||
if s.filter != nil {
|
|
||||||
switch s.filter.GetMatchType() {
|
|
||||||
case acl.EACLRecord_FilterInfo_StringEqual:
|
|
||||||
res = StringEqual
|
|
||||||
case acl.EACLRecord_FilterInfo_StringNotEqual:
|
|
||||||
res = StringNotEqual
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetMatchType sets the match type of the filter.
|
|
||||||
//
|
|
||||||
// If filter is not initialized, nothing changes.
|
|
||||||
//
|
|
||||||
// MatchType is set to EACLRecord_FilterInfo_MatchUnknown if argument is not one of:
|
|
||||||
// - StringEqual;
|
|
||||||
// - StringNotEqual.
|
|
||||||
func (s FilterWrapper) SetMatchType(v MatchType) {
|
|
||||||
if s.filter != nil {
|
|
||||||
switch v {
|
|
||||||
case StringEqual:
|
|
||||||
s.filter.SetMatchType(acl.EACLRecord_FilterInfo_StringEqual)
|
|
||||||
case StringNotEqual:
|
|
||||||
s.filter.SetMatchType(acl.EACLRecord_FilterInfo_StringNotEqual)
|
|
||||||
default:
|
|
||||||
s.filter.SetMatchType(acl.EACLRecord_FilterInfo_MatchUnknown)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name returns the name of filtering header.
|
|
||||||
//
|
|
||||||
// If filter is not initialized, empty string returns.
|
|
||||||
func (s FilterWrapper) Name() string {
|
|
||||||
if s.filter == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.filter.GetHeaderName()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetName sets the name of the filtering header.
|
|
||||||
//
|
|
||||||
// If filter is not initialized, nothing changes.
|
|
||||||
func (s FilterWrapper) SetName(v string) {
|
|
||||||
if s.filter != nil {
|
|
||||||
s.filter.SetHeaderName(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value returns the value of filtering header.
|
|
||||||
//
|
|
||||||
// If filter is not initialized, empty string returns.
|
|
||||||
func (s FilterWrapper) Value() string {
|
|
||||||
if s.filter == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.filter.GetHeaderVal()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetValue sets the value of filtering header.
|
|
||||||
//
|
|
||||||
// If filter is not initialized, nothing changes.
|
|
||||||
func (s FilterWrapper) SetValue(v string) {
|
|
||||||
if s.filter != nil {
|
|
||||||
s.filter.SetHeaderVal(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HeaderType returns the header type of the filter.
|
|
||||||
//
|
|
||||||
// If filter is not initialized, HdrTypeUnknown returns.
|
|
||||||
//
|
|
||||||
// Returns HdrTypeUnknown if Header is not one of:
|
|
||||||
// - EACLRecord_FilterInfo_Request;
|
|
||||||
// - EACLRecord_FilterInfo_ObjectSystem;
|
|
||||||
// - EACLRecord_FilterInfo_ObjectUser.
|
|
||||||
func (s FilterWrapper) HeaderType() (res HeaderType) {
|
|
||||||
res = HdrTypeUnknown
|
|
||||||
|
|
||||||
if s.filter != nil {
|
|
||||||
switch s.filter.GetHeader() {
|
|
||||||
case acl.EACLRecord_FilterInfo_Request:
|
|
||||||
res = HdrTypeRequest
|
|
||||||
case acl.EACLRecord_FilterInfo_ObjectSystem:
|
|
||||||
res = HdrTypeObjSys
|
|
||||||
case acl.EACLRecord_FilterInfo_ObjectUser:
|
|
||||||
res = HdrTypeObjUsr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetHeaderType sets the header type of the filter.
|
|
||||||
//
|
|
||||||
// If filter is not initialized, nothing changes.
|
|
||||||
//
|
|
||||||
// Header is set to EACLRecord_FilterInfo_HeaderUnknown if argument is not one of:
|
|
||||||
// - HdrTypeRequest;
|
|
||||||
// - HdrTypeObjSys;
|
|
||||||
// - HdrTypeObjUsr.
|
|
||||||
func (s FilterWrapper) SetHeaderType(t HeaderType) {
|
|
||||||
if s.filter != nil {
|
|
||||||
switch t {
|
|
||||||
case HdrTypeRequest:
|
|
||||||
s.filter.SetHeader(acl.EACLRecord_FilterInfo_Request)
|
|
||||||
case HdrTypeObjSys:
|
|
||||||
s.filter.SetHeader(acl.EACLRecord_FilterInfo_ObjectSystem)
|
|
||||||
case HdrTypeObjUsr:
|
|
||||||
s.filter.SetHeader(acl.EACLRecord_FilterInfo_ObjectUser)
|
|
||||||
default:
|
|
||||||
s.filter.SetHeader(acl.EACLRecord_FilterInfo_HeaderUnknown)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Group returns the access group of the target.
|
|
||||||
//
|
|
||||||
// If target is not initialized, GroupUnknown returns.
|
|
||||||
//
|
|
||||||
// Returns GroupUnknown if Target is not one of:
|
|
||||||
// - Target_User;
|
|
||||||
// - GroupSystem;
|
|
||||||
// - GroupOthers.
|
|
||||||
func (s TargetWrapper) Group() (res Group) {
|
|
||||||
res = GroupUnknown
|
|
||||||
|
|
||||||
if s.target != nil {
|
|
||||||
switch s.target.GetTarget() {
|
|
||||||
case acl.Target_User:
|
|
||||||
res = GroupUser
|
|
||||||
case acl.Target_System:
|
|
||||||
res = GroupSystem
|
|
||||||
case acl.Target_Others:
|
|
||||||
res = GroupOthers
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetGroup sets the access group of the target.
|
|
||||||
//
|
|
||||||
// If target is not initialized, nothing changes.
|
|
||||||
//
|
|
||||||
// Target is set to Target_Unknown if argument is not one of:
|
|
||||||
// - GroupUser;
|
|
||||||
// - GroupSystem;
|
|
||||||
// - GroupOthers.
|
|
||||||
func (s TargetWrapper) SetGroup(g Group) {
|
|
||||||
if s.target != nil {
|
|
||||||
switch g {
|
|
||||||
case GroupUser:
|
|
||||||
s.target.SetTarget(acl.Target_User)
|
|
||||||
case GroupSystem:
|
|
||||||
s.target.SetTarget(acl.Target_System)
|
|
||||||
case GroupOthers:
|
|
||||||
s.target.SetTarget(acl.Target_Others)
|
|
||||||
default:
|
|
||||||
s.target.SetTarget(acl.Target_Unknown)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// KeyList returns the key list of the target.
|
|
||||||
//
|
|
||||||
// If target is not initialized, nil returns.
|
|
||||||
func (s TargetWrapper) KeyList() [][]byte {
|
|
||||||
if s.target == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.target.GetKeyList()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetKeyList sets the key list of the target.
|
|
||||||
//
|
|
||||||
// If target is not initialized, nothing changes.
|
|
||||||
func (s TargetWrapper) SetKeyList(v [][]byte) {
|
|
||||||
if s.target != nil {
|
|
||||||
s.target.SetKeyList(v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// OperationType returns the operation type of the record.
|
|
||||||
//
|
|
||||||
// If record is not initialized, OpTypeUnknown returns.
|
|
||||||
//
|
|
||||||
// Returns OpTypeUnknown if Operation is not one of:
|
|
||||||
// - EACLRecord_HEAD;
|
|
||||||
// - EACLRecord_PUT;
|
|
||||||
// - EACLRecord_SEARCH;
|
|
||||||
// - EACLRecord_GET;
|
|
||||||
// - EACLRecord_GETRANGE;
|
|
||||||
// - EACLRecord_GETRANGEHASH;
|
|
||||||
// - EACLRecord_DELETE.
|
|
||||||
func (s RecordWrapper) OperationType() (res OperationType) {
|
|
||||||
res = OpTypeUnknown
|
|
||||||
|
|
||||||
if s.record != nil {
|
|
||||||
switch s.record.GetOperation() {
|
|
||||||
case acl.EACLRecord_HEAD:
|
|
||||||
res = OpTypeHead
|
|
||||||
case acl.EACLRecord_PUT:
|
|
||||||
res = OpTypePut
|
|
||||||
case acl.EACLRecord_SEARCH:
|
|
||||||
res = OpTypeSearch
|
|
||||||
case acl.EACLRecord_GET:
|
|
||||||
res = OpTypeGet
|
|
||||||
case acl.EACLRecord_GETRANGE:
|
|
||||||
res = OpTypeRange
|
|
||||||
case acl.EACLRecord_GETRANGEHASH:
|
|
||||||
res = OpTypeRangeHash
|
|
||||||
case acl.EACLRecord_DELETE:
|
|
||||||
res = OpTypeDelete
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetOperationType sets the operation type of the record.
|
|
||||||
//
|
|
||||||
// If record is not initialized, nothing changes.
|
|
||||||
//
|
|
||||||
// Operation is set to EACLRecord_OPERATION_UNKNOWN if argument is not one of:
|
|
||||||
// - OpTypeHead;
|
|
||||||
// - OpTypePut;
|
|
||||||
// - OpTypeSearch;
|
|
||||||
// - OpTypeGet;
|
|
||||||
// - OpTypeRange;
|
|
||||||
// - OpTypeRangeHash;
|
|
||||||
// - OpTypeDelete.
|
|
||||||
func (s RecordWrapper) SetOperationType(v OperationType) {
|
|
||||||
if s.record != nil {
|
|
||||||
switch v {
|
|
||||||
case OpTypeHead:
|
|
||||||
s.record.SetOperation(acl.EACLRecord_HEAD)
|
|
||||||
case OpTypePut:
|
|
||||||
s.record.SetOperation(acl.EACLRecord_PUT)
|
|
||||||
case OpTypeSearch:
|
|
||||||
s.record.SetOperation(acl.EACLRecord_SEARCH)
|
|
||||||
case OpTypeGet:
|
|
||||||
s.record.SetOperation(acl.EACLRecord_GET)
|
|
||||||
case OpTypeRange:
|
|
||||||
s.record.SetOperation(acl.EACLRecord_GETRANGE)
|
|
||||||
case OpTypeRangeHash:
|
|
||||||
s.record.SetOperation(acl.EACLRecord_GETRANGEHASH)
|
|
||||||
case OpTypeDelete:
|
|
||||||
s.record.SetOperation(acl.EACLRecord_DELETE)
|
|
||||||
default:
|
|
||||||
s.record.SetOperation(acl.EACLRecord_OPERATION_UNKNOWN)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Action returns the action of the record.
|
|
||||||
//
|
|
||||||
// If record is not initialized, ActionUnknown returns.
|
|
||||||
//
|
|
||||||
// Returns ActionUnknown if Action is not one of:
|
|
||||||
// - EACLRecord_Deny;
|
|
||||||
// - EACLRecord_Allow.
|
|
||||||
func (s RecordWrapper) Action() (res Action) {
|
|
||||||
res = ActionUnknown
|
|
||||||
|
|
||||||
if s.record != nil {
|
|
||||||
switch s.record.GetAction() {
|
|
||||||
case acl.EACLRecord_Deny:
|
|
||||||
res = ActionDeny
|
|
||||||
case acl.EACLRecord_Allow:
|
|
||||||
res = ActionAllow
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetAction sets the action of the record.
|
|
||||||
//
|
|
||||||
// If record is not initialized, nothing changes.
|
|
||||||
//
|
|
||||||
// Action is set to EACLRecord_ActionUnknown if argument is not one of:
|
|
||||||
// - ActionDeny;
|
|
||||||
// - ActionAllow.
|
|
||||||
func (s RecordWrapper) SetAction(v Action) {
|
|
||||||
if s.record != nil {
|
|
||||||
switch v {
|
|
||||||
case ActionDeny:
|
|
||||||
s.record.SetAction(acl.EACLRecord_Deny)
|
|
||||||
case ActionAllow:
|
|
||||||
s.record.SetAction(acl.EACLRecord_Allow)
|
|
||||||
default:
|
|
||||||
s.record.SetAction(acl.EACLRecord_ActionUnknown)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HeaderFilters returns the header filter list of the record.
|
|
||||||
//
|
|
||||||
// If record is not initialized, nil returns.
|
|
||||||
func (s RecordWrapper) HeaderFilters() []HeaderFilter {
|
|
||||||
if s.record == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
filters := s.record.GetFilters()
|
|
||||||
|
|
||||||
res := make([]HeaderFilter, 0, len(filters))
|
|
||||||
|
|
||||||
for i := range filters {
|
|
||||||
res = append(res, WrapFilterInfo(filters[i]))
|
|
||||||
}
|
|
||||||
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetHeaderFilters sets the header filter list of the record.
|
|
||||||
//
|
|
||||||
// Ignores nil elements of argument.
|
|
||||||
// If record is not initialized, nothing changes.
|
|
||||||
func (s RecordWrapper) SetHeaderFilters(v []HeaderFilter) {
|
|
||||||
if s.record == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
filters := make([]*acl.EACLRecord_FilterInfo, 0, len(v))
|
|
||||||
|
|
||||||
for i := range v {
|
|
||||||
if v[i] == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
w := WrapFilterInfo(nil)
|
|
||||||
w.SetMatchType(v[i].MatchType())
|
|
||||||
w.SetHeaderType(v[i].HeaderType())
|
|
||||||
w.SetName(v[i].Name())
|
|
||||||
w.SetValue(v[i].Value())
|
|
||||||
|
|
||||||
filters = append(filters, w.filter)
|
|
||||||
}
|
|
||||||
|
|
||||||
s.record.SetFilters(filters)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TargetList returns the target list of the record.
|
|
||||||
//
|
|
||||||
// If record is not initialized, nil returns.
|
|
||||||
func (s RecordWrapper) TargetList() []Target {
|
|
||||||
if s.record == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
targets := s.record.GetTargets()
|
|
||||||
|
|
||||||
res := make([]Target, 0, len(targets))
|
|
||||||
|
|
||||||
for i := range targets {
|
|
||||||
res = append(res, WrapTarget(targets[i]))
|
|
||||||
}
|
|
||||||
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetTargetList sets the target list of the record.
|
|
||||||
//
|
|
||||||
// Ignores nil elements of argument.
|
|
||||||
// If record is not initialized, nothing changes.
|
|
||||||
func (s RecordWrapper) SetTargetList(v []Target) {
|
|
||||||
if s.record == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
targets := make([]*acl.EACLRecord_TargetInfo, 0, len(v))
|
|
||||||
|
|
||||||
for i := range v {
|
|
||||||
if v[i] == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
w := WrapTarget(nil)
|
|
||||||
w.SetGroup(v[i].Group())
|
|
||||||
w.SetKeyList(v[i].KeyList())
|
|
||||||
|
|
||||||
targets = append(targets, w.target)
|
|
||||||
}
|
|
||||||
|
|
||||||
s.record.SetTargets(targets)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Records returns the record list of the table.
|
|
||||||
//
|
|
||||||
// If table is not initialized, nil returns.
|
|
||||||
func (s TableWrapper) Records() []Record {
|
|
||||||
if s.table == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
records := s.table.GetRecords()
|
|
||||||
|
|
||||||
res := make([]Record, 0, len(records))
|
|
||||||
|
|
||||||
for i := range records {
|
|
||||||
res = append(res, WrapRecord(records[i]))
|
|
||||||
}
|
|
||||||
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetRecords sets the record list of the table.
|
|
||||||
//
|
|
||||||
// Ignores nil elements of argument.
|
|
||||||
// If table is not initialized, nothing changes.
|
|
||||||
func (s TableWrapper) SetRecords(v []Record) {
|
|
||||||
if s.table == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
records := make([]*acl.EACLRecord, 0, len(v))
|
|
||||||
|
|
||||||
for i := range v {
|
|
||||||
if v[i] == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
w := WrapRecord(nil)
|
|
||||||
w.SetOperationType(v[i].OperationType())
|
|
||||||
w.SetAction(v[i].Action())
|
|
||||||
w.SetHeaderFilters(v[i].HeaderFilters())
|
|
||||||
w.SetTargetList(v[i].TargetList())
|
|
||||||
|
|
||||||
records = append(records, w.record)
|
|
||||||
}
|
|
||||||
|
|
||||||
s.table.SetRecords(records)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -1,136 +0,0 @@
|
||||||
package eacl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestEACLFilterWrapper(t *testing.T) {
|
|
||||||
s := WrapFilterInfo(nil)
|
|
||||||
|
|
||||||
mt := StringEqual
|
|
||||||
s.SetMatchType(mt)
|
|
||||||
require.Equal(t, mt, s.MatchType())
|
|
||||||
|
|
||||||
ht := HdrTypeObjUsr
|
|
||||||
s.SetHeaderType(ht)
|
|
||||||
require.Equal(t, ht, s.HeaderType())
|
|
||||||
|
|
||||||
n := "name"
|
|
||||||
s.SetName(n)
|
|
||||||
require.Equal(t, n, s.Name())
|
|
||||||
|
|
||||||
v := "value"
|
|
||||||
s.SetValue(v)
|
|
||||||
require.Equal(t, v, s.Value())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEACLTargetWrapper(t *testing.T) {
|
|
||||||
s := WrapTarget(nil)
|
|
||||||
|
|
||||||
group := Group(3)
|
|
||||||
s.SetGroup(group)
|
|
||||||
require.Equal(t, group, s.Group())
|
|
||||||
|
|
||||||
keys := [][]byte{
|
|
||||||
{1, 2, 3},
|
|
||||||
{4, 5, 6},
|
|
||||||
}
|
|
||||||
s.SetKeyList(keys)
|
|
||||||
require.Equal(t, keys, s.KeyList())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEACLRecordWrapper(t *testing.T) {
|
|
||||||
s := WrapRecord(nil)
|
|
||||||
|
|
||||||
action := ActionAllow
|
|
||||||
s.SetAction(action)
|
|
||||||
require.Equal(t, action, s.Action())
|
|
||||||
|
|
||||||
opType := OperationType(5)
|
|
||||||
s.SetOperationType(opType)
|
|
||||||
require.Equal(t, opType, s.OperationType())
|
|
||||||
|
|
||||||
f1Name := "name1"
|
|
||||||
f1 := WrapFilterInfo(nil)
|
|
||||||
f1.SetName(f1Name)
|
|
||||||
|
|
||||||
f2Name := "name2"
|
|
||||||
f2 := WrapFilterInfo(nil)
|
|
||||||
f2.SetName(f2Name)
|
|
||||||
|
|
||||||
s.SetHeaderFilters([]HeaderFilter{f1, f2})
|
|
||||||
|
|
||||||
filters := s.HeaderFilters()
|
|
||||||
require.Len(t, filters, 2)
|
|
||||||
require.Equal(t, f1Name, filters[0].Name())
|
|
||||||
require.Equal(t, f2Name, filters[1].Name())
|
|
||||||
|
|
||||||
group1 := Group(1)
|
|
||||||
t1 := WrapTarget(nil)
|
|
||||||
t1.SetGroup(group1)
|
|
||||||
|
|
||||||
group2 := Group(2)
|
|
||||||
t2 := WrapTarget(nil)
|
|
||||||
t2.SetGroup(group2)
|
|
||||||
|
|
||||||
s.SetTargetList([]Target{t1, t2})
|
|
||||||
|
|
||||||
targets := s.TargetList()
|
|
||||||
require.Len(t, targets, 2)
|
|
||||||
require.Equal(t, group1, targets[0].Group())
|
|
||||||
require.Equal(t, group2, targets[1].Group())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEACLTableWrapper(t *testing.T) {
|
|
||||||
s := WrapTable(nil)
|
|
||||||
|
|
||||||
action1 := Action(1)
|
|
||||||
r1 := WrapRecord(nil)
|
|
||||||
r1.SetAction(action1)
|
|
||||||
|
|
||||||
action2 := Action(2)
|
|
||||||
r2 := WrapRecord(nil)
|
|
||||||
r2.SetAction(action2)
|
|
||||||
|
|
||||||
s.SetRecords([]Record{r1, r2})
|
|
||||||
|
|
||||||
records := s.Records()
|
|
||||||
require.Len(t, records, 2)
|
|
||||||
require.Equal(t, action1, records[0].Action())
|
|
||||||
require.Equal(t, action2, records[1].Action())
|
|
||||||
|
|
||||||
s2, err := UnmarshalTable(MarshalTable(s))
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
records1 := s.Records()
|
|
||||||
records2 := s2.Records()
|
|
||||||
require.Len(t, records1, len(records2))
|
|
||||||
|
|
||||||
for i := range records1 {
|
|
||||||
require.Equal(t, records1[i].Action(), records2[i].Action())
|
|
||||||
require.Equal(t, records1[i].OperationType(), records2[i].OperationType())
|
|
||||||
|
|
||||||
targets1 := records1[i].TargetList()
|
|
||||||
targets2 := records2[i].TargetList()
|
|
||||||
require.Len(t, targets1, len(targets2))
|
|
||||||
|
|
||||||
for j := range targets1 {
|
|
||||||
require.Equal(t, targets1[j].Group(), targets2[j].Group())
|
|
||||||
require.Equal(t, targets1[j].KeyList(), targets2[j].KeyList())
|
|
||||||
}
|
|
||||||
|
|
||||||
filters1 := records1[i].HeaderFilters()
|
|
||||||
filters2 := records2[i].HeaderFilters()
|
|
||||||
require.Len(t, filters1, len(filters2))
|
|
||||||
|
|
||||||
for j := range filters1 {
|
|
||||||
require.Equal(t, filters1[j].MatchType(), filters2[j].MatchType())
|
|
||||||
require.Equal(t, filters1[j].HeaderType(), filters2[j].HeaderType())
|
|
||||||
require.Equal(t, filters1[j].Name(), filters2[j].Name())
|
|
||||||
require.Equal(t, filters1[j].Value(), filters2[j].Value())
|
|
||||||
require.Equal(t, filters1[j].Value(), filters2[j].Value())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
57
acl/types.go
57
acl/types.go
|
@ -1,57 +0,0 @@
|
||||||
package acl
|
|
||||||
|
|
||||||
|
|
||||||
// SetMatchType is MatchType field setter.
|
|
||||||
func (m *EACLRecord_FilterInfo) SetMatchType(v EACLRecord_FilterInfo_MatchType) {
|
|
||||||
m.MatchType = v
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetHeader is a Header field setter.
|
|
||||||
func (m *EACLRecord_FilterInfo) SetHeader(v EACLRecord_FilterInfo_Header) {
|
|
||||||
m.Header = v
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetHeaderName is a HeaderName field setter.
|
|
||||||
func (m *EACLRecord_FilterInfo) SetHeaderName(v string) {
|
|
||||||
m.HeaderName = v
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetHeaderVal is a HeaderVal field setter.
|
|
||||||
func (m *EACLRecord_FilterInfo) SetHeaderVal(v string) {
|
|
||||||
m.HeaderVal = v
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetTarget is a Target field setter.
|
|
||||||
func (m *EACLRecord_TargetInfo) SetTarget(v Target) {
|
|
||||||
m.Target = v
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetKeyList is a KeyList field setter.
|
|
||||||
func (m *EACLRecord_TargetInfo) SetKeyList(v [][]byte) {
|
|
||||||
m.KeyList = v
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetOperation is an Operation field setter.
|
|
||||||
func (m *EACLRecord) SetOperation(v EACLRecord_Operation) {
|
|
||||||
m.Operation = v
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetAction is an Action field setter.
|
|
||||||
func (m *EACLRecord) SetAction(v EACLRecord_Action) {
|
|
||||||
m.Action = v
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetFilters is a Filters field setter.
|
|
||||||
func (m *EACLRecord) SetFilters(v []*EACLRecord_FilterInfo) {
|
|
||||||
m.Filters = v
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetTargets is a Targets field setter.
|
|
||||||
func (m *EACLRecord) SetTargets(v []*EACLRecord_TargetInfo) {
|
|
||||||
m.Targets = v
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetRecords is a Records field setter.
|
|
||||||
func (m *EACLTable) SetRecords(v []*EACLRecord) {
|
|
||||||
m.Records = v
|
|
||||||
}
|
|
BIN
acl/types.pb.go
BIN
acl/types.pb.go
Binary file not shown.
106
acl/types.proto
106
acl/types.proto
|
@ -1,106 +0,0 @@
|
||||||
syntax = "proto3";
|
|
||||||
package acl;
|
|
||||||
option go_package = "github.com/nspcc-dev/neofs-api-go/acl";
|
|
||||||
option csharp_namespace = "NeoFS.API.Acl";
|
|
||||||
|
|
||||||
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
|
|
||||||
option (gogoproto.stable_marshaler_all) = true;
|
|
||||||
|
|
||||||
// Target of the access control rule in access control list.
|
|
||||||
enum Target {
|
|
||||||
// Unknown target, default value.
|
|
||||||
Unknown = 0;
|
|
||||||
|
|
||||||
// User target rule is applied if sender is the owner of the container.
|
|
||||||
User = 1;
|
|
||||||
|
|
||||||
// System target rule is applied if sender is the storage node within the
|
|
||||||
// container or inner ring node.
|
|
||||||
System = 2;
|
|
||||||
|
|
||||||
// Others target rule is applied if sender is not user or system target.
|
|
||||||
Others = 3;
|
|
||||||
|
|
||||||
// PubKey target rule is applied if sender has public key provided in
|
|
||||||
// extended ACL.
|
|
||||||
PubKey = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
// EACLRecord groups information about extended ACL rule.
|
|
||||||
message EACLRecord {
|
|
||||||
// Operation is an enumeration of operation types.
|
|
||||||
enum Operation {
|
|
||||||
OPERATION_UNKNOWN = 0;
|
|
||||||
GET = 1;
|
|
||||||
HEAD = 2;
|
|
||||||
PUT = 3;
|
|
||||||
DELETE = 4;
|
|
||||||
SEARCH = 5;
|
|
||||||
GETRANGE = 6;
|
|
||||||
GETRANGEHASH = 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Operation carries type of operation.
|
|
||||||
Operation operation = 1 [(gogoproto.customname) = "Operation", json_name="Operation"];
|
|
||||||
|
|
||||||
// Action is an enumeration of EACL actions.
|
|
||||||
enum Action {
|
|
||||||
ActionUnknown = 0;
|
|
||||||
Allow = 1;
|
|
||||||
Deny = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Action carries ACL target action.
|
|
||||||
Action action = 2 [(gogoproto.customname) = "Action", json_name="Action"];
|
|
||||||
|
|
||||||
// FilterInfo groups information about filter.
|
|
||||||
message FilterInfo {
|
|
||||||
// Header is an enumeration of filtering header types.
|
|
||||||
enum Header {
|
|
||||||
HeaderUnknown = 0;
|
|
||||||
Request = 1;
|
|
||||||
ObjectSystem = 2;
|
|
||||||
ObjectUser = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Header carries type of header.
|
|
||||||
Header header = 1 [(gogoproto.customname) = "Header", json_name="HeaderType"];
|
|
||||||
|
|
||||||
// MatchType is an enumeration of match types.
|
|
||||||
enum MatchType {
|
|
||||||
MatchUnknown = 0;
|
|
||||||
StringEqual = 1;
|
|
||||||
StringNotEqual = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// MatchType carries type of match.
|
|
||||||
MatchType matchType = 2 [(gogoproto.customname) = "MatchType", json_name="MatchType"];
|
|
||||||
|
|
||||||
// HeaderName carries name of filtering header.
|
|
||||||
string HeaderName = 3 [json_name="Name"];
|
|
||||||
|
|
||||||
// HeaderVal carries value of filtering header.
|
|
||||||
string HeaderVal = 4 [json_name="Value"];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filters carries set of filters.
|
|
||||||
repeated FilterInfo Filters = 3 [json_name="Filters"];
|
|
||||||
|
|
||||||
// TargetInfo groups information about extended ACL target.
|
|
||||||
message TargetInfo {
|
|
||||||
// Target carries target of ACL rule.
|
|
||||||
acl.Target Target = 1 [json_name="Role"];
|
|
||||||
|
|
||||||
// KeyList carries public keys of ACL target.
|
|
||||||
repeated bytes KeyList = 2 [json_name="Keys"];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Targets carries information about extended ACL target list.
|
|
||||||
repeated TargetInfo Targets = 4 [json_name="Targets"];
|
|
||||||
}
|
|
||||||
|
|
||||||
// EACLRecord carries the information about extended ACL rules.
|
|
||||||
message EACLTable {
|
|
||||||
// Records carries list of extended ACL rule records.
|
|
||||||
repeated EACLRecord Records = 1 [json_name="Records"];
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
package bootstrap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/service"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NodeType type alias.
|
|
||||||
type NodeType = service.NodeRole
|
|
Binary file not shown.
|
@ -1,42 +0,0 @@
|
||||||
syntax = "proto3";
|
|
||||||
package bootstrap;
|
|
||||||
option go_package = "github.com/nspcc-dev/neofs-api-go/bootstrap";
|
|
||||||
option csharp_namespace = "NeoFS.API.Bootstrap";
|
|
||||||
|
|
||||||
import "service/meta.proto";
|
|
||||||
import "service/verify.proto";
|
|
||||||
import "bootstrap/types.proto";
|
|
||||||
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
|
|
||||||
|
|
||||||
option (gogoproto.stable_marshaler_all) = true;
|
|
||||||
|
|
||||||
// Bootstrap service allows neofs-node to connect to the network. Node should
|
|
||||||
// perform at least one bootstrap request in the epoch to stay in the network
|
|
||||||
// for the next epoch.
|
|
||||||
service Bootstrap {
|
|
||||||
// Process is method that allows to register node in the network and receive actual netmap
|
|
||||||
rpc Process(Request) returns (bootstrap.SpreadMap);
|
|
||||||
}
|
|
||||||
|
|
||||||
message Request {
|
|
||||||
// Node state
|
|
||||||
enum State {
|
|
||||||
// used by default
|
|
||||||
Unknown = 0;
|
|
||||||
// used to inform that node online
|
|
||||||
Online = 1;
|
|
||||||
// used to inform that node offline
|
|
||||||
Offline = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type is NodeType, can be InnerRingNode (type=1) or StorageNode (type=2)
|
|
||||||
int32 type = 1 [(gogoproto.customname) = "Type" , (gogoproto.nullable) = false, (gogoproto.customtype) = "NodeType"];
|
|
||||||
// Info contains information about node
|
|
||||||
bootstrap.NodeInfo info = 2 [(gogoproto.nullable) = false];
|
|
||||||
// State contains node status
|
|
||||||
State state = 3;
|
|
||||||
// RequestMetaHeader contains information about request meta headers (should be embedded into message)
|
|
||||||
service.RequestMetaHeader Meta = 98 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
// RequestVerificationHeader is a set of signatures of every NeoFS Node that processed request (should be embedded into message)
|
|
||||||
service.RequestVerificationHeader Verify = 99 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
package bootstrap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/service"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SignedData returns payload bytes of the request.
|
|
||||||
func (m Request) SignedData() ([]byte, error) {
|
|
||||||
return service.SignedDataFromReader(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedDataSize returns payload size of the request.
|
|
||||||
func (m Request) SignedDataSize() (sz int) {
|
|
||||||
sz += m.GetType().Size()
|
|
||||||
|
|
||||||
sz += m.GetState().Size()
|
|
||||||
|
|
||||||
info := m.GetInfo()
|
|
||||||
sz += info.Size()
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadSignedData copies payload bytes to passed buffer.
|
|
||||||
//
|
|
||||||
// If the Request size is insufficient, io.ErrUnexpectedEOF returns.
|
|
||||||
func (m Request) ReadSignedData(p []byte) (int, error) {
|
|
||||||
if len(p) < m.SignedDataSize() {
|
|
||||||
return 0, io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
|
|
||||||
var off int
|
|
||||||
|
|
||||||
off += copy(p[off:], m.GetType().Bytes())
|
|
||||||
|
|
||||||
off += copy(p[off:], m.GetState().Bytes())
|
|
||||||
|
|
||||||
info := m.GetInfo()
|
|
||||||
// FIXME: implement and use stable functions
|
|
||||||
n, err := info.MarshalTo(p[off:])
|
|
||||||
off += n
|
|
||||||
|
|
||||||
return off, err
|
|
||||||
}
|
|
|
@ -1,82 +0,0 @@
|
||||||
package bootstrap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/service"
|
|
||||||
"github.com/nspcc-dev/neofs-crypto/test"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestRequestSign(t *testing.T) {
|
|
||||||
sk := test.DecodeKey(0)
|
|
||||||
|
|
||||||
type sigType interface {
|
|
||||||
service.RequestData
|
|
||||||
service.SignKeyPairAccumulator
|
|
||||||
service.SignKeyPairSource
|
|
||||||
SetToken(*service.Token)
|
|
||||||
}
|
|
||||||
|
|
||||||
items := []struct {
|
|
||||||
constructor func() sigType
|
|
||||||
payloadCorrupt []func(sigType)
|
|
||||||
}{
|
|
||||||
{ // Request
|
|
||||||
constructor: func() sigType {
|
|
||||||
return new(Request)
|
|
||||||
},
|
|
||||||
payloadCorrupt: []func(sigType){
|
|
||||||
func(s sigType) {
|
|
||||||
req := s.(*Request)
|
|
||||||
|
|
||||||
req.SetType(req.GetType() + 1)
|
|
||||||
},
|
|
||||||
func(s sigType) {
|
|
||||||
req := s.(*Request)
|
|
||||||
|
|
||||||
req.SetState(req.GetState() + 1)
|
|
||||||
},
|
|
||||||
func(s sigType) {
|
|
||||||
req := s.(*Request)
|
|
||||||
|
|
||||||
info := req.GetInfo()
|
|
||||||
info.Address += "1"
|
|
||||||
|
|
||||||
req.SetInfo(info)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, item := range items {
|
|
||||||
{ // token corruptions
|
|
||||||
v := item.constructor()
|
|
||||||
|
|
||||||
token := new(service.Token)
|
|
||||||
v.SetToken(token)
|
|
||||||
|
|
||||||
require.NoError(t, service.SignRequestData(sk, v))
|
|
||||||
|
|
||||||
require.NoError(t, service.VerifyRequestData(v))
|
|
||||||
|
|
||||||
token.SetSessionKey(append(token.GetSessionKey(), 1))
|
|
||||||
|
|
||||||
require.Error(t, service.VerifyRequestData(v))
|
|
||||||
}
|
|
||||||
|
|
||||||
{ // payload corruptions
|
|
||||||
for _, corruption := range item.payloadCorrupt {
|
|
||||||
v := item.constructor()
|
|
||||||
|
|
||||||
require.NoError(t, service.SignRequestData(sk, v))
|
|
||||||
|
|
||||||
require.NoError(t, service.VerifyRequestData(v))
|
|
||||||
|
|
||||||
corruption(v)
|
|
||||||
|
|
||||||
require.Error(t, service.VerifyRequestData(v))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,137 +0,0 @@
|
||||||
package bootstrap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
|
||||||
"encoding/hex"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/object"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
// NodeStatus is a bitwise status field of the node.
|
|
||||||
NodeStatus uint64
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
storageFullMask = 0x1
|
|
||||||
|
|
||||||
optionCapacity = "/Capacity:"
|
|
||||||
optionPrice = "/Price:"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
_ proto.Message = (*NodeInfo)(nil)
|
|
||||||
_ proto.Message = (*SpreadMap)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
var requestEndianness = binary.BigEndian
|
|
||||||
|
|
||||||
// Equals checks whether two NodeInfo has same address.
|
|
||||||
func (m NodeInfo) Equals(n1 NodeInfo) bool {
|
|
||||||
return m.Address == n1.Address && bytes.Equal(m.PubKey, n1.PubKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Full checks if node has enough space for storing users objects.
|
|
||||||
func (n NodeStatus) Full() bool {
|
|
||||||
return n&storageFullMask > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetFull changes state of node to indicate if node has enough space for storing users objects.
|
|
||||||
// If value is true - there's not enough space.
|
|
||||||
func (n *NodeStatus) SetFull(value bool) {
|
|
||||||
switch value {
|
|
||||||
case true:
|
|
||||||
*n |= NodeStatus(storageFullMask)
|
|
||||||
case false:
|
|
||||||
*n &= NodeStatus(^uint64(storageFullMask))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Price returns price in 1e-8*GAS/Megabyte per month.
|
|
||||||
// User set price in GAS/Terabyte per month.
|
|
||||||
func (m NodeInfo) Price() uint64 {
|
|
||||||
for i := range m.Options {
|
|
||||||
if strings.HasPrefix(m.Options[i], optionPrice) {
|
|
||||||
n, err := strconv.ParseFloat(m.Options[i][len(optionPrice):], 64)
|
|
||||||
if err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return uint64(n*1e8) / uint64(object.UnitsMB) // UnitsMB == megabytes in 1 terabyte
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Capacity returns node's capacity as reported by user.
|
|
||||||
func (m NodeInfo) Capacity() uint64 {
|
|
||||||
for i := range m.Options {
|
|
||||||
if strings.HasPrefix(m.Options[i], optionCapacity) {
|
|
||||||
n, err := strconv.ParseUint(m.Options[i][len(optionCapacity):], 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns string representation of NodeInfo.
|
|
||||||
func (m NodeInfo) String() string {
|
|
||||||
return "(NodeInfo)<" +
|
|
||||||
"Address:" + m.Address +
|
|
||||||
", " +
|
|
||||||
"PublicKey:" + hex.EncodeToString(m.PubKey) +
|
|
||||||
", " +
|
|
||||||
"Options: [" + strings.Join(m.Options, ",") + "]>"
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns string representation of SpreadMap.
|
|
||||||
func (m SpreadMap) String() string {
|
|
||||||
result := make([]string, 0, len(m.NetMap))
|
|
||||||
for i := range m.NetMap {
|
|
||||||
result = append(result, m.NetMap[i].String())
|
|
||||||
}
|
|
||||||
return "(SpreadMap)<" +
|
|
||||||
"Epoch: " + strconv.FormatUint(m.Epoch, 10) +
|
|
||||||
", " +
|
|
||||||
"Netmap: [" + strings.Join(result, ",") + "]>"
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetType is a Type field getter.
|
|
||||||
func (m Request) GetType() NodeType {
|
|
||||||
return m.Type
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetType is a Type field setter.
|
|
||||||
func (m *Request) SetType(t NodeType) {
|
|
||||||
m.Type = t
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetState is a State field setter.
|
|
||||||
func (m *Request) SetState(state Request_State) {
|
|
||||||
m.State = state
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetInfo is an Info field getter.
|
|
||||||
func (m *Request) SetInfo(info NodeInfo) {
|
|
||||||
m.Info = info
|
|
||||||
}
|
|
||||||
|
|
||||||
// Size returns the size necessary for a binary representation of the state.
|
|
||||||
func (x Request_State) Size() int {
|
|
||||||
return 4
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bytes returns a binary representation of the state.
|
|
||||||
func (x Request_State) Bytes() []byte {
|
|
||||||
data := make([]byte, x.Size())
|
|
||||||
|
|
||||||
requestEndianness.PutUint32(data, uint32(x))
|
|
||||||
|
|
||||||
return data
|
|
||||||
}
|
|
Binary file not shown.
|
@ -1,29 +0,0 @@
|
||||||
syntax = "proto3";
|
|
||||||
package bootstrap;
|
|
||||||
option go_package = "github.com/nspcc-dev/neofs-api-go/bootstrap";
|
|
||||||
option csharp_namespace = "NeoFS.API.Bootstrap";
|
|
||||||
|
|
||||||
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
|
|
||||||
|
|
||||||
option (gogoproto.stable_marshaler_all) = true;
|
|
||||||
|
|
||||||
option (gogoproto.stringer_all) = false;
|
|
||||||
option (gogoproto.goproto_stringer_all) = false;
|
|
||||||
|
|
||||||
message SpreadMap {
|
|
||||||
// Epoch is current epoch for netmap
|
|
||||||
uint64 Epoch = 1;
|
|
||||||
// NetMap is a set of NodeInfos
|
|
||||||
repeated NodeInfo NetMap = 2 [(gogoproto.nullable) = false];
|
|
||||||
}
|
|
||||||
|
|
||||||
message NodeInfo {
|
|
||||||
// Address is a node [multi-address](https://github.com/multiformats/multiaddr)
|
|
||||||
string Address = 1 [(gogoproto.jsontag) = "address"];
|
|
||||||
// PubKey is a compressed public key representation in bytes
|
|
||||||
bytes PubKey = 2 [(gogoproto.jsontag) = "pubkey,omitempty"];
|
|
||||||
// Options is set of node optional information, such as storage capacity, node location, price and etc
|
|
||||||
repeated string Options = 3 [(gogoproto.jsontag) = "options,omitempty"];
|
|
||||||
// Status is bitmap status of the node
|
|
||||||
uint64 Status = 4 [(gogoproto.jsontag) = "status", (gogoproto.nullable) = false, (gogoproto.customtype) = "NodeStatus"];
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
package bootstrap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestRequestGettersSetters(t *testing.T) {
|
|
||||||
t.Run("type", func(t *testing.T) {
|
|
||||||
rt := NodeType(1)
|
|
||||||
m := new(Request)
|
|
||||||
|
|
||||||
m.SetType(rt)
|
|
||||||
|
|
||||||
require.Equal(t, rt, m.GetType())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("state", func(t *testing.T) {
|
|
||||||
st := Request_State(1)
|
|
||||||
m := new(Request)
|
|
||||||
|
|
||||||
m.SetState(st)
|
|
||||||
|
|
||||||
require.Equal(t, st, m.GetState())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("info", func(t *testing.T) {
|
|
||||||
info := NodeInfo{
|
|
||||||
Address: "some address",
|
|
||||||
}
|
|
||||||
|
|
||||||
m := new(Request)
|
|
||||||
|
|
||||||
m.SetInfo(info)
|
|
||||||
|
|
||||||
require.Equal(t, info, m.GetInfo())
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
package chain
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/ecdsa"
|
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
|
||||||
)
|
|
||||||
|
|
||||||
// WalletAddress implements NEO address.
|
|
||||||
type WalletAddress [AddressLength]byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
// AddressLength contains size of address,
|
|
||||||
// 1 byte of address version + 20 bytes of ScriptHash + 4 bytes of checksum.
|
|
||||||
AddressLength = 25
|
|
||||||
)
|
|
||||||
|
|
||||||
// KeyToAddress returns NEO address composed from public key.
|
|
||||||
func KeyToAddress(key *ecdsa.PublicKey) string {
|
|
||||||
if key == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
neoPublicKey := keys.PublicKey{
|
|
||||||
X: key.X,
|
|
||||||
Y: key.Y,
|
|
||||||
}
|
|
||||||
|
|
||||||
return neoPublicKey.Address()
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
package chain
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/hex"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
crypto "github.com/nspcc-dev/neofs-crypto"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
type addressTestCase struct {
|
|
||||||
name string
|
|
||||||
publicKey string
|
|
||||||
wallet string
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestKeyToAddress(t *testing.T) {
|
|
||||||
tests := []addressTestCase{
|
|
||||||
{
|
|
||||||
"nil key",
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"correct key",
|
|
||||||
"031a6c6fbbdf02ca351745fa86b9ba5a9452d785ac4f7fc2b7548ca2a46c4fcf4a",
|
|
||||||
"NgzuJWWGVEwFGsRrgzj8knswEYRJrTe7sm",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range tests {
|
|
||||||
t.Run(tests[i].name, func(t *testing.T) {
|
|
||||||
data, err := hex.DecodeString(tests[i].publicKey)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
key := crypto.UnmarshalPublicKey(data)
|
|
||||||
|
|
||||||
require.Equal(t, tests[i].wallet, KeyToAddress(key))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
package container
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/internal"
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/refs"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
// CID type alias.
|
|
||||||
CID = refs.CID
|
|
||||||
// UUID type alias.
|
|
||||||
UUID = refs.UUID
|
|
||||||
// OwnerID type alias.
|
|
||||||
OwnerID = refs.OwnerID
|
|
||||||
// MessageID type alias.
|
|
||||||
MessageID = refs.MessageID
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// ErrNotFound is raised when container could not be found.
|
|
||||||
ErrNotFound = internal.Error("could not find container")
|
|
||||||
)
|
|
||||||
|
|
||||||
// PrepareData prepares bytes representation of PutRequest to satisfy SignedRequest interface.
|
|
||||||
func (m *PutRequest) PrepareData() ([]byte, error) {
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
buf = new(bytes.Buffer)
|
|
||||||
capBytes = make([]byte, 8)
|
|
||||||
aclBytes = make([]byte, 4)
|
|
||||||
)
|
|
||||||
|
|
||||||
binary.BigEndian.PutUint64(capBytes, m.Capacity)
|
|
||||||
binary.BigEndian.PutUint32(capBytes, m.BasicACL)
|
|
||||||
|
|
||||||
if _, err = buf.Write(m.MessageID.Bytes()); err != nil {
|
|
||||||
return nil, errors.Wrap(err, "could not write message id")
|
|
||||||
} else if _, err = buf.Write(capBytes); err != nil {
|
|
||||||
return nil, errors.Wrap(err, "could not write capacity")
|
|
||||||
} else if _, err = buf.Write(m.OwnerID.Bytes()); err != nil {
|
|
||||||
return nil, errors.Wrap(err, "could not write pub")
|
|
||||||
} else if data, err := m.Rules.Marshal(); err != nil {
|
|
||||||
return nil, errors.Wrap(err, "could not marshal placement")
|
|
||||||
} else if _, err = buf.Write(data); err != nil {
|
|
||||||
return nil, errors.Wrap(err, "could not write placement")
|
|
||||||
} else if _, err = buf.Write(aclBytes); err != nil {
|
|
||||||
return nil, errors.Wrap(err, "could not write basic acl")
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrepareData prepares bytes representation of DeleteRequest to satisfy SignedRequest interface.
|
|
||||||
func (m *DeleteRequest) PrepareData() ([]byte, error) {
|
|
||||||
return m.CID.Bytes(), nil
|
|
||||||
}
|
|
Binary file not shown.
|
@ -1,146 +0,0 @@
|
||||||
syntax = "proto3";
|
|
||||||
package container;
|
|
||||||
option go_package = "github.com/nspcc-dev/neofs-api-go/container";
|
|
||||||
option csharp_namespace = "NeoFS.API.Container";
|
|
||||||
|
|
||||||
import "service/meta.proto";
|
|
||||||
import "service/verify.proto";
|
|
||||||
import "container/types.proto";
|
|
||||||
import "github.com/nspcc-dev/netmap/selector.proto";
|
|
||||||
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
|
|
||||||
|
|
||||||
option (gogoproto.stable_marshaler_all) = true;
|
|
||||||
|
|
||||||
// Container service provides API for manipulating with the container.
|
|
||||||
service Service {
|
|
||||||
// Put request proposes container to the inner ring nodes. They will
|
|
||||||
// accept new container if user has enough deposit. All containers
|
|
||||||
// are accepted by the consensus, therefore it is asynchronous process.
|
|
||||||
rpc Put(PutRequest) returns (PutResponse);
|
|
||||||
|
|
||||||
// Delete container removes it from the inner ring container storage. It
|
|
||||||
// also asynchronous process done by consensus.
|
|
||||||
rpc Delete(DeleteRequest) returns (DeleteResponse);
|
|
||||||
|
|
||||||
// Get container returns container instance
|
|
||||||
rpc Get(GetRequest) returns (GetResponse);
|
|
||||||
|
|
||||||
// List returns all user's containers
|
|
||||||
rpc List(ListRequest) returns (ListResponse);
|
|
||||||
|
|
||||||
// SetExtendedACL changes extended ACL rules of the container
|
|
||||||
rpc SetExtendedACL(SetExtendedACLRequest) returns (SetExtendedACLResponse);
|
|
||||||
|
|
||||||
// GetExtendedACL returns extended ACL rules of the container
|
|
||||||
rpc GetExtendedACL(GetExtendedACLRequest) returns (GetExtendedACLResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
message PutRequest {
|
|
||||||
// MessageID is a nonce for uniq container id calculation
|
|
||||||
bytes MessageID = 1 [(gogoproto.customtype) = "MessageID", (gogoproto.nullable) = false];
|
|
||||||
|
|
||||||
// Capacity defines amount of data that can be stored in the container (doesn't used for now).
|
|
||||||
uint64 Capacity = 2;
|
|
||||||
|
|
||||||
// OwnerID is a wallet address
|
|
||||||
bytes OwnerID = 3 [(gogoproto.customtype) = "OwnerID", (gogoproto.nullable) = false];
|
|
||||||
|
|
||||||
// Rules define storage policy for the object inside the container.
|
|
||||||
netmap.PlacementRule rules = 4 [(gogoproto.nullable) = false];
|
|
||||||
|
|
||||||
// BasicACL of the container.
|
|
||||||
uint32 BasicACL = 5;
|
|
||||||
|
|
||||||
// RequestMetaHeader contains information about request meta headers (should be embedded into message)
|
|
||||||
service.RequestMetaHeader Meta = 98 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
// RequestVerificationHeader is a set of signatures of every NeoFS Node that processed request (should be embedded into message)
|
|
||||||
service.RequestVerificationHeader Verify = 99 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
}
|
|
||||||
|
|
||||||
message PutResponse {
|
|
||||||
// CID (container id) is a SHA256 hash of the container structure
|
|
||||||
bytes CID = 1 [(gogoproto.customtype) = "CID", (gogoproto.nullable) = false];
|
|
||||||
}
|
|
||||||
|
|
||||||
message DeleteRequest {
|
|
||||||
// CID (container id) is a SHA256 hash of the container structure
|
|
||||||
bytes CID = 1 [(gogoproto.customtype) = "CID", (gogoproto.nullable) = false];
|
|
||||||
|
|
||||||
// RequestMetaHeader contains information about request meta headers (should be embedded into message)
|
|
||||||
service.RequestMetaHeader Meta = 98 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
// RequestVerificationHeader is a set of signatures of every NeoFS Node that processed request (should be embedded into message)
|
|
||||||
service.RequestVerificationHeader Verify = 99 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteResponse is empty because delete operation is asynchronous and done
|
|
||||||
// via consensus in inner ring nodes
|
|
||||||
message DeleteResponse { }
|
|
||||||
|
|
||||||
|
|
||||||
message GetRequest {
|
|
||||||
// CID (container id) is a SHA256 hash of the container structure
|
|
||||||
bytes CID = 1 [(gogoproto.customtype) = "CID", (gogoproto.nullable) = false];
|
|
||||||
|
|
||||||
// RequestMetaHeader contains information about request meta headers (should be embedded into message)
|
|
||||||
service.RequestMetaHeader Meta = 98 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
// RequestVerificationHeader is a set of signatures of every NeoFS Node that processed request (should be embedded into message)
|
|
||||||
service.RequestVerificationHeader Verify = 99 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
}
|
|
||||||
|
|
||||||
message GetResponse {
|
|
||||||
// Container is a structure that contains placement rules and owner id
|
|
||||||
container.Container Container = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ListRequest {
|
|
||||||
// OwnerID is a wallet address
|
|
||||||
bytes OwnerID = 1 [(gogoproto.customtype) = "OwnerID", (gogoproto.nullable) = false];
|
|
||||||
// RequestMetaHeader contains information about request meta headers (should be embedded into message)
|
|
||||||
service.RequestMetaHeader Meta = 98 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
// RequestVerificationHeader is a set of signatures of every NeoFS Node that processed request (should be embedded into message)
|
|
||||||
service.RequestVerificationHeader Verify = 99 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
}
|
|
||||||
|
|
||||||
message ListResponse {
|
|
||||||
// CID (container id) is list of SHA256 hashes of the container structures
|
|
||||||
repeated bytes CID = 1 [(gogoproto.customtype) = "CID", (gogoproto.nullable) = false];
|
|
||||||
}
|
|
||||||
|
|
||||||
message ExtendedACLKey {
|
|
||||||
// ID (container id) is a SHA256 hash of the container structure
|
|
||||||
bytes ID = 1 [(gogoproto.customtype) = "CID", (gogoproto.nullable) = false];
|
|
||||||
}
|
|
||||||
|
|
||||||
message ExtendedACLValue {
|
|
||||||
// EACL carries binary representation of the table of extended ACL rules
|
|
||||||
bytes EACL = 1;
|
|
||||||
// Signature carries EACL field signature
|
|
||||||
bytes Signature = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message SetExtendedACLRequest {
|
|
||||||
// Key carries key to extended ACL information
|
|
||||||
ExtendedACLKey Key = 1 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
// Value carries extended ACL information
|
|
||||||
ExtendedACLValue Value = 2 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
// RequestMetaHeader contains information about request meta headers (should be embedded into message)
|
|
||||||
service.RequestMetaHeader Meta = 98 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
// RequestVerificationHeader is a set of signatures of every NeoFS Node that processed request (should be embedded into message)
|
|
||||||
service.RequestVerificationHeader Verify = 99 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
}
|
|
||||||
|
|
||||||
message SetExtendedACLResponse {}
|
|
||||||
|
|
||||||
message GetExtendedACLRequest {
|
|
||||||
// Key carries key to extended ACL information
|
|
||||||
ExtendedACLKey Key = 1 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
// RequestMetaHeader contains information about request meta headers (should be embedded into message)
|
|
||||||
service.RequestMetaHeader Meta = 98 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
// RequestVerificationHeader is a set of signatures of every NeoFS Node that processed request (should be embedded into message)
|
|
||||||
service.RequestVerificationHeader Verify = 99 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
}
|
|
||||||
|
|
||||||
message GetExtendedACLResponse {
|
|
||||||
// ACL carries extended ACL information
|
|
||||||
ExtendedACLValue ACL = 1 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
}
|
|
|
@ -1,194 +0,0 @@
|
||||||
package container
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
service "github.com/nspcc-dev/neofs-api-go/service"
|
|
||||||
)
|
|
||||||
|
|
||||||
var requestEndianness = binary.BigEndian
|
|
||||||
|
|
||||||
// SignedData returns payload bytes of the request.
|
|
||||||
func (m PutRequest) SignedData() ([]byte, error) {
|
|
||||||
return service.SignedDataFromReader(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedDataSize returns payload size of the request.
|
|
||||||
func (m PutRequest) SignedDataSize() (sz int) {
|
|
||||||
sz += m.GetMessageID().Size()
|
|
||||||
|
|
||||||
sz += 8
|
|
||||||
|
|
||||||
sz += m.GetOwnerID().Size()
|
|
||||||
|
|
||||||
rules := m.GetRules()
|
|
||||||
sz += rules.Size()
|
|
||||||
|
|
||||||
sz += 4
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadSignedData copies payload bytes to passed buffer.
|
|
||||||
//
|
|
||||||
// If the Request size is insufficient, io.ErrUnexpectedEOF returns.
|
|
||||||
func (m PutRequest) ReadSignedData(p []byte) (int, error) {
|
|
||||||
if len(p) < m.SignedDataSize() {
|
|
||||||
return 0, io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
|
|
||||||
var off int
|
|
||||||
|
|
||||||
off += copy(p[off:], m.GetMessageID().Bytes())
|
|
||||||
|
|
||||||
requestEndianness.PutUint64(p[off:], m.GetCapacity())
|
|
||||||
off += 8
|
|
||||||
|
|
||||||
off += copy(p[off:], m.GetOwnerID().Bytes())
|
|
||||||
|
|
||||||
rules := m.GetRules()
|
|
||||||
// FIXME: implement and use stable functions
|
|
||||||
n, err := rules.MarshalTo(p[off:])
|
|
||||||
off += n
|
|
||||||
if err != nil {
|
|
||||||
return off, err
|
|
||||||
}
|
|
||||||
|
|
||||||
requestEndianness.PutUint32(p[off:], m.GetBasicACL())
|
|
||||||
off += 4
|
|
||||||
|
|
||||||
return off, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedData returns payload bytes of the request.
|
|
||||||
func (m DeleteRequest) SignedData() ([]byte, error) {
|
|
||||||
return service.SignedDataFromReader(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedDataSize returns payload size of the request.
|
|
||||||
func (m DeleteRequest) SignedDataSize() int {
|
|
||||||
return m.GetCID().Size()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadSignedData copies payload bytes to passed buffer.
|
|
||||||
//
|
|
||||||
// If the Request size is insufficient, io.ErrUnexpectedEOF returns.
|
|
||||||
func (m DeleteRequest) ReadSignedData(p []byte) (int, error) {
|
|
||||||
if len(p) < m.SignedDataSize() {
|
|
||||||
return 0, io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
|
|
||||||
var off int
|
|
||||||
|
|
||||||
off += copy(p[off:], m.GetCID().Bytes())
|
|
||||||
|
|
||||||
return off, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedData returns payload bytes of the request.
|
|
||||||
func (m GetRequest) SignedData() ([]byte, error) {
|
|
||||||
return service.SignedDataFromReader(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedDataSize returns payload size of the request.
|
|
||||||
func (m GetRequest) SignedDataSize() int {
|
|
||||||
return m.GetCID().Size()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadSignedData copies payload bytes to passed buffer.
|
|
||||||
//
|
|
||||||
// If the Request size is insufficient, io.ErrUnexpectedEOF returns.
|
|
||||||
func (m GetRequest) ReadSignedData(p []byte) (int, error) {
|
|
||||||
if len(p) < m.SignedDataSize() {
|
|
||||||
return 0, io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
|
|
||||||
var off int
|
|
||||||
|
|
||||||
off += copy(p[off:], m.GetCID().Bytes())
|
|
||||||
|
|
||||||
return off, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedData returns payload bytes of the request.
|
|
||||||
func (m ListRequest) SignedData() ([]byte, error) {
|
|
||||||
return service.SignedDataFromReader(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedDataSize returns payload size of the request.
|
|
||||||
func (m ListRequest) SignedDataSize() int {
|
|
||||||
return m.GetOwnerID().Size()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadSignedData copies payload bytes to passed buffer.
|
|
||||||
//
|
|
||||||
// If the Request size is insufficient, io.ErrUnexpectedEOF returns.
|
|
||||||
func (m ListRequest) ReadSignedData(p []byte) (int, error) {
|
|
||||||
if len(p) < m.SignedDataSize() {
|
|
||||||
return 0, io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
|
|
||||||
var off int
|
|
||||||
|
|
||||||
off += copy(p[off:], m.GetOwnerID().Bytes())
|
|
||||||
|
|
||||||
return off, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedData returns payload bytes of the request.
|
|
||||||
func (m GetExtendedACLRequest) SignedData() ([]byte, error) {
|
|
||||||
return service.SignedDataFromReader(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedDataSize returns payload size of the request.
|
|
||||||
func (m GetExtendedACLRequest) SignedDataSize() int {
|
|
||||||
return m.GetID().Size()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadSignedData copies payload bytes to passed buffer.
|
|
||||||
//
|
|
||||||
// If the Request size is insufficient, io.ErrUnexpectedEOF returns.
|
|
||||||
func (m GetExtendedACLRequest) ReadSignedData(p []byte) (int, error) {
|
|
||||||
if len(p) < m.SignedDataSize() {
|
|
||||||
return 0, io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
|
|
||||||
var off int
|
|
||||||
|
|
||||||
off += copy(p[off:], m.GetID().Bytes())
|
|
||||||
|
|
||||||
return off, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedData returns payload bytes of the request.
|
|
||||||
func (m SetExtendedACLRequest) SignedData() ([]byte, error) {
|
|
||||||
return service.SignedDataFromReader(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedDataSize returns payload size of the request.
|
|
||||||
func (m SetExtendedACLRequest) SignedDataSize() int {
|
|
||||||
return 0 +
|
|
||||||
m.GetID().Size() +
|
|
||||||
len(m.GetEACL()) +
|
|
||||||
len(m.GetSignature())
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadSignedData copies payload bytes to passed buffer.
|
|
||||||
//
|
|
||||||
// If the Request size is insufficient, io.ErrUnexpectedEOF returns.
|
|
||||||
func (m SetExtendedACLRequest) ReadSignedData(p []byte) (int, error) {
|
|
||||||
if len(p) < m.SignedDataSize() {
|
|
||||||
return 0, io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
|
|
||||||
var off int
|
|
||||||
|
|
||||||
off += copy(p[off:], m.GetID().Bytes())
|
|
||||||
|
|
||||||
off += copy(p[off:], m.GetEACL())
|
|
||||||
|
|
||||||
off += copy(p[off:], m.GetSignature())
|
|
||||||
|
|
||||||
return off, nil
|
|
||||||
}
|
|
|
@ -1,187 +0,0 @@
|
||||||
package container
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/service"
|
|
||||||
"github.com/nspcc-dev/neofs-crypto/test"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestRequestSign(t *testing.T) {
|
|
||||||
sk := test.DecodeKey(0)
|
|
||||||
|
|
||||||
type sigType interface {
|
|
||||||
service.RequestData
|
|
||||||
service.SignKeyPairAccumulator
|
|
||||||
service.SignKeyPairSource
|
|
||||||
SetToken(*service.Token)
|
|
||||||
}
|
|
||||||
|
|
||||||
items := []struct {
|
|
||||||
constructor func() sigType
|
|
||||||
payloadCorrupt []func(sigType)
|
|
||||||
}{
|
|
||||||
{ // PutRequest
|
|
||||||
constructor: func() sigType {
|
|
||||||
return new(PutRequest)
|
|
||||||
},
|
|
||||||
payloadCorrupt: []func(sigType){
|
|
||||||
func(s sigType) {
|
|
||||||
req := s.(*PutRequest)
|
|
||||||
|
|
||||||
id := req.GetMessageID()
|
|
||||||
id[0]++
|
|
||||||
|
|
||||||
req.SetMessageID(id)
|
|
||||||
},
|
|
||||||
func(s sigType) {
|
|
||||||
req := s.(*PutRequest)
|
|
||||||
|
|
||||||
req.SetCapacity(req.GetCapacity() + 1)
|
|
||||||
},
|
|
||||||
func(s sigType) {
|
|
||||||
req := s.(*PutRequest)
|
|
||||||
|
|
||||||
owner := req.GetOwnerID()
|
|
||||||
owner[0]++
|
|
||||||
|
|
||||||
req.SetOwnerID(owner)
|
|
||||||
},
|
|
||||||
func(s sigType) {
|
|
||||||
req := s.(*PutRequest)
|
|
||||||
|
|
||||||
rules := req.GetRules()
|
|
||||||
rules.ReplFactor++
|
|
||||||
|
|
||||||
req.SetRules(rules)
|
|
||||||
},
|
|
||||||
func(s sigType) {
|
|
||||||
req := s.(*PutRequest)
|
|
||||||
|
|
||||||
req.SetBasicACL(req.GetBasicACL() + 1)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ // DeleteRequest
|
|
||||||
constructor: func() sigType {
|
|
||||||
return new(DeleteRequest)
|
|
||||||
},
|
|
||||||
payloadCorrupt: []func(sigType){
|
|
||||||
func(s sigType) {
|
|
||||||
req := s.(*DeleteRequest)
|
|
||||||
|
|
||||||
cid := req.GetCID()
|
|
||||||
cid[0]++
|
|
||||||
|
|
||||||
req.SetCID(cid)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ // GetRequest
|
|
||||||
constructor: func() sigType {
|
|
||||||
return new(GetRequest)
|
|
||||||
},
|
|
||||||
payloadCorrupt: []func(sigType){
|
|
||||||
func(s sigType) {
|
|
||||||
req := s.(*GetRequest)
|
|
||||||
|
|
||||||
cid := req.GetCID()
|
|
||||||
cid[0]++
|
|
||||||
|
|
||||||
req.SetCID(cid)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ // ListRequest
|
|
||||||
constructor: func() sigType {
|
|
||||||
return new(ListRequest)
|
|
||||||
},
|
|
||||||
payloadCorrupt: []func(sigType){
|
|
||||||
func(s sigType) {
|
|
||||||
req := s.(*ListRequest)
|
|
||||||
|
|
||||||
owner := req.GetOwnerID()
|
|
||||||
owner[0]++
|
|
||||||
|
|
||||||
req.SetOwnerID(owner)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ // GetExtendedACLRequest
|
|
||||||
constructor: func() sigType {
|
|
||||||
return new(GetExtendedACLRequest)
|
|
||||||
},
|
|
||||||
payloadCorrupt: []func(sigType){
|
|
||||||
func(s sigType) {
|
|
||||||
req := s.(*GetExtendedACLRequest)
|
|
||||||
|
|
||||||
id := req.GetID()
|
|
||||||
id[0]++
|
|
||||||
|
|
||||||
req.SetID(id)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ // SetExtendedACLRequest
|
|
||||||
constructor: func() sigType {
|
|
||||||
return new(SetExtendedACLRequest)
|
|
||||||
},
|
|
||||||
payloadCorrupt: []func(sigType){
|
|
||||||
func(s sigType) {
|
|
||||||
req := s.(*SetExtendedACLRequest)
|
|
||||||
|
|
||||||
id := req.GetID()
|
|
||||||
id[0]++
|
|
||||||
|
|
||||||
req.SetID(id)
|
|
||||||
},
|
|
||||||
func(s sigType) {
|
|
||||||
req := s.(*SetExtendedACLRequest)
|
|
||||||
|
|
||||||
req.SetEACL(
|
|
||||||
append(req.GetEACL(), 1),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
func(s sigType) {
|
|
||||||
req := s.(*SetExtendedACLRequest)
|
|
||||||
|
|
||||||
req.SetSignature(
|
|
||||||
append(req.GetSignature(), 1),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, item := range items {
|
|
||||||
{ // token corruptions
|
|
||||||
v := item.constructor()
|
|
||||||
|
|
||||||
token := new(service.Token)
|
|
||||||
v.SetToken(token)
|
|
||||||
|
|
||||||
require.NoError(t, service.SignRequestData(sk, v))
|
|
||||||
|
|
||||||
require.NoError(t, service.VerifyRequestData(v))
|
|
||||||
|
|
||||||
token.SetSessionKey(append(token.GetSessionKey(), 1))
|
|
||||||
|
|
||||||
require.Error(t, service.VerifyRequestData(v))
|
|
||||||
}
|
|
||||||
|
|
||||||
{ // payload corruptions
|
|
||||||
for _, corruption := range item.payloadCorrupt {
|
|
||||||
v := item.constructor()
|
|
||||||
|
|
||||||
require.NoError(t, service.SignRequestData(sk, v))
|
|
||||||
|
|
||||||
require.NoError(t, service.VerifyRequestData(v))
|
|
||||||
|
|
||||||
corruption(v)
|
|
||||||
|
|
||||||
require.Error(t, service.VerifyRequestData(v))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,188 +0,0 @@
|
||||||
package container
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
|
|
||||||
"github.com/gogo/protobuf/proto"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/internal"
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/refs"
|
|
||||||
"github.com/nspcc-dev/neofs-crypto/test"
|
|
||||||
"github.com/nspcc-dev/netmap"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
_ internal.Custom = (*Container)(nil)
|
|
||||||
|
|
||||||
emptySalt = (UUID{}).Bytes()
|
|
||||||
emptyOwner = (OwnerID{}).Bytes()
|
|
||||||
)
|
|
||||||
|
|
||||||
// New creates new user container based on capacity, OwnerID, ACL and PlacementRules.
|
|
||||||
func New(cap uint64, owner OwnerID, acl uint32, rules netmap.PlacementRule) (*Container, error) {
|
|
||||||
if bytes.Equal(owner[:], emptyOwner) {
|
|
||||||
return nil, refs.ErrEmptyOwner
|
|
||||||
} else if cap == 0 {
|
|
||||||
return nil, refs.ErrEmptyCapacity
|
|
||||||
}
|
|
||||||
|
|
||||||
salt, err := uuid.NewRandom()
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "could not create salt")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Container{
|
|
||||||
OwnerID: owner,
|
|
||||||
Salt: UUID(salt),
|
|
||||||
Capacity: cap,
|
|
||||||
Rules: rules,
|
|
||||||
BasicACL: acl,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bytes returns bytes representation of Container.
|
|
||||||
func (m *Container) Bytes() []byte {
|
|
||||||
data, err := m.Marshal()
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
// ID returns generated ContainerID based on Container (data).
|
|
||||||
func (m *Container) ID() (CID, error) {
|
|
||||||
if m.Empty() {
|
|
||||||
return CID{}, refs.ErrEmptyContainer
|
|
||||||
}
|
|
||||||
data, err := m.Marshal()
|
|
||||||
if err != nil {
|
|
||||||
return CID{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return refs.CIDForBytes(data), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge used by proto.Clone
|
|
||||||
func (m *Container) Merge(src proto.Message) {
|
|
||||||
if tmp, ok := src.(*Container); ok {
|
|
||||||
*m = *tmp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Empty checks that container is empty.
|
|
||||||
func (m *Container) Empty() bool {
|
|
||||||
return m.Capacity == 0 || bytes.Equal(m.Salt.Bytes(), emptySalt) || bytes.Equal(m.OwnerID.Bytes(), emptyOwner)
|
|
||||||
}
|
|
||||||
|
|
||||||
// -- Test container definition -- //
|
|
||||||
|
|
||||||
// NewTestContainer returns test container.
|
|
||||||
// WARNING: DON'T USE THIS OUTSIDE TESTS.
|
|
||||||
func NewTestContainer() (*Container, error) {
|
|
||||||
key := test.DecodeKey(0)
|
|
||||||
owner, err := refs.NewOwnerID(&key.PublicKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return New(100, owner, 0xFFFFFFFF, netmap.PlacementRule{
|
|
||||||
ReplFactor: 2,
|
|
||||||
SFGroups: []netmap.SFGroup{
|
|
||||||
{
|
|
||||||
Selectors: []netmap.Select{
|
|
||||||
{Key: "Country", Count: 1},
|
|
||||||
{Key: netmap.NodesBucket, Count: 2},
|
|
||||||
},
|
|
||||||
Filters: []netmap.Filter{
|
|
||||||
{Key: "Country", F: netmap.FilterIn("USA")},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMessageID is a MessageID field getter.
|
|
||||||
func (m PutRequest) GetMessageID() MessageID {
|
|
||||||
return m.MessageID
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetMessageID is a MessageID field getter.
|
|
||||||
func (m *PutRequest) SetMessageID(id MessageID) {
|
|
||||||
m.MessageID = id
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetCapacity is a Capacity field setter.
|
|
||||||
func (m *PutRequest) SetCapacity(c uint64) {
|
|
||||||
m.Capacity = c
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetOwnerID is an OwnerID field getter.
|
|
||||||
func (m PutRequest) GetOwnerID() OwnerID {
|
|
||||||
return m.OwnerID
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetOwnerID is an OwnerID field setter.
|
|
||||||
func (m *PutRequest) SetOwnerID(owner OwnerID) {
|
|
||||||
m.OwnerID = owner
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetRules is a Rules field setter.
|
|
||||||
func (m *PutRequest) SetRules(rules netmap.PlacementRule) {
|
|
||||||
m.Rules = rules
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetBasicACL is a BasicACL field setter.
|
|
||||||
func (m *PutRequest) SetBasicACL(acl uint32) {
|
|
||||||
m.BasicACL = acl
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCID is a CID field getter.
|
|
||||||
func (m DeleteRequest) GetCID() CID {
|
|
||||||
return m.CID
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetCID is a CID field setter.
|
|
||||||
func (m *DeleteRequest) SetCID(cid CID) {
|
|
||||||
m.CID = cid
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCID is a CID field getter.
|
|
||||||
func (m GetRequest) GetCID() CID {
|
|
||||||
return m.CID
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetCID is a CID field setter.
|
|
||||||
func (m *GetRequest) SetCID(cid CID) {
|
|
||||||
m.CID = cid
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetOwnerID is an OwnerID field getter.
|
|
||||||
func (m ListRequest) GetOwnerID() OwnerID {
|
|
||||||
return m.OwnerID
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetOwnerID is an OwnerID field setter.
|
|
||||||
func (m *ListRequest) SetOwnerID(owner OwnerID) {
|
|
||||||
m.OwnerID = owner
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetID is an ID field getter.
|
|
||||||
func (m ExtendedACLKey) GetID() CID {
|
|
||||||
return m.ID
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetID is an ID field setter.
|
|
||||||
func (m *ExtendedACLKey) SetID(v CID) {
|
|
||||||
m.ID = v
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetEACL is an EACL field setter.
|
|
||||||
func (m *ExtendedACLValue) SetEACL(v []byte) {
|
|
||||||
m.EACL = v
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetSignature is a Signature field setter.
|
|
||||||
func (m *ExtendedACLValue) SetSignature(sig []byte) {
|
|
||||||
m.Signature = sig
|
|
||||||
}
|
|
Binary file not shown.
|
@ -1,24 +0,0 @@
|
||||||
syntax = "proto3";
|
|
||||||
package container;
|
|
||||||
option go_package = "github.com/nspcc-dev/neofs-api-go/container";
|
|
||||||
option csharp_namespace = "NeoFS.API.Container";
|
|
||||||
|
|
||||||
import "github.com/nspcc-dev/netmap/selector.proto";
|
|
||||||
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
|
|
||||||
|
|
||||||
option (gogoproto.stable_marshaler_all) = true;
|
|
||||||
|
|
||||||
// The Container service definition.
|
|
||||||
message Container {
|
|
||||||
// OwnerID is a wallet address.
|
|
||||||
bytes OwnerID = 1 [(gogoproto.customtype) = "OwnerID", (gogoproto.nullable) = false];
|
|
||||||
// Salt is a nonce for unique container id calculation.
|
|
||||||
bytes Salt = 2 [(gogoproto.customtype) = "UUID", (gogoproto.nullable) = false];
|
|
||||||
// Capacity defines amount of data that can be stored in the container (doesn't used for now).
|
|
||||||
uint64 Capacity = 3;
|
|
||||||
// Rules define storage policy for the object inside the container.
|
|
||||||
netmap.PlacementRule Rules = 4 [(gogoproto.nullable) = false];
|
|
||||||
// BasicACL with access control rules for owner, system, others and
|
|
||||||
// permission bits for bearer token and extended ACL.
|
|
||||||
uint32 BasicACL = 5;
|
|
||||||
}
|
|
|
@ -1,162 +0,0 @@
|
||||||
package container
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/gogo/protobuf/proto"
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/refs"
|
|
||||||
"github.com/nspcc-dev/neofs-crypto/test"
|
|
||||||
"github.com/nspcc-dev/netmap"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCID(t *testing.T) {
|
|
||||||
t.Run("check that marshal/unmarshal works like expected", func(t *testing.T) {
|
|
||||||
var (
|
|
||||||
c2 Container
|
|
||||||
cid2 CID
|
|
||||||
key = test.DecodeKey(0)
|
|
||||||
)
|
|
||||||
|
|
||||||
rules := netmap.PlacementRule{
|
|
||||||
ReplFactor: 2,
|
|
||||||
SFGroups: []netmap.SFGroup{
|
|
||||||
{
|
|
||||||
Selectors: []netmap.Select{
|
|
||||||
{Key: "Country", Count: 1},
|
|
||||||
{Key: netmap.NodesBucket, Count: 2},
|
|
||||||
},
|
|
||||||
Filters: []netmap.Filter{
|
|
||||||
{Key: "Country", F: netmap.FilterIn("USA")},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
owner, err := refs.NewOwnerID(&key.PublicKey)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
c1, err := New(10, owner, 0xDEADBEEF, rules)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
data, err := proto.Marshal(c1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.NoError(t, c2.Unmarshal(data))
|
|
||||||
require.Equal(t, c1, &c2)
|
|
||||||
|
|
||||||
cid1, err := c1.ID()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
data, err = proto.Marshal(&cid1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NoError(t, cid2.Unmarshal(data))
|
|
||||||
|
|
||||||
require.Equal(t, cid1, cid2)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPutRequestGettersSetters(t *testing.T) {
|
|
||||||
t.Run("owner", func(t *testing.T) {
|
|
||||||
owner := OwnerID{1, 2, 3}
|
|
||||||
m := new(PutRequest)
|
|
||||||
|
|
||||||
m.SetOwnerID(owner)
|
|
||||||
|
|
||||||
require.Equal(t, owner, m.GetOwnerID())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("capacity", func(t *testing.T) {
|
|
||||||
cp := uint64(3)
|
|
||||||
m := new(PutRequest)
|
|
||||||
|
|
||||||
m.SetCapacity(cp)
|
|
||||||
|
|
||||||
require.Equal(t, cp, m.GetCapacity())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("message ID", func(t *testing.T) {
|
|
||||||
id, err := refs.NewUUID()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
m := new(PutRequest)
|
|
||||||
|
|
||||||
m.SetMessageID(id)
|
|
||||||
|
|
||||||
require.Equal(t, id, m.GetMessageID())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("rules", func(t *testing.T) {
|
|
||||||
rules := netmap.PlacementRule{
|
|
||||||
ReplFactor: 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
m := new(PutRequest)
|
|
||||||
|
|
||||||
m.SetRules(rules)
|
|
||||||
|
|
||||||
require.Equal(t, rules, m.GetRules())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("basic ACL", func(t *testing.T) {
|
|
||||||
bACL := uint32(5)
|
|
||||||
m := new(PutRequest)
|
|
||||||
|
|
||||||
m.SetBasicACL(bACL)
|
|
||||||
|
|
||||||
require.Equal(t, bACL, m.GetBasicACL())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDeleteRequestGettersSetters(t *testing.T) {
|
|
||||||
t.Run("cid", func(t *testing.T) {
|
|
||||||
cid := CID{1, 2, 3}
|
|
||||||
m := new(DeleteRequest)
|
|
||||||
|
|
||||||
m.SetCID(cid)
|
|
||||||
|
|
||||||
require.Equal(t, cid, m.GetCID())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetRequestGettersSetters(t *testing.T) {
|
|
||||||
t.Run("cid", func(t *testing.T) {
|
|
||||||
cid := CID{1, 2, 3}
|
|
||||||
m := new(GetRequest)
|
|
||||||
|
|
||||||
m.SetCID(cid)
|
|
||||||
|
|
||||||
require.Equal(t, cid, m.GetCID())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestListRequestGettersSetters(t *testing.T) {
|
|
||||||
t.Run("owner", func(t *testing.T) {
|
|
||||||
owner := OwnerID{1, 2, 3}
|
|
||||||
m := new(PutRequest)
|
|
||||||
|
|
||||||
m.SetOwnerID(owner)
|
|
||||||
|
|
||||||
require.Equal(t, owner, m.GetOwnerID())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestExtendedACLKey(t *testing.T) {
|
|
||||||
s := new(ExtendedACLKey)
|
|
||||||
|
|
||||||
id := CID{1, 2, 3}
|
|
||||||
s.SetID(id)
|
|
||||||
require.Equal(t, id, s.GetID())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestExtendedACLValue(t *testing.T) {
|
|
||||||
s := new(ExtendedACLValue)
|
|
||||||
|
|
||||||
acl := []byte{1, 2, 3}
|
|
||||||
s.SetEACL(acl)
|
|
||||||
require.Equal(t, acl, s.GetEACL())
|
|
||||||
|
|
||||||
sig := []byte{4, 5, 6}
|
|
||||||
s.SetSignature(sig)
|
|
||||||
require.Equal(t, sig, s.GetSignature())
|
|
||||||
}
|
|
|
@ -1,110 +0,0 @@
|
||||||
package decimal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GASPrecision contains precision for NEO Gas token.
|
|
||||||
const GASPrecision = 8
|
|
||||||
|
|
||||||
// Zero is empty Decimal value.
|
|
||||||
var Zero = &Decimal{}
|
|
||||||
|
|
||||||
// New returns new Decimal (in satoshi).
|
|
||||||
func New(v int64) *Decimal {
|
|
||||||
return NewWithPrecision(v, GASPrecision)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewGAS returns new Decimal * 1e8 (in GAS).
|
|
||||||
func NewGAS(v int64) *Decimal {
|
|
||||||
v *= int64(math.Pow10(GASPrecision))
|
|
||||||
return NewWithPrecision(v, GASPrecision)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewWithPrecision returns new Decimal with custom precision.
|
|
||||||
func NewWithPrecision(v int64, p uint32) *Decimal {
|
|
||||||
return &Decimal{Value: v, Precision: p}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseFloat return new Decimal parsed from float64 * 1e8 (in GAS).
|
|
||||||
func ParseFloat(v float64) *Decimal {
|
|
||||||
return new(Decimal).Parse(v, GASPrecision)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseFloatWithPrecision returns new Decimal parsed from float64 * 1^p.
|
|
||||||
func ParseFloatWithPrecision(v float64, p int) *Decimal {
|
|
||||||
return new(Decimal).Parse(v, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy returns copy of current Decimal.
|
|
||||||
func (m *Decimal) Copy() *Decimal { return &Decimal{Value: m.Value, Precision: m.Precision} }
|
|
||||||
|
|
||||||
// Parse returns parsed Decimal from float64 * 1^p.
|
|
||||||
func (m *Decimal) Parse(v float64, p int) *Decimal {
|
|
||||||
m.Value = int64(v * math.Pow10(p))
|
|
||||||
m.Precision = uint32(p)
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns string representation of Decimal.
|
|
||||||
func (m Decimal) String() string {
|
|
||||||
buf := new(strings.Builder)
|
|
||||||
val := m.Value
|
|
||||||
dec := int64(math.Pow10(int(m.Precision)))
|
|
||||||
if val < 0 {
|
|
||||||
buf.WriteRune('-')
|
|
||||||
val = -val
|
|
||||||
}
|
|
||||||
str := strconv.FormatInt(val/dec, 10)
|
|
||||||
buf.WriteString(str)
|
|
||||||
val %= dec
|
|
||||||
if val > 0 {
|
|
||||||
buf.WriteRune('.')
|
|
||||||
str = strconv.FormatInt(val, 10)
|
|
||||||
for i := len(str); i < int(m.Precision); i++ {
|
|
||||||
buf.WriteRune('0')
|
|
||||||
}
|
|
||||||
buf.WriteString(strings.TrimRight(str, "0"))
|
|
||||||
}
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add returns d + m.
|
|
||||||
func (m Decimal) Add(d *Decimal) *Decimal {
|
|
||||||
precision := m.Precision
|
|
||||||
if precision < d.Precision {
|
|
||||||
precision = d.Precision
|
|
||||||
}
|
|
||||||
return &Decimal{
|
|
||||||
Value: m.Value + d.Value,
|
|
||||||
Precision: precision,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Zero checks that Decimal is empty.
|
|
||||||
func (m Decimal) Zero() bool { return m.Value == 0 }
|
|
||||||
|
|
||||||
// Equal checks that current Decimal is equal to passed Decimal.
|
|
||||||
func (m Decimal) Equal(v *Decimal) bool { return m.Value == v.Value && m.Precision == v.Precision }
|
|
||||||
|
|
||||||
// GT checks that m > v.
|
|
||||||
func (m Decimal) GT(v *Decimal) bool { return m.Value > v.Value }
|
|
||||||
|
|
||||||
// GTE checks that m >= v.
|
|
||||||
func (m Decimal) GTE(v *Decimal) bool { return m.Value >= v.Value }
|
|
||||||
|
|
||||||
// LT checks that m < v.
|
|
||||||
func (m Decimal) LT(v *Decimal) bool { return m.Value < v.Value }
|
|
||||||
|
|
||||||
// LTE checks that m <= v.
|
|
||||||
func (m Decimal) LTE(v *Decimal) bool { return m.Value <= v.Value }
|
|
||||||
|
|
||||||
// Neg returns negative representation of current Decimal (m * -1).
|
|
||||||
func (m Decimal) Neg() *Decimal {
|
|
||||||
return &Decimal{
|
|
||||||
Value: m.Value * -1,
|
|
||||||
Precision: m.Precision,
|
|
||||||
}
|
|
||||||
}
|
|
Binary file not shown.
|
@ -1,18 +0,0 @@
|
||||||
syntax = "proto3";
|
|
||||||
package decimal;
|
|
||||||
option go_package = "github.com/nspcc-dev/neofs-api-go/decimal";
|
|
||||||
option csharp_namespace = "NeoFS.API.Decimal";
|
|
||||||
|
|
||||||
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
|
|
||||||
|
|
||||||
option (gogoproto.stable_marshaler_all) = true;
|
|
||||||
|
|
||||||
// Decimal is a structure used for representation of assets amount
|
|
||||||
message Decimal {
|
|
||||||
option (gogoproto.goproto_stringer) = false;
|
|
||||||
|
|
||||||
// Value is value number
|
|
||||||
int64 Value = 1;
|
|
||||||
// Precision is precision number
|
|
||||||
uint32 Precision = 2;
|
|
||||||
}
|
|
|
@ -1,445 +0,0 @@
|
||||||
package decimal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestDecimal_Parse(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
value float64
|
|
||||||
name string
|
|
||||||
expect *Decimal
|
|
||||||
}{
|
|
||||||
{name: "empty", expect: &Decimal{Precision: GASPrecision}},
|
|
||||||
|
|
||||||
{
|
|
||||||
value: 100,
|
|
||||||
name: "100 GAS",
|
|
||||||
expect: &Decimal{Value: 1e10, Precision: GASPrecision},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for i := range tests {
|
|
||||||
tt := tests[i]
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
require.Equal(t, tt.expect, ParseFloat(tt.value))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDecimal_ParseWithPrecision(t *testing.T) {
|
|
||||||
type args struct {
|
|
||||||
v float64
|
|
||||||
p int
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
args args
|
|
||||||
name string
|
|
||||||
expect *Decimal
|
|
||||||
}{
|
|
||||||
{name: "empty", expect: &Decimal{}},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: "empty precision",
|
|
||||||
expect: &Decimal{Value: 0, Precision: 0},
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: "100 GAS",
|
|
||||||
args: args{100, GASPrecision},
|
|
||||||
expect: &Decimal{Value: 1e10, Precision: GASPrecision},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for i := range tests {
|
|
||||||
tt := tests[i]
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
require.Equal(t, tt.expect,
|
|
||||||
ParseFloatWithPrecision(tt.args.v, tt.args.p))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNew(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
val int64
|
|
||||||
expect *Decimal
|
|
||||||
}{
|
|
||||||
{name: "empty", expect: &Decimal{Value: 0, Precision: GASPrecision}},
|
|
||||||
{name: "100 GAS", val: 1e10, expect: &Decimal{Value: 1e10, Precision: GASPrecision}},
|
|
||||||
}
|
|
||||||
for i := range tests {
|
|
||||||
tt := tests[i]
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
require.Equalf(t, tt.expect, New(tt.val), tt.name)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewGAS(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
val int64
|
|
||||||
expect *Decimal
|
|
||||||
}{
|
|
||||||
{name: "empty", expect: &Decimal{Value: 0, Precision: GASPrecision}},
|
|
||||||
{name: "100 GAS", val: 100, expect: &Decimal{Value: 1e10, Precision: GASPrecision}},
|
|
||||||
}
|
|
||||||
for i := range tests {
|
|
||||||
tt := tests[i]
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
require.Equalf(t, tt.expect, NewGAS(tt.val), tt.name)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func TestNewWithPrecision(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
val int64
|
|
||||||
pre uint32
|
|
||||||
expect *Decimal
|
|
||||||
}{
|
|
||||||
{name: "empty", expect: &Decimal{}},
|
|
||||||
{name: "100 GAS", val: 1e10, pre: GASPrecision, expect: &Decimal{Value: 1e10, Precision: GASPrecision}},
|
|
||||||
}
|
|
||||||
for i := range tests {
|
|
||||||
tt := tests[i]
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
require.Equalf(t, tt.expect, NewWithPrecision(tt.val, tt.pre), tt.name)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDecimal_Neg(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
val int64
|
|
||||||
expect *Decimal
|
|
||||||
}{
|
|
||||||
{name: "empty", expect: &Decimal{Value: 0, Precision: GASPrecision}},
|
|
||||||
{name: "100 GAS", val: 1e10, expect: &Decimal{Value: -1e10, Precision: GASPrecision}},
|
|
||||||
}
|
|
||||||
for i := range tests {
|
|
||||||
tt := tests[i]
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
require.NotPanicsf(t, func() {
|
|
||||||
require.Equalf(t, tt.expect, New(tt.val).Neg(), tt.name)
|
|
||||||
}, tt.name)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDecimal_String(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
expect string
|
|
||||||
value *Decimal
|
|
||||||
}{
|
|
||||||
{name: "empty", expect: "0", value: &Decimal{}},
|
|
||||||
{name: "100 GAS", expect: "100", value: &Decimal{Value: 1e10, Precision: GASPrecision}},
|
|
||||||
{name: "-100 GAS", expect: "-100", value: &Decimal{Value: -1e10, Precision: GASPrecision}},
|
|
||||||
}
|
|
||||||
for i := range tests {
|
|
||||||
tt := tests[i]
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
require.Equalf(t, tt.expect, tt.value.String(), tt.name)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const SomethingElsePrecision = 5
|
|
||||||
|
|
||||||
func TestDecimal_Add(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
expect *Decimal
|
|
||||||
values [2]*Decimal
|
|
||||||
}{
|
|
||||||
{name: "empty", expect: &Decimal{}, values: [2]*Decimal{{}, {}}},
|
|
||||||
{
|
|
||||||
name: "5 GAS + 2 GAS",
|
|
||||||
expect: &Decimal{Value: 7e8, Precision: GASPrecision},
|
|
||||||
values: [2]*Decimal{
|
|
||||||
{Value: 2e8, Precision: GASPrecision},
|
|
||||||
{Value: 5e8, Precision: GASPrecision},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "1e2 + 1e3",
|
|
||||||
expect: &Decimal{Value: 1.1e3, Precision: 3},
|
|
||||||
values: [2]*Decimal{
|
|
||||||
{Value: 1e2, Precision: 2},
|
|
||||||
{Value: 1e3, Precision: 3},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "5 GAS + 10 SomethingElse",
|
|
||||||
expect: &Decimal{Value: 5.01e8, Precision: GASPrecision},
|
|
||||||
values: [2]*Decimal{
|
|
||||||
{Value: 5e8, Precision: GASPrecision},
|
|
||||||
{Value: 1e6, Precision: SomethingElsePrecision},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for i := range tests {
|
|
||||||
tt := tests[i]
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
require.NotPanicsf(t, func() {
|
|
||||||
{ // A + B
|
|
||||||
one := tt.values[0]
|
|
||||||
two := tt.values[1]
|
|
||||||
require.Equalf(t, tt.expect, one.Add(two), tt.name)
|
|
||||||
t.Log(one.Add(two))
|
|
||||||
}
|
|
||||||
|
|
||||||
{ // B + A
|
|
||||||
one := tt.values[0]
|
|
||||||
two := tt.values[1]
|
|
||||||
require.Equalf(t, tt.expect, two.Add(one), tt.name)
|
|
||||||
t.Log(two.Add(one))
|
|
||||||
}
|
|
||||||
}, tt.name)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDecimal_Copy(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
expect *Decimal
|
|
||||||
value *Decimal
|
|
||||||
}{
|
|
||||||
{name: "zero", expect: Zero},
|
|
||||||
{
|
|
||||||
name: "5 GAS",
|
|
||||||
expect: &Decimal{Value: 5e8, Precision: GASPrecision},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "100 GAS",
|
|
||||||
expect: &Decimal{Value: 1e10, Precision: GASPrecision},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for i := range tests {
|
|
||||||
tt := tests[i]
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
require.NotPanicsf(t, func() {
|
|
||||||
require.Equal(t, tt.expect, tt.expect.Copy())
|
|
||||||
}, tt.name)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDecimal_Zero(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
expect bool
|
|
||||||
value *Decimal
|
|
||||||
}{
|
|
||||||
{name: "zero", expect: true, value: Zero},
|
|
||||||
{
|
|
||||||
name: "5 GAS",
|
|
||||||
expect: false,
|
|
||||||
value: &Decimal{Value: 5e8, Precision: GASPrecision},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "100 GAS",
|
|
||||||
expect: false,
|
|
||||||
value: &Decimal{Value: 1e10, Precision: GASPrecision},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for i := range tests {
|
|
||||||
tt := tests[i]
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
require.NotPanicsf(t, func() {
|
|
||||||
require.Truef(t, tt.expect == tt.value.Zero(), tt.name)
|
|
||||||
}, tt.name)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDecimal_Equal(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
expect bool
|
|
||||||
values [2]*Decimal
|
|
||||||
}{
|
|
||||||
{name: "zero == zero", expect: true, values: [2]*Decimal{Zero, Zero}},
|
|
||||||
{
|
|
||||||
name: "5 GAS != 2 GAS",
|
|
||||||
expect: false,
|
|
||||||
values: [2]*Decimal{
|
|
||||||
{Value: 5e8, Precision: GASPrecision},
|
|
||||||
{Value: 2e8, Precision: GASPrecision},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "100 GAS == 100 GAS",
|
|
||||||
expect: true,
|
|
||||||
values: [2]*Decimal{
|
|
||||||
{Value: 1e10, Precision: GASPrecision},
|
|
||||||
{Value: 1e10, Precision: GASPrecision},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for i := range tests {
|
|
||||||
tt := tests[i]
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
require.NotPanicsf(t, func() {
|
|
||||||
require.Truef(t, tt.expect == (tt.values[0].Equal(tt.values[1])), tt.name)
|
|
||||||
}, tt.name)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDecimal_GT(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
expect bool
|
|
||||||
values [2]*Decimal
|
|
||||||
}{
|
|
||||||
{name: "two zeros", expect: false, values: [2]*Decimal{Zero, Zero}},
|
|
||||||
{
|
|
||||||
name: "5 GAS > 2 GAS",
|
|
||||||
expect: true,
|
|
||||||
values: [2]*Decimal{
|
|
||||||
{Value: 5e8, Precision: GASPrecision},
|
|
||||||
{Value: 2e8, Precision: GASPrecision},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "100 GAS !> 100 GAS",
|
|
||||||
expect: false,
|
|
||||||
values: [2]*Decimal{
|
|
||||||
{Value: 1e10, Precision: GASPrecision},
|
|
||||||
{Value: 1e10, Precision: GASPrecision},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for i := range tests {
|
|
||||||
tt := tests[i]
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
require.NotPanicsf(t, func() {
|
|
||||||
require.Truef(t, tt.expect == (tt.values[0].GT(tt.values[1])), tt.name)
|
|
||||||
}, tt.name)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDecimal_GTE(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
expect bool
|
|
||||||
values [2]*Decimal
|
|
||||||
}{
|
|
||||||
{name: "two zeros", expect: true, values: [2]*Decimal{Zero, Zero}},
|
|
||||||
{
|
|
||||||
name: "5 GAS >= 2 GAS",
|
|
||||||
expect: true,
|
|
||||||
values: [2]*Decimal{
|
|
||||||
{Value: 5e8, Precision: GASPrecision},
|
|
||||||
{Value: 2e8, Precision: GASPrecision},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "1 GAS !>= 100 GAS",
|
|
||||||
expect: false,
|
|
||||||
values: [2]*Decimal{
|
|
||||||
{Value: 1e8, Precision: GASPrecision},
|
|
||||||
{Value: 1e10, Precision: GASPrecision},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for i := range tests {
|
|
||||||
tt := tests[i]
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
require.NotPanicsf(t, func() {
|
|
||||||
require.Truef(t, tt.expect == (tt.values[0].GTE(tt.values[1])), tt.name)
|
|
||||||
}, tt.name)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDecimal_LT(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
expect bool
|
|
||||||
values [2]*Decimal
|
|
||||||
}{
|
|
||||||
{name: "two zeros", expect: false, values: [2]*Decimal{Zero, Zero}},
|
|
||||||
{
|
|
||||||
name: "5 GAS !< 2 GAS",
|
|
||||||
expect: false,
|
|
||||||
values: [2]*Decimal{
|
|
||||||
{Value: 5e8, Precision: GASPrecision},
|
|
||||||
{Value: 2e8, Precision: GASPrecision},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "1 GAS < 100 GAS",
|
|
||||||
expect: true,
|
|
||||||
values: [2]*Decimal{
|
|
||||||
{Value: 1e8, Precision: GASPrecision},
|
|
||||||
{Value: 1e10, Precision: GASPrecision},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "100 GAS !< 100 GAS",
|
|
||||||
expect: false,
|
|
||||||
values: [2]*Decimal{
|
|
||||||
{Value: 1e10, Precision: GASPrecision},
|
|
||||||
{Value: 1e10, Precision: GASPrecision},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for i := range tests {
|
|
||||||
tt := tests[i]
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
require.NotPanicsf(t, func() {
|
|
||||||
require.Truef(t, tt.expect == (tt.values[0].LT(tt.values[1])), tt.name)
|
|
||||||
}, tt.name)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDecimal_LTE(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
expect bool
|
|
||||||
values [2]*Decimal
|
|
||||||
}{
|
|
||||||
{name: "two zeros", expect: true, values: [2]*Decimal{Zero, Zero}},
|
|
||||||
{
|
|
||||||
name: "5 GAS <= 2 GAS",
|
|
||||||
expect: false,
|
|
||||||
values: [2]*Decimal{
|
|
||||||
{Value: 5e8, Precision: GASPrecision},
|
|
||||||
{Value: 2e8, Precision: GASPrecision},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "1 GAS <= 100 GAS",
|
|
||||||
expect: true,
|
|
||||||
values: [2]*Decimal{
|
|
||||||
{Value: 1e8, Precision: GASPrecision},
|
|
||||||
{Value: 1e10, Precision: GASPrecision},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "100 GAS !<= 1 GAS",
|
|
||||||
expect: false,
|
|
||||||
values: [2]*Decimal{
|
|
||||||
{Value: 1e10, Precision: GASPrecision},
|
|
||||||
{Value: 1e8, Precision: GASPrecision},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for i := range tests {
|
|
||||||
tt := tests[i]
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
require.NotPanicsf(t, func() {
|
|
||||||
require.Truef(t, tt.expect == (tt.values[0].LTE(tt.values[1])), tt.name)
|
|
||||||
}, tt.name)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
106
hash/hash.go
106
hash/hash.go
|
@ -1,106 +0,0 @@
|
||||||
package hash
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
|
|
||||||
"github.com/gogo/protobuf/proto"
|
|
||||||
"github.com/mr-tron/base58"
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/internal"
|
|
||||||
"github.com/nspcc-dev/tzhash/tz"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HomomorphicHashSize contains size of HH.
|
|
||||||
const HomomorphicHashSize = 64
|
|
||||||
|
|
||||||
// Hash is implementation of HomomorphicHash.
|
|
||||||
type Hash [HomomorphicHashSize]byte
|
|
||||||
|
|
||||||
// ErrWrongDataSize raised when wrong size of bytes is passed to unmarshal HH.
|
|
||||||
const ErrWrongDataSize = internal.Error("wrong data size")
|
|
||||||
|
|
||||||
var (
|
|
||||||
_ internal.Custom = (*Hash)(nil)
|
|
||||||
|
|
||||||
emptyHH [HomomorphicHashSize]byte
|
|
||||||
)
|
|
||||||
|
|
||||||
// Size returns size of Hash (HomomorphicHashSize).
|
|
||||||
func (h Hash) Size() int { return HomomorphicHashSize }
|
|
||||||
|
|
||||||
// Empty checks that Hash is empty.
|
|
||||||
func (h Hash) Empty() bool { return bytes.Equal(h.Bytes(), emptyHH[:]) }
|
|
||||||
|
|
||||||
// Reset sets current Hash to empty value.
|
|
||||||
func (h *Hash) Reset() { *h = Hash{} }
|
|
||||||
|
|
||||||
// ProtoMessage method to satisfy proto.Message interface.
|
|
||||||
func (h Hash) ProtoMessage() {}
|
|
||||||
|
|
||||||
// Bytes represents Hash as bytes.
|
|
||||||
func (h Hash) Bytes() []byte {
|
|
||||||
buf := make([]byte, HomomorphicHashSize)
|
|
||||||
copy(buf, h[:])
|
|
||||||
return h[:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Marshal returns bytes representation of Hash.
|
|
||||||
func (h Hash) Marshal() ([]byte, error) { return h.Bytes(), nil }
|
|
||||||
|
|
||||||
// MarshalTo tries to marshal Hash into passed bytes and returns count of copied bytes.
|
|
||||||
func (h *Hash) MarshalTo(data []byte) (int, error) { return copy(data, h.Bytes()), nil }
|
|
||||||
|
|
||||||
// Unmarshal tries to parse bytes into valid Hash.
|
|
||||||
func (h *Hash) Unmarshal(data []byte) error {
|
|
||||||
if ln := len(data); ln != HomomorphicHashSize {
|
|
||||||
return errors.Wrapf(ErrWrongDataSize, "expect=%d, actual=%d", HomomorphicHashSize, ln)
|
|
||||||
}
|
|
||||||
|
|
||||||
copy((*h)[:], data)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns string representation of Hash.
|
|
||||||
func (h Hash) String() string { return base58.Encode(h[:]) }
|
|
||||||
|
|
||||||
// Equal checks that current Hash is equal to passed Hash.
|
|
||||||
func (h Hash) Equal(hash Hash) bool { return h == hash }
|
|
||||||
|
|
||||||
// Verify validates if current hash generated from passed data.
|
|
||||||
func (h Hash) Verify(data []byte) bool { return h.Equal(Sum(data)) }
|
|
||||||
|
|
||||||
// Validate checks if combined hashes are equal to current Hash.
|
|
||||||
func (h Hash) Validate(hashes []Hash) bool {
|
|
||||||
hashBytes := make([][]byte, 0, len(hashes))
|
|
||||||
for i := range hashes {
|
|
||||||
hashBytes = append(hashBytes, hashes[i].Bytes())
|
|
||||||
}
|
|
||||||
ok, err := tz.Validate(h.Bytes(), hashBytes)
|
|
||||||
return err == nil && ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge used by proto.Clone
|
|
||||||
func (h *Hash) Merge(src proto.Message) {
|
|
||||||
if tmp, ok := src.(*Hash); ok {
|
|
||||||
*h = *tmp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sum returns Tillich-Zémor checksum of data.
|
|
||||||
func Sum(data []byte) Hash { return tz.Sum(data) }
|
|
||||||
|
|
||||||
// Concat combines hashes based on homomorphic property.
|
|
||||||
func Concat(hashes []Hash) (Hash, error) {
|
|
||||||
var (
|
|
||||||
hash Hash
|
|
||||||
h = make([][]byte, 0, len(hashes))
|
|
||||||
)
|
|
||||||
for i := range hashes {
|
|
||||||
h = append(h, hashes[i].Bytes())
|
|
||||||
}
|
|
||||||
cat, err := tz.Concat(h)
|
|
||||||
if err != nil {
|
|
||||||
return hash, err
|
|
||||||
}
|
|
||||||
return hash, hash.Unmarshal(cat)
|
|
||||||
}
|
|
|
@ -1,166 +0,0 @@
|
||||||
package hash
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/rand"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_Sum(t *testing.T) {
|
|
||||||
var (
|
|
||||||
data = []byte("Hello world")
|
|
||||||
sum = Sum(data)
|
|
||||||
hash = []byte{0, 0, 0, 0, 1, 79, 16, 173, 134, 90, 176, 77, 114, 165, 253, 114, 0, 0, 0, 0, 0, 148,
|
|
||||||
172, 222, 98, 248, 15, 99, 205, 129, 66, 91, 0, 0, 0, 0, 0, 138, 173, 39, 228, 231, 239, 123,
|
|
||||||
170, 96, 186, 61, 0, 0, 0, 0, 0, 90, 69, 237, 131, 90, 161, 73, 38, 164, 185, 55}
|
|
||||||
)
|
|
||||||
|
|
||||||
require.Equal(t, hash, sum.Bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_Validate(t *testing.T) {
|
|
||||||
var (
|
|
||||||
data = []byte("Hello world")
|
|
||||||
hash = Sum(data)
|
|
||||||
pieces = splitData(data, 2)
|
|
||||||
ln = len(pieces)
|
|
||||||
hashes = make([]Hash, 0, ln)
|
|
||||||
)
|
|
||||||
|
|
||||||
for i := 0; i < ln; i++ {
|
|
||||||
hashes = append(hashes, Sum(pieces[i]))
|
|
||||||
}
|
|
||||||
|
|
||||||
require.True(t, hash.Validate(hashes))
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_Concat(t *testing.T) {
|
|
||||||
var (
|
|
||||||
data = []byte("Hello world")
|
|
||||||
hash = Sum(data)
|
|
||||||
pieces = splitData(data, 2)
|
|
||||||
ln = len(pieces)
|
|
||||||
hashes = make([]Hash, 0, ln)
|
|
||||||
)
|
|
||||||
|
|
||||||
for i := 0; i < ln; i++ {
|
|
||||||
hashes = append(hashes, Sum(pieces[i]))
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := Concat(hashes)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, hash, res)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_HashChunks(t *testing.T) {
|
|
||||||
var (
|
|
||||||
chars = []byte("+")
|
|
||||||
size = 1400
|
|
||||||
data = bytes.Repeat(chars, size)
|
|
||||||
hash = Sum(data)
|
|
||||||
count = 150
|
|
||||||
)
|
|
||||||
|
|
||||||
hashes, err := dataHashes(data, count)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Len(t, hashes, count)
|
|
||||||
|
|
||||||
require.True(t, hash.Validate(hashes))
|
|
||||||
|
|
||||||
// 100 / 150 = 0
|
|
||||||
hashes, err = dataHashes(data[:100], count)
|
|
||||||
require.Error(t, err)
|
|
||||||
require.Nil(t, hashes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestXOR(t *testing.T) {
|
|
||||||
var (
|
|
||||||
dl = 10
|
|
||||||
data = make([]byte, dl)
|
|
||||||
)
|
|
||||||
|
|
||||||
_, err := rand.Read(data)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
t.Run("XOR with <nil> salt", func(t *testing.T) {
|
|
||||||
res := SaltXOR(data, nil)
|
|
||||||
require.Equal(t, res, data)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("XOR with empty salt", func(t *testing.T) {
|
|
||||||
xorWithSalt(t, data, 0)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("XOR with salt same data size", func(t *testing.T) {
|
|
||||||
xorWithSalt(t, data, dl)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("XOR with salt shorter than data aliquot", func(t *testing.T) {
|
|
||||||
xorWithSalt(t, data, dl/2)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("XOR with salt shorter than data aliquant", func(t *testing.T) {
|
|
||||||
xorWithSalt(t, data, dl/3/+1)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("XOR with salt longer than data aliquot", func(t *testing.T) {
|
|
||||||
xorWithSalt(t, data, dl*2)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("XOR with salt longer than data aliquant", func(t *testing.T) {
|
|
||||||
xorWithSalt(t, data, dl*2-1)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func xorWithSalt(t *testing.T, data []byte, saltSize int) {
|
|
||||||
var (
|
|
||||||
direct, reverse []byte
|
|
||||||
salt = make([]byte, saltSize)
|
|
||||||
)
|
|
||||||
|
|
||||||
_, err := rand.Read(salt)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
direct = SaltXOR(data, salt)
|
|
||||||
require.Len(t, direct, len(data))
|
|
||||||
|
|
||||||
reverse = SaltXOR(direct, salt)
|
|
||||||
require.Len(t, reverse, len(data))
|
|
||||||
|
|
||||||
require.Equal(t, reverse, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func splitData(buf []byte, lim int) [][]byte {
|
|
||||||
var piece []byte
|
|
||||||
pieces := make([][]byte, 0, len(buf)/lim+1)
|
|
||||||
for len(buf) >= lim {
|
|
||||||
piece, buf = buf[:lim], buf[lim:]
|
|
||||||
pieces = append(pieces, piece)
|
|
||||||
}
|
|
||||||
if len(buf) > 0 {
|
|
||||||
pieces = append(pieces, buf)
|
|
||||||
}
|
|
||||||
return pieces
|
|
||||||
}
|
|
||||||
|
|
||||||
func dataHashes(data []byte, count int) ([]Hash, error) {
|
|
||||||
var (
|
|
||||||
ln = len(data)
|
|
||||||
mis = ln / count
|
|
||||||
off = (count - 1) * mis
|
|
||||||
hashes = make([]Hash, 0, count)
|
|
||||||
)
|
|
||||||
if mis == 0 {
|
|
||||||
return nil, errors.Errorf("could not split %d bytes to %d pieces", ln, count)
|
|
||||||
}
|
|
||||||
|
|
||||||
pieces := splitData(data[:off], mis)
|
|
||||||
pieces = append(pieces, data[off:])
|
|
||||||
for i := 0; i < count; i++ {
|
|
||||||
hashes = append(hashes, Sum(pieces[i]))
|
|
||||||
}
|
|
||||||
return hashes, nil
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
package hash
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HashesSlice is a collection that satisfies sort.Interface and can be
|
|
||||||
// sorted by the routines in sort package.
|
|
||||||
type HashesSlice []Hash
|
|
||||||
|
|
||||||
// -- HashesSlice -- an inner type to sort Objects
|
|
||||||
// Len is the number of elements in the collection.
|
|
||||||
func (hs HashesSlice) Len() int { return len(hs) }
|
|
||||||
|
|
||||||
// Less reports whether the element with
|
|
||||||
// index i should be sorted before the element with index j.
|
|
||||||
func (hs HashesSlice) Less(i, j int) bool { return bytes.Compare(hs[i].Bytes(), hs[j].Bytes()) == -1 }
|
|
||||||
|
|
||||||
// Swap swaps the elements with indexes i and j.
|
|
||||||
func (hs HashesSlice) Swap(i, j int) { hs[i], hs[j] = hs[j], hs[i] }
|
|
17
hash/salt.go
17
hash/salt.go
|
@ -1,17 +0,0 @@
|
||||||
package hash
|
|
||||||
|
|
||||||
// SaltXOR xors bits of data with salt
|
|
||||||
// repeating salt if necessary.
|
|
||||||
func SaltXOR(data, salt []byte) (result []byte) {
|
|
||||||
result = make([]byte, len(data))
|
|
||||||
ls := len(salt)
|
|
||||||
if ls == 0 {
|
|
||||||
copy(result, data)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range result {
|
|
||||||
result[i] = data[i] ^ salt[i%ls]
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
package internal
|
|
||||||
|
|
||||||
// Error is a custom error.
|
|
||||||
type Error string
|
|
||||||
|
|
||||||
// Error is an implementation of error interface.
|
|
||||||
func (e Error) Error() string { return string(e) }
|
|
|
@ -1,19 +0,0 @@
|
||||||
package internal
|
|
||||||
|
|
||||||
import "github.com/gogo/protobuf/proto"
|
|
||||||
|
|
||||||
// Custom contains methods to satisfy proto.Message
|
|
||||||
// including custom methods to satisfy protobuf for
|
|
||||||
// non-proto defined types.
|
|
||||||
type Custom interface {
|
|
||||||
Size() int
|
|
||||||
Empty() bool
|
|
||||||
Bytes() []byte
|
|
||||||
Marshal() ([]byte, error)
|
|
||||||
MarshalTo(data []byte) (int, error)
|
|
||||||
Unmarshal(data []byte) error
|
|
||||||
proto.Message
|
|
||||||
|
|
||||||
// Should contains for proto.Clone
|
|
||||||
proto.Merger
|
|
||||||
}
|
|
143
object/doc.go
143
object/doc.go
|
@ -1,143 +0,0 @@
|
||||||
/*
|
|
||||||
Package object manages main storage structure in the system. All storage
|
|
||||||
operations are performed with the objects. During lifetime object might be
|
|
||||||
transformed into another object by cutting its payload or adding meta
|
|
||||||
information. All transformation may be reversed, therefore source object
|
|
||||||
will be able to restore.
|
|
||||||
|
|
||||||
Object structure
|
|
||||||
|
|
||||||
Object consists of Payload and Header. Payload is unlimited but storage nodes
|
|
||||||
may have a policy to store objects with a limited payload. In this case object
|
|
||||||
with large payload will be transformed into the chain of objects with small
|
|
||||||
payload.
|
|
||||||
|
|
||||||
Headers are simple key-value fields that divided into two groups: system
|
|
||||||
headers and extended headers. System headers contain information about
|
|
||||||
protocol version, object id, payload length in bytes, owner id, container id
|
|
||||||
and object creation timestamp (both in epochs and unix time). All these fields
|
|
||||||
must be set up in the correct object.
|
|
||||||
|
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
|
|
||||||
| System Headers |
|
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
|
|
||||||
| Version : 1 |
|
|
||||||
| Payload Length : 21673465 |
|
|
||||||
| Object ID : 465208e2-ba4f-4f99-ad47-82a59f4192d4 |
|
|
||||||
| Owner ID : AShvoCbSZ7VfRiPkVb1tEcBLiJrcbts1tt |
|
|
||||||
| Container ID : FGobtRZA6sBZv2i9k4L7TiTtnuP6E788qa278xfj3Fxj |
|
|
||||||
| Created At : Epoch#10, 1573033162 |
|
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
|
|
||||||
| Extended Headers |
|
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
|
|
||||||
| User Header : <user-defined-key>, <user-defined-value> |
|
|
||||||
| Verification Header : <session public key>, <owner's signature> |
|
|
||||||
| Homomorphic Hash : 0x23d35a56ae... |
|
|
||||||
| Payload Checksum : 0x1bd34abs75... |
|
|
||||||
| Integrity Header : <header checksum>, <session signature> |
|
|
||||||
| Transformation : Payload Split |
|
|
||||||
| Link-parent : cae08935-b4ba-499a-bf6c-98276c1e6c0b |
|
|
||||||
| Link-next : c3b40fbf-3798-4b61-a189-2992b5fb5070 |
|
|
||||||
| Payload Checksum : 0x1f387a5c36... |
|
|
||||||
| Integrity Header : <header checksum>, <session signature> |
|
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
|
|
||||||
| Payload |
|
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
|
|
||||||
| 0xd1581963a342d231... |
|
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
|
|
||||||
|
|
||||||
There are different kinds of extended headers. A correct object must contain
|
|
||||||
verification header, homomorphic hash header, payload checksum and
|
|
||||||
integrity header. The order of headers is matter. Let's look through all
|
|
||||||
these headers.
|
|
||||||
|
|
||||||
Link header points to the connected objects. During object transformation, large
|
|
||||||
object might be transformed into the chain of smaller objects. One of these
|
|
||||||
objects drops payload and has several "Child" links. We call this object as
|
|
||||||
zero-object. Others will have "Parent" link to the zero-object, "Previous"
|
|
||||||
and "Next" links in the payload chain.
|
|
||||||
|
|
||||||
[ Object ID:1 ] = > transformed
|
|
||||||
`- [ Zero-Object ID:1 ]
|
|
||||||
`- Link-child ID:2
|
|
||||||
`- Link-child ID:3
|
|
||||||
`- Link-child ID:4
|
|
||||||
`- Payload [null]
|
|
||||||
`- [ Object ID:2 ]
|
|
||||||
`- Link-parent ID:1
|
|
||||||
`- Link-next ID:3
|
|
||||||
`- Payload [ 0x13ba... ]
|
|
||||||
`- [ Object ID:3 ]
|
|
||||||
`- Link-parent ID:1
|
|
||||||
`- Link-previous ID:2
|
|
||||||
`- Link-next ID:4
|
|
||||||
`- Payload [ 0xcd34... ]
|
|
||||||
`- [ Object ID:4 ]
|
|
||||||
`- Link-parent ID:1
|
|
||||||
`- Link-previous ID:3
|
|
||||||
`- Payload [ 0xef86... ]
|
|
||||||
|
|
||||||
Storage groups are also objects. They have "Storage Group" links to all
|
|
||||||
objects in the group. Links are set by nodes during transformations and,
|
|
||||||
in general, they should not be set by user manually.
|
|
||||||
|
|
||||||
Redirect headers are not used yet, they will be implemented and described
|
|
||||||
later.
|
|
||||||
|
|
||||||
User header is a key-value pair of string that can be defined by user. User
|
|
||||||
can use these headers as search attribute. You can store any meta information
|
|
||||||
about object there, e.g. object's nicename.
|
|
||||||
|
|
||||||
Transformation header notifies that object was transformed by some pre-defined
|
|
||||||
way. This header sets up before object is transformed and all headers after
|
|
||||||
transformation must be located after transformation header. During reverse
|
|
||||||
transformation, all headers under transformation header will be cut out.
|
|
||||||
|
|
||||||
+-+-+-+-+-+-+-+-+-+- +-+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+-+
|
|
||||||
| Payload checksum | | Payload checksum | | Payload checksum |
|
|
||||||
| Integrity header | => | Integrity header | + | Integrity header |
|
|
||||||
+-+-+-+-+-+-+-+-+-+- | Transformation | | Transformation |
|
|
||||||
| Large payload | | New Checksum | | New Checksum |
|
|
||||||
+-+-+-+-+-+-+-+-+-+- | New Integrity | | New Integrity |
|
|
||||||
+-+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+-+
|
|
||||||
| Small payload | | Small payload |
|
|
||||||
+-+-+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+-+-+
|
|
||||||
|
|
||||||
For now, we use only one type of transformation: payload split transformation.
|
|
||||||
This header set up by node automatically.
|
|
||||||
|
|
||||||
Tombstone header notifies that this object was deleted by user. Objects with
|
|
||||||
tombstone header do not have payload, but they still contain meta information
|
|
||||||
in the headers. This way we implement two-phase commit for object removal.
|
|
||||||
Storage nodes will eventually delete all tombstone objects. If you want to
|
|
||||||
delete object, you must create new object with the same object id, with
|
|
||||||
tombstone header, correct signatures and without payload.
|
|
||||||
|
|
||||||
Verification header contains session information. To put the object in
|
|
||||||
the system user must create session. It is required because objects might
|
|
||||||
be transformed and therefore must be re-signed. To do that node creates
|
|
||||||
a pair of session public and private keys. Object owner delegates permission to
|
|
||||||
re-sign objects by signing session public key. This header contains session
|
|
||||||
public key and owner's signature of this key. You must specify this header
|
|
||||||
manually.
|
|
||||||
|
|
||||||
Homomorphic hash header contains homomorphic hash of the source object.
|
|
||||||
Transformations do not affect this header. This header used by data audit and
|
|
||||||
set by node automatically.
|
|
||||||
|
|
||||||
Payload checksum contains checksum of the actual object payload. All payload
|
|
||||||
transformation must set new payload checksum headers. This header set by node
|
|
||||||
automatically.
|
|
||||||
|
|
||||||
Integrity header contains checksum of the header and signature of the
|
|
||||||
session key. This header must be last in the list of extended headers.
|
|
||||||
Checksum is calculated by marshaling all above headers, including system
|
|
||||||
headers. This header set by node automatically.
|
|
||||||
|
|
||||||
Storage group header is presented in storage group objects. It contains
|
|
||||||
information for data audit: size of validated data, homomorphic has of this
|
|
||||||
data, storage group expiration time in epochs or unix time.
|
|
||||||
|
|
||||||
|
|
||||||
*/
|
|
||||||
package object
|
|
|
@ -1,64 +0,0 @@
|
||||||
package object
|
|
||||||
|
|
||||||
// todo: all extensions must be transferred to the separate util library
|
|
||||||
|
|
||||||
import "github.com/nspcc-dev/neofs-api-go/storagegroup"
|
|
||||||
|
|
||||||
// IsLinking checks if object has children links to another objects.
|
|
||||||
// We have to check payload size because zero-object must have zero
|
|
||||||
// payload and non-zero payload length field in system header.
|
|
||||||
func (m Object) IsLinking() bool {
|
|
||||||
for i := range m.Headers {
|
|
||||||
switch v := m.Headers[i].Value.(type) {
|
|
||||||
case *Header_Link:
|
|
||||||
if v.Link.GetType() == Link_Child {
|
|
||||||
return m.SystemHeader.PayloadLength > 0 && len(m.Payload) == 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Links returns slice of ids of specified link type
|
|
||||||
func (m *Object) Links(t Link_Type) []ID {
|
|
||||||
var res []ID
|
|
||||||
for i := range m.Headers {
|
|
||||||
switch v := m.Headers[i].Value.(type) {
|
|
||||||
case *Header_Link:
|
|
||||||
if v.Link.GetType() == t {
|
|
||||||
res = append(res, v.Link.ID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tombstone returns tombstone header if it is presented in extended headers.
|
|
||||||
func (m Object) Tombstone() *Tombstone {
|
|
||||||
_, h := m.LastHeader(HeaderType(TombstoneHdr))
|
|
||||||
if h != nil {
|
|
||||||
return h.Value.(*Header_Tombstone).Tombstone
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsTombstone checks if object has tombstone header.
|
|
||||||
func (m Object) IsTombstone() bool {
|
|
||||||
n, _ := m.LastHeader(HeaderType(TombstoneHdr))
|
|
||||||
return n != -1
|
|
||||||
}
|
|
||||||
|
|
||||||
// StorageGroup returns storage group structure if it is presented in extended headers.
|
|
||||||
func (m Object) StorageGroup() (*storagegroup.StorageGroup, error) {
|
|
||||||
_, sgHdr := m.LastHeader(HeaderType(StorageGroupHdr))
|
|
||||||
if sgHdr == nil {
|
|
||||||
return nil, ErrHeaderNotFound
|
|
||||||
}
|
|
||||||
return sgHdr.Value.(*Header_StorageGroup).StorageGroup, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetStorageGroup sets storage group header in the object.
|
|
||||||
// It will replace existing storage group header or add a new one.
|
|
||||||
func (m *Object) SetStorageGroup(group *storagegroup.StorageGroup) {
|
|
||||||
m.SetHeader(&Header{Value: &Header_StorageGroup{StorageGroup: group}})
|
|
||||||
}
|
|
|
@ -1,196 +0,0 @@
|
||||||
package object
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/hash"
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/internal"
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/refs"
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/service"
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/session"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
// ID is a type alias of object id.
|
|
||||||
ID = refs.ObjectID
|
|
||||||
|
|
||||||
// CID is a type alias of container id.
|
|
||||||
CID = refs.CID
|
|
||||||
|
|
||||||
// SGID is a type alias of storage group id.
|
|
||||||
SGID = refs.SGID
|
|
||||||
|
|
||||||
// OwnerID is a type alias of owner id.
|
|
||||||
OwnerID = refs.OwnerID
|
|
||||||
|
|
||||||
// Hash is a type alias of Homomorphic hash.
|
|
||||||
Hash = hash.Hash
|
|
||||||
|
|
||||||
// Token is a type alias of session token.
|
|
||||||
Token = session.Token
|
|
||||||
|
|
||||||
// Request defines object rpc requests.
|
|
||||||
// All object operations must have TTL, Epoch, Type, Container ID and
|
|
||||||
// permission of usage previous network map.
|
|
||||||
Request interface {
|
|
||||||
service.SeizedRequestMetaContainer
|
|
||||||
|
|
||||||
CID() CID
|
|
||||||
Type() RequestType
|
|
||||||
AllowPreviousNetMap() bool
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// starts enum for amount of bytes.
|
|
||||||
_ int64 = 1 << (10 * iota)
|
|
||||||
|
|
||||||
// UnitsKB defines amount of bytes in one kilobyte.
|
|
||||||
UnitsKB
|
|
||||||
|
|
||||||
// UnitsMB defines amount of bytes in one megabyte.
|
|
||||||
UnitsMB
|
|
||||||
|
|
||||||
// UnitsGB defines amount of bytes in one gigabyte.
|
|
||||||
UnitsGB
|
|
||||||
|
|
||||||
// UnitsTB defines amount of bytes in one terabyte.
|
|
||||||
UnitsTB
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// ErrNotFound is raised when object is not found in the system.
|
|
||||||
ErrNotFound = internal.Error("could not find object")
|
|
||||||
|
|
||||||
// ErrHeaderExpected is raised when first message in protobuf stream does not contain user header.
|
|
||||||
ErrHeaderExpected = internal.Error("expected header as a first message in stream")
|
|
||||||
|
|
||||||
// KeyStorageGroup is a key for a search object by storage group id.
|
|
||||||
KeyStorageGroup = "STORAGE_GROUP"
|
|
||||||
|
|
||||||
// KeyNoChildren is a key for searching object that have no children links.
|
|
||||||
KeyNoChildren = "LEAF"
|
|
||||||
|
|
||||||
// KeyParent is a key for searching object by id of parent object.
|
|
||||||
KeyParent = "PARENT"
|
|
||||||
|
|
||||||
// KeyHasParent is a key for searching object that have parent link.
|
|
||||||
KeyHasParent = "HAS_PAR"
|
|
||||||
|
|
||||||
// KeyTombstone is a key for searching object that have tombstone header.
|
|
||||||
KeyTombstone = "TOMBSTONE"
|
|
||||||
|
|
||||||
// KeyChild is a key for searching object by id of child link.
|
|
||||||
KeyChild = "CHILD"
|
|
||||||
|
|
||||||
// KeyPrev is a key for searching object by id of previous link.
|
|
||||||
KeyPrev = "PREV"
|
|
||||||
|
|
||||||
// KeyNext is a key for searching object by id of next link.
|
|
||||||
KeyNext = "NEXT"
|
|
||||||
|
|
||||||
// KeyID is a key for searching object by object id.
|
|
||||||
KeyID = "ID"
|
|
||||||
|
|
||||||
// KeyCID is a key for searching object by container id.
|
|
||||||
KeyCID = "CID"
|
|
||||||
|
|
||||||
// KeyOwnerID is a key for searching object by owner id.
|
|
||||||
KeyOwnerID = "OWNERID"
|
|
||||||
|
|
||||||
// KeyRootObject is a key for searching object that are zero-object or do
|
|
||||||
// not have any children.
|
|
||||||
KeyRootObject = "ROOT_OBJECT"
|
|
||||||
)
|
|
||||||
|
|
||||||
func checkIsNotFull(v interface{}) bool {
|
|
||||||
var obj *Object
|
|
||||||
|
|
||||||
switch t := v.(type) {
|
|
||||||
case *GetResponse:
|
|
||||||
obj = t.GetObject()
|
|
||||||
case *PutRequest:
|
|
||||||
if h := t.GetHeader(); h != nil {
|
|
||||||
obj = h.Object
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
panic("unknown type")
|
|
||||||
}
|
|
||||||
|
|
||||||
return obj == nil || obj.SystemHeader.PayloadLength != uint64(len(obj.Payload)) && !obj.IsLinking()
|
|
||||||
}
|
|
||||||
|
|
||||||
// NotFull checks if protobuf stream provided whole object for get operation.
|
|
||||||
func (m *GetResponse) NotFull() bool { return checkIsNotFull(m) }
|
|
||||||
|
|
||||||
// NotFull checks if protobuf stream provided whole object for put operation.
|
|
||||||
func (m *PutRequest) NotFull() bool { return checkIsNotFull(m) }
|
|
||||||
|
|
||||||
// CID returns container id value from object put request.
|
|
||||||
func (m *PutRequest) CID() (cid CID) {
|
|
||||||
if header := m.GetHeader(); header == nil {
|
|
||||||
return
|
|
||||||
} else if obj := header.GetObject(); obj == nil {
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
return obj.SystemHeader.CID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CID returns container id value from object get request.
|
|
||||||
func (m *GetRequest) CID() CID { return m.Address.CID }
|
|
||||||
|
|
||||||
// CID returns container id value from object head request.
|
|
||||||
func (m *HeadRequest) CID() CID { return m.Address.CID }
|
|
||||||
|
|
||||||
// CID returns container id value from object search request.
|
|
||||||
func (m *SearchRequest) CID() CID { return m.ContainerID }
|
|
||||||
|
|
||||||
// CID returns container id value from object delete request.
|
|
||||||
func (m *DeleteRequest) CID() CID { return m.Address.CID }
|
|
||||||
|
|
||||||
// CID returns container id value from object get range request.
|
|
||||||
func (m *GetRangeRequest) CID() CID { return m.Address.CID }
|
|
||||||
|
|
||||||
// CID returns container id value from object get range hash request.
|
|
||||||
func (m *GetRangeHashRequest) CID() CID { return m.Address.CID }
|
|
||||||
|
|
||||||
// AllowPreviousNetMap returns permission to use previous network map in object put request.
|
|
||||||
func (m *PutRequest) AllowPreviousNetMap() bool { return false }
|
|
||||||
|
|
||||||
// AllowPreviousNetMap returns permission to use previous network map in object get request.
|
|
||||||
func (m *GetRequest) AllowPreviousNetMap() bool { return true }
|
|
||||||
|
|
||||||
// AllowPreviousNetMap returns permission to use previous network map in object head request.
|
|
||||||
func (m *HeadRequest) AllowPreviousNetMap() bool { return true }
|
|
||||||
|
|
||||||
// AllowPreviousNetMap returns permission to use previous network map in object search request.
|
|
||||||
func (m *SearchRequest) AllowPreviousNetMap() bool { return true }
|
|
||||||
|
|
||||||
// AllowPreviousNetMap returns permission to use previous network map in object delete request.
|
|
||||||
func (m *DeleteRequest) AllowPreviousNetMap() bool { return false }
|
|
||||||
|
|
||||||
// AllowPreviousNetMap returns permission to use previous network map in object get range request.
|
|
||||||
func (m *GetRangeRequest) AllowPreviousNetMap() bool { return false }
|
|
||||||
|
|
||||||
// AllowPreviousNetMap returns permission to use previous network map in object get range hash request.
|
|
||||||
func (m *GetRangeHashRequest) AllowPreviousNetMap() bool { return false }
|
|
||||||
|
|
||||||
// Type returns type of the object put request.
|
|
||||||
func (m *PutRequest) Type() RequestType { return RequestPut }
|
|
||||||
|
|
||||||
// Type returns type of the object get request.
|
|
||||||
func (m *GetRequest) Type() RequestType { return RequestGet }
|
|
||||||
|
|
||||||
// Type returns type of the object head request.
|
|
||||||
func (m *HeadRequest) Type() RequestType { return RequestHead }
|
|
||||||
|
|
||||||
// Type returns type of the object search request.
|
|
||||||
func (m *SearchRequest) Type() RequestType { return RequestSearch }
|
|
||||||
|
|
||||||
// Type returns type of the object delete request.
|
|
||||||
func (m *DeleteRequest) Type() RequestType { return RequestDelete }
|
|
||||||
|
|
||||||
// Type returns type of the object get range request.
|
|
||||||
func (m *GetRangeRequest) Type() RequestType { return RequestRange }
|
|
||||||
|
|
||||||
// Type returns type of the object get range hash request.
|
|
||||||
func (m *GetRangeHashRequest) Type() RequestType { return RequestRangeHash }
|
|
Binary file not shown.
|
@ -1,197 +0,0 @@
|
||||||
syntax = "proto3";
|
|
||||||
package object;
|
|
||||||
option go_package = "github.com/nspcc-dev/neofs-api-go/object";
|
|
||||||
option csharp_namespace = "NeoFS.API.Object";
|
|
||||||
|
|
||||||
import "refs/types.proto";
|
|
||||||
import "object/types.proto";
|
|
||||||
import "service/meta.proto";
|
|
||||||
import "service/verify.proto";
|
|
||||||
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
|
|
||||||
|
|
||||||
option (gogoproto.stable_marshaler_all) = true;
|
|
||||||
|
|
||||||
// Object service provides API for manipulating with the object.
|
|
||||||
service Service {
|
|
||||||
|
|
||||||
// Get the object from container. Response uses gRPC stream. First response
|
|
||||||
// message carry object of requested address. Chunk messages are parts of
|
|
||||||
// the object's payload if it is needed. All messages except first carry
|
|
||||||
// chunks. Requested object can be restored by concatenation of object
|
|
||||||
// message payload and all chunks keeping receiving order.
|
|
||||||
rpc Get(GetRequest) returns (stream GetResponse);
|
|
||||||
|
|
||||||
// Put the object into container. Request uses gRPC stream. First message
|
|
||||||
// SHOULD BE type of PutHeader. Container id and Owner id of object SHOULD
|
|
||||||
// BE set. Session token SHOULD BE obtained before put operation (see
|
|
||||||
// session package). Chunk messages considered by server as part of object
|
|
||||||
// payload. All messages except first SHOULD BE chunks. Chunk messages
|
|
||||||
// SHOULD BE sent in direct order of fragmentation.
|
|
||||||
rpc Put(stream PutRequest) returns (PutResponse);
|
|
||||||
|
|
||||||
// Delete the object from a container
|
|
||||||
rpc Delete(DeleteRequest) returns (DeleteResponse);
|
|
||||||
|
|
||||||
// Head returns the object without data payload. Object in the
|
|
||||||
// response has system header only. If full headers flag is set, extended
|
|
||||||
// headers are also present.
|
|
||||||
rpc Head(HeadRequest) returns (HeadResponse);
|
|
||||||
|
|
||||||
// Search objects in container. Version of query language format SHOULD BE
|
|
||||||
// set to 1. Search query represented in serialized format (see query
|
|
||||||
// package).
|
|
||||||
rpc Search(SearchRequest) returns (stream SearchResponse);
|
|
||||||
|
|
||||||
// GetRange of data payload. Range is a pair (offset, length).
|
|
||||||
// Requested range can be restored by concatenation of all chunks
|
|
||||||
// keeping receiving order.
|
|
||||||
rpc GetRange(GetRangeRequest) returns (stream GetRangeResponse);
|
|
||||||
|
|
||||||
// GetRangeHash returns homomorphic hash of object payload range after XOR
|
|
||||||
// operation. Ranges are set of pairs (offset, length). Hashes order in
|
|
||||||
// response corresponds to ranges order in request. Homomorphic hash is
|
|
||||||
// calculated for XORed data.
|
|
||||||
rpc GetRangeHash(GetRangeHashRequest) returns (GetRangeHashResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
message GetRequest {
|
|
||||||
// Address of object (container id + object id)
|
|
||||||
refs.Address Address = 1 [(gogoproto.nullable) = false];
|
|
||||||
// RequestMetaHeader contains information about request meta headers (should be embedded into message)
|
|
||||||
service.RequestMetaHeader Meta = 98 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
// RequestVerificationHeader is a set of signatures of every NeoFS Node that processed request (should be embedded into message)
|
|
||||||
service.RequestVerificationHeader Verify = 99 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
}
|
|
||||||
|
|
||||||
message GetResponse {
|
|
||||||
oneof R {
|
|
||||||
// Object header and some payload
|
|
||||||
Object object = 1;
|
|
||||||
// Chunk of remaining payload
|
|
||||||
bytes Chunk = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResponseMetaHeader contains meta information based on request processing by server (should be embedded into message)
|
|
||||||
service.ResponseMetaHeader Meta = 99 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
}
|
|
||||||
|
|
||||||
message PutRequest {
|
|
||||||
message PutHeader {
|
|
||||||
// Object with at least container id and owner id fields
|
|
||||||
Object Object = 1;
|
|
||||||
// Number of the object copies to store within the RPC call (zero is processed according to the placement rules)
|
|
||||||
uint32 CopiesNumber = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
oneof R {
|
|
||||||
// Header should be the first message in the stream
|
|
||||||
PutHeader Header = 1;
|
|
||||||
// Chunk should be a remaining message in stream should be chunks
|
|
||||||
bytes Chunk = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequestMetaHeader contains information about request meta headers (should be embedded into message)
|
|
||||||
service.RequestMetaHeader Meta = 98 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
// RequestVerificationHeader is a set of signatures of every NeoFS Node that processed request (should be embedded into message)
|
|
||||||
service.RequestVerificationHeader Verify = 99 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
}
|
|
||||||
|
|
||||||
message PutResponse {
|
|
||||||
// Address of object (container id + object id)
|
|
||||||
refs.Address Address = 1 [(gogoproto.nullable) = false];
|
|
||||||
// ResponseMetaHeader contains meta information based on request processing by server (should be embedded into message)
|
|
||||||
service.ResponseMetaHeader Meta = 99 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
}
|
|
||||||
message DeleteRequest {
|
|
||||||
// Address of object (container id + object id)
|
|
||||||
refs.Address Address = 1 [(gogoproto.nullable) = false];
|
|
||||||
// OwnerID is a wallet address
|
|
||||||
bytes OwnerID = 2 [(gogoproto.nullable) = false, (gogoproto.customtype) = "OwnerID"];
|
|
||||||
// RequestMetaHeader contains information about request meta headers (should be embedded into message)
|
|
||||||
service.RequestMetaHeader Meta = 98 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
// RequestVerificationHeader is a set of signatures of every NeoFS Node that processed request (should be embedded into message)
|
|
||||||
service.RequestVerificationHeader Verify = 99 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteResponse is empty because we cannot guarantee permanent object removal
|
|
||||||
// in distributed system.
|
|
||||||
message DeleteResponse {
|
|
||||||
// ResponseMetaHeader contains meta information based on request processing by server (should be embedded into message)
|
|
||||||
service.ResponseMetaHeader Meta = 99 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
}
|
|
||||||
|
|
||||||
message HeadRequest {
|
|
||||||
// Address of object (container id + object id)
|
|
||||||
refs.Address Address = 1 [(gogoproto.nullable) = false, (gogoproto.customtype) = "Address"];
|
|
||||||
// FullHeaders can be set true for extended headers in the object
|
|
||||||
bool FullHeaders = 2;
|
|
||||||
// RequestMetaHeader contains information about request meta headers (should be embedded into message)
|
|
||||||
service.RequestMetaHeader Meta = 98 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
// RequestVerificationHeader is a set of signatures of every NeoFS Node that processed request (should be embedded into message)
|
|
||||||
service.RequestVerificationHeader Verify = 99 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
}
|
|
||||||
message HeadResponse {
|
|
||||||
// Object without payload
|
|
||||||
Object Object = 1;
|
|
||||||
// ResponseMetaHeader contains meta information based on request processing by server (should be embedded into message)
|
|
||||||
service.ResponseMetaHeader Meta = 99 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
}
|
|
||||||
|
|
||||||
message SearchRequest {
|
|
||||||
// ContainerID for searching the object
|
|
||||||
bytes ContainerID = 1 [(gogoproto.nullable) = false, (gogoproto.customtype) = "CID"];
|
|
||||||
// Query in the binary serialized format
|
|
||||||
bytes Query = 2;
|
|
||||||
// QueryVersion is a version of search query format
|
|
||||||
uint32 QueryVersion = 3;
|
|
||||||
// RequestMetaHeader contains information about request meta headers (should be embedded into message)
|
|
||||||
service.RequestMetaHeader Meta = 98 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
// RequestVerificationHeader is a set of signatures of every NeoFS Node that processed request (should be embedded into message)
|
|
||||||
service.RequestVerificationHeader Verify = 99 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
}
|
|
||||||
|
|
||||||
message SearchResponse {
|
|
||||||
// Addresses of found objects
|
|
||||||
repeated refs.Address Addresses = 1 [(gogoproto.nullable) = false];
|
|
||||||
// ResponseMetaHeader contains meta information based on request processing by server (should be embedded into message)
|
|
||||||
service.ResponseMetaHeader Meta = 99 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
}
|
|
||||||
|
|
||||||
message GetRangeRequest {
|
|
||||||
// Address of object (container id + object id)
|
|
||||||
refs.Address Address = 1 [(gogoproto.nullable) = false];
|
|
||||||
// Range of object's payload to return
|
|
||||||
Range Range = 2 [(gogoproto.nullable) = false];
|
|
||||||
// RequestMetaHeader contains information about request meta headers (should be embedded into message)
|
|
||||||
service.RequestMetaHeader Meta = 98 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
// RequestVerificationHeader is a set of signatures of every NeoFS Node that processed request (should be embedded into message)
|
|
||||||
service.RequestVerificationHeader Verify = 99 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
}
|
|
||||||
|
|
||||||
message GetRangeResponse {
|
|
||||||
// Fragment of object's payload
|
|
||||||
bytes Fragment = 1;
|
|
||||||
// ResponseMetaHeader contains meta information based on request processing by server (should be embedded into message)
|
|
||||||
service.ResponseMetaHeader Meta = 99 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
}
|
|
||||||
|
|
||||||
message GetRangeHashRequest {
|
|
||||||
// Address of object (container id + object id)
|
|
||||||
refs.Address Address = 1 [(gogoproto.nullable) = false];
|
|
||||||
// Ranges of object's payload to calculate homomorphic hash
|
|
||||||
repeated Range Ranges = 2 [(gogoproto.nullable) = false];
|
|
||||||
// Salt is used to XOR object's payload ranges before hashing, it can be nil
|
|
||||||
bytes Salt = 3;
|
|
||||||
// RequestMetaHeader contains information about request meta headers (should be embedded into message)
|
|
||||||
service.RequestMetaHeader Meta = 98 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
// RequestVerificationHeader is a set of signatures of every NeoFS Node that processed request (should be embedded into message)
|
|
||||||
service.RequestVerificationHeader Verify = 99 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
}
|
|
||||||
|
|
||||||
message GetRangeHashResponse {
|
|
||||||
// Hashes is a homomorphic hashes of all ranges
|
|
||||||
repeated bytes Hashes = 1 [(gogoproto.customtype) = "Hash", (gogoproto.nullable) = false];
|
|
||||||
// ResponseMetaHeader contains meta information based on request processing by server (should be embedded into message)
|
|
||||||
service.ResponseMetaHeader Meta = 99 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
package object
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestRequest(t *testing.T) {
|
|
||||||
cases := []Request{
|
|
||||||
&PutRequest{},
|
|
||||||
&GetRequest{},
|
|
||||||
&HeadRequest{},
|
|
||||||
&SearchRequest{},
|
|
||||||
&DeleteRequest{},
|
|
||||||
&GetRangeRequest{},
|
|
||||||
&GetRangeHashRequest{},
|
|
||||||
MakePutRequestHeader(nil),
|
|
||||||
MakePutRequestHeader(&Object{}),
|
|
||||||
}
|
|
||||||
|
|
||||||
types := []RequestType{
|
|
||||||
RequestPut,
|
|
||||||
RequestGet,
|
|
||||||
RequestHead,
|
|
||||||
RequestSearch,
|
|
||||||
RequestDelete,
|
|
||||||
RequestRange,
|
|
||||||
RequestRangeHash,
|
|
||||||
RequestPut,
|
|
||||||
RequestPut,
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range cases {
|
|
||||||
v := cases[i]
|
|
||||||
|
|
||||||
t.Run(fmt.Sprintf("%T_%d", v, i), func(t *testing.T) {
|
|
||||||
require.NotPanics(t, func() { v.CID() })
|
|
||||||
require.Equal(t, types[i], v.Type())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
43
object/sg.go
43
object/sg.go
|
@ -1,43 +0,0 @@
|
||||||
package object
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/refs"
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/storagegroup"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Here are defined getter functions for objects that contain storage group
|
|
||||||
// information.
|
|
||||||
|
|
||||||
var _ storagegroup.Provider = (*Object)(nil)
|
|
||||||
|
|
||||||
// Group returns slice of object ids that are part of a storage group.
|
|
||||||
func (m *Object) Group() []refs.ObjectID {
|
|
||||||
sgLinks := m.Links(Link_StorageGroup)
|
|
||||||
sort.Sort(storagegroup.IDList(sgLinks))
|
|
||||||
return sgLinks
|
|
||||||
}
|
|
||||||
|
|
||||||
// Zones returns validation zones of storage group.
|
|
||||||
func (m *Object) Zones() []storagegroup.ZoneInfo {
|
|
||||||
sgInfo, err := m.StorageGroup()
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return []storagegroup.ZoneInfo{
|
|
||||||
{
|
|
||||||
Hash: sgInfo.ValidationHash,
|
|
||||||
Size: sgInfo.ValidationDataSize,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IDInfo returns meta information about storage group.
|
|
||||||
func (m *Object) IDInfo() *storagegroup.IdentificationInfo {
|
|
||||||
return &storagegroup.IdentificationInfo{
|
|
||||||
SGID: m.SystemHeader.ID,
|
|
||||||
CID: m.SystemHeader.CID,
|
|
||||||
OwnerID: m.SystemHeader.OwnerID,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,88 +0,0 @@
|
||||||
package object
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math/rand"
|
|
||||||
"sort"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/hash"
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/storagegroup"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestObject_StorageGroup(t *testing.T) {
|
|
||||||
t.Run("group method", func(t *testing.T) {
|
|
||||||
var linkCount byte = 100
|
|
||||||
|
|
||||||
obj := &Object{Headers: make([]Header, 0, linkCount)}
|
|
||||||
require.Empty(t, obj.Group())
|
|
||||||
|
|
||||||
idList := make([]ID, linkCount)
|
|
||||||
for i := byte(0); i < linkCount; i++ {
|
|
||||||
idList[i] = ID{i}
|
|
||||||
obj.Headers = append(obj.Headers, Header{
|
|
||||||
Value: &Header_Link{Link: &Link{
|
|
||||||
Type: Link_StorageGroup,
|
|
||||||
ID: idList[i],
|
|
||||||
}},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
rand.Shuffle(len(obj.Headers), func(i, j int) { obj.Headers[i], obj.Headers[j] = obj.Headers[j], obj.Headers[i] })
|
|
||||||
sort.Sort(storagegroup.IDList(idList))
|
|
||||||
require.Equal(t, idList, obj.Group())
|
|
||||||
})
|
|
||||||
t.Run("identification method", func(t *testing.T) {
|
|
||||||
oid, cid, owner := ID{1}, CID{2}, OwnerID{3}
|
|
||||||
obj := &Object{
|
|
||||||
SystemHeader: SystemHeader{
|
|
||||||
ID: oid,
|
|
||||||
OwnerID: owner,
|
|
||||||
CID: cid,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
idInfo := obj.IDInfo()
|
|
||||||
require.Equal(t, oid, idInfo.SGID)
|
|
||||||
require.Equal(t, cid, idInfo.CID)
|
|
||||||
require.Equal(t, owner, idInfo.OwnerID)
|
|
||||||
})
|
|
||||||
t.Run("zones method", func(t *testing.T) {
|
|
||||||
sgSize := uint64(100)
|
|
||||||
|
|
||||||
d := make([]byte, sgSize)
|
|
||||||
_, err := rand.Read(d)
|
|
||||||
require.NoError(t, err)
|
|
||||||
sgHash := hash.Sum(d)
|
|
||||||
|
|
||||||
obj := &Object{
|
|
||||||
Headers: []Header{
|
|
||||||
{
|
|
||||||
Value: &Header_StorageGroup{
|
|
||||||
StorageGroup: &storagegroup.StorageGroup{
|
|
||||||
ValidationDataSize: sgSize,
|
|
||||||
ValidationHash: sgHash,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
sumSize uint64
|
|
||||||
zones = obj.Zones()
|
|
||||||
hashes = make([]Hash, len(zones))
|
|
||||||
)
|
|
||||||
|
|
||||||
for i := range zones {
|
|
||||||
sumSize += zones[i].Size
|
|
||||||
hashes[i] = zones[i].Hash
|
|
||||||
}
|
|
||||||
|
|
||||||
sumHash, err := hash.Concat(hashes)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, sgSize, sumSize)
|
|
||||||
require.Equal(t, sgHash, sumHash)
|
|
||||||
})
|
|
||||||
}
|
|
272
object/sign.go
272
object/sign.go
|
@ -1,272 +0,0 @@
|
||||||
package object
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"encoding/binary"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/service"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SignedData returns payload bytes of the request.
|
|
||||||
//
|
|
||||||
// If payload is nil, ErrHeaderNotFound returns.
|
|
||||||
func (m PutRequest) SignedData() ([]byte, error) {
|
|
||||||
return service.SignedDataFromReader(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadSignedData copies payload bytes to passed buffer.
|
|
||||||
//
|
|
||||||
// If the buffer size is insufficient, io.ErrUnexpectedEOF returns.
|
|
||||||
func (m PutRequest) ReadSignedData(p []byte) (int, error) {
|
|
||||||
r := m.GetR()
|
|
||||||
if r == nil {
|
|
||||||
return 0, ErrHeaderNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
return r.MarshalTo(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedDataSize returns the size of payload of the Put request.
|
|
||||||
//
|
|
||||||
// If payload is nil, -1 returns.
|
|
||||||
func (m PutRequest) SignedDataSize() int {
|
|
||||||
r := m.GetR()
|
|
||||||
if r == nil {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
return r.Size()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedData returns payload bytes of the request.
|
|
||||||
func (m GetRequest) SignedData() ([]byte, error) {
|
|
||||||
return service.SignedDataFromReader(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadSignedData copies payload bytes to passed buffer.
|
|
||||||
//
|
|
||||||
// If the buffer size is insufficient, io.ErrUnexpectedEOF returns.
|
|
||||||
func (m GetRequest) ReadSignedData(p []byte) (int, error) {
|
|
||||||
addr := m.GetAddress()
|
|
||||||
|
|
||||||
if len(p) < m.SignedDataSize() {
|
|
||||||
return 0, io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
|
|
||||||
var off int
|
|
||||||
|
|
||||||
off += copy(p[off:], addr.CID.Bytes())
|
|
||||||
|
|
||||||
off += copy(p[off:], addr.ObjectID.Bytes())
|
|
||||||
|
|
||||||
return off, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedDataSize returns payload size of the request.
|
|
||||||
func (m GetRequest) SignedDataSize() int {
|
|
||||||
return addressSize(m.GetAddress())
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedData returns payload bytes of the request.
|
|
||||||
func (m HeadRequest) SignedData() ([]byte, error) {
|
|
||||||
return service.SignedDataFromReader(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadSignedData copies payload bytes to passed buffer.
|
|
||||||
//
|
|
||||||
// If the buffer size is insufficient, io.ErrUnexpectedEOF returns.
|
|
||||||
func (m HeadRequest) ReadSignedData(p []byte) (int, error) {
|
|
||||||
if len(p) < m.SignedDataSize() {
|
|
||||||
return 0, io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.GetFullHeaders() {
|
|
||||||
p[0] = 1
|
|
||||||
} else {
|
|
||||||
p[0] = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
off := 1
|
|
||||||
|
|
||||||
off += copy(p[off:], m.Address.CID.Bytes())
|
|
||||||
|
|
||||||
off += copy(p[off:], m.Address.ObjectID.Bytes())
|
|
||||||
|
|
||||||
return off, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedDataSize returns payload size of the request.
|
|
||||||
func (m HeadRequest) SignedDataSize() int {
|
|
||||||
return addressSize(m.Address) + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedData returns payload bytes of the request.
|
|
||||||
func (m DeleteRequest) SignedData() ([]byte, error) {
|
|
||||||
return service.SignedDataFromReader(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadSignedData copies payload bytes to passed buffer.
|
|
||||||
//
|
|
||||||
// If the buffer size is insufficient, io.ErrUnexpectedEOF returns.
|
|
||||||
func (m DeleteRequest) ReadSignedData(p []byte) (int, error) {
|
|
||||||
if len(p) < m.SignedDataSize() {
|
|
||||||
return 0, io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
|
|
||||||
var off int
|
|
||||||
|
|
||||||
off += copy(p[off:], m.OwnerID.Bytes())
|
|
||||||
|
|
||||||
off += copy(p[off:], addressBytes(m.Address))
|
|
||||||
|
|
||||||
return off, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedDataSize returns payload size of the request.
|
|
||||||
func (m DeleteRequest) SignedDataSize() int {
|
|
||||||
return m.OwnerID.Size() + addressSize(m.Address)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedData returns payload bytes of the request.
|
|
||||||
func (m GetRangeRequest) SignedData() ([]byte, error) {
|
|
||||||
return service.SignedDataFromReader(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadSignedData copies payload bytes to passed buffer.
|
|
||||||
//
|
|
||||||
// If the buffer size is insufficient, io.ErrUnexpectedEOF returns.
|
|
||||||
func (m GetRangeRequest) ReadSignedData(p []byte) (int, error) {
|
|
||||||
if len(p) < m.SignedDataSize() {
|
|
||||||
return 0, io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err := (&m.Range).MarshalTo(p)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
n += copy(p[n:], addressBytes(m.GetAddress()))
|
|
||||||
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedDataSize returns payload size of the request.
|
|
||||||
func (m GetRangeRequest) SignedDataSize() int {
|
|
||||||
return (&m.Range).Size() + addressSize(m.GetAddress())
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedData returns payload bytes of the request.
|
|
||||||
func (m GetRangeHashRequest) SignedData() ([]byte, error) {
|
|
||||||
return service.SignedDataFromReader(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadSignedData copies payload bytes to passed buffer.
|
|
||||||
//
|
|
||||||
// If the buffer size is insufficient, io.ErrUnexpectedEOF returns.
|
|
||||||
func (m GetRangeHashRequest) ReadSignedData(p []byte) (int, error) {
|
|
||||||
if len(p) < m.SignedDataSize() {
|
|
||||||
return 0, io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
|
|
||||||
var off int
|
|
||||||
|
|
||||||
off += copy(p[off:], addressBytes(m.GetAddress()))
|
|
||||||
|
|
||||||
off += copy(p[off:], rangeSetBytes(m.GetRanges()))
|
|
||||||
|
|
||||||
off += copy(p[off:], m.GetSalt())
|
|
||||||
|
|
||||||
return off, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedDataSize returns payload size of the request.
|
|
||||||
func (m GetRangeHashRequest) SignedDataSize() int {
|
|
||||||
var sz int
|
|
||||||
|
|
||||||
sz += addressSize(m.GetAddress())
|
|
||||||
|
|
||||||
sz += rangeSetSize(m.GetRanges())
|
|
||||||
|
|
||||||
sz += len(m.GetSalt())
|
|
||||||
|
|
||||||
return sz
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedData returns payload bytes of the request.
|
|
||||||
func (m SearchRequest) SignedData() ([]byte, error) {
|
|
||||||
return service.SignedDataFromReader(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadSignedData copies payload bytes to passed buffer.
|
|
||||||
//
|
|
||||||
// If the buffer size is insufficient, io.ErrUnexpectedEOF returns.
|
|
||||||
func (m SearchRequest) ReadSignedData(p []byte) (int, error) {
|
|
||||||
if len(p) < m.SignedDataSize() {
|
|
||||||
return 0, io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
|
|
||||||
var off int
|
|
||||||
|
|
||||||
off += copy(p[off:], m.CID().Bytes())
|
|
||||||
|
|
||||||
binary.BigEndian.PutUint32(p[off:], m.GetQueryVersion())
|
|
||||||
off += 4
|
|
||||||
|
|
||||||
off += copy(p[off:], m.GetQuery())
|
|
||||||
|
|
||||||
return off, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedDataSize returns payload size of the request.
|
|
||||||
func (m SearchRequest) SignedDataSize() int {
|
|
||||||
var sz int
|
|
||||||
|
|
||||||
sz += m.CID().Size()
|
|
||||||
|
|
||||||
sz += 4 // uint32 Version
|
|
||||||
|
|
||||||
sz += len(m.GetQuery())
|
|
||||||
|
|
||||||
return sz
|
|
||||||
}
|
|
||||||
|
|
||||||
func rangeSetSize(rs []Range) int {
|
|
||||||
return 4 + len(rs)*16 // two uint64 fields
|
|
||||||
}
|
|
||||||
|
|
||||||
func rangeSetBytes(rs []Range) []byte {
|
|
||||||
data := make([]byte, rangeSetSize(rs))
|
|
||||||
|
|
||||||
binary.BigEndian.PutUint32(data, uint32(len(rs)))
|
|
||||||
|
|
||||||
off := 4
|
|
||||||
|
|
||||||
for i := range rs {
|
|
||||||
binary.BigEndian.PutUint64(data[off:], rs[i].Offset)
|
|
||||||
off += 8
|
|
||||||
|
|
||||||
binary.BigEndian.PutUint64(data[off:], rs[i].Length)
|
|
||||||
off += 8
|
|
||||||
}
|
|
||||||
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
func addressSize(addr Address) int {
|
|
||||||
return addr.CID.Size() + addr.ObjectID.Size()
|
|
||||||
}
|
|
||||||
|
|
||||||
func addressBytes(addr Address) []byte {
|
|
||||||
return append(addr.CID.Bytes(), addr.ObjectID.Bytes()...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedData returns the result of the ChecksumSignature field getter.
|
|
||||||
func (m IntegrityHeader) SignedData() ([]byte, error) {
|
|
||||||
return m.GetHeadersChecksum(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddSignKey calls the ChecksumSignature field setter with signature argument.
|
|
||||||
func (m *IntegrityHeader) AddSignKey(sign []byte, _ *ecdsa.PublicKey) {
|
|
||||||
m.SetSignature(sign)
|
|
||||||
}
|
|
|
@ -1,239 +0,0 @@
|
||||||
package object
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/service"
|
|
||||||
"github.com/nspcc-dev/neofs-crypto/test"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSignVerifyRequests(t *testing.T) {
|
|
||||||
sk := test.DecodeKey(0)
|
|
||||||
|
|
||||||
type sigType interface {
|
|
||||||
service.RequestData
|
|
||||||
service.SignKeyPairAccumulator
|
|
||||||
service.SignKeyPairSource
|
|
||||||
SetToken(*Token)
|
|
||||||
}
|
|
||||||
|
|
||||||
items := []struct {
|
|
||||||
constructor func() sigType
|
|
||||||
payloadCorrupt []func(sigType)
|
|
||||||
}{
|
|
||||||
{ // PutRequest.PutHeader
|
|
||||||
constructor: func() sigType {
|
|
||||||
return MakePutRequestHeader(new(Object))
|
|
||||||
},
|
|
||||||
payloadCorrupt: []func(sigType){
|
|
||||||
func(s sigType) {
|
|
||||||
obj := s.(*PutRequest).GetR().(*PutRequest_Header).Header.GetObject()
|
|
||||||
obj.SystemHeader.PayloadLength++
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ // PutRequest.Chunk
|
|
||||||
constructor: func() sigType {
|
|
||||||
return MakePutRequestChunk(make([]byte, 10))
|
|
||||||
},
|
|
||||||
payloadCorrupt: []func(sigType){
|
|
||||||
func(s sigType) {
|
|
||||||
h := s.(*PutRequest).GetR().(*PutRequest_Chunk)
|
|
||||||
h.Chunk[0]++
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ // GetRequest
|
|
||||||
constructor: func() sigType {
|
|
||||||
return new(GetRequest)
|
|
||||||
},
|
|
||||||
payloadCorrupt: []func(sigType){
|
|
||||||
func(s sigType) {
|
|
||||||
s.(*GetRequest).Address.CID[0]++
|
|
||||||
},
|
|
||||||
func(s sigType) {
|
|
||||||
s.(*GetRequest).Address.ObjectID[0]++
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ // HeadRequest
|
|
||||||
constructor: func() sigType {
|
|
||||||
return new(HeadRequest)
|
|
||||||
},
|
|
||||||
payloadCorrupt: []func(sigType){
|
|
||||||
func(s sigType) {
|
|
||||||
s.(*HeadRequest).Address.CID[0]++
|
|
||||||
},
|
|
||||||
func(s sigType) {
|
|
||||||
s.(*HeadRequest).Address.ObjectID[0]++
|
|
||||||
},
|
|
||||||
func(s sigType) {
|
|
||||||
s.(*HeadRequest).FullHeaders = true
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ // DeleteRequest
|
|
||||||
constructor: func() sigType {
|
|
||||||
return new(DeleteRequest)
|
|
||||||
},
|
|
||||||
payloadCorrupt: []func(sigType){
|
|
||||||
func(s sigType) {
|
|
||||||
s.(*DeleteRequest).OwnerID[0]++
|
|
||||||
},
|
|
||||||
func(s sigType) {
|
|
||||||
s.(*DeleteRequest).Address.CID[0]++
|
|
||||||
},
|
|
||||||
func(s sigType) {
|
|
||||||
s.(*DeleteRequest).Address.ObjectID[0]++
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ // GetRangeRequest
|
|
||||||
constructor: func() sigType {
|
|
||||||
return new(GetRangeRequest)
|
|
||||||
},
|
|
||||||
payloadCorrupt: []func(sigType){
|
|
||||||
func(s sigType) {
|
|
||||||
s.(*GetRangeRequest).Range.Length++
|
|
||||||
},
|
|
||||||
func(s sigType) {
|
|
||||||
s.(*GetRangeRequest).Range.Offset++
|
|
||||||
},
|
|
||||||
func(s sigType) {
|
|
||||||
s.(*GetRangeRequest).Address.CID[0]++
|
|
||||||
},
|
|
||||||
func(s sigType) {
|
|
||||||
s.(*GetRangeRequest).Address.ObjectID[0]++
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ // GetRangeHashRequest
|
|
||||||
constructor: func() sigType {
|
|
||||||
return &GetRangeHashRequest{
|
|
||||||
Ranges: []Range{{}},
|
|
||||||
Salt: []byte{1, 2, 3},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
payloadCorrupt: []func(sigType){
|
|
||||||
func(s sigType) {
|
|
||||||
s.(*GetRangeHashRequest).Address.CID[0]++
|
|
||||||
},
|
|
||||||
func(s sigType) {
|
|
||||||
s.(*GetRangeHashRequest).Address.ObjectID[0]++
|
|
||||||
},
|
|
||||||
func(s sigType) {
|
|
||||||
s.(*GetRangeHashRequest).Salt[0]++
|
|
||||||
},
|
|
||||||
func(s sigType) {
|
|
||||||
s.(*GetRangeHashRequest).Ranges[0].Length++
|
|
||||||
},
|
|
||||||
func(s sigType) {
|
|
||||||
s.(*GetRangeHashRequest).Ranges[0].Offset++
|
|
||||||
},
|
|
||||||
func(s sigType) {
|
|
||||||
s.(*GetRangeHashRequest).Ranges = nil
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ // GetRangeHashRequest
|
|
||||||
constructor: func() sigType {
|
|
||||||
return &SearchRequest{
|
|
||||||
Query: []byte{1, 2, 3},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
payloadCorrupt: []func(sigType){
|
|
||||||
func(s sigType) {
|
|
||||||
s.(*SearchRequest).ContainerID[0]++
|
|
||||||
},
|
|
||||||
func(s sigType) {
|
|
||||||
s.(*SearchRequest).Query[0]++
|
|
||||||
},
|
|
||||||
func(s sigType) {
|
|
||||||
s.(*SearchRequest).QueryVersion++
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, item := range items {
|
|
||||||
{ // token corruptions
|
|
||||||
v := item.constructor()
|
|
||||||
|
|
||||||
token := new(Token)
|
|
||||||
v.SetToken(token)
|
|
||||||
|
|
||||||
require.NoError(t, service.SignRequestData(sk, v))
|
|
||||||
|
|
||||||
require.NoError(t, service.VerifyRequestData(v))
|
|
||||||
|
|
||||||
token.SetSessionKey(append(token.GetSessionKey(), 1))
|
|
||||||
|
|
||||||
require.Error(t, service.VerifyRequestData(v))
|
|
||||||
}
|
|
||||||
|
|
||||||
{ // payload corruptions
|
|
||||||
for _, corruption := range item.payloadCorrupt {
|
|
||||||
v := item.constructor()
|
|
||||||
|
|
||||||
require.NoError(t, service.SignRequestData(sk, v))
|
|
||||||
|
|
||||||
require.NoError(t, service.VerifyRequestData(v))
|
|
||||||
|
|
||||||
corruption(v)
|
|
||||||
|
|
||||||
require.Error(t, service.VerifyRequestData(v))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHeadRequest_ReadSignedData(t *testing.T) {
|
|
||||||
t.Run("full headers", func(t *testing.T) {
|
|
||||||
req := new(HeadRequest)
|
|
||||||
|
|
||||||
// unset FullHeaders flag
|
|
||||||
req.SetFullHeaders(false)
|
|
||||||
|
|
||||||
// allocate two different buffers for reading
|
|
||||||
buf1 := testData(t, req.SignedDataSize())
|
|
||||||
buf2 := testData(t, req.SignedDataSize())
|
|
||||||
|
|
||||||
// read to both buffers
|
|
||||||
n1, err := req.ReadSignedData(buf1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
n2, err := req.ReadSignedData(buf2)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, buf1[:n1], buf2[:n2])
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func testData(t *testing.T, sz int) []byte {
|
|
||||||
data := make([]byte, sz)
|
|
||||||
|
|
||||||
_, err := rand.Read(data)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIntegrityHeaderSignMethods(t *testing.T) {
|
|
||||||
// create new IntegrityHeader
|
|
||||||
s := new(IntegrityHeader)
|
|
||||||
|
|
||||||
// set test headers checksum
|
|
||||||
s.SetHeadersChecksum([]byte{1, 2, 3})
|
|
||||||
|
|
||||||
data, err := s.SignedData()
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, data, s.GetHeadersChecksum())
|
|
||||||
|
|
||||||
// add signature
|
|
||||||
sig := []byte{4, 5, 6}
|
|
||||||
s.AddSignKey(sig, nil)
|
|
||||||
|
|
||||||
require.Equal(t, sig, s.GetSignature())
|
|
||||||
}
|
|
422
object/types.go
422
object/types.go
|
@ -1,422 +0,0 @@
|
||||||
package object
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/gogo/protobuf/proto"
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/internal"
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/refs"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
// Pred defines a predicate function that can check if passed header
|
|
||||||
// satisfies predicate condition. It is used to find headers of
|
|
||||||
// specific type.
|
|
||||||
Pred = func(*Header) bool
|
|
||||||
|
|
||||||
// Address is a type alias of object Address.
|
|
||||||
Address = refs.Address
|
|
||||||
|
|
||||||
// PositionReader defines object reader that returns slice of bytes
|
|
||||||
// for specified object and data range.
|
|
||||||
PositionReader interface {
|
|
||||||
PRead(ctx context.Context, addr refs.Address, rng Range) ([]byte, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequestType of the object service requests.
|
|
||||||
RequestType int
|
|
||||||
|
|
||||||
headerType int
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// ErrVerifyPayload is raised when payload checksum cannot be verified.
|
|
||||||
ErrVerifyPayload = internal.Error("can't verify payload")
|
|
||||||
|
|
||||||
// ErrVerifyHeader is raised when object integrity cannot be verified.
|
|
||||||
ErrVerifyHeader = internal.Error("can't verify header")
|
|
||||||
|
|
||||||
// ErrHeaderNotFound is raised when requested header not found.
|
|
||||||
ErrHeaderNotFound = internal.Error("header not found")
|
|
||||||
|
|
||||||
// ErrVerifySignature is raised when signature cannot be verified.
|
|
||||||
ErrVerifySignature = internal.Error("can't verify signature")
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
_ headerType = iota
|
|
||||||
// LinkHdr is a link header type.
|
|
||||||
LinkHdr
|
|
||||||
// RedirectHdr is a redirect header type.
|
|
||||||
RedirectHdr
|
|
||||||
// UserHdr is a user defined header type.
|
|
||||||
UserHdr
|
|
||||||
// TransformHdr is a transformation header type.
|
|
||||||
TransformHdr
|
|
||||||
// TombstoneHdr is a tombstone header type.
|
|
||||||
TombstoneHdr
|
|
||||||
// TokenHdr is a token header type.
|
|
||||||
TokenHdr
|
|
||||||
// HomoHashHdr is a homomorphic hash header type.
|
|
||||||
HomoHashHdr
|
|
||||||
// PayloadChecksumHdr is a payload checksum header type.
|
|
||||||
PayloadChecksumHdr
|
|
||||||
// IntegrityHdr is a integrity header type.
|
|
||||||
IntegrityHdr
|
|
||||||
// StorageGroupHdr is a storage group header type.
|
|
||||||
StorageGroupHdr
|
|
||||||
// PublicKeyHdr is a public key header type.
|
|
||||||
PublicKeyHdr
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
_ RequestType = iota
|
|
||||||
// RequestPut is a type for object put request.
|
|
||||||
RequestPut
|
|
||||||
// RequestGet is a type for object get request.
|
|
||||||
RequestGet
|
|
||||||
// RequestHead is a type for object head request.
|
|
||||||
RequestHead
|
|
||||||
// RequestSearch is a type for object search request.
|
|
||||||
RequestSearch
|
|
||||||
// RequestRange is a type for object range request.
|
|
||||||
RequestRange
|
|
||||||
// RequestRangeHash is a type for object hash range request.
|
|
||||||
RequestRangeHash
|
|
||||||
// RequestDelete is a type for object delete request.
|
|
||||||
RequestDelete
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
_ internal.Custom = (*Object)(nil)
|
|
||||||
|
|
||||||
emptyObject = new(Object).Bytes()
|
|
||||||
)
|
|
||||||
|
|
||||||
// String returns printable name of the request type.
|
|
||||||
func (s RequestType) String() string {
|
|
||||||
switch s {
|
|
||||||
case RequestPut:
|
|
||||||
return "PUT"
|
|
||||||
case RequestGet:
|
|
||||||
return "GET"
|
|
||||||
case RequestHead:
|
|
||||||
return "HEAD"
|
|
||||||
case RequestSearch:
|
|
||||||
return "SEARCH"
|
|
||||||
case RequestRange:
|
|
||||||
return "RANGE"
|
|
||||||
case RequestRangeHash:
|
|
||||||
return "RANGE_HASH"
|
|
||||||
case RequestDelete:
|
|
||||||
return "DELETE"
|
|
||||||
default:
|
|
||||||
return "UNKNOWN"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bytes returns marshaled object in a binary format.
|
|
||||||
func (m Object) Bytes() []byte { data, _ := m.Marshal(); return data }
|
|
||||||
|
|
||||||
// Empty checks if object does not contain any information.
|
|
||||||
func (m Object) Empty() bool { return bytes.Equal(m.Bytes(), emptyObject) }
|
|
||||||
|
|
||||||
// LastHeader returns last header of the specified type. Type must be
|
|
||||||
// specified as a Pred function.
|
|
||||||
func (m Object) LastHeader(f Pred) (int, *Header) {
|
|
||||||
for i := len(m.Headers) - 1; i >= 0; i-- {
|
|
||||||
if f != nil && f(&m.Headers[i]) {
|
|
||||||
return i, &m.Headers[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddHeader adds passed header to the end of extended header list.
|
|
||||||
func (m *Object) AddHeader(h *Header) {
|
|
||||||
m.Headers = append(m.Headers, *h)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetPayload sets payload field and payload length in the system header.
|
|
||||||
func (m *Object) SetPayload(payload []byte) {
|
|
||||||
m.Payload = payload
|
|
||||||
m.SystemHeader.PayloadLength = uint64(len(payload))
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetHeader replaces existing extended header or adds new one to the end of
|
|
||||||
// extended header list.
|
|
||||||
func (m *Object) SetHeader(h *Header) {
|
|
||||||
// looking for the header of that type
|
|
||||||
for i := range m.Headers {
|
|
||||||
if m.Headers[i].typeOf(h.Value) {
|
|
||||||
// if we found one - set it with new value and return
|
|
||||||
m.Headers[i] = *h
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// if we did not find one - add this header
|
|
||||||
m.AddHeader(h)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge used by proto.Clone
|
|
||||||
func (m *Object) Merge(src proto.Message) {
|
|
||||||
if tmp, ok := src.(*Object); ok {
|
|
||||||
tmp.CopyTo(m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Header) typeOf(t isHeader_Value) (ok bool) {
|
|
||||||
switch t.(type) {
|
|
||||||
case *Header_Link:
|
|
||||||
_, ok = m.Value.(*Header_Link)
|
|
||||||
case *Header_Redirect:
|
|
||||||
_, ok = m.Value.(*Header_Redirect)
|
|
||||||
case *Header_UserHeader:
|
|
||||||
_, ok = m.Value.(*Header_UserHeader)
|
|
||||||
case *Header_Transform:
|
|
||||||
_, ok = m.Value.(*Header_Transform)
|
|
||||||
case *Header_Tombstone:
|
|
||||||
_, ok = m.Value.(*Header_Tombstone)
|
|
||||||
case *Header_Token:
|
|
||||||
_, ok = m.Value.(*Header_Token)
|
|
||||||
case *Header_HomoHash:
|
|
||||||
_, ok = m.Value.(*Header_HomoHash)
|
|
||||||
case *Header_PayloadChecksum:
|
|
||||||
_, ok = m.Value.(*Header_PayloadChecksum)
|
|
||||||
case *Header_Integrity:
|
|
||||||
_, ok = m.Value.(*Header_Integrity)
|
|
||||||
case *Header_StorageGroup:
|
|
||||||
_, ok = m.Value.(*Header_StorageGroup)
|
|
||||||
case *Header_PublicKey:
|
|
||||||
_, ok = m.Value.(*Header_PublicKey)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// HeaderType returns predicate that check if extended header is a header
|
|
||||||
// of specified type.
|
|
||||||
func HeaderType(t headerType) Pred {
|
|
||||||
switch t {
|
|
||||||
case LinkHdr:
|
|
||||||
return func(h *Header) bool { _, ok := h.Value.(*Header_Link); return ok }
|
|
||||||
case RedirectHdr:
|
|
||||||
return func(h *Header) bool { _, ok := h.Value.(*Header_Redirect); return ok }
|
|
||||||
case UserHdr:
|
|
||||||
return func(h *Header) bool { _, ok := h.Value.(*Header_UserHeader); return ok }
|
|
||||||
case TransformHdr:
|
|
||||||
return func(h *Header) bool { _, ok := h.Value.(*Header_Transform); return ok }
|
|
||||||
case TombstoneHdr:
|
|
||||||
return func(h *Header) bool { _, ok := h.Value.(*Header_Tombstone); return ok }
|
|
||||||
case TokenHdr:
|
|
||||||
return func(h *Header) bool { _, ok := h.Value.(*Header_Token); return ok }
|
|
||||||
case HomoHashHdr:
|
|
||||||
return func(h *Header) bool { _, ok := h.Value.(*Header_HomoHash); return ok }
|
|
||||||
case PayloadChecksumHdr:
|
|
||||||
return func(h *Header) bool { _, ok := h.Value.(*Header_PayloadChecksum); return ok }
|
|
||||||
case IntegrityHdr:
|
|
||||||
return func(h *Header) bool { _, ok := h.Value.(*Header_Integrity); return ok }
|
|
||||||
case StorageGroupHdr:
|
|
||||||
return func(h *Header) bool { _, ok := h.Value.(*Header_StorageGroup); return ok }
|
|
||||||
case PublicKeyHdr:
|
|
||||||
return func(h *Header) bool { _, ok := h.Value.(*Header_PublicKey); return ok }
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy creates full copy of the object.
|
|
||||||
func (m *Object) Copy() (obj *Object) {
|
|
||||||
obj = new(Object)
|
|
||||||
m.CopyTo(obj)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// CopyTo creates fills passed object with the data from the current object.
|
|
||||||
// This function creates copies on every available data slice.
|
|
||||||
func (m *Object) CopyTo(o *Object) {
|
|
||||||
o.SystemHeader = m.SystemHeader
|
|
||||||
|
|
||||||
if m.Headers != nil {
|
|
||||||
o.Headers = make([]Header, len(m.Headers))
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.Payload != nil {
|
|
||||||
o.Payload = make([]byte, len(m.Payload))
|
|
||||||
copy(o.Payload, m.Payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range m.Headers {
|
|
||||||
switch v := m.Headers[i].Value.(type) {
|
|
||||||
case *Header_Link:
|
|
||||||
link := *v.Link
|
|
||||||
o.Headers[i] = Header{
|
|
||||||
Value: &Header_Link{
|
|
||||||
Link: &link,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
case *Header_HomoHash:
|
|
||||||
hash := proto.Clone(&v.HomoHash).(*Hash)
|
|
||||||
o.Headers[i] = Header{
|
|
||||||
Value: &Header_HomoHash{
|
|
||||||
HomoHash: *hash,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
case *Header_Token:
|
|
||||||
token := *v.Token
|
|
||||||
o.Headers[i] = Header{
|
|
||||||
Value: &Header_Token{
|
|
||||||
Token: &token,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
o.Headers[i] = *proto.Clone(&m.Headers[i]).(*Header)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Address returns object's address.
|
|
||||||
func (m Object) Address() *refs.Address {
|
|
||||||
return &refs.Address{
|
|
||||||
ObjectID: m.SystemHeader.ID,
|
|
||||||
CID: m.SystemHeader.CID,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m CreationPoint) String() string {
|
|
||||||
return fmt.Sprintf(`{UnixTime=%d Epoch=%d}`, m.UnixTime, m.Epoch)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stringify converts object into string format.
|
|
||||||
func Stringify(dst io.Writer, obj *Object) error {
|
|
||||||
// put empty line
|
|
||||||
if _, err := fmt.Fprintln(dst); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// put object line
|
|
||||||
if _, err := fmt.Fprintln(dst, "Object:"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// put system headers
|
|
||||||
if _, err := fmt.Fprintln(dst, "\tSystemHeader:"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
sysHeaders := []string{"ID", "CID", "OwnerID", "Version", "PayloadLength", "CreatedAt"}
|
|
||||||
v := reflect.ValueOf(obj.SystemHeader)
|
|
||||||
for _, key := range sysHeaders {
|
|
||||||
if !v.FieldByName(key).IsValid() {
|
|
||||||
return errors.Errorf("invalid system header key: %q", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
val := v.FieldByName(key).Interface()
|
|
||||||
if _, err := fmt.Fprintf(dst, "\t\t- %s=%v\n", key, val); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// put user headers
|
|
||||||
if _, err := fmt.Fprintln(dst, "\tUserHeaders:"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, header := range obj.Headers {
|
|
||||||
var (
|
|
||||||
typ = reflect.ValueOf(header.Value)
|
|
||||||
key string
|
|
||||||
val interface{}
|
|
||||||
)
|
|
||||||
|
|
||||||
switch t := typ.Interface().(type) {
|
|
||||||
case *Header_Link:
|
|
||||||
key = "Link"
|
|
||||||
val = fmt.Sprintf(`{Type=%s ID=%s}`, t.Link.Type, t.Link.ID)
|
|
||||||
case *Header_Redirect:
|
|
||||||
key = "Redirect"
|
|
||||||
val = fmt.Sprintf(`{CID=%s OID=%s}`, t.Redirect.CID, t.Redirect.ObjectID)
|
|
||||||
case *Header_UserHeader:
|
|
||||||
key = "UserHeader"
|
|
||||||
val = fmt.Sprintf(`{Key=%s Val=%s}`, t.UserHeader.Key, t.UserHeader.Value)
|
|
||||||
case *Header_Transform:
|
|
||||||
key = "Transform"
|
|
||||||
val = t.Transform.Type.String()
|
|
||||||
case *Header_Tombstone:
|
|
||||||
key = "Tombstone"
|
|
||||||
val = "MARKED"
|
|
||||||
case *Header_Token:
|
|
||||||
key = "Token"
|
|
||||||
val = fmt.Sprintf("{"+
|
|
||||||
"ID=%s OwnerID=%s Verb=%s Address=%s Created=%d ValidUntil=%d SessionKey=%02x Signature=%02x"+
|
|
||||||
"}",
|
|
||||||
t.Token.GetID(),
|
|
||||||
t.Token.GetOwnerID(),
|
|
||||||
t.Token.GetVerb(),
|
|
||||||
t.Token.GetAddress(),
|
|
||||||
t.Token.CreationEpoch(),
|
|
||||||
t.Token.ExpirationEpoch(),
|
|
||||||
t.Token.GetSessionKey(),
|
|
||||||
t.Token.GetSignature())
|
|
||||||
case *Header_HomoHash:
|
|
||||||
key = "HomoHash"
|
|
||||||
val = t.HomoHash
|
|
||||||
case *Header_PayloadChecksum:
|
|
||||||
key = "PayloadChecksum"
|
|
||||||
val = t.PayloadChecksum
|
|
||||||
case *Header_Integrity:
|
|
||||||
key = "Integrity"
|
|
||||||
val = fmt.Sprintf(`{Checksum=%02x Signature=%02x}`,
|
|
||||||
t.Integrity.HeadersChecksum,
|
|
||||||
t.Integrity.ChecksumSignature)
|
|
||||||
case *Header_StorageGroup:
|
|
||||||
key = "StorageGroup"
|
|
||||||
val = fmt.Sprintf(`{DataSize=%d Hash=%02x Lifetime={Unit=%s Value=%d}}`,
|
|
||||||
t.StorageGroup.ValidationDataSize,
|
|
||||||
t.StorageGroup.ValidationHash,
|
|
||||||
t.StorageGroup.Lifetime.Unit,
|
|
||||||
t.StorageGroup.Lifetime.Value)
|
|
||||||
case *Header_PublicKey:
|
|
||||||
key = "PublicKey"
|
|
||||||
val = t.PublicKey.Value
|
|
||||||
default:
|
|
||||||
key = "Unknown"
|
|
||||||
val = t
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := fmt.Fprintf(dst, "\t\t- Type=%s\n\t\t Value=%v\n", key, val); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// put payload
|
|
||||||
if _, err := fmt.Fprintf(dst, "\tPayload: %#v\n", obj.Payload); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetFullHeaders is a FullHeaders field setter.
|
|
||||||
func (m *HeadRequest) SetFullHeaders(v bool) {
|
|
||||||
m.FullHeaders = v
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSignature is a ChecksumSignature field getter.
|
|
||||||
func (m IntegrityHeader) GetSignature() []byte {
|
|
||||||
return m.ChecksumSignature
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetSignature is a ChecksumSignature field setter.
|
|
||||||
func (m *IntegrityHeader) SetSignature(v []byte) {
|
|
||||||
m.ChecksumSignature = v
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetHeadersChecksum is a HeadersChecksum field setter.
|
|
||||||
func (m *IntegrityHeader) SetHeadersChecksum(v []byte) {
|
|
||||||
m.HeadersChecksum = v
|
|
||||||
}
|
|
Binary file not shown.
|
@ -1,134 +0,0 @@
|
||||||
syntax = "proto3";
|
|
||||||
package object;
|
|
||||||
option go_package = "github.com/nspcc-dev/neofs-api-go/object";
|
|
||||||
option csharp_namespace = "NeoFS.API.Object";
|
|
||||||
|
|
||||||
import "refs/types.proto";
|
|
||||||
import "service/verify.proto";
|
|
||||||
import "storagegroup/types.proto";
|
|
||||||
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
|
|
||||||
|
|
||||||
option (gogoproto.stable_marshaler_all) = true;
|
|
||||||
|
|
||||||
message Range {
|
|
||||||
// Offset of the data range
|
|
||||||
uint64 Offset = 1;
|
|
||||||
// Length of the data range
|
|
||||||
uint64 Length = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message UserHeader {
|
|
||||||
// Key of the user's header
|
|
||||||
string Key = 1;
|
|
||||||
// Value of the user's header
|
|
||||||
string Value = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Header {
|
|
||||||
oneof Value {
|
|
||||||
// Link to other objects
|
|
||||||
Link Link = 1;
|
|
||||||
// Redirect not used yet
|
|
||||||
refs.Address Redirect = 2;
|
|
||||||
// UserHeader is a set of KV headers defined by user
|
|
||||||
UserHeader UserHeader = 3;
|
|
||||||
// Transform defines transform operation (e.g. payload split)
|
|
||||||
Transform Transform = 4;
|
|
||||||
// Tombstone header that set up in deleted objects
|
|
||||||
Tombstone Tombstone = 5;
|
|
||||||
// Token header contains token of the session within which the object was created
|
|
||||||
service.Token Token = 6;
|
|
||||||
// HomoHash is a homomorphic hash of original object payload
|
|
||||||
bytes HomoHash = 7 [(gogoproto.customtype) = "Hash"];
|
|
||||||
// PayloadChecksum of actual object's payload
|
|
||||||
bytes PayloadChecksum = 8;
|
|
||||||
// Integrity header with checksum of all above headers in the object
|
|
||||||
IntegrityHeader Integrity = 9;
|
|
||||||
// StorageGroup contains meta information for the data audit
|
|
||||||
storagegroup.StorageGroup StorageGroup = 10;
|
|
||||||
// PublicKey of owner of the object. Key is used for verification and can be based on NeoID or x509 cert.
|
|
||||||
PublicKey PublicKey = 11;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message Tombstone {}
|
|
||||||
|
|
||||||
message SystemHeader {
|
|
||||||
// Version of the object structure
|
|
||||||
uint64 Version = 1;
|
|
||||||
// PayloadLength is an object payload length
|
|
||||||
uint64 PayloadLength = 2;
|
|
||||||
|
|
||||||
// ID is an object identifier, is a valid UUIDv4
|
|
||||||
bytes ID = 3 [(gogoproto.customtype) = "ID", (gogoproto.nullable) = false];
|
|
||||||
// OwnerID is a wallet address
|
|
||||||
bytes OwnerID = 4 [(gogoproto.customtype) = "OwnerID", (gogoproto.nullable) = false];
|
|
||||||
// CID is a SHA256 hash of the container structure (container identifier)
|
|
||||||
bytes CID = 5 [(gogoproto.customtype) = "CID", (gogoproto.nullable) = false];
|
|
||||||
// CreatedAt is a timestamp of object creation
|
|
||||||
CreationPoint CreatedAt = 6 [(gogoproto.nullable) = false];
|
|
||||||
}
|
|
||||||
|
|
||||||
message CreationPoint {
|
|
||||||
option (gogoproto.goproto_stringer) = false;
|
|
||||||
|
|
||||||
// UnixTime is a date of creation in unixtime format
|
|
||||||
int64 UnixTime = 1;
|
|
||||||
// Epoch is a date of creation in NeoFS epochs
|
|
||||||
uint64 Epoch = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message IntegrityHeader {
|
|
||||||
// HeadersChecksum is a checksum of all above headers in the object
|
|
||||||
bytes HeadersChecksum = 1;
|
|
||||||
// ChecksumSignature is an user's signature of checksum to verify if it is correct
|
|
||||||
bytes ChecksumSignature = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Link {
|
|
||||||
enum Type {
|
|
||||||
Unknown = 0;
|
|
||||||
// Parent object created during object transformation
|
|
||||||
Parent = 1;
|
|
||||||
// Previous object in the linked list created during object transformation
|
|
||||||
Previous = 2;
|
|
||||||
// Next object in the linked list created during object transformation
|
|
||||||
Next = 3;
|
|
||||||
// Child object created during object transformation
|
|
||||||
Child = 4;
|
|
||||||
// Object that included into this storage group
|
|
||||||
StorageGroup = 5;
|
|
||||||
}
|
|
||||||
// Type of link
|
|
||||||
Type type = 1;
|
|
||||||
// ID is an object identifier, is a valid UUIDv4
|
|
||||||
bytes ID = 2 [(gogoproto.customtype) = "ID", (gogoproto.nullable) = false];
|
|
||||||
}
|
|
||||||
|
|
||||||
message Transform {
|
|
||||||
enum Type {
|
|
||||||
Unknown = 0;
|
|
||||||
// Split sets when object created after payload split
|
|
||||||
Split = 1;
|
|
||||||
// Sign sets when object created after re-signing (doesn't used)
|
|
||||||
Sign = 2;
|
|
||||||
// Mould sets when object created after filling missing headers in the object
|
|
||||||
Mould = 3;
|
|
||||||
}
|
|
||||||
// Type of object transformation
|
|
||||||
Type type = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Object {
|
|
||||||
// SystemHeader describes system header
|
|
||||||
SystemHeader SystemHeader = 1 [(gogoproto.nullable) = false];
|
|
||||||
// Headers describes a set of an extended headers
|
|
||||||
repeated Header Headers = 2 [(gogoproto.nullable) = false];
|
|
||||||
// Payload is an object's payload
|
|
||||||
bytes Payload = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message PublicKey {
|
|
||||||
// Value contains marshaled ecdsa public key
|
|
||||||
bytes Value = 1;
|
|
||||||
}
|
|
|
@ -1,234 +0,0 @@
|
||||||
package object
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/gogo/protobuf/proto"
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/refs"
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/service"
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/storagegroup"
|
|
||||||
"github.com/nspcc-dev/neofs-crypto/test"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestStringify(t *testing.T) {
|
|
||||||
res := `
|
|
||||||
Object:
|
|
||||||
SystemHeader:
|
|
||||||
- ID=7e0b9c6c-aabc-4985-949e-2680e577b48b
|
|
||||||
- CID=11111111111111111111111111111111
|
|
||||||
- OwnerID=NQHKh7fKGieCPrPuiEkY58ucRFwWMyU1Mc
|
|
||||||
- Version=1
|
|
||||||
- PayloadLength=1
|
|
||||||
- CreatedAt={UnixTime=1 Epoch=1}
|
|
||||||
UserHeaders:
|
|
||||||
- Type=Link
|
|
||||||
Value={Type=Child ID=7e0b9c6c-aabc-4985-949e-2680e577b48b}
|
|
||||||
- Type=Redirect
|
|
||||||
Value={CID=11111111111111111111111111111111 OID=7e0b9c6c-aabc-4985-949e-2680e577b48b}
|
|
||||||
- Type=UserHeader
|
|
||||||
Value={Key=test_key Val=test_value}
|
|
||||||
- Type=Transform
|
|
||||||
Value=Split
|
|
||||||
- Type=Tombstone
|
|
||||||
Value=MARKED
|
|
||||||
- Type=Token
|
|
||||||
Value={ID=7e0b9c6c-aabc-4985-949e-2680e577b48b OwnerID=NQHKh7fKGieCPrPuiEkY58ucRFwWMyU1Mc Verb=Search Address=11111111111111111111111111111111/7e0b9c6c-aabc-4985-949e-2680e577b48b Created=1 ValidUntil=2 SessionKey=010203040506 Signature=010203040506}
|
|
||||||
- Type=HomoHash
|
|
||||||
Value=1111111111111111111111111111111111111111111111111111111111111111
|
|
||||||
- Type=PayloadChecksum
|
|
||||||
Value=[1 2 3 4 5 6]
|
|
||||||
- Type=Integrity
|
|
||||||
Value={Checksum=010203040506 Signature=010203040506}
|
|
||||||
- Type=StorageGroup
|
|
||||||
Value={DataSize=5 Hash=31313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131313131 Lifetime={Unit=UnixTime Value=555}}
|
|
||||||
- Type=PublicKey
|
|
||||||
Value=[1 2 3 4 5 6]
|
|
||||||
Payload: []byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7}
|
|
||||||
`
|
|
||||||
|
|
||||||
key := test.DecodeKey(0)
|
|
||||||
|
|
||||||
uid, err := refs.NewOwnerID(&key.PublicKey)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
var oid refs.UUID
|
|
||||||
|
|
||||||
require.NoError(t, oid.Parse("7e0b9c6c-aabc-4985-949e-2680e577b48b"))
|
|
||||||
|
|
||||||
obj := &Object{
|
|
||||||
SystemHeader: SystemHeader{
|
|
||||||
Version: 1,
|
|
||||||
PayloadLength: 1,
|
|
||||||
ID: oid,
|
|
||||||
OwnerID: uid,
|
|
||||||
CID: CID{},
|
|
||||||
CreatedAt: CreationPoint{
|
|
||||||
UnixTime: 1,
|
|
||||||
Epoch: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Payload: []byte{1, 2, 3, 4, 5, 6, 7},
|
|
||||||
}
|
|
||||||
|
|
||||||
// *Header_Link
|
|
||||||
obj.Headers = append(obj.Headers, Header{
|
|
||||||
Value: &Header_Link{
|
|
||||||
Link: &Link{ID: oid, Type: Link_Child},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// *Header_Redirect
|
|
||||||
obj.Headers = append(obj.Headers, Header{
|
|
||||||
Value: &Header_Redirect{
|
|
||||||
Redirect: &Address{ObjectID: oid, CID: CID{}},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// *Header_UserHeader
|
|
||||||
obj.Headers = append(obj.Headers, Header{
|
|
||||||
Value: &Header_UserHeader{
|
|
||||||
UserHeader: &UserHeader{
|
|
||||||
Key: "test_key",
|
|
||||||
Value: "test_value",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// *Header_Transform
|
|
||||||
obj.Headers = append(obj.Headers, Header{
|
|
||||||
Value: &Header_Transform{
|
|
||||||
Transform: &Transform{
|
|
||||||
Type: Transform_Split,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// *Header_Tombstone
|
|
||||||
obj.Headers = append(obj.Headers, Header{
|
|
||||||
Value: &Header_Tombstone{
|
|
||||||
Tombstone: &Tombstone{},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
token := new(Token)
|
|
||||||
token.SetID(oid)
|
|
||||||
token.SetOwnerID(uid)
|
|
||||||
token.SetVerb(service.Token_Info_Search)
|
|
||||||
token.SetAddress(Address{ObjectID: oid, CID: refs.CID{}})
|
|
||||||
token.SetCreationEpoch(1)
|
|
||||||
token.SetExpirationEpoch(2)
|
|
||||||
token.SetSessionKey([]byte{1, 2, 3, 4, 5, 6})
|
|
||||||
token.SetSignature([]byte{1, 2, 3, 4, 5, 6})
|
|
||||||
|
|
||||||
// *Header_Token
|
|
||||||
obj.Headers = append(obj.Headers, Header{
|
|
||||||
Value: &Header_Token{
|
|
||||||
Token: token,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// *Header_HomoHash
|
|
||||||
obj.Headers = append(obj.Headers, Header{
|
|
||||||
Value: &Header_HomoHash{
|
|
||||||
HomoHash: Hash{},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// *Header_PayloadChecksum
|
|
||||||
obj.Headers = append(obj.Headers, Header{
|
|
||||||
Value: &Header_PayloadChecksum{
|
|
||||||
PayloadChecksum: []byte{1, 2, 3, 4, 5, 6},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// *Header_Integrity
|
|
||||||
obj.Headers = append(obj.Headers, Header{
|
|
||||||
Value: &Header_Integrity{
|
|
||||||
Integrity: &IntegrityHeader{
|
|
||||||
HeadersChecksum: []byte{1, 2, 3, 4, 5, 6},
|
|
||||||
ChecksumSignature: []byte{1, 2, 3, 4, 5, 6},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// *Header_StorageGroup
|
|
||||||
obj.Headers = append(obj.Headers, Header{
|
|
||||||
Value: &Header_StorageGroup{
|
|
||||||
StorageGroup: &storagegroup.StorageGroup{
|
|
||||||
ValidationDataSize: 5,
|
|
||||||
ValidationHash: storagegroup.Hash{},
|
|
||||||
Lifetime: &storagegroup.StorageGroup_Lifetime{
|
|
||||||
Unit: storagegroup.StorageGroup_Lifetime_UnixTime,
|
|
||||||
Value: 555,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// *Header_PublicKey
|
|
||||||
obj.Headers = append(obj.Headers, Header{
|
|
||||||
Value: &Header_PublicKey{
|
|
||||||
PublicKey: &PublicKey{Value: []byte{1, 2, 3, 4, 5, 6}},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
|
|
||||||
require.NoError(t, Stringify(buf, obj))
|
|
||||||
require.Equal(t, res, buf.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestObject_Copy(t *testing.T) {
|
|
||||||
t.Run("token header", func(t *testing.T) {
|
|
||||||
token := new(Token)
|
|
||||||
token.SetID(service.TokenID{1, 2, 3})
|
|
||||||
|
|
||||||
obj := new(Object)
|
|
||||||
|
|
||||||
obj.AddHeader(&Header{
|
|
||||||
Value: &Header_Token{
|
|
||||||
Token: token,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
{ // Copying
|
|
||||||
cp := obj.Copy()
|
|
||||||
|
|
||||||
_, h := cp.LastHeader(HeaderType(TokenHdr))
|
|
||||||
require.NotNil(t, h)
|
|
||||||
require.Equal(t, token, h.GetValue().(*Header_Token).Token)
|
|
||||||
}
|
|
||||||
|
|
||||||
{ // Cloning
|
|
||||||
cl := proto.Clone(obj).(*Object)
|
|
||||||
require.Equal(t, obj, cl)
|
|
||||||
|
|
||||||
_, h := cl.LastHeader(HeaderType(TokenHdr))
|
|
||||||
h.GetToken().SetID(service.TokenID{3, 2, 1})
|
|
||||||
|
|
||||||
require.NotEqual(t, token, h.GetToken())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIntegrityHeaderGettersSetters(t *testing.T) {
|
|
||||||
t.Run("headers checksum", func(t *testing.T) {
|
|
||||||
data := []byte{1, 2, 3}
|
|
||||||
|
|
||||||
v := new(IntegrityHeader)
|
|
||||||
|
|
||||||
v.SetHeadersChecksum(data)
|
|
||||||
require.Equal(t, data, v.GetHeadersChecksum())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("headers checksum", func(t *testing.T) {
|
|
||||||
data := []byte{1, 2, 3}
|
|
||||||
|
|
||||||
v := new(IntegrityHeader)
|
|
||||||
|
|
||||||
v.SetSignature(data)
|
|
||||||
require.Equal(t, data, v.GetSignature())
|
|
||||||
})
|
|
||||||
}
|
|
100
object/utils.go
100
object/utils.go
|
@ -1,100 +0,0 @@
|
||||||
package object
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FilenameHeader is a user header key for names of files, stored by third
|
|
||||||
// party apps. We recommend to use this header to be compatible with neofs
|
|
||||||
// http gate, neofs minio gate and neofs-dropper application.
|
|
||||||
const FilenameHeader = "filename"
|
|
||||||
|
|
||||||
// ByteSize used to format bytes
|
|
||||||
type ByteSize uint64
|
|
||||||
|
|
||||||
// String represents ByteSize in string format
|
|
||||||
func (b ByteSize) String() string {
|
|
||||||
var (
|
|
||||||
dec int64
|
|
||||||
unit string
|
|
||||||
num = int64(b)
|
|
||||||
)
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case num > UnitsTB:
|
|
||||||
unit = "TB"
|
|
||||||
dec = UnitsTB
|
|
||||||
case num > UnitsGB:
|
|
||||||
unit = "GB"
|
|
||||||
dec = UnitsGB
|
|
||||||
case num > UnitsMB:
|
|
||||||
unit = "MB"
|
|
||||||
dec = UnitsMB
|
|
||||||
case num > UnitsKB:
|
|
||||||
unit = "KB"
|
|
||||||
dec = UnitsKB
|
|
||||||
default:
|
|
||||||
dec = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return strconv.FormatFloat(float64(num)/float64(dec), 'g', 6, 64) + unit
|
|
||||||
}
|
|
||||||
|
|
||||||
// MakePutRequestHeader combines object and session token value
|
|
||||||
// into header of object put request.
|
|
||||||
func MakePutRequestHeader(obj *Object) *PutRequest {
|
|
||||||
return &PutRequest{
|
|
||||||
R: &PutRequest_Header{Header: &PutRequest_PutHeader{
|
|
||||||
Object: obj,
|
|
||||||
}},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MakePutRequestChunk splits data into chunks that will be transferred
|
|
||||||
// in the protobuf stream.
|
|
||||||
func MakePutRequestChunk(chunk []byte) *PutRequest {
|
|
||||||
return &PutRequest{R: &PutRequest_Chunk{Chunk: chunk}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func errMaxSizeExceeded(size uint64) error {
|
|
||||||
return errors.Errorf("object payload size exceed: %s", ByteSize(size).String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReceiveGetResponse receives object by chunks from the protobuf stream
|
|
||||||
// and combine it into single get response structure.
|
|
||||||
func ReceiveGetResponse(c Service_GetClient, maxSize uint64) (*GetResponse, error) {
|
|
||||||
res, err := c.Recv()
|
|
||||||
if err == io.EOF {
|
|
||||||
return res, err
|
|
||||||
} else if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
obj := res.GetObject()
|
|
||||||
if obj == nil {
|
|
||||||
return nil, ErrHeaderExpected
|
|
||||||
}
|
|
||||||
|
|
||||||
if obj.SystemHeader.PayloadLength > maxSize {
|
|
||||||
return nil, errMaxSizeExceeded(maxSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
if res.NotFull() {
|
|
||||||
payload := make([]byte, obj.SystemHeader.PayloadLength)
|
|
||||||
offset := copy(payload, obj.Payload)
|
|
||||||
|
|
||||||
var r *GetResponse
|
|
||||||
for r, err = c.Recv(); err == nil; r, err = c.Recv() {
|
|
||||||
offset += copy(payload[offset:], r.GetChunk())
|
|
||||||
}
|
|
||||||
if err != io.EOF {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
obj.Payload = payload
|
|
||||||
}
|
|
||||||
|
|
||||||
return res, nil
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
package object
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestByteSize_String(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
name string
|
|
||||||
expect string
|
|
||||||
actual ByteSize
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "0 bytes",
|
|
||||||
expect: "0",
|
|
||||||
actual: ByteSize(0),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "101 bytes",
|
|
||||||
expect: "101",
|
|
||||||
actual: ByteSize(101),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "112.84KB",
|
|
||||||
expect: "112.84KB",
|
|
||||||
actual: ByteSize(115548),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "80.44MB",
|
|
||||||
expect: "80.44MB",
|
|
||||||
actual: ByteSize(84347453),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "905.144GB",
|
|
||||||
expect: "905.144GB",
|
|
||||||
actual: ByteSize(971891061884),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "1.857TB",
|
|
||||||
expect: "1.857TB",
|
|
||||||
actual: ByteSize(2041793092780),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range cases {
|
|
||||||
tt := cases[i]
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
require.Equal(t, tt.expect, tt.actual.String())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,149 +0,0 @@
|
||||||
package object
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/sha256"
|
|
||||||
|
|
||||||
crypto "github.com/nspcc-dev/neofs-crypto"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (m Object) headersData(check bool) ([]byte, error) {
|
|
||||||
var bytebuf = new(bytes.Buffer)
|
|
||||||
|
|
||||||
// fixme: we must marshal fields one by one without protobuf marshaling
|
|
||||||
// protobuf marshaling does not guarantee the same result
|
|
||||||
|
|
||||||
if sysheader, err := m.SystemHeader.Marshal(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if _, err := bytebuf.Write(sysheader); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
n, _ := m.LastHeader(HeaderType(IntegrityHdr))
|
|
||||||
for i := range m.Headers {
|
|
||||||
if check && i == n {
|
|
||||||
// ignore last integrity header in order to check headers data
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if header, err := m.Headers[i].Marshal(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if _, err := bytebuf.Write(header); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return bytebuf.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Object) headersChecksum(check bool) ([]byte, error) {
|
|
||||||
data, err := m.headersData(check)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
checksum := sha256.Sum256(data)
|
|
||||||
return checksum[:], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PayloadChecksum calculates sha256 checksum of object payload.
|
|
||||||
func (m Object) PayloadChecksum() []byte {
|
|
||||||
checksum := sha256.Sum256(m.Payload)
|
|
||||||
return checksum[:]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Object) verifySignature(key []byte, ih *IntegrityHeader) error {
|
|
||||||
pk := crypto.UnmarshalPublicKey(key)
|
|
||||||
if crypto.Verify(pk, ih.HeadersChecksum, ih.ChecksumSignature) == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return ErrVerifySignature
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify performs local integrity check by finding verification header and
|
|
||||||
// integrity header. If header integrity is passed, function verifies
|
|
||||||
// checksum of the object payload.
|
|
||||||
// todo: move this verification logic into separate library
|
|
||||||
func (m Object) Verify() error {
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
checksum []byte
|
|
||||||
pubkey []byte
|
|
||||||
)
|
|
||||||
ind, ih := m.LastHeader(HeaderType(IntegrityHdr))
|
|
||||||
if ih == nil || ind != len(m.Headers)-1 {
|
|
||||||
return ErrHeaderNotFound
|
|
||||||
}
|
|
||||||
integrity := ih.Value.(*Header_Integrity).Integrity
|
|
||||||
|
|
||||||
// Prepare structures
|
|
||||||
_, vh := m.LastHeader(HeaderType(TokenHdr))
|
|
||||||
if vh == nil {
|
|
||||||
_, pkh := m.LastHeader(HeaderType(PublicKeyHdr))
|
|
||||||
if pkh == nil {
|
|
||||||
return ErrHeaderNotFound
|
|
||||||
}
|
|
||||||
pubkey = pkh.Value.(*Header_PublicKey).PublicKey.Value
|
|
||||||
} else {
|
|
||||||
pubkey = vh.Value.(*Header_Token).Token.GetSessionKey()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify signature
|
|
||||||
err = m.verifySignature(pubkey, integrity)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "public key: %x", pubkey)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify checksum of header
|
|
||||||
checksum, err = m.headersChecksum(true)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !bytes.Equal(integrity.HeadersChecksum, checksum) {
|
|
||||||
return ErrVerifyHeader
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify checksum of payload
|
|
||||||
if m.SystemHeader.PayloadLength > 0 && !m.IsLinking() {
|
|
||||||
checksum = m.PayloadChecksum()
|
|
||||||
|
|
||||||
_, ph := m.LastHeader(HeaderType(PayloadChecksumHdr))
|
|
||||||
if ph == nil {
|
|
||||||
return ErrHeaderNotFound
|
|
||||||
}
|
|
||||||
if !bytes.Equal(ph.Value.(*Header_PayloadChecksum).PayloadChecksum, checksum) {
|
|
||||||
return ErrVerifyPayload
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateIntegrityHeader returns signed integrity header for the object
|
|
||||||
func CreateIntegrityHeader(obj *Object, key *ecdsa.PrivateKey) (*Header, error) {
|
|
||||||
headerChecksum, err := obj.headersChecksum(false)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
headerChecksumSignature, err := crypto.Sign(key, headerChecksum)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Header{Value: &Header_Integrity{
|
|
||||||
Integrity: &IntegrityHeader{
|
|
||||||
HeadersChecksum: headerChecksum,
|
|
||||||
ChecksumSignature: headerChecksumSignature,
|
|
||||||
},
|
|
||||||
}}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign creates new integrity header and adds it to the end of the list of
|
|
||||||
// extended headers.
|
|
||||||
func (m *Object) Sign(key *ecdsa.PrivateKey) error {
|
|
||||||
ih, err := CreateIntegrityHeader(m, key)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
m.AddHeader(ih)
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,144 +0,0 @@
|
||||||
package object
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/container"
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/refs"
|
|
||||||
crypto "github.com/nspcc-dev/neofs-crypto"
|
|
||||||
"github.com/nspcc-dev/neofs-crypto/test"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestObject_Verify(t *testing.T) {
|
|
||||||
key := test.DecodeKey(0)
|
|
||||||
sessionkey := test.DecodeKey(1)
|
|
||||||
|
|
||||||
payload := make([]byte, 1024*1024)
|
|
||||||
|
|
||||||
cnr, err := container.NewTestContainer()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
cid, err := cnr.ID()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
id, err := uuid.NewRandom()
|
|
||||||
uid := refs.UUID(id)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
obj := &Object{
|
|
||||||
SystemHeader: SystemHeader{
|
|
||||||
ID: uid,
|
|
||||||
CID: cid,
|
|
||||||
OwnerID: refs.OwnerID([refs.OwnerIDSize]byte{}),
|
|
||||||
},
|
|
||||||
Headers: []Header{
|
|
||||||
{
|
|
||||||
Value: &Header_UserHeader{
|
|
||||||
UserHeader: &UserHeader{
|
|
||||||
Key: "Profession",
|
|
||||||
Value: "Developer",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Value: &Header_UserHeader{
|
|
||||||
UserHeader: &UserHeader{
|
|
||||||
Key: "Language",
|
|
||||||
Value: "GO",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
obj.SetPayload(payload)
|
|
||||||
obj.SetHeader(&Header{Value: &Header_PayloadChecksum{[]byte("incorrect checksum")}})
|
|
||||||
|
|
||||||
t.Run("error no integrity header and pubkey", func(t *testing.T) {
|
|
||||||
err = obj.Verify()
|
|
||||||
require.EqualError(t, err, ErrHeaderNotFound.Error())
|
|
||||||
})
|
|
||||||
|
|
||||||
badHeaderChecksum := []byte("incorrect checksum")
|
|
||||||
signature, err := crypto.Sign(sessionkey, badHeaderChecksum)
|
|
||||||
require.NoError(t, err)
|
|
||||||
ih := &IntegrityHeader{
|
|
||||||
HeadersChecksum: badHeaderChecksum,
|
|
||||||
ChecksumSignature: signature,
|
|
||||||
}
|
|
||||||
obj.SetHeader(&Header{Value: &Header_Integrity{ih}})
|
|
||||||
|
|
||||||
t.Run("error no validation header", func(t *testing.T) {
|
|
||||||
err = obj.Verify()
|
|
||||||
require.EqualError(t, err, ErrHeaderNotFound.Error())
|
|
||||||
})
|
|
||||||
|
|
||||||
dataPK := crypto.MarshalPublicKey(&sessionkey.PublicKey)
|
|
||||||
signature, err = crypto.Sign(key, dataPK)
|
|
||||||
tok := new(Token)
|
|
||||||
tok.SetSignature(signature)
|
|
||||||
tok.SetSessionKey(dataPK)
|
|
||||||
obj.AddHeader(&Header{Value: &Header_Token{Token: tok}})
|
|
||||||
|
|
||||||
// validation header is not last
|
|
||||||
t.Run("error validation header is not last", func(t *testing.T) {
|
|
||||||
err = obj.Verify()
|
|
||||||
require.EqualError(t, err, ErrHeaderNotFound.Error())
|
|
||||||
})
|
|
||||||
|
|
||||||
obj.Headers = obj.Headers[:len(obj.Headers)-2]
|
|
||||||
obj.AddHeader(&Header{Value: &Header_Token{Token: tok}})
|
|
||||||
obj.SetHeader(&Header{Value: &Header_Integrity{ih}})
|
|
||||||
|
|
||||||
t.Run("error invalid header checksum", func(t *testing.T) {
|
|
||||||
err = obj.Verify()
|
|
||||||
require.EqualError(t, err, ErrVerifyHeader.Error())
|
|
||||||
})
|
|
||||||
|
|
||||||
obj.Headers = obj.Headers[:len(obj.Headers)-1]
|
|
||||||
genIH, err := CreateIntegrityHeader(obj, sessionkey)
|
|
||||||
require.NoError(t, err)
|
|
||||||
obj.SetHeader(genIH)
|
|
||||||
|
|
||||||
t.Run("error invalid payload checksum", func(t *testing.T) {
|
|
||||||
err = obj.Verify()
|
|
||||||
require.EqualError(t, err, ErrVerifyPayload.Error())
|
|
||||||
})
|
|
||||||
|
|
||||||
obj.SetHeader(&Header{Value: &Header_PayloadChecksum{obj.PayloadChecksum()}})
|
|
||||||
|
|
||||||
obj.Headers = obj.Headers[:len(obj.Headers)-1]
|
|
||||||
genIH, err = CreateIntegrityHeader(obj, sessionkey)
|
|
||||||
require.NoError(t, err)
|
|
||||||
obj.SetHeader(genIH)
|
|
||||||
|
|
||||||
t.Run("correct with tok", func(t *testing.T) {
|
|
||||||
err = obj.Verify()
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
|
|
||||||
pkh := Header{Value: &Header_PublicKey{&PublicKey{
|
|
||||||
Value: crypto.MarshalPublicKey(&key.PublicKey),
|
|
||||||
}}}
|
|
||||||
// replace tok with pkh
|
|
||||||
obj.Headers[len(obj.Headers)-2] = pkh
|
|
||||||
// re-sign object
|
|
||||||
obj.Sign(sessionkey)
|
|
||||||
|
|
||||||
t.Run("incorrect with bad public key", func(t *testing.T) {
|
|
||||||
err = obj.Verify()
|
|
||||||
require.Error(t, err)
|
|
||||||
})
|
|
||||||
|
|
||||||
obj.SetHeader(&Header{Value: &Header_PublicKey{&PublicKey{
|
|
||||||
Value: dataPK,
|
|
||||||
}}})
|
|
||||||
obj.Sign(sessionkey)
|
|
||||||
|
|
||||||
t.Run("correct with good public key", func(t *testing.T) {
|
|
||||||
err = obj.Verify()
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
package query
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/gogo/protobuf/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
_ proto.Message = (*Query)(nil)
|
|
||||||
_ proto.Message = (*Filter)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
// String returns string representation of Filter.
|
|
||||||
func (m Filter) String() string {
|
|
||||||
b := new(strings.Builder)
|
|
||||||
b.WriteString("<Filter '$" + m.Name + "' ")
|
|
||||||
switch m.Type {
|
|
||||||
case Filter_Exact:
|
|
||||||
b.WriteString("==")
|
|
||||||
case Filter_Regex:
|
|
||||||
b.WriteString("~=")
|
|
||||||
default:
|
|
||||||
b.WriteString("??")
|
|
||||||
}
|
|
||||||
b.WriteString(" '" + m.Value + "'>")
|
|
||||||
return b.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns string representation of Query.
|
|
||||||
func (m Query) String() string {
|
|
||||||
b := new(strings.Builder)
|
|
||||||
b.WriteString("<Query [")
|
|
||||||
ln := len(m.Filters)
|
|
||||||
for i := 0; i < ln; i++ {
|
|
||||||
b.WriteString(m.Filters[i].String())
|
|
||||||
if ln-1 != i {
|
|
||||||
b.WriteByte(',')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
b.WriteByte(']')
|
|
||||||
return b.String()
|
|
||||||
}
|
|
Binary file not shown.
|
@ -1,33 +0,0 @@
|
||||||
syntax = "proto3";
|
|
||||||
package query;
|
|
||||||
option go_package = "github.com/nspcc-dev/neofs-api-go/query";
|
|
||||||
option csharp_namespace = "NeoFS.API.Query";
|
|
||||||
|
|
||||||
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
|
|
||||||
|
|
||||||
option (gogoproto.stable_marshaler_all) = true;
|
|
||||||
|
|
||||||
message Filter {
|
|
||||||
option (gogoproto.goproto_stringer) = false;
|
|
||||||
|
|
||||||
enum Type {
|
|
||||||
// Exact sets when value of filter should be equal to the header value
|
|
||||||
Exact = 0;
|
|
||||||
// Regex sets when value of filter should match the header value by the regular expression
|
|
||||||
Regex = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type of filter
|
|
||||||
Type type = 1 [(gogoproto.customname) = "Type"];
|
|
||||||
// Name of field that should be filtered
|
|
||||||
string Name = 2;
|
|
||||||
// Value that should be used for filter
|
|
||||||
string Value = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Query {
|
|
||||||
option (gogoproto.goproto_stringer) = false;
|
|
||||||
|
|
||||||
// Filters is set of filters, should not be empty
|
|
||||||
repeated Filter Filters = 1 [(gogoproto.nullable) = false];
|
|
||||||
}
|
|
|
@ -1,80 +0,0 @@
|
||||||
package refs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/sha256"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/gogo/protobuf/proto"
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
joinSeparator = "/"
|
|
||||||
|
|
||||||
// ErrWrongAddress is raised when wrong address is passed to Address.Parse ParseAddress.
|
|
||||||
ErrWrongAddress = internal.Error("wrong address")
|
|
||||||
|
|
||||||
// ErrEmptyAddress is raised when empty address is passed to Address.Parse ParseAddress.
|
|
||||||
ErrEmptyAddress = internal.Error("empty address")
|
|
||||||
)
|
|
||||||
|
|
||||||
// ParseAddress parses address from string representation into new Address.
|
|
||||||
func ParseAddress(str string) (*Address, error) {
|
|
||||||
var addr Address
|
|
||||||
return &addr, addr.Parse(str)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse parses address from string representation into current Address.
|
|
||||||
func (m *Address) Parse(addr string) error {
|
|
||||||
if m == nil {
|
|
||||||
return ErrEmptyAddress
|
|
||||||
}
|
|
||||||
|
|
||||||
items := strings.Split(addr, joinSeparator)
|
|
||||||
if len(items) != 2 {
|
|
||||||
return ErrWrongAddress
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := m.CID.Parse(items[0]); err != nil {
|
|
||||||
return err
|
|
||||||
} else if err := m.ObjectID.Parse(items[1]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns string representation of Address.
|
|
||||||
func (m Address) String() string {
|
|
||||||
return strings.Join([]string{m.CID.String(), m.ObjectID.String()}, joinSeparator)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsFull checks that ContainerID and ObjectID is not empty.
|
|
||||||
func (m Address) IsFull() bool {
|
|
||||||
return !m.CID.Empty() && !m.ObjectID.Empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equal checks that current Address is equal to passed Address.
|
|
||||||
func (m Address) Equal(a2 *Address) bool {
|
|
||||||
return m.CID.Equal(a2.CID) && m.ObjectID.Equal(a2.ObjectID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hash returns []byte that used as a key for storage bucket.
|
|
||||||
func (m Address) Hash() ([]byte, error) {
|
|
||||||
if !m.IsFull() {
|
|
||||||
return nil, ErrEmptyAddress
|
|
||||||
}
|
|
||||||
h := sha256.Sum256(append(m.ObjectID.Bytes(), m.CID.Bytes()...))
|
|
||||||
return h[:], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge used by proto.Clone
|
|
||||||
func (m *Address) Merge(src proto.Message) {
|
|
||||||
if addr, ok := src.(*Address); ok {
|
|
||||||
cid := proto.Clone(&addr.CID).(*CID)
|
|
||||||
oid := proto.Clone(&addr.ObjectID).(*ObjectID)
|
|
||||||
|
|
||||||
m.CID = *cid
|
|
||||||
m.ObjectID = *oid
|
|
||||||
}
|
|
||||||
}
|
|
104
refs/cid.go
104
refs/cid.go
|
@ -1,104 +0,0 @@
|
||||||
package refs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/sha256"
|
|
||||||
|
|
||||||
"github.com/gogo/protobuf/proto"
|
|
||||||
"github.com/mr-tron/base58"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CIDForBytes creates CID for passed bytes.
|
|
||||||
func CIDForBytes(data []byte) CID { return sha256.Sum256(data) }
|
|
||||||
|
|
||||||
// CIDFromBytes parses CID from passed bytes.
|
|
||||||
func CIDFromBytes(data []byte) (cid CID, err error) {
|
|
||||||
if ln := len(data); ln != CIDSize {
|
|
||||||
return CID{}, errors.Wrapf(ErrWrongDataSize, "expect=%d, actual=%d", CIDSize, ln)
|
|
||||||
}
|
|
||||||
|
|
||||||
copy(cid[:], data)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// CIDFromString parses CID from string representation of CID.
|
|
||||||
func CIDFromString(c string) (CID, error) {
|
|
||||||
var cid CID
|
|
||||||
decoded, err := base58.Decode(c)
|
|
||||||
if err != nil {
|
|
||||||
return cid, err
|
|
||||||
}
|
|
||||||
return CIDFromBytes(decoded)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Size returns size of CID (CIDSize).
|
|
||||||
func (c CID) Size() int { return CIDSize }
|
|
||||||
|
|
||||||
// Parse tries to parse CID from string representation.
|
|
||||||
func (c *CID) Parse(cid string) error {
|
|
||||||
var err error
|
|
||||||
if *c, err = CIDFromString(cid); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Empty checks that current CID is empty.
|
|
||||||
func (c CID) Empty() bool { return bytes.Equal(c.Bytes(), emptyCID) }
|
|
||||||
|
|
||||||
// Equal checks that current CID is equal to passed CID.
|
|
||||||
func (c CID) Equal(cid CID) bool { return bytes.Equal(c.Bytes(), cid.Bytes()) }
|
|
||||||
|
|
||||||
// Marshal returns CID bytes representation.
|
|
||||||
func (c CID) Marshal() ([]byte, error) { return c.Bytes(), nil }
|
|
||||||
|
|
||||||
// MarshalBinary returns CID bytes representation.
|
|
||||||
func (c CID) MarshalBinary() ([]byte, error) { return c.Bytes(), nil }
|
|
||||||
|
|
||||||
// MarshalTo marshal CID to bytes representation into passed bytes.
|
|
||||||
func (c *CID) MarshalTo(data []byte) (int, error) { return copy(data, c.Bytes()), nil }
|
|
||||||
|
|
||||||
// ProtoMessage method to satisfy proto.Message interface.
|
|
||||||
func (c CID) ProtoMessage() {}
|
|
||||||
|
|
||||||
// String returns string representation of CID.
|
|
||||||
func (c CID) String() string { return base58.Encode(c[:]) }
|
|
||||||
|
|
||||||
// Reset resets current CID to zero value.
|
|
||||||
func (c *CID) Reset() { *c = CID{} }
|
|
||||||
|
|
||||||
// Bytes returns CID bytes representation.
|
|
||||||
func (c CID) Bytes() []byte {
|
|
||||||
buf := make([]byte, CIDSize)
|
|
||||||
copy(buf, c[:])
|
|
||||||
return buf
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalBinary tries to parse bytes representation of CID.
|
|
||||||
func (c *CID) UnmarshalBinary(data []byte) error { return c.Unmarshal(data) }
|
|
||||||
|
|
||||||
// Unmarshal tries to parse bytes representation of CID.
|
|
||||||
func (c *CID) Unmarshal(data []byte) error {
|
|
||||||
if ln := len(data); ln != CIDSize {
|
|
||||||
return errors.Wrapf(ErrWrongDataSize, "expect=%d, actual=%d", CIDSize, ln)
|
|
||||||
}
|
|
||||||
|
|
||||||
copy((*c)[:], data)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify validates that current CID is generated for passed bytes data.
|
|
||||||
func (c CID) Verify(data []byte) error {
|
|
||||||
if id := CIDForBytes(data); !bytes.Equal(c[:], id[:]) {
|
|
||||||
return errors.New("wrong hash for data")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge used by proto.Clone
|
|
||||||
func (c *CID) Merge(src proto.Message) {
|
|
||||||
if cid, ok := src.(*CID); ok {
|
|
||||||
*c = *cid
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,77 +0,0 @@
|
||||||
package refs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/ecdsa"
|
|
||||||
|
|
||||||
"github.com/gogo/protobuf/proto"
|
|
||||||
"github.com/mr-tron/base58"
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/chain"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewOwnerID returns generated OwnerID from passed public key.
|
|
||||||
func NewOwnerID(key *ecdsa.PublicKey) (owner OwnerID, err error) {
|
|
||||||
if key == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var d []byte
|
|
||||||
|
|
||||||
d, err = base58.Decode(chain.KeyToAddress(key))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
copy(owner[:], d)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Size returns OwnerID size in bytes (OwnerIDSize).
|
|
||||||
func (OwnerID) Size() int { return OwnerIDSize }
|
|
||||||
|
|
||||||
// Empty checks that current OwnerID is empty value.
|
|
||||||
func (o OwnerID) Empty() bool { return bytes.Equal(o.Bytes(), emptyOwner) }
|
|
||||||
|
|
||||||
// Equal checks that current OwnerID is equal to passed OwnerID.
|
|
||||||
func (o OwnerID) Equal(id OwnerID) bool { return bytes.Equal(o.Bytes(), id.Bytes()) }
|
|
||||||
|
|
||||||
// Reset sets current OwnerID to empty value.
|
|
||||||
func (o *OwnerID) Reset() { *o = OwnerID{} }
|
|
||||||
|
|
||||||
// ProtoMessage method to satisfy proto.Message interface.
|
|
||||||
func (OwnerID) ProtoMessage() {}
|
|
||||||
|
|
||||||
// Marshal returns OwnerID bytes representation.
|
|
||||||
func (o OwnerID) Marshal() ([]byte, error) { return o.Bytes(), nil }
|
|
||||||
|
|
||||||
// MarshalTo copies OwnerID bytes representation into passed slice of bytes.
|
|
||||||
func (o OwnerID) MarshalTo(data []byte) (int, error) { return copy(data, o.Bytes()), nil }
|
|
||||||
|
|
||||||
// String returns string representation of OwnerID.
|
|
||||||
func (o OwnerID) String() string { return base58.Encode(o[:]) }
|
|
||||||
|
|
||||||
// Bytes returns OwnerID bytes representation.
|
|
||||||
func (o OwnerID) Bytes() []byte {
|
|
||||||
buf := make([]byte, OwnerIDSize)
|
|
||||||
copy(buf, o[:])
|
|
||||||
return buf
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unmarshal tries to parse OwnerID bytes representation into current OwnerID.
|
|
||||||
func (o *OwnerID) Unmarshal(data []byte) error {
|
|
||||||
if ln := len(data); ln != OwnerIDSize {
|
|
||||||
return errors.Wrapf(ErrWrongDataSize, "expect=%d, actual=%d", OwnerIDSize, ln)
|
|
||||||
}
|
|
||||||
|
|
||||||
copy((*o)[:], data)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge used by proto.Clone
|
|
||||||
func (o *OwnerID) Merge(src proto.Message) {
|
|
||||||
if uid, ok := src.(*OwnerID); ok {
|
|
||||||
*o = *uid
|
|
||||||
}
|
|
||||||
}
|
|
14
refs/sgid.go
14
refs/sgid.go
|
@ -1,14 +0,0 @@
|
||||||
package refs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SGIDFromBytes parse bytes representation of SGID into new SGID value.
|
|
||||||
func SGIDFromBytes(data []byte) (sgid SGID, err error) {
|
|
||||||
if ln := len(data); ln != SGIDSize {
|
|
||||||
return SGID{}, errors.Wrapf(ErrWrongDataSize, "expect=%d, actual=%d", SGIDSize, ln)
|
|
||||||
}
|
|
||||||
copy(sgid[:], data)
|
|
||||||
return
|
|
||||||
}
|
|
123
refs/types.go
123
refs/types.go
|
@ -1,123 +0,0 @@
|
||||||
// This package contains basic structures implemented in Go, such as
|
|
||||||
//
|
|
||||||
// CID - container id
|
|
||||||
// OwnerID - owner id
|
|
||||||
// ObjectID - object id
|
|
||||||
// SGID - storage group id
|
|
||||||
// Address - contains object id and container id
|
|
||||||
// UUID - a 128 bit (16 byte) Universal Unique Identifier as defined in RFC 4122
|
|
||||||
|
|
||||||
package refs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/sha256"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/chain"
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
// CID is implementation of ContainerID.
|
|
||||||
CID [CIDSize]byte
|
|
||||||
|
|
||||||
// UUID wrapper over github.com/google/uuid.UUID.
|
|
||||||
UUID uuid.UUID
|
|
||||||
|
|
||||||
// SGID is type alias of UUID.
|
|
||||||
SGID = UUID
|
|
||||||
|
|
||||||
// ObjectID is type alias of UUID.
|
|
||||||
ObjectID = UUID
|
|
||||||
|
|
||||||
// MessageID is type alias of UUID.
|
|
||||||
MessageID = UUID
|
|
||||||
|
|
||||||
// OwnerID is wrapper over neofs-proto/chain.WalletAddress.
|
|
||||||
OwnerID chain.WalletAddress
|
|
||||||
)
|
|
||||||
|
|
||||||
// OwnerIDSource is an interface of the container of an OwnerID value with read access.
|
|
||||||
type OwnerIDSource interface {
|
|
||||||
GetOwnerID() OwnerID
|
|
||||||
}
|
|
||||||
|
|
||||||
// OwnerIDContainer is an interface of the container of an OwnerID value.
|
|
||||||
type OwnerIDContainer interface {
|
|
||||||
OwnerIDSource
|
|
||||||
SetOwnerID(OwnerID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddressContainer is an interface of the container of object address value.
|
|
||||||
type AddressContainer interface {
|
|
||||||
GetAddress() Address
|
|
||||||
SetAddress(Address)
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
// UUIDSize contains size of UUID.
|
|
||||||
UUIDSize = 16
|
|
||||||
|
|
||||||
// SGIDSize contains size of SGID.
|
|
||||||
SGIDSize = UUIDSize
|
|
||||||
|
|
||||||
// CIDSize contains size of CID.
|
|
||||||
CIDSize = sha256.Size
|
|
||||||
|
|
||||||
// OwnerIDSize contains size of OwnerID.
|
|
||||||
OwnerIDSize = chain.AddressLength
|
|
||||||
|
|
||||||
// ErrWrongDataSize is raised when passed bytes into Unmarshal have wrong size.
|
|
||||||
ErrWrongDataSize = internal.Error("wrong data size")
|
|
||||||
|
|
||||||
// ErrEmptyOwner is raised when empty OwnerID is passed into container.New.
|
|
||||||
ErrEmptyOwner = internal.Error("owner cant be empty")
|
|
||||||
|
|
||||||
// ErrEmptyCapacity is raised when empty Capacity is passed container.New.
|
|
||||||
ErrEmptyCapacity = internal.Error("capacity cant be empty")
|
|
||||||
|
|
||||||
// ErrEmptyContainer is raised when it CID method is called for an empty container.
|
|
||||||
ErrEmptyContainer = internal.Error("cannot return ID for empty container")
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
emptyCID = (CID{}).Bytes()
|
|
||||||
emptyUUID = (UUID{}).Bytes()
|
|
||||||
emptyOwner = (OwnerID{}).Bytes()
|
|
||||||
|
|
||||||
_ internal.Custom = (*CID)(nil)
|
|
||||||
_ internal.Custom = (*SGID)(nil)
|
|
||||||
_ internal.Custom = (*UUID)(nil)
|
|
||||||
_ internal.Custom = (*OwnerID)(nil)
|
|
||||||
_ internal.Custom = (*ObjectID)(nil)
|
|
||||||
_ internal.Custom = (*MessageID)(nil)
|
|
||||||
|
|
||||||
// NewSGID method alias.
|
|
||||||
NewSGID = NewUUID
|
|
||||||
|
|
||||||
// NewObjectID method alias.
|
|
||||||
NewObjectID = NewUUID
|
|
||||||
|
|
||||||
// NewMessageID method alias.
|
|
||||||
NewMessageID = NewUUID
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewUUID returns a Random (Version 4) UUID.
|
|
||||||
//
|
|
||||||
// The strength of the UUIDs is based on the strength of the crypto/rand
|
|
||||||
// package.
|
|
||||||
//
|
|
||||||
// A note about uniqueness derived from the UUID Wikipedia entry:
|
|
||||||
//
|
|
||||||
// Randomly generated UUIDs have 122 random bits. One's annual risk of being
|
|
||||||
// hit by a meteorite is estimated to be one chance in 17 billion, that
|
|
||||||
// means the probability is about 0.00000000006 (6 × 10−11),
|
|
||||||
// equivalent to the odds of creating a few tens of trillions of UUIDs in a
|
|
||||||
// year and having one duplicate.
|
|
||||||
func NewUUID() (UUID, error) {
|
|
||||||
id, err := uuid.NewRandom()
|
|
||||||
if err != nil {
|
|
||||||
return UUID{}, err
|
|
||||||
}
|
|
||||||
return UUID(id), nil
|
|
||||||
}
|
|
BIN
refs/types.pb.go
BIN
refs/types.pb.go
Binary file not shown.
|
@ -1,19 +0,0 @@
|
||||||
syntax = "proto3";
|
|
||||||
package refs;
|
|
||||||
option go_package = "github.com/nspcc-dev/neofs-api-go/refs";
|
|
||||||
option csharp_namespace = "NeoFS.API.Refs";
|
|
||||||
|
|
||||||
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
|
|
||||||
|
|
||||||
option (gogoproto.stable_marshaler_all) = true;
|
|
||||||
|
|
||||||
option (gogoproto.stringer_all) = false;
|
|
||||||
option (gogoproto.goproto_stringer_all) = false;
|
|
||||||
|
|
||||||
// Address of object (container id + object id)
|
|
||||||
message Address {
|
|
||||||
// ObjectID is an object identifier, valid UUIDv4 represented in bytes
|
|
||||||
bytes ObjectID = 1[(gogoproto.customtype) = "ObjectID", (gogoproto.nullable) = false];
|
|
||||||
// CID is container identifier
|
|
||||||
bytes CID = 2[(gogoproto.customtype) = "CID", (gogoproto.nullable) = false];
|
|
||||||
}
|
|
|
@ -1,141 +0,0 @@
|
||||||
package refs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/gogo/protobuf/proto"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/nspcc-dev/neofs-crypto/test"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSGID(t *testing.T) {
|
|
||||||
t.Run("check that marshal/unmarshal works like expected", func(t *testing.T) {
|
|
||||||
var sgid1, sgid2 UUID
|
|
||||||
|
|
||||||
sgid1, err := NewSGID()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
data, err := proto.Marshal(&sgid1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.NoError(t, sgid2.Unmarshal(data))
|
|
||||||
require.Equal(t, sgid1, sgid2)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("check that proto.Clone works like expected", func(t *testing.T) {
|
|
||||||
var (
|
|
||||||
sgid1 UUID
|
|
||||||
sgid2 *UUID
|
|
||||||
)
|
|
||||||
|
|
||||||
sgid1, err := NewSGID()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
sgid2 = proto.Clone(&sgid1).(*SGID)
|
|
||||||
require.Equal(t, sgid1, *sgid2)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUUID(t *testing.T) {
|
|
||||||
t.Run("parse should work like expected", func(t *testing.T) {
|
|
||||||
var u UUID
|
|
||||||
|
|
||||||
id, err := uuid.NewRandom()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.NoError(t, u.Parse(id.String()))
|
|
||||||
require.Equal(t, id.String(), u.String())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("check that marshal/unmarshal works like expected", func(t *testing.T) {
|
|
||||||
var u1, u2 UUID
|
|
||||||
|
|
||||||
u1 = UUID{0x8f, 0xe4, 0xeb, 0xa0, 0xb8, 0xfb, 0x49, 0x3b, 0xbb, 0x1d, 0x1d, 0x13, 0x6e, 0x69, 0xfc, 0xf7}
|
|
||||||
|
|
||||||
data, err := proto.Marshal(&u1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.NoError(t, u2.Unmarshal(data))
|
|
||||||
require.Equal(t, u1, u2)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("check that marshal/unmarshal works like expected even for msg id", func(t *testing.T) {
|
|
||||||
var u2 MessageID
|
|
||||||
|
|
||||||
u1, err := NewMessageID()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
data, err := proto.Marshal(&u1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.NoError(t, u2.Unmarshal(data))
|
|
||||||
require.Equal(t, u1, u2)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOwnerID(t *testing.T) {
|
|
||||||
t.Run("check that marshal/unmarshal works like expected", func(t *testing.T) {
|
|
||||||
var u1, u2 OwnerID
|
|
||||||
|
|
||||||
owner, err := NewOwnerID(nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.True(t, owner.Empty())
|
|
||||||
|
|
||||||
key := test.DecodeKey(0)
|
|
||||||
|
|
||||||
u1, err = NewOwnerID(&key.PublicKey)
|
|
||||||
require.NoError(t, err)
|
|
||||||
data, err := proto.Marshal(&u1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.NoError(t, u2.Unmarshal(data))
|
|
||||||
require.Equal(t, u1, u2)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("check that proto.Clone works like expected", func(t *testing.T) {
|
|
||||||
var u2 *OwnerID
|
|
||||||
|
|
||||||
key := test.DecodeKey(0)
|
|
||||||
|
|
||||||
u1, err := NewOwnerID(&key.PublicKey)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
u2 = proto.Clone(&u1).(*OwnerID)
|
|
||||||
require.Equal(t, u1, *u2)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAddress(t *testing.T) {
|
|
||||||
cid := CIDForBytes([]byte("test"))
|
|
||||||
|
|
||||||
id, err := NewObjectID()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
expect := strings.Join([]string{
|
|
||||||
cid.String(),
|
|
||||||
id.String(),
|
|
||||||
}, joinSeparator)
|
|
||||||
|
|
||||||
require.NotPanics(t, func() {
|
|
||||||
actual := (Address{
|
|
||||||
ObjectID: id,
|
|
||||||
CID: cid,
|
|
||||||
}).String()
|
|
||||||
|
|
||||||
require.Equal(t, expect, actual)
|
|
||||||
})
|
|
||||||
|
|
||||||
var temp Address
|
|
||||||
require.NoError(t, temp.Parse(expect))
|
|
||||||
require.Equal(t, expect, temp.String())
|
|
||||||
|
|
||||||
actual, err := ParseAddress(expect)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, expect, actual.String())
|
|
||||||
|
|
||||||
addr := proto.Clone(actual).(*Address)
|
|
||||||
require.Equal(t, actual, addr)
|
|
||||||
require.Equal(t, expect, addr.String())
|
|
||||||
}
|
|
84
refs/uuid.go
84
refs/uuid.go
|
@ -1,84 +0,0 @@
|
||||||
package refs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/hex"
|
|
||||||
|
|
||||||
"github.com/gogo/protobuf/proto"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
func encodeHex(dst []byte, uuid UUID) {
|
|
||||||
hex.Encode(dst, uuid[:4])
|
|
||||||
dst[8] = '-'
|
|
||||||
hex.Encode(dst[9:13], uuid[4:6])
|
|
||||||
dst[13] = '-'
|
|
||||||
hex.Encode(dst[14:18], uuid[6:8])
|
|
||||||
dst[18] = '-'
|
|
||||||
hex.Encode(dst[19:23], uuid[8:10])
|
|
||||||
dst[23] = '-'
|
|
||||||
hex.Encode(dst[24:], uuid[10:])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Size returns size in bytes of UUID (UUIDSize).
|
|
||||||
func (UUID) Size() int { return UUIDSize }
|
|
||||||
|
|
||||||
// Empty checks that current UUID is empty value.
|
|
||||||
func (u UUID) Empty() bool { return bytes.Equal(u.Bytes(), emptyUUID) }
|
|
||||||
|
|
||||||
// Reset sets current UUID to empty value.
|
|
||||||
func (u *UUID) Reset() { *u = [UUIDSize]byte{} }
|
|
||||||
|
|
||||||
// ProtoMessage method to satisfy proto.Message.
|
|
||||||
func (UUID) ProtoMessage() {}
|
|
||||||
|
|
||||||
// Marshal returns UUID bytes representation.
|
|
||||||
func (u UUID) Marshal() ([]byte, error) { return u.Bytes(), nil }
|
|
||||||
|
|
||||||
// MarshalTo returns UUID bytes representation.
|
|
||||||
func (u UUID) MarshalTo(data []byte) (int, error) { return copy(data, u[:]), nil }
|
|
||||||
|
|
||||||
// Bytes returns UUID bytes representation.
|
|
||||||
func (u UUID) Bytes() []byte {
|
|
||||||
buf := make([]byte, UUIDSize)
|
|
||||||
copy(buf, u[:])
|
|
||||||
return buf
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equal checks that current UUID is equal to passed UUID.
|
|
||||||
func (u UUID) Equal(u2 UUID) bool { return bytes.Equal(u.Bytes(), u2.Bytes()) }
|
|
||||||
|
|
||||||
func (u UUID) String() string {
|
|
||||||
var buf [36]byte
|
|
||||||
encodeHex(buf[:], u)
|
|
||||||
return string(buf[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unmarshal tries to parse UUID bytes representation.
|
|
||||||
func (u *UUID) Unmarshal(data []byte) error {
|
|
||||||
if ln := len(data); ln != UUIDSize {
|
|
||||||
return errors.Wrapf(ErrWrongDataSize, "expect=%d, actual=%d", UUIDSize, ln)
|
|
||||||
}
|
|
||||||
|
|
||||||
copy((*u)[:], data)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse tries to parse UUID string representation.
|
|
||||||
func (u *UUID) Parse(id string) error {
|
|
||||||
tmp, err := uuid.Parse(id)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "could not parse `%s`", id)
|
|
||||||
}
|
|
||||||
|
|
||||||
copy((*u)[:], tmp[:])
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge used by proto.Clone
|
|
||||||
func (u *UUID) Merge(src proto.Message) {
|
|
||||||
if tmp, ok := src.(*UUID); ok {
|
|
||||||
*u = *tmp
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/refs"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TokenID is a type alias of UUID ref.
|
|
||||||
type TokenID = refs.UUID
|
|
||||||
|
|
||||||
// OwnerID is a type alias of OwnerID ref.
|
|
||||||
type OwnerID = refs.OwnerID
|
|
||||||
|
|
||||||
// Address is a type alias of Address ref.
|
|
||||||
type Address = refs.Address
|
|
||||||
|
|
||||||
// AddressContainer is a type alias of refs.AddressContainer.
|
|
||||||
type AddressContainer = refs.AddressContainer
|
|
||||||
|
|
||||||
// OwnerIDContainer is a type alias of refs.OwnerIDContainer.
|
|
||||||
type OwnerIDContainer = refs.OwnerIDContainer
|
|
|
@ -1,140 +0,0 @@
|
||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/refs"
|
|
||||||
crypto "github.com/nspcc-dev/neofs-crypto"
|
|
||||||
)
|
|
||||||
|
|
||||||
type signedBearerToken struct {
|
|
||||||
BearerToken
|
|
||||||
}
|
|
||||||
|
|
||||||
type bearerMsgWrapper struct {
|
|
||||||
*BearerTokenMsg
|
|
||||||
}
|
|
||||||
|
|
||||||
const fixedBearerTokenDataSize = 0 +
|
|
||||||
refs.OwnerIDSize +
|
|
||||||
8
|
|
||||||
|
|
||||||
// NewSignedBearerToken wraps passed BearerToken in a component suitable for signing.
|
|
||||||
//
|
|
||||||
// Result can be used in AddSignatureWithKey function.
|
|
||||||
func NewSignedBearerToken(token BearerToken) DataWithSignKeyAccumulator {
|
|
||||||
return &signedBearerToken{
|
|
||||||
BearerToken: token,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewVerifiedBearerToken wraps passed SessionToken in a component suitable for signature verification.
|
|
||||||
//
|
|
||||||
// Result can be used in VerifySignatureWithKey function.
|
|
||||||
func NewVerifiedBearerToken(token BearerToken) DataWithSignature {
|
|
||||||
return &signedBearerToken{
|
|
||||||
BearerToken: token,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddSignKey calls a Signature field setter and an OwnerKey field setter with corresponding arguments.
|
|
||||||
func (s signedBearerToken) AddSignKey(sig []byte, key *ecdsa.PublicKey) {
|
|
||||||
if s.BearerToken != nil {
|
|
||||||
s.SetSignature(sig)
|
|
||||||
|
|
||||||
s.SetOwnerKey(
|
|
||||||
crypto.MarshalPublicKey(key),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedData returns token information in a binary representation.
|
|
||||||
func (s signedBearerToken) SignedData() ([]byte, error) {
|
|
||||||
return SignedDataFromReader(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedDataSize returns the length of signed token information slice.
|
|
||||||
func (s signedBearerToken) SignedDataSize() int {
|
|
||||||
return bearerTokenInfoSize(s.BearerToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadSignedData copies a binary representation of the token information to passed buffer.
|
|
||||||
//
|
|
||||||
// If buffer length is less than required, io.ErrUnexpectedEOF returns.
|
|
||||||
func (s signedBearerToken) ReadSignedData(p []byte) (int, error) {
|
|
||||||
sz := s.SignedDataSize()
|
|
||||||
if len(p) < sz {
|
|
||||||
return 0, io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
|
|
||||||
copyBearerTokenSignedData(p, s.BearerToken)
|
|
||||||
|
|
||||||
return sz, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func bearerTokenInfoSize(v ACLRulesSource) int {
|
|
||||||
if v == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return fixedBearerTokenDataSize + len(v.GetACLRules())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fills passed buffer with signing token information bytes.
|
|
||||||
// Does not check buffer length, it is understood that enough space is allocated in it.
|
|
||||||
//
|
|
||||||
// If passed BearerTokenInfo, buffer remains unchanged.
|
|
||||||
func copyBearerTokenSignedData(buf []byte, token BearerTokenInfo) {
|
|
||||||
if token == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var off int
|
|
||||||
|
|
||||||
off += copy(buf[off:], token.GetACLRules())
|
|
||||||
|
|
||||||
off += copy(buf[off:], token.GetOwnerID().Bytes())
|
|
||||||
|
|
||||||
tokenEndianness.PutUint64(buf[off:], token.ExpirationEpoch())
|
|
||||||
off += 8
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetACLRules is an ACLRules field setter.
|
|
||||||
func (m *BearerTokenMsg_Info) SetACLRules(v []byte) {
|
|
||||||
m.ACLRules = v
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetValidUntil is a ValidUntil field setter.
|
|
||||||
func (m *BearerTokenMsg_Info) SetValidUntil(v uint64) {
|
|
||||||
m.ValidUntil = v
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetOwnerID if an OwnerID field getter.
|
|
||||||
func (m BearerTokenMsg_Info) GetOwnerID() OwnerID {
|
|
||||||
return m.OwnerID
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetOwnerID is an OwnerID field setter.
|
|
||||||
func (m *BearerTokenMsg_Info) SetOwnerID(v OwnerID) {
|
|
||||||
m.OwnerID = v
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExpirationEpoch returns the result of ValidUntil field getter.
|
|
||||||
func (m BearerTokenMsg_Info) ExpirationEpoch() uint64 {
|
|
||||||
return m.GetValidUntil()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetExpirationEpoch passes argument to ValidUntil field setter.
|
|
||||||
func (m *BearerTokenMsg_Info) SetExpirationEpoch(v uint64) {
|
|
||||||
m.SetValidUntil(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetOwnerKey is an OwnerKey field setter.
|
|
||||||
func (m *BearerTokenMsg) SetOwnerKey(v []byte) {
|
|
||||||
m.OwnerKey = v
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetSignature is a Signature field setter.
|
|
||||||
func (m *BearerTokenMsg) SetSignature(v []byte) {
|
|
||||||
m.Signature = v
|
|
||||||
}
|
|
|
@ -1,199 +0,0 @@
|
||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-crypto/test"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
type testBearerToken struct {
|
|
||||||
aclRules []byte
|
|
||||||
expEpoch uint64
|
|
||||||
owner OwnerID
|
|
||||||
key []byte
|
|
||||||
sig []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s testBearerToken) GetACLRules() []byte {
|
|
||||||
return s.aclRules
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *testBearerToken) SetACLRules(v []byte) {
|
|
||||||
s.aclRules = v
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s testBearerToken) ExpirationEpoch() uint64 {
|
|
||||||
return s.expEpoch
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *testBearerToken) SetExpirationEpoch(v uint64) {
|
|
||||||
s.expEpoch = v
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s testBearerToken) GetOwnerID() OwnerID {
|
|
||||||
return s.owner
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *testBearerToken) SetOwnerID(v OwnerID) {
|
|
||||||
s.owner = v
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s testBearerToken) GetOwnerKey() []byte {
|
|
||||||
return s.key
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *testBearerToken) SetOwnerKey(v []byte) {
|
|
||||||
s.key = v
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s testBearerToken) GetSignature() []byte {
|
|
||||||
return s.sig
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *testBearerToken) SetSignature(v []byte) {
|
|
||||||
s.sig = v
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBearerTokenMsgGettersSetters(t *testing.T) {
|
|
||||||
var tok BearerToken = new(testBearerToken)
|
|
||||||
|
|
||||||
{ // ACLRules
|
|
||||||
rules := []byte{1, 2, 3}
|
|
||||||
|
|
||||||
tok.SetACLRules(rules)
|
|
||||||
|
|
||||||
require.Equal(t, rules, tok.GetACLRules())
|
|
||||||
}
|
|
||||||
|
|
||||||
{ // OwnerID
|
|
||||||
ownerID := OwnerID{}
|
|
||||||
_, err := rand.Read(ownerID[:])
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
tok.SetOwnerID(ownerID)
|
|
||||||
|
|
||||||
require.Equal(t, ownerID, tok.GetOwnerID())
|
|
||||||
}
|
|
||||||
|
|
||||||
{ // ValidUntil
|
|
||||||
e := uint64(5)
|
|
||||||
|
|
||||||
tok.SetExpirationEpoch(e)
|
|
||||||
|
|
||||||
require.Equal(t, e, tok.ExpirationEpoch())
|
|
||||||
}
|
|
||||||
|
|
||||||
{ // OwnerKey
|
|
||||||
key := make([]byte, 10)
|
|
||||||
_, err := rand.Read(key)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
tok.SetOwnerKey(key)
|
|
||||||
|
|
||||||
require.Equal(t, key, tok.GetOwnerKey())
|
|
||||||
}
|
|
||||||
|
|
||||||
{ // Signature
|
|
||||||
sig := make([]byte, 10)
|
|
||||||
_, err := rand.Read(sig)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
tok.SetSignature(sig)
|
|
||||||
|
|
||||||
require.Equal(t, sig, tok.GetSignature())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSignVerifyBearerToken(t *testing.T) {
|
|
||||||
var token BearerToken = new(testBearerToken)
|
|
||||||
|
|
||||||
// create private key for signing
|
|
||||||
sk := test.DecodeKey(0)
|
|
||||||
pk := &sk.PublicKey
|
|
||||||
|
|
||||||
rules := []byte{1, 2, 3}
|
|
||||||
token.SetACLRules(rules)
|
|
||||||
|
|
||||||
ownerID := OwnerID{}
|
|
||||||
_, err := rand.Read(ownerID[:])
|
|
||||||
require.NoError(t, err)
|
|
||||||
token.SetOwnerID(ownerID)
|
|
||||||
|
|
||||||
fEpoch := uint64(2)
|
|
||||||
token.SetExpirationEpoch(fEpoch)
|
|
||||||
|
|
||||||
signedToken := NewSignedBearerToken(token)
|
|
||||||
verifiedToken := NewVerifiedBearerToken(token)
|
|
||||||
|
|
||||||
// sign and verify token
|
|
||||||
require.NoError(t, AddSignatureWithKey(sk, signedToken))
|
|
||||||
require.NoError(t, VerifySignatureWithKey(pk, verifiedToken))
|
|
||||||
|
|
||||||
items := []struct {
|
|
||||||
corrupt func()
|
|
||||||
restore func()
|
|
||||||
}{
|
|
||||||
{ // ACLRules
|
|
||||||
corrupt: func() {
|
|
||||||
token.SetACLRules(append(rules, 1))
|
|
||||||
},
|
|
||||||
restore: func() {
|
|
||||||
token.SetACLRules(rules)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ // Owner ID
|
|
||||||
corrupt: func() {
|
|
||||||
ownerID[0]++
|
|
||||||
token.SetOwnerID(ownerID)
|
|
||||||
},
|
|
||||||
restore: func() {
|
|
||||||
ownerID[0]--
|
|
||||||
token.SetOwnerID(ownerID)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ // Expiration epoch
|
|
||||||
corrupt: func() {
|
|
||||||
token.SetExpirationEpoch(fEpoch + 1)
|
|
||||||
},
|
|
||||||
restore: func() {
|
|
||||||
token.SetExpirationEpoch(fEpoch)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, v := range items {
|
|
||||||
v.corrupt()
|
|
||||||
require.Error(t, VerifySignatureWithKey(pk, verifiedToken))
|
|
||||||
v.restore()
|
|
||||||
require.NoError(t, VerifySignatureWithKey(pk, verifiedToken))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBearerTokenMsg_Setters(t *testing.T) {
|
|
||||||
s := new(BearerTokenMsg)
|
|
||||||
|
|
||||||
aclRules := []byte{1, 2, 3}
|
|
||||||
s.SetACLRules(aclRules)
|
|
||||||
require.Equal(t, aclRules, s.GetACLRules())
|
|
||||||
|
|
||||||
validUntil := uint64(6)
|
|
||||||
s.SetValidUntil(validUntil)
|
|
||||||
require.Equal(t, validUntil, s.GetValidUntil())
|
|
||||||
|
|
||||||
s.SetExpirationEpoch(validUntil + 1)
|
|
||||||
require.Equal(t, validUntil+1, s.ExpirationEpoch())
|
|
||||||
|
|
||||||
ownerID := OwnerID{1, 2, 3}
|
|
||||||
s.SetOwnerID(ownerID)
|
|
||||||
require.Equal(t, ownerID, s.GetOwnerID())
|
|
||||||
|
|
||||||
ownerKey := []byte{4, 5, 6}
|
|
||||||
s.SetOwnerKey(ownerKey)
|
|
||||||
require.Equal(t, ownerKey, s.GetOwnerKey())
|
|
||||||
|
|
||||||
sig := []byte{7, 8, 9}
|
|
||||||
s.SetSignature(sig)
|
|
||||||
require.Equal(t, sig, s.GetSignature())
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
package service
|
|
||||||
|
|
||||||
// SetEpoch is an Epoch field setter.
|
|
||||||
func (m *ResponseMetaHeader) SetEpoch(v uint64) {
|
|
||||||
m.Epoch = v
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetEpoch is an Epoch field setter.
|
|
||||||
func (m *RequestMetaHeader) SetEpoch(v uint64) {
|
|
||||||
m.Epoch = v
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGetSetEpoch(t *testing.T) {
|
|
||||||
v := uint64(5)
|
|
||||||
|
|
||||||
items := []EpochContainer{
|
|
||||||
new(ResponseMetaHeader),
|
|
||||||
new(RequestMetaHeader),
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, item := range items {
|
|
||||||
item.SetEpoch(v)
|
|
||||||
require.Equal(t, v, item.GetEpoch())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
package service
|
|
||||||
|
|
||||||
import "github.com/nspcc-dev/neofs-api-go/internal"
|
|
||||||
|
|
||||||
// ErrNilToken is returned by functions that expect
|
|
||||||
// a non-nil token argument, but received nil.
|
|
||||||
const ErrNilToken = internal.Error("token is nil")
|
|
||||||
|
|
||||||
// ErrInvalidTTL means that the TTL value does not
|
|
||||||
// satisfy a specific criterion.
|
|
||||||
const ErrInvalidTTL = internal.Error("invalid TTL value")
|
|
||||||
|
|
||||||
// ErrInvalidPublicKeyBytes means that the public key could not be unmarshaled.
|
|
||||||
const ErrInvalidPublicKeyBytes = internal.Error("cannot load public key")
|
|
||||||
|
|
||||||
// ErrCannotFindOwner is raised when signatures empty in GetOwner.
|
|
||||||
const ErrCannotFindOwner = internal.Error("cannot find owner public key")
|
|
||||||
|
|
||||||
// ErrWrongOwner is raised when passed OwnerID
|
|
||||||
// not equal to present PublicKey
|
|
||||||
const ErrWrongOwner = internal.Error("wrong owner")
|
|
||||||
|
|
||||||
// ErrNilSignedDataSource returned by functions that expect a non-nil
|
|
||||||
// SignedDataSource, but received nil.
|
|
||||||
const ErrNilSignedDataSource = internal.Error("signed data source is nil")
|
|
||||||
|
|
||||||
// ErrNilSignatureKeySource is returned by functions that expect a non-nil
|
|
||||||
// SignatureKeySource, but received nil.
|
|
||||||
const ErrNilSignatureKeySource = internal.Error("empty key-signature source")
|
|
||||||
|
|
||||||
// ErrEmptyDataWithSignature is returned by functions that expect
|
|
||||||
// a non-nil DataWithSignature, but received nil.
|
|
||||||
const ErrEmptyDataWithSignature = internal.Error("empty data with signature")
|
|
||||||
|
|
||||||
// ErrNegativeLength is returned by functions that received
|
|
||||||
// negative length for slice allocation.
|
|
||||||
const ErrNegativeLength = internal.Error("negative slice length")
|
|
||||||
|
|
||||||
// ErrNilRequestSignedData is returned by functions that expect
|
|
||||||
// a non-nil RequestSignedData, but received nil.
|
|
||||||
const ErrNilRequestSignedData = internal.Error("request signed data is nil")
|
|
||||||
|
|
||||||
// ErrNilRequestVerifyData is returned by functions that expect
|
|
||||||
// a non-nil RequestVerifyData, but received nil.
|
|
||||||
const ErrNilRequestVerifyData = internal.Error("request verification data is nil")
|
|
||||||
|
|
||||||
// ErrNilSignedDataReader is returned by functions that expect
|
|
||||||
// a non-nil SignedDataReader, but received nil.
|
|
||||||
const ErrNilSignedDataReader = internal.Error("signed data reader is nil")
|
|
||||||
|
|
||||||
// ErrNilSignKeyPairAccumulator is returned by functions that expect
|
|
||||||
// a non-nil SignKeyPairAccumulator, but received nil.
|
|
||||||
const ErrNilSignKeyPairAccumulator = internal.Error("signature-key pair accumulator is nil")
|
|
131
service/meta.go
131
service/meta.go
|
@ -1,131 +0,0 @@
|
||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
type extHdrWrapper struct {
|
|
||||||
msg *RequestExtendedHeader_KV
|
|
||||||
}
|
|
||||||
|
|
||||||
type extHdrSrcWrapper struct {
|
|
||||||
extHdrSrc ExtendedHeadersSource
|
|
||||||
}
|
|
||||||
|
|
||||||
// CutMeta returns current value and sets RequestMetaHeader to empty value.
|
|
||||||
func (m *RequestMetaHeader) CutMeta() RequestMetaHeader {
|
|
||||||
cp := *m
|
|
||||||
m.Reset()
|
|
||||||
return cp
|
|
||||||
}
|
|
||||||
|
|
||||||
// RestoreMeta sets current RequestMetaHeader to passed value.
|
|
||||||
func (m *RequestMetaHeader) RestoreMeta(v RequestMetaHeader) {
|
|
||||||
*m = v
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExtendedHeadersSignedData wraps passed ExtendedHeadersSource and returns SignedDataSource.
|
|
||||||
func ExtendedHeadersSignedData(headers ExtendedHeadersSource) SignedDataSource {
|
|
||||||
return &extHdrSrcWrapper{
|
|
||||||
extHdrSrc: headers,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedData returns extended headers in a binary representation.
|
|
||||||
func (s extHdrSrcWrapper) SignedData() ([]byte, error) {
|
|
||||||
return SignedDataFromReader(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedDataSize returns the length of extended headers slice.
|
|
||||||
func (s extHdrSrcWrapper) SignedDataSize() (res int) {
|
|
||||||
if s.extHdrSrc != nil {
|
|
||||||
for _, h := range s.extHdrSrc.ExtendedHeaders() {
|
|
||||||
if h != nil {
|
|
||||||
res += len(h.Key()) + len(h.Value())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadSignedData copies a binary representation of the extended headers to passed buffer.
|
|
||||||
//
|
|
||||||
// If buffer length is less than required, io.ErrUnexpectedEOF returns.
|
|
||||||
func (s extHdrSrcWrapper) ReadSignedData(p []byte) (int, error) {
|
|
||||||
sz := s.SignedDataSize()
|
|
||||||
if len(p) < sz {
|
|
||||||
return 0, io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.extHdrSrc != nil {
|
|
||||||
off := 0
|
|
||||||
for _, h := range s.extHdrSrc.ExtendedHeaders() {
|
|
||||||
if h == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
off += copy(p[off:], []byte(h.Key()))
|
|
||||||
|
|
||||||
off += copy(p[off:], []byte(h.Value()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return sz, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetK is a K field setter.
|
|
||||||
func (m *RequestExtendedHeader_KV) SetK(v string) {
|
|
||||||
m.K = v
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetV is a V field setter.
|
|
||||||
func (m *RequestExtendedHeader_KV) SetV(v string) {
|
|
||||||
m.V = v
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetHeaders is a Headers field setter.
|
|
||||||
func (m *RequestExtendedHeader) SetHeaders(v []RequestExtendedHeader_KV) {
|
|
||||||
m.Headers = v
|
|
||||||
}
|
|
||||||
|
|
||||||
func wrapExtendedHeaderKV(msg *RequestExtendedHeader_KV) extHdrWrapper {
|
|
||||||
return extHdrWrapper{
|
|
||||||
msg: msg,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Key returns the result of K field getter.
|
|
||||||
//
|
|
||||||
// If message is nil, empty string returns.
|
|
||||||
func (m extHdrWrapper) Key() string {
|
|
||||||
if m.msg != nil {
|
|
||||||
return m.msg.GetK()
|
|
||||||
}
|
|
||||||
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value returns the result of V field getter.
|
|
||||||
//
|
|
||||||
// If message is nil, empty string returns.
|
|
||||||
func (m extHdrWrapper) Value() string {
|
|
||||||
if m.msg != nil {
|
|
||||||
return m.msg.GetV()
|
|
||||||
}
|
|
||||||
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExtendedHeaders composes ExtendedHeader list from the Headers field getter result.
|
|
||||||
func (m RequestExtendedHeader) ExtendedHeaders() []ExtendedHeader {
|
|
||||||
hs := m.GetHeaders()
|
|
||||||
|
|
||||||
res := make([]ExtendedHeader, 0, len(hs))
|
|
||||||
|
|
||||||
for i := range hs {
|
|
||||||
res = append(res, wrapExtendedHeaderKV(&hs[i]))
|
|
||||||
}
|
|
||||||
|
|
||||||
return res
|
|
||||||
}
|
|
Binary file not shown.
|
@ -1,49 +0,0 @@
|
||||||
syntax = "proto3";
|
|
||||||
package service;
|
|
||||||
option go_package = "github.com/nspcc-dev/neofs-api-go/service";
|
|
||||||
option csharp_namespace = "NeoFS.API.Service";
|
|
||||||
|
|
||||||
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
|
|
||||||
|
|
||||||
option (gogoproto.stable_marshaler_all) = true;
|
|
||||||
|
|
||||||
// RequestMetaHeader contains information about request meta headers
|
|
||||||
// (should be embedded into message)
|
|
||||||
message RequestMetaHeader {
|
|
||||||
// TTL must be larger than zero, it decreased in every NeoFS Node
|
|
||||||
uint32 TTL = 1;
|
|
||||||
// Epoch for user can be empty, because node sets epoch to the actual value
|
|
||||||
uint64 Epoch = 2;
|
|
||||||
// Version defines protocol version
|
|
||||||
// TODO: not used for now, should be implemented in future
|
|
||||||
uint32 Version = 3;
|
|
||||||
// Raw determines whether the request is raw or not
|
|
||||||
bool Raw = 4;
|
|
||||||
// ExtendedHeader carries extended headers of the request
|
|
||||||
RequestExtendedHeader ExtendedHeader = 5 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResponseMetaHeader contains meta information based on request processing by server
|
|
||||||
// (should be embedded into message)
|
|
||||||
message ResponseMetaHeader {
|
|
||||||
// Current NeoFS epoch on server
|
|
||||||
uint64 Epoch = 1;
|
|
||||||
// Version defines protocol version
|
|
||||||
// TODO: not used for now, should be implemented in future
|
|
||||||
uint32 Version = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequestExtendedHeader contains extended headers of request
|
|
||||||
message RequestExtendedHeader {
|
|
||||||
// KV contains string key-value pair
|
|
||||||
message KV {
|
|
||||||
// K carries extended header key
|
|
||||||
string K = 1;
|
|
||||||
|
|
||||||
// V carries extended header value
|
|
||||||
string V = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Headers carries list of key-value headers
|
|
||||||
repeated KV Headers = 1 [(gogoproto.nullable) = false];
|
|
||||||
}
|
|
|
@ -1,99 +0,0 @@
|
||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCutRestoreMeta(t *testing.T) {
|
|
||||||
items := []func() SeizedMetaHeaderContainer{
|
|
||||||
func() SeizedMetaHeaderContainer {
|
|
||||||
m := new(RequestMetaHeader)
|
|
||||||
m.SetEpoch(1)
|
|
||||||
return m
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, item := range items {
|
|
||||||
v1 := item()
|
|
||||||
m1 := v1.CutMeta()
|
|
||||||
v1.RestoreMeta(m1)
|
|
||||||
|
|
||||||
require.Equal(t, item(), v1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRequestExtendedHeader_KV_Setters(t *testing.T) {
|
|
||||||
s := new(RequestExtendedHeader_KV)
|
|
||||||
|
|
||||||
key := "key"
|
|
||||||
s.SetK(key)
|
|
||||||
require.Equal(t, key, s.GetK())
|
|
||||||
|
|
||||||
val := "val"
|
|
||||||
s.SetV(val)
|
|
||||||
require.Equal(t, val, s.GetV())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRequestExtendedHeader_SetHeaders(t *testing.T) {
|
|
||||||
s := new(RequestExtendedHeader)
|
|
||||||
|
|
||||||
hdr := RequestExtendedHeader_KV{}
|
|
||||||
hdr.SetK("key")
|
|
||||||
hdr.SetV("val")
|
|
||||||
|
|
||||||
hdrs := []RequestExtendedHeader_KV{
|
|
||||||
hdr,
|
|
||||||
}
|
|
||||||
|
|
||||||
s.SetHeaders(hdrs)
|
|
||||||
|
|
||||||
require.Equal(t, hdrs, s.GetHeaders())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestExtHdrWrapper(t *testing.T) {
|
|
||||||
s := wrapExtendedHeaderKV(nil)
|
|
||||||
require.Empty(t, s.Key())
|
|
||||||
require.Empty(t, s.Value())
|
|
||||||
|
|
||||||
msg := new(RequestExtendedHeader_KV)
|
|
||||||
s = wrapExtendedHeaderKV(msg)
|
|
||||||
|
|
||||||
key := "key"
|
|
||||||
msg.SetK(key)
|
|
||||||
require.Equal(t, key, s.Key())
|
|
||||||
|
|
||||||
val := "val"
|
|
||||||
msg.SetV(val)
|
|
||||||
require.Equal(t, val, s.Value())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRequestExtendedHeader_ExtendedHeaders(t *testing.T) {
|
|
||||||
var (
|
|
||||||
k1, v1 = "key1", "value1"
|
|
||||||
k2, v2 = "key2", "value2"
|
|
||||||
h1 = new(RequestExtendedHeader_KV)
|
|
||||||
h2 = new(RequestExtendedHeader_KV)
|
|
||||||
)
|
|
||||||
|
|
||||||
h1.SetK(k1)
|
|
||||||
h1.SetV(v1)
|
|
||||||
|
|
||||||
h2.SetK(k2)
|
|
||||||
h2.SetV(v2)
|
|
||||||
|
|
||||||
s := new(RequestExtendedHeader)
|
|
||||||
s.SetHeaders([]RequestExtendedHeader_KV{
|
|
||||||
*h1, *h2,
|
|
||||||
})
|
|
||||||
|
|
||||||
xHdrs := s.ExtendedHeaders()
|
|
||||||
require.Len(t, xHdrs, 2)
|
|
||||||
|
|
||||||
require.Equal(t, k1, xHdrs[0].Key())
|
|
||||||
require.Equal(t, v1, xHdrs[0].Value())
|
|
||||||
|
|
||||||
require.Equal(t, k2, xHdrs[1].Key())
|
|
||||||
require.Equal(t, v2, xHdrs[1].Value())
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
package service
|
|
||||||
|
|
||||||
// SetRaw is a Raw field setter.
|
|
||||||
func (m *RequestMetaHeader) SetRaw(raw bool) {
|
|
||||||
m.Raw = raw
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGetSetRaw(t *testing.T) {
|
|
||||||
items := []RawContainer{
|
|
||||||
new(RequestMetaHeader),
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, item := range items {
|
|
||||||
// init with false
|
|
||||||
item.SetRaw(false)
|
|
||||||
|
|
||||||
item.SetRaw(true)
|
|
||||||
require.True(t, item.GetRaw())
|
|
||||||
|
|
||||||
item.SetRaw(false)
|
|
||||||
require.False(t, item.GetRaw())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
package service
|
|
||||||
|
|
||||||
import "encoding/binary"
|
|
||||||
|
|
||||||
const (
|
|
||||||
_ NodeRole = iota
|
|
||||||
// InnerRingNode that work like IR node.
|
|
||||||
InnerRingNode
|
|
||||||
// StorageNode that work like a storage node.
|
|
||||||
StorageNode
|
|
||||||
)
|
|
||||||
|
|
||||||
// String is method, that represent NodeRole as string.
|
|
||||||
func (nt NodeRole) String() string {
|
|
||||||
switch nt {
|
|
||||||
case InnerRingNode:
|
|
||||||
return "InnerRingNode"
|
|
||||||
case StorageNode:
|
|
||||||
return "StorageNode"
|
|
||||||
default:
|
|
||||||
return "Unknown"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Size returns the size necessary for a binary representation of the NodeRole.
|
|
||||||
func (nt NodeRole) Size() int {
|
|
||||||
return 4
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bytes returns a binary representation of the NodeRole.
|
|
||||||
func (nt NodeRole) Bytes() []byte {
|
|
||||||
data := make([]byte, nt.Size())
|
|
||||||
|
|
||||||
binary.BigEndian.PutUint32(data, uint32(nt))
|
|
||||||
|
|
||||||
return data
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNodeRole_String(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
nt NodeRole
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{want: "Unknown"},
|
|
||||||
{nt: StorageNode, want: "StorageNode"},
|
|
||||||
{nt: InnerRingNode, want: "InnerRingNode"},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.want, func(t *testing.T) {
|
|
||||||
require.Equal(t, tt.want, tt.nt.String())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
404
service/sign.go
404
service/sign.go
|
@ -1,404 +0,0 @@
|
||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"io"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
crypto "github.com/nspcc-dev/neofs-crypto"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
type keySign struct {
|
|
||||||
key *ecdsa.PublicKey
|
|
||||||
sign []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
type signSourceGroup struct {
|
|
||||||
SignKeyPairSource
|
|
||||||
SignKeyPairAccumulator
|
|
||||||
|
|
||||||
sources []SignedDataSource
|
|
||||||
}
|
|
||||||
|
|
||||||
type signReadersGroup struct {
|
|
||||||
SignKeyPairSource
|
|
||||||
SignKeyPairAccumulator
|
|
||||||
|
|
||||||
readers []SignedDataReader
|
|
||||||
}
|
|
||||||
|
|
||||||
var bytesPool = sync.Pool{
|
|
||||||
New: func() interface{} {
|
|
||||||
return make([]byte, 5<<20)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSignature is a sign field getter.
|
|
||||||
func (s keySign) GetSignature() []byte {
|
|
||||||
return s.sign
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPublicKey is a key field getter,
|
|
||||||
func (s keySign) GetPublicKey() *ecdsa.PublicKey {
|
|
||||||
return s.key
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unites passed key with signature and returns SignKeyPair interface.
|
|
||||||
func newSignatureKeyPair(key *ecdsa.PublicKey, sign []byte) SignKeyPair {
|
|
||||||
return &keySign{
|
|
||||||
key: key,
|
|
||||||
sign: sign,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns data from DataSignatureAccumulator for signature creation/verification.
|
|
||||||
//
|
|
||||||
// If passed DataSignatureAccumulator provides a SignedDataReader interface, data for signature is obtained
|
|
||||||
// using this interface for optimization. In this case, it is understood that reading into the slice D
|
|
||||||
// that the method DataForSignature returns does not change D.
|
|
||||||
//
|
|
||||||
// If returned length of data is negative, ErrNegativeLength returns.
|
|
||||||
func dataForSignature(src SignedDataSource) ([]byte, error) {
|
|
||||||
if src == nil {
|
|
||||||
return nil, ErrNilSignedDataSource
|
|
||||||
}
|
|
||||||
|
|
||||||
r, ok := src.(SignedDataReader)
|
|
||||||
if !ok {
|
|
||||||
return src.SignedData()
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := bytesPool.Get().([]byte)
|
|
||||||
|
|
||||||
if size := r.SignedDataSize(); size < 0 {
|
|
||||||
return nil, ErrNegativeLength
|
|
||||||
} else if size <= cap(buf) {
|
|
||||||
buf = buf[:size]
|
|
||||||
} else {
|
|
||||||
buf = make([]byte, size)
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err := r.ReadSignedData(buf)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf[:n], nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// DataSignature returns the signature of data obtained using the private key.
|
|
||||||
//
|
|
||||||
// If passed data container is nil, ErrNilSignedDataSource returns.
|
|
||||||
// If passed private key is nil, crypto.ErrEmptyPrivateKey returns.
|
|
||||||
// If the data container or the signature function returns an error, it is returned directly.
|
|
||||||
func DataSignature(key *ecdsa.PrivateKey, src SignedDataSource) ([]byte, error) {
|
|
||||||
if key == nil {
|
|
||||||
return nil, crypto.ErrEmptyPrivateKey
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := dataForSignature(src)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer bytesPool.Put(data)
|
|
||||||
|
|
||||||
return crypto.Sign(key, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddSignatureWithKey calculates the data signature and adds it to accumulator with public key.
|
|
||||||
//
|
|
||||||
// Any change of data provoke signature breakdown.
|
|
||||||
//
|
|
||||||
// Returns signing errors only.
|
|
||||||
func AddSignatureWithKey(key *ecdsa.PrivateKey, v DataWithSignKeyAccumulator) error {
|
|
||||||
sign, err := DataSignature(key, v)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
v.AddSignKey(sign, &key.PublicKey)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks passed key-signature pairs for data from the passed container.
|
|
||||||
//
|
|
||||||
// If passed key-signatures pair set is empty, nil returns immediately.
|
|
||||||
func verifySignatures(src SignedDataSource, items ...SignKeyPair) error {
|
|
||||||
if len(items) <= 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := dataForSignature(src)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer bytesPool.Put(data)
|
|
||||||
|
|
||||||
for i := range items {
|
|
||||||
if i > 0 {
|
|
||||||
// add previous key bytes to the signed message
|
|
||||||
|
|
||||||
signKeyDataSrc := SignKeyPairsSignedData(items[i-1])
|
|
||||||
|
|
||||||
signKeyData, err := signKeyDataSrc.SignedData()
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "could not get signed data of key-signature #%d", i)
|
|
||||||
}
|
|
||||||
|
|
||||||
data = append(data, signKeyData...)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := crypto.Verify(
|
|
||||||
items[i].GetPublicKey(),
|
|
||||||
data,
|
|
||||||
items[i].GetSignature(),
|
|
||||||
); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifySignatures checks passed key-signature pairs for data from the passed container.
|
|
||||||
//
|
|
||||||
// If passed data source is nil, ErrNilSignedDataSource returns.
|
|
||||||
// If check data is not ready, corresponding error returns.
|
|
||||||
// If at least one of the pairs is invalid, an error returns.
|
|
||||||
func VerifySignatures(src SignedDataSource, items ...SignKeyPair) error {
|
|
||||||
return verifySignatures(src, items...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyAccumulatedSignatures checks if accumulated key-signature pairs are valid.
|
|
||||||
//
|
|
||||||
// Behaves like VerifySignatures.
|
|
||||||
// If passed key-signature source is empty, ErrNilSignatureKeySource returns.
|
|
||||||
func VerifyAccumulatedSignatures(src DataWithSignKeySource) error {
|
|
||||||
if src == nil {
|
|
||||||
return ErrNilSignatureKeySource
|
|
||||||
}
|
|
||||||
|
|
||||||
return verifySignatures(src, src.GetSignKeyPairs()...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifySignatureWithKey checks data signature from the passed container with passed key.
|
|
||||||
//
|
|
||||||
// If passed data with signature is nil, ErrEmptyDataWithSignature returns.
|
|
||||||
// If passed key is nil, crypto.ErrEmptyPublicKey returns.
|
|
||||||
// A non-nil error returns if and only if the signature does not pass verification.
|
|
||||||
func VerifySignatureWithKey(key *ecdsa.PublicKey, src DataWithSignature) error {
|
|
||||||
if src == nil {
|
|
||||||
return ErrEmptyDataWithSignature
|
|
||||||
} else if key == nil {
|
|
||||||
return crypto.ErrEmptyPublicKey
|
|
||||||
}
|
|
||||||
|
|
||||||
return verifySignatures(
|
|
||||||
src,
|
|
||||||
newSignatureKeyPair(
|
|
||||||
key,
|
|
||||||
src.GetSignature(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignRequestData calculates request data signature and adds it to accumulator.
|
|
||||||
//
|
|
||||||
// Any change of request data provoke signature breakdown.
|
|
||||||
//
|
|
||||||
// If passed private key is nil, crypto.ErrEmptyPrivateKey returns.
|
|
||||||
// If passed RequestSignedData is nil, ErrNilRequestSignedData returns.
|
|
||||||
func SignRequestData(key *ecdsa.PrivateKey, src RequestSignedData) error {
|
|
||||||
if src == nil {
|
|
||||||
return ErrNilRequestSignedData
|
|
||||||
}
|
|
||||||
|
|
||||||
sigSrc, err := GroupSignedPayloads(
|
|
||||||
src,
|
|
||||||
src,
|
|
||||||
NewSignedSessionToken(
|
|
||||||
src.GetSessionToken(),
|
|
||||||
),
|
|
||||||
NewSignedBearerToken(
|
|
||||||
src.GetBearerToken(),
|
|
||||||
),
|
|
||||||
ExtendedHeadersSignedData(src),
|
|
||||||
SignKeyPairsSignedData(src.GetSignKeyPairs()...),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return AddSignatureWithKey(key, sigSrc)
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyRequestData checks if accumulated key-signature pairs of data with token are valid.
|
|
||||||
//
|
|
||||||
// If passed RequestVerifyData is nil, ErrNilRequestVerifyData returns.
|
|
||||||
func VerifyRequestData(src RequestVerifyData) error {
|
|
||||||
if src == nil {
|
|
||||||
return ErrNilRequestVerifyData
|
|
||||||
}
|
|
||||||
|
|
||||||
verSrc, err := GroupVerifyPayloads(
|
|
||||||
src,
|
|
||||||
src,
|
|
||||||
NewVerifiedSessionToken(
|
|
||||||
src.GetSessionToken(),
|
|
||||||
),
|
|
||||||
NewVerifiedBearerToken(
|
|
||||||
src.GetBearerToken(),
|
|
||||||
),
|
|
||||||
ExtendedHeadersSignedData(src),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return VerifyAccumulatedSignatures(verSrc)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedData returns payload bytes concatenation from all sources keeping order.
|
|
||||||
func (s signSourceGroup) SignedData() ([]byte, error) {
|
|
||||||
chunks := make([][]byte, 0, len(s.sources))
|
|
||||||
sz := 0
|
|
||||||
|
|
||||||
for i := range s.sources {
|
|
||||||
data, err := s.sources[i].SignedData()
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrapf(err, "could not get signed payload of element #%d", i)
|
|
||||||
}
|
|
||||||
|
|
||||||
chunks = append(chunks, data)
|
|
||||||
|
|
||||||
sz += len(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
res := make([]byte, sz)
|
|
||||||
off := 0
|
|
||||||
|
|
||||||
for i := range chunks {
|
|
||||||
off += copy(res[off:], chunks[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedData returns payload bytes concatenation from all readers.
|
|
||||||
func (s signReadersGroup) SignedData() ([]byte, error) {
|
|
||||||
return SignedDataFromReader(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedDataSize returns the sum of sizes of all readers.
|
|
||||||
func (s signReadersGroup) SignedDataSize() (sz int) {
|
|
||||||
for i := range s.readers {
|
|
||||||
sz += s.readers[i].SignedDataSize()
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadSignedData reads data from all readers to passed buffer keeping order.
|
|
||||||
//
|
|
||||||
// If the buffer size is insufficient, io.ErrUnexpectedEOF returns.
|
|
||||||
func (s signReadersGroup) ReadSignedData(p []byte) (int, error) {
|
|
||||||
sz := s.SignedDataSize()
|
|
||||||
if len(p) < sz {
|
|
||||||
return 0, io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
|
|
||||||
off := 0
|
|
||||||
|
|
||||||
for i := range s.readers {
|
|
||||||
n, err := s.readers[i].ReadSignedData(p[off:])
|
|
||||||
off += n
|
|
||||||
if err != nil {
|
|
||||||
return off, errors.Wrapf(err, "could not read signed payload of element #%d", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return off, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GroupSignedPayloads groups SignKeyPairAccumulator and SignedDataSource list to DataWithSignKeyAccumulator.
|
|
||||||
//
|
|
||||||
// If passed SignKeyPairAccumulator is nil, ErrNilSignKeyPairAccumulator returns.
|
|
||||||
//
|
|
||||||
// Signed payload of the result is a concatenation of payloads of list elements keeping order.
|
|
||||||
// Nil elements in list are ignored.
|
|
||||||
//
|
|
||||||
// If all elements implement SignedDataReader, result implements it too.
|
|
||||||
func GroupSignedPayloads(acc SignKeyPairAccumulator, sources ...SignedDataSource) (DataWithSignKeyAccumulator, error) {
|
|
||||||
if acc == nil {
|
|
||||||
return nil, ErrNilSignKeyPairAccumulator
|
|
||||||
}
|
|
||||||
|
|
||||||
return groupPayloads(acc, nil, sources...), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GroupVerifyPayloads groups SignKeyPairSource and SignedDataSource list to DataWithSignKeySource.
|
|
||||||
//
|
|
||||||
// If passed SignKeyPairSource is nil, ErrNilSignatureKeySource returns.
|
|
||||||
//
|
|
||||||
// Signed payload of the result is a concatenation of payloads of list elements keeping order.
|
|
||||||
// Nil elements in list are ignored.
|
|
||||||
//
|
|
||||||
// If all elements implement SignedDataReader, result implements it too.
|
|
||||||
func GroupVerifyPayloads(src SignKeyPairSource, sources ...SignedDataSource) (DataWithSignKeySource, error) {
|
|
||||||
if src == nil {
|
|
||||||
return nil, ErrNilSignatureKeySource
|
|
||||||
}
|
|
||||||
|
|
||||||
return groupPayloads(nil, src, sources...), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func groupPayloads(acc SignKeyPairAccumulator, src SignKeyPairSource, sources ...SignedDataSource) interface {
|
|
||||||
SignedDataSource
|
|
||||||
SignKeyPairSource
|
|
||||||
SignKeyPairAccumulator
|
|
||||||
} {
|
|
||||||
var allReaders bool
|
|
||||||
|
|
||||||
for i := range sources {
|
|
||||||
if sources[i] == nil {
|
|
||||||
continue
|
|
||||||
} else if _, allReaders = sources[i].(SignedDataReader); !allReaders {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !allReaders {
|
|
||||||
res := &signSourceGroup{
|
|
||||||
SignKeyPairSource: src,
|
|
||||||
SignKeyPairAccumulator: acc,
|
|
||||||
|
|
||||||
sources: make([]SignedDataSource, 0, len(sources)),
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range sources {
|
|
||||||
if sources[i] != nil {
|
|
||||||
res.sources = append(res.sources, sources[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
res := &signReadersGroup{
|
|
||||||
SignKeyPairSource: src,
|
|
||||||
SignKeyPairAccumulator: acc,
|
|
||||||
|
|
||||||
readers: make([]SignedDataReader, 0, len(sources)),
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range sources {
|
|
||||||
if sources[i] != nil {
|
|
||||||
res.readers = append(res.readers, sources[i].(SignedDataReader))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return res
|
|
||||||
}
|
|
|
@ -1,400 +0,0 @@
|
||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/rand"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
crypto "github.com/nspcc-dev/neofs-crypto"
|
|
||||||
"github.com/nspcc-dev/neofs-crypto/test"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
type testSignedDataSrc struct {
|
|
||||||
err error
|
|
||||||
data []byte
|
|
||||||
token SessionToken
|
|
||||||
|
|
||||||
bearer BearerToken
|
|
||||||
|
|
||||||
extHdrs []ExtendedHeader
|
|
||||||
|
|
||||||
signKeys []SignKeyPair
|
|
||||||
}
|
|
||||||
|
|
||||||
type testSignedDataReader struct {
|
|
||||||
*testSignedDataSrc
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s testSignedDataSrc) GetSignature() []byte {
|
|
||||||
if len(s.signKeys) > 0 {
|
|
||||||
return s.signKeys[0].GetSignature()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s testSignedDataSrc) GetSignKeyPairs() []SignKeyPair {
|
|
||||||
return s.signKeys
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s testSignedDataSrc) SignedData() ([]byte, error) {
|
|
||||||
return s.data, s.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *testSignedDataSrc) AddSignKey(sig []byte, key *ecdsa.PublicKey) {
|
|
||||||
s.signKeys = append(s.signKeys,
|
|
||||||
newSignatureKeyPair(key, sig),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testData(t *testing.T, sz int) []byte {
|
|
||||||
d := make([]byte, sz)
|
|
||||||
_, err := rand.Read(d)
|
|
||||||
require.NoError(t, err)
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s testSignedDataSrc) GetSessionToken() SessionToken {
|
|
||||||
return s.token
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s testSignedDataSrc) GetBearerToken() BearerToken {
|
|
||||||
return s.bearer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s testSignedDataSrc) ExtendedHeaders() []ExtendedHeader {
|
|
||||||
return s.extHdrs
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s testSignedDataReader) SignedDataSize() int {
|
|
||||||
return len(s.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s testSignedDataReader) ReadSignedData(buf []byte) (int, error) {
|
|
||||||
if s.err != nil {
|
|
||||||
return 0, s.err
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
if len(buf) < len(s.data) {
|
|
||||||
err = io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
return copy(buf, s.data), err
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDataSignature(t *testing.T) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// nil private key
|
|
||||||
_, err = DataSignature(nil, nil)
|
|
||||||
require.EqualError(t, err, crypto.ErrEmptyPrivateKey.Error())
|
|
||||||
|
|
||||||
// create test private key
|
|
||||||
sk := test.DecodeKey(0)
|
|
||||||
|
|
||||||
// nil private key
|
|
||||||
_, err = DataSignature(sk, nil)
|
|
||||||
require.EqualError(t, err, ErrNilSignedDataSource.Error())
|
|
||||||
|
|
||||||
t.Run("common signed data source", func(t *testing.T) {
|
|
||||||
// create test data source
|
|
||||||
src := &testSignedDataSrc{
|
|
||||||
data: testData(t, 10),
|
|
||||||
}
|
|
||||||
|
|
||||||
// create custom error for data source
|
|
||||||
src.err = errors.New("test error for data source")
|
|
||||||
|
|
||||||
_, err = DataSignature(sk, src)
|
|
||||||
require.EqualError(t, err, src.err.Error())
|
|
||||||
|
|
||||||
// reset error to nil
|
|
||||||
src.err = nil
|
|
||||||
|
|
||||||
// calculate data signature
|
|
||||||
sig, err := DataSignature(sk, src)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// ascertain that the signature passes verification
|
|
||||||
require.NoError(t, crypto.Verify(&sk.PublicKey, src.data, sig))
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("signed data reader", func(t *testing.T) {
|
|
||||||
// create test signed data reader
|
|
||||||
src := &testSignedDataSrc{
|
|
||||||
data: testData(t, 10),
|
|
||||||
}
|
|
||||||
|
|
||||||
// create custom error for signed data reader
|
|
||||||
src.err = errors.New("test error for signed data reader")
|
|
||||||
|
|
||||||
sig, err := DataSignature(sk, src)
|
|
||||||
require.EqualError(t, err, src.err.Error())
|
|
||||||
|
|
||||||
// reset error to nil
|
|
||||||
src.err = nil
|
|
||||||
|
|
||||||
// calculate data signature
|
|
||||||
sig, err = DataSignature(sk, src)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// ascertain that the signature passes verification
|
|
||||||
require.NoError(t, crypto.Verify(&sk.PublicKey, src.data, sig))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAddSignatureWithKey(t *testing.T) {
|
|
||||||
require.NoError(t,
|
|
||||||
AddSignatureWithKey(
|
|
||||||
test.DecodeKey(0),
|
|
||||||
&testSignedDataSrc{
|
|
||||||
data: testData(t, 10),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVerifySignatures(t *testing.T) {
|
|
||||||
// empty signatures
|
|
||||||
require.NoError(t, VerifySignatures(nil))
|
|
||||||
|
|
||||||
// create test signature source
|
|
||||||
src := &testSignedDataSrc{
|
|
||||||
data: testData(t, 10),
|
|
||||||
}
|
|
||||||
|
|
||||||
// create private key for test
|
|
||||||
sk := test.DecodeKey(0)
|
|
||||||
|
|
||||||
// calculate a signature of the data
|
|
||||||
sig, err := crypto.Sign(sk, src.data)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// ascertain that verification is passed
|
|
||||||
require.NoError(t,
|
|
||||||
VerifySignatures(
|
|
||||||
src,
|
|
||||||
newSignatureKeyPair(&sk.PublicKey, sig),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
// break the signature
|
|
||||||
sig[0]++
|
|
||||||
|
|
||||||
require.Error(t,
|
|
||||||
VerifySignatures(
|
|
||||||
src,
|
|
||||||
newSignatureKeyPair(&sk.PublicKey, sig),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
// restore the signature
|
|
||||||
sig[0]--
|
|
||||||
|
|
||||||
// empty data source
|
|
||||||
require.EqualError(t,
|
|
||||||
VerifySignatures(nil, nil),
|
|
||||||
ErrNilSignedDataSource.Error(),
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVerifyAccumulatedSignatures(t *testing.T) {
|
|
||||||
// nil signature source
|
|
||||||
require.EqualError(t,
|
|
||||||
VerifyAccumulatedSignatures(nil),
|
|
||||||
ErrNilSignatureKeySource.Error(),
|
|
||||||
)
|
|
||||||
|
|
||||||
// create test private key
|
|
||||||
sk := test.DecodeKey(0)
|
|
||||||
|
|
||||||
signKey := new(RequestVerificationHeader_Signature)
|
|
||||||
signKey.Peer = crypto.MarshalPublicKey(&sk.PublicKey)
|
|
||||||
|
|
||||||
// create signature source
|
|
||||||
src := &testSignedDataSrc{
|
|
||||||
data: testData(t, 10),
|
|
||||||
|
|
||||||
signKeys: []SignKeyPair{signKey},
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// calculate a signature
|
|
||||||
signKey.Sign, err = crypto.Sign(sk, src.data)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// ascertain that verification is passed
|
|
||||||
require.NoError(t, VerifyAccumulatedSignatures(src))
|
|
||||||
|
|
||||||
// break the signature
|
|
||||||
signKey.Sign[0]++
|
|
||||||
|
|
||||||
// ascertain that verification is failed
|
|
||||||
require.Error(t, VerifyAccumulatedSignatures(src))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVerifySignatureWithKey(t *testing.T) {
|
|
||||||
// nil signature source
|
|
||||||
require.EqualError(t,
|
|
||||||
VerifySignatureWithKey(nil, nil),
|
|
||||||
ErrEmptyDataWithSignature.Error(),
|
|
||||||
)
|
|
||||||
|
|
||||||
signKey := new(RequestVerificationHeader_Signature)
|
|
||||||
|
|
||||||
// create test signature source
|
|
||||||
src := &testSignedDataSrc{
|
|
||||||
data: testData(t, 10),
|
|
||||||
|
|
||||||
signKeys: []SignKeyPair{signKey},
|
|
||||||
}
|
|
||||||
|
|
||||||
// nil public key
|
|
||||||
require.EqualError(t,
|
|
||||||
VerifySignatureWithKey(nil, src),
|
|
||||||
crypto.ErrEmptyPublicKey.Error(),
|
|
||||||
)
|
|
||||||
|
|
||||||
// create test private key
|
|
||||||
sk := test.DecodeKey(0)
|
|
||||||
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// calculate a signature
|
|
||||||
signKey.Sign, err = crypto.Sign(sk, src.data)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// ascertain that verification is passed
|
|
||||||
require.NoError(t, VerifySignatureWithKey(&sk.PublicKey, src))
|
|
||||||
|
|
||||||
// break the signature
|
|
||||||
signKey.Sign[0]++
|
|
||||||
|
|
||||||
// ascertain that verification is failed
|
|
||||||
require.Error(t, VerifySignatureWithKey(&sk.PublicKey, src))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSignVerifyRequestData(t *testing.T) {
|
|
||||||
// sign with empty RequestSignedData
|
|
||||||
require.EqualError(t,
|
|
||||||
SignRequestData(nil, nil),
|
|
||||||
ErrNilRequestSignedData.Error(),
|
|
||||||
)
|
|
||||||
|
|
||||||
// verify with empty RequestVerifyData
|
|
||||||
require.EqualError(t,
|
|
||||||
VerifyRequestData(nil),
|
|
||||||
ErrNilRequestVerifyData.Error(),
|
|
||||||
)
|
|
||||||
|
|
||||||
// create test session token
|
|
||||||
var (
|
|
||||||
token = new(Token)
|
|
||||||
initVerb = Token_Info_Verb(1)
|
|
||||||
|
|
||||||
bearer = new(BearerTokenMsg)
|
|
||||||
bearerEpoch = uint64(8)
|
|
||||||
|
|
||||||
extHdrKey = "key"
|
|
||||||
extHdr = new(RequestExtendedHeader_KV)
|
|
||||||
)
|
|
||||||
|
|
||||||
token.SetVerb(initVerb)
|
|
||||||
|
|
||||||
bearer.SetExpirationEpoch(bearerEpoch)
|
|
||||||
|
|
||||||
extHdr.SetK(extHdrKey)
|
|
||||||
|
|
||||||
// create test data with token
|
|
||||||
src := &testSignedDataSrc{
|
|
||||||
data: testData(t, 10),
|
|
||||||
token: token,
|
|
||||||
|
|
||||||
bearer: bearer,
|
|
||||||
|
|
||||||
extHdrs: []ExtendedHeader{
|
|
||||||
wrapExtendedHeaderKV(extHdr),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// create test private key
|
|
||||||
sk := test.DecodeKey(0)
|
|
||||||
|
|
||||||
// sign with private key
|
|
||||||
require.NoError(t, SignRequestData(sk, src))
|
|
||||||
|
|
||||||
// ascertain that verification is passed
|
|
||||||
require.NoError(t, VerifyRequestData(src))
|
|
||||||
|
|
||||||
// break the data
|
|
||||||
src.data[0]++
|
|
||||||
|
|
||||||
// ascertain that verification is failed
|
|
||||||
require.Error(t, VerifyRequestData(src))
|
|
||||||
|
|
||||||
// restore the data
|
|
||||||
src.data[0]--
|
|
||||||
|
|
||||||
// break the token
|
|
||||||
token.SetVerb(initVerb + 1)
|
|
||||||
|
|
||||||
// ascertain that verification is failed
|
|
||||||
require.Error(t, VerifyRequestData(src))
|
|
||||||
|
|
||||||
// restore the token
|
|
||||||
token.SetVerb(initVerb)
|
|
||||||
|
|
||||||
// ascertain that verification is passed
|
|
||||||
require.NoError(t, VerifyRequestData(src))
|
|
||||||
|
|
||||||
// break the Bearer token
|
|
||||||
bearer.SetExpirationEpoch(bearerEpoch + 1)
|
|
||||||
|
|
||||||
// ascertain that verification is failed
|
|
||||||
require.Error(t, VerifyRequestData(src))
|
|
||||||
|
|
||||||
// restore the Bearer token
|
|
||||||
bearer.SetExpirationEpoch(bearerEpoch)
|
|
||||||
|
|
||||||
// ascertain that verification is passed
|
|
||||||
require.NoError(t, VerifyRequestData(src))
|
|
||||||
|
|
||||||
// break the extended header
|
|
||||||
extHdr.SetK(extHdrKey + "1")
|
|
||||||
|
|
||||||
// ascertain that verification is failed
|
|
||||||
require.Error(t, VerifyRequestData(src))
|
|
||||||
|
|
||||||
// restore the extended header
|
|
||||||
extHdr.SetK(extHdrKey)
|
|
||||||
|
|
||||||
// ascertain that verification is passed
|
|
||||||
require.NoError(t, VerifyRequestData(src))
|
|
||||||
|
|
||||||
// wrap to data reader
|
|
||||||
rdr := &testSignedDataReader{
|
|
||||||
testSignedDataSrc: src,
|
|
||||||
}
|
|
||||||
|
|
||||||
// sign with private key
|
|
||||||
require.NoError(t, SignRequestData(sk, rdr))
|
|
||||||
|
|
||||||
// ascertain that verification is passed
|
|
||||||
require.NoError(t, VerifyRequestData(rdr))
|
|
||||||
|
|
||||||
if len(rdr.GetSignKeyPairs()) < 2 {
|
|
||||||
// add one more signature
|
|
||||||
require.NoError(t, SignRequestData(test.DecodeKey(1), rdr))
|
|
||||||
}
|
|
||||||
|
|
||||||
// change key-signature order
|
|
||||||
rdr.signKeys[0], rdr.signKeys[1] = rdr.signKeys[1], rdr.signKeys[0]
|
|
||||||
|
|
||||||
// ascertain that verification is failed
|
|
||||||
require.Error(t, VerifyRequestData(src))
|
|
||||||
}
|
|
265
service/token.go
265
service/token.go
|
@ -1,265 +0,0 @@
|
||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"encoding/binary"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/refs"
|
|
||||||
crypto "github.com/nspcc-dev/neofs-crypto"
|
|
||||||
)
|
|
||||||
|
|
||||||
type signAccumWithToken struct {
|
|
||||||
SignedDataSource
|
|
||||||
SignKeyPairAccumulator
|
|
||||||
SignKeyPairSource
|
|
||||||
|
|
||||||
token SessionToken
|
|
||||||
}
|
|
||||||
|
|
||||||
type signDataReaderWithToken struct {
|
|
||||||
SignedDataSource
|
|
||||||
SignKeyPairAccumulator
|
|
||||||
SignKeyPairSource
|
|
||||||
|
|
||||||
rdr SignedDataReader
|
|
||||||
|
|
||||||
token SessionToken
|
|
||||||
}
|
|
||||||
|
|
||||||
type signedSessionToken struct {
|
|
||||||
SessionToken
|
|
||||||
}
|
|
||||||
|
|
||||||
const verbSize = 4
|
|
||||||
|
|
||||||
const fixedTokenDataSize = 0 +
|
|
||||||
refs.UUIDSize +
|
|
||||||
refs.OwnerIDSize +
|
|
||||||
verbSize +
|
|
||||||
refs.UUIDSize +
|
|
||||||
refs.CIDSize +
|
|
||||||
8 +
|
|
||||||
8
|
|
||||||
|
|
||||||
var tokenEndianness = binary.BigEndian
|
|
||||||
|
|
||||||
// GetID is an ID field getter.
|
|
||||||
func (m Token_Info) GetID() TokenID {
|
|
||||||
return m.ID
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetID is an ID field setter.
|
|
||||||
func (m *Token_Info) SetID(id TokenID) {
|
|
||||||
m.ID = id
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetOwnerID is an OwnerID field getter.
|
|
||||||
func (m Token_Info) GetOwnerID() OwnerID {
|
|
||||||
return m.OwnerID
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetOwnerID is an OwnerID field setter.
|
|
||||||
func (m *Token_Info) SetOwnerID(id OwnerID) {
|
|
||||||
m.OwnerID = id
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetVerb is a Verb field setter.
|
|
||||||
func (m *Token_Info) SetVerb(verb Token_Info_Verb) {
|
|
||||||
m.Verb = verb
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAddress is an Address field getter.
|
|
||||||
func (m Token_Info) GetAddress() Address {
|
|
||||||
return m.Address
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetAddress is an Address field setter.
|
|
||||||
func (m *Token_Info) SetAddress(addr Address) {
|
|
||||||
m.Address = addr
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreationEpoch is a Created field getter.
|
|
||||||
func (m TokenLifetime) CreationEpoch() uint64 {
|
|
||||||
return m.Created
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetCreationEpoch is a Created field setter.
|
|
||||||
func (m *TokenLifetime) SetCreationEpoch(e uint64) {
|
|
||||||
m.Created = e
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExpirationEpoch is a ValidUntil field getter.
|
|
||||||
func (m TokenLifetime) ExpirationEpoch() uint64 {
|
|
||||||
return m.ValidUntil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetExpirationEpoch is a ValidUntil field setter.
|
|
||||||
func (m *TokenLifetime) SetExpirationEpoch(e uint64) {
|
|
||||||
m.ValidUntil = e
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetSessionKey is a SessionKey field setter.
|
|
||||||
func (m *Token_Info) SetSessionKey(key []byte) {
|
|
||||||
m.SessionKey = key
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetOwnerKey is an OwnerKey field setter.
|
|
||||||
func (m *Token_Info) SetOwnerKey(key []byte) {
|
|
||||||
m.OwnerKey = key
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetSignature is a Signature field setter.
|
|
||||||
func (m *Token) SetSignature(sig []byte) {
|
|
||||||
m.Signature = sig
|
|
||||||
}
|
|
||||||
|
|
||||||
// Size returns the size of a binary representation of the verb.
|
|
||||||
func (x Token_Info_Verb) Size() int {
|
|
||||||
return verbSize
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bytes returns a binary representation of the verb.
|
|
||||||
func (x Token_Info_Verb) Bytes() []byte {
|
|
||||||
data := make([]byte, verbSize)
|
|
||||||
tokenEndianness.PutUint32(data, uint32(x))
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddSignKey calls a Signature field setter and an OwnerKey field setter with corresponding arguments.
|
|
||||||
func (s signedSessionToken) AddSignKey(sig []byte, key *ecdsa.PublicKey) {
|
|
||||||
if s.SessionToken != nil {
|
|
||||||
s.SessionToken.SetSignature(sig)
|
|
||||||
|
|
||||||
s.SessionToken.SetOwnerKey(
|
|
||||||
crypto.MarshalPublicKey(key),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedData returns token information in a binary representation.
|
|
||||||
func (s signedSessionToken) SignedData() ([]byte, error) {
|
|
||||||
return SignedDataFromReader(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedDataSize returns the length of signed token information slice.
|
|
||||||
func (s signedSessionToken) SignedDataSize() int {
|
|
||||||
return tokenInfoSize(s.SessionToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadSignedData copies a binary representation of the token information to passed buffer.
|
|
||||||
//
|
|
||||||
// If buffer length is less than required, io.ErrUnexpectedEOF returns.
|
|
||||||
func (s signedSessionToken) ReadSignedData(p []byte) (int, error) {
|
|
||||||
sz := s.SignedDataSize()
|
|
||||||
if len(p) < sz {
|
|
||||||
return 0, io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
|
|
||||||
copyTokenSignedData(p, s.SessionToken)
|
|
||||||
|
|
||||||
return sz, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSignedSessionToken wraps passed SessionToken in a component suitable for signing.
|
|
||||||
//
|
|
||||||
// Result can be used in AddSignatureWithKey function.
|
|
||||||
func NewSignedSessionToken(token SessionToken) DataWithSignKeyAccumulator {
|
|
||||||
return &signedSessionToken{
|
|
||||||
SessionToken: token,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewVerifiedSessionToken wraps passed SessionToken in a component suitable for signature verification.
|
|
||||||
//
|
|
||||||
// Result can be used in VerifySignatureWithKey function.
|
|
||||||
func NewVerifiedSessionToken(token SessionToken) DataWithSignature {
|
|
||||||
return &signedSessionToken{
|
|
||||||
SessionToken: token,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func tokenInfoSize(v SessionKeySource) int {
|
|
||||||
if v == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return fixedTokenDataSize + len(v.GetSessionKey())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fills passed buffer with signing token information bytes.
|
|
||||||
// Does not check buffer length, it is understood that enough space is allocated in it.
|
|
||||||
//
|
|
||||||
// If passed SessionTokenInfo, buffer remains unchanged.
|
|
||||||
func copyTokenSignedData(buf []byte, token SessionTokenInfo) {
|
|
||||||
if token == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var off int
|
|
||||||
|
|
||||||
off += copy(buf[off:], token.GetID().Bytes())
|
|
||||||
|
|
||||||
off += copy(buf[off:], token.GetOwnerID().Bytes())
|
|
||||||
|
|
||||||
off += copy(buf[off:], token.GetVerb().Bytes())
|
|
||||||
|
|
||||||
addr := token.GetAddress()
|
|
||||||
off += copy(buf[off:], addr.CID.Bytes())
|
|
||||||
off += copy(buf[off:], addr.ObjectID.Bytes())
|
|
||||||
|
|
||||||
tokenEndianness.PutUint64(buf[off:], token.CreationEpoch())
|
|
||||||
off += 8
|
|
||||||
|
|
||||||
tokenEndianness.PutUint64(buf[off:], token.ExpirationEpoch())
|
|
||||||
off += 8
|
|
||||||
|
|
||||||
copy(buf[off:], token.GetSessionKey())
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignedData concatenates signed data with session token information. Returns concatenation result.
|
|
||||||
//
|
|
||||||
// Token bytes are added if and only if token is not nil.
|
|
||||||
func (s signAccumWithToken) SignedData() ([]byte, error) {
|
|
||||||
data, err := s.SignedDataSource.SignedData()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
tokenData := make([]byte, tokenInfoSize(s.token))
|
|
||||||
|
|
||||||
copyTokenSignedData(tokenData, s.token)
|
|
||||||
|
|
||||||
return append(data, tokenData...), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s signDataReaderWithToken) SignedDataSize() int {
|
|
||||||
sz := s.rdr.SignedDataSize()
|
|
||||||
if sz < 0 {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
sz += tokenInfoSize(s.token)
|
|
||||||
|
|
||||||
return sz
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s signDataReaderWithToken) ReadSignedData(p []byte) (int, error) {
|
|
||||||
dataSize := s.rdr.SignedDataSize()
|
|
||||||
if dataSize < 0 {
|
|
||||||
return 0, ErrNegativeLength
|
|
||||||
}
|
|
||||||
|
|
||||||
sumSize := dataSize + tokenInfoSize(s.token)
|
|
||||||
|
|
||||||
if len(p) < sumSize {
|
|
||||||
return 0, io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
|
|
||||||
if n, err := s.rdr.ReadSignedData(p); err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
copyTokenSignedData(p[dataSize:], s.token)
|
|
||||||
|
|
||||||
return sumSize, nil
|
|
||||||
}
|
|
|
@ -1,248 +0,0 @@
|
||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/refs"
|
|
||||||
crypto "github.com/nspcc-dev/neofs-crypto"
|
|
||||||
"github.com/nspcc-dev/neofs-crypto/test"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestTokenGettersSetters(t *testing.T) {
|
|
||||||
var tok SessionToken = new(Token)
|
|
||||||
|
|
||||||
{ // ID
|
|
||||||
id, err := refs.NewUUID()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
tok.SetID(id)
|
|
||||||
|
|
||||||
require.Equal(t, id, tok.GetID())
|
|
||||||
}
|
|
||||||
|
|
||||||
{ // OwnerID
|
|
||||||
ownerID := OwnerID{}
|
|
||||||
_, err := rand.Read(ownerID[:])
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
tok.SetOwnerID(ownerID)
|
|
||||||
|
|
||||||
require.Equal(t, ownerID, tok.GetOwnerID())
|
|
||||||
}
|
|
||||||
|
|
||||||
{ // Verb
|
|
||||||
verb := Token_Info_Verb(3)
|
|
||||||
|
|
||||||
tok.SetVerb(verb)
|
|
||||||
|
|
||||||
require.Equal(t, verb, tok.GetVerb())
|
|
||||||
}
|
|
||||||
|
|
||||||
{ // Address
|
|
||||||
addr := Address{}
|
|
||||||
_, err := rand.Read(addr.CID[:])
|
|
||||||
require.NoError(t, err)
|
|
||||||
_, err = rand.Read(addr.ObjectID[:])
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
tok.SetAddress(addr)
|
|
||||||
|
|
||||||
require.Equal(t, addr, tok.GetAddress())
|
|
||||||
}
|
|
||||||
|
|
||||||
{ // Created
|
|
||||||
e := uint64(5)
|
|
||||||
|
|
||||||
tok.SetCreationEpoch(e)
|
|
||||||
|
|
||||||
require.Equal(t, e, tok.CreationEpoch())
|
|
||||||
}
|
|
||||||
|
|
||||||
{ // ValidUntil
|
|
||||||
e := uint64(5)
|
|
||||||
|
|
||||||
tok.SetExpirationEpoch(e)
|
|
||||||
|
|
||||||
require.Equal(t, e, tok.ExpirationEpoch())
|
|
||||||
}
|
|
||||||
|
|
||||||
{ // SessionKey
|
|
||||||
key := make([]byte, 10)
|
|
||||||
_, err := rand.Read(key)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
tok.SetSessionKey(key)
|
|
||||||
|
|
||||||
require.Equal(t, key, tok.GetSessionKey())
|
|
||||||
}
|
|
||||||
|
|
||||||
{ // Signature
|
|
||||||
sig := make([]byte, 10)
|
|
||||||
_, err := rand.Read(sig)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
tok.SetSignature(sig)
|
|
||||||
|
|
||||||
require.Equal(t, sig, tok.GetSignature())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSignToken(t *testing.T) {
|
|
||||||
var token SessionToken = new(Token)
|
|
||||||
|
|
||||||
// create private key for signing
|
|
||||||
sk := test.DecodeKey(0)
|
|
||||||
pk := &sk.PublicKey
|
|
||||||
|
|
||||||
id := TokenID{}
|
|
||||||
_, err := rand.Read(id[:])
|
|
||||||
require.NoError(t, err)
|
|
||||||
token.SetID(id)
|
|
||||||
|
|
||||||
ownerID := OwnerID{}
|
|
||||||
_, err = rand.Read(ownerID[:])
|
|
||||||
require.NoError(t, err)
|
|
||||||
token.SetOwnerID(ownerID)
|
|
||||||
|
|
||||||
verb := Token_Info_Verb(1)
|
|
||||||
token.SetVerb(verb)
|
|
||||||
|
|
||||||
addr := Address{}
|
|
||||||
_, err = rand.Read(addr.ObjectID[:])
|
|
||||||
require.NoError(t, err)
|
|
||||||
_, err = rand.Read(addr.CID[:])
|
|
||||||
require.NoError(t, err)
|
|
||||||
token.SetAddress(addr)
|
|
||||||
|
|
||||||
cEpoch := uint64(1)
|
|
||||||
token.SetCreationEpoch(cEpoch)
|
|
||||||
|
|
||||||
fEpoch := uint64(2)
|
|
||||||
token.SetExpirationEpoch(fEpoch)
|
|
||||||
|
|
||||||
sessionKey := make([]byte, 10)
|
|
||||||
_, err = rand.Read(sessionKey[:])
|
|
||||||
require.NoError(t, err)
|
|
||||||
token.SetSessionKey(sessionKey)
|
|
||||||
|
|
||||||
signedToken := NewSignedSessionToken(token)
|
|
||||||
verifiedToken := NewVerifiedSessionToken(token)
|
|
||||||
|
|
||||||
// sign and verify token
|
|
||||||
require.NoError(t, AddSignatureWithKey(sk, signedToken))
|
|
||||||
require.NoError(t, VerifySignatureWithKey(pk, verifiedToken))
|
|
||||||
|
|
||||||
items := []struct {
|
|
||||||
corrupt func()
|
|
||||||
restore func()
|
|
||||||
}{
|
|
||||||
{ // ID
|
|
||||||
corrupt: func() {
|
|
||||||
id[0]++
|
|
||||||
token.SetID(id)
|
|
||||||
},
|
|
||||||
restore: func() {
|
|
||||||
id[0]--
|
|
||||||
token.SetID(id)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ // Owner ID
|
|
||||||
corrupt: func() {
|
|
||||||
ownerID[0]++
|
|
||||||
token.SetOwnerID(ownerID)
|
|
||||||
},
|
|
||||||
restore: func() {
|
|
||||||
ownerID[0]--
|
|
||||||
token.SetOwnerID(ownerID)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ // Verb
|
|
||||||
corrupt: func() {
|
|
||||||
token.SetVerb(verb + 1)
|
|
||||||
},
|
|
||||||
restore: func() {
|
|
||||||
token.SetVerb(verb)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ // ObjectID
|
|
||||||
corrupt: func() {
|
|
||||||
addr.ObjectID[0]++
|
|
||||||
token.SetAddress(addr)
|
|
||||||
},
|
|
||||||
restore: func() {
|
|
||||||
addr.ObjectID[0]--
|
|
||||||
token.SetAddress(addr)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ // CID
|
|
||||||
corrupt: func() {
|
|
||||||
addr.CID[0]++
|
|
||||||
token.SetAddress(addr)
|
|
||||||
},
|
|
||||||
restore: func() {
|
|
||||||
addr.CID[0]--
|
|
||||||
token.SetAddress(addr)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ // Creation epoch
|
|
||||||
corrupt: func() {
|
|
||||||
token.SetCreationEpoch(cEpoch + 1)
|
|
||||||
},
|
|
||||||
restore: func() {
|
|
||||||
token.SetCreationEpoch(cEpoch)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ // Expiration epoch
|
|
||||||
corrupt: func() {
|
|
||||||
token.SetExpirationEpoch(fEpoch + 1)
|
|
||||||
},
|
|
||||||
restore: func() {
|
|
||||||
token.SetExpirationEpoch(fEpoch)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ // Session key
|
|
||||||
corrupt: func() {
|
|
||||||
sessionKey[0]++
|
|
||||||
token.SetSessionKey(sessionKey)
|
|
||||||
},
|
|
||||||
restore: func() {
|
|
||||||
sessionKey[0]--
|
|
||||||
token.SetSessionKey(sessionKey)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, v := range items {
|
|
||||||
v.corrupt()
|
|
||||||
require.Error(t, VerifySignatureWithKey(pk, verifiedToken))
|
|
||||||
v.restore()
|
|
||||||
require.NoError(t, VerifySignatureWithKey(pk, verifiedToken))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSignedSessionToken_AddSignKey(t *testing.T) {
|
|
||||||
// nil SessionToken
|
|
||||||
s := new(signedSessionToken)
|
|
||||||
|
|
||||||
require.NotPanics(t, func() {
|
|
||||||
s.AddSignKey(nil, nil)
|
|
||||||
})
|
|
||||||
|
|
||||||
// create test public key and signature
|
|
||||||
pk := &test.DecodeKey(0).PublicKey
|
|
||||||
sig := []byte{1, 2, 3}
|
|
||||||
|
|
||||||
s.SessionToken = new(Token)
|
|
||||||
|
|
||||||
// add key-signature pair to SessionToken
|
|
||||||
s.AddSignKey(sig, pk)
|
|
||||||
|
|
||||||
require.Equal(t, sig, s.GetSignature())
|
|
||||||
|
|
||||||
require.Equal(t,
|
|
||||||
crypto.MarshalPublicKey(pk),
|
|
||||||
s.GetOwnerKey(),
|
|
||||||
)
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue