Remove v1 code

This commit is contained in:
Alex Vanin 2020-08-12 09:37:59 +03:00 committed by Stanislav Bogatyrev
parent ed7879a89e
commit 0a5d0ff1a2
140 changed files with 0 additions and 45161 deletions

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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