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) } 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 o.Headers = make([]Header, len(m.Headers)) o.Payload = make([]byte, len(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: o.Headers[i] = Header{ Value: &Header_HomoHash{ HomoHash: v.HomoHash, }, } case *Header_Token: o.Headers[i] = Header{ Value: &Header_Token{ Token: v.Token, }, } default: o.Headers[i] = *proto.Clone(&m.Headers[i]).(*Header) } } copy(o.Payload, m.Payload) } // 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 }