forked from TrueCloudLab/frostfs-api-go
Remove v1 code
This commit is contained in:
parent
ed7879a89e
commit
0a5d0ff1a2
140 changed files with 0 additions and 45161 deletions
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 }
|
5050
object/service.pb.go
5050
object/service.pb.go
File diff suppressed because it is too large
Load diff
|
@ -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
|
||||
}
|
3568
object/types.pb.go
3568
object/types.pb.go
File diff suppressed because it is too large
Load diff
|
@ -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)
|
||||
})
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue