package record import ( "errors" "fmt" "sync" "github.com/safing/portmaster/base/database/accessor" "github.com/safing/structures/container" "github.com/safing/structures/dsd" "github.com/safing/structures/varint" ) // Wrapper wraps raw data and implements the Record interface. type Wrapper struct { Base sync.Mutex Format uint8 Data []byte } // NewRawWrapper returns a record wrapper for the given data, including metadata. This is normally only used by storage backends when loading records. func NewRawWrapper(database, key string, data []byte) (*Wrapper, error) { version, offset, err := varint.Unpack8(data) if err != nil { return nil, err } if version != 1 { return nil, fmt.Errorf("incompatible record version: %d", version) } metaSection, n, err := varint.GetNextBlock(data[offset:]) if err != nil { return nil, fmt.Errorf("could not get meta section: %w", err) } offset += n newMeta := &Meta{} _, err = dsd.Load(metaSection, newMeta) if err != nil { return nil, fmt.Errorf("could not unmarshal meta section: %w", err) } var format uint8 = dsd.RAW if !newMeta.IsDeleted() { format, n, err = varint.Unpack8(data[offset:]) if err != nil { return nil, fmt.Errorf("could not get dsd format: %w", err) } offset += n } return &Wrapper{ Base{ database, key, newMeta, }, sync.Mutex{}, format, data[offset:], }, nil } // NewWrapper returns a new record wrapper for the given data. func NewWrapper(key string, meta *Meta, format uint8, data []byte) (*Wrapper, error) { dbName, dbKey := ParseKey(key) return &Wrapper{ Base{ dbName: dbName, dbKey: dbKey, meta: meta, }, sync.Mutex{}, format, data, }, nil } // NewWrapperFromDatabase returns a new record wrapper for the given data. func NewWrapperFromDatabase(dbName, dbKey string, meta *Meta, format uint8, data []byte) (*Wrapper, error) { return &Wrapper{ Base{ dbName: dbName, dbKey: dbKey, meta: meta, }, sync.Mutex{}, format, data, }, nil } // Marshal marshals the format and data. func (w *Wrapper) Marshal(r Record, format uint8) ([]byte, error) { if w.Meta() == nil { return nil, errors.New("missing meta") } if w.Meta().Deleted > 0 { return nil, nil } if format != dsd.AUTO && format != w.Format { return nil, errors.New("could not dump model, wrapped object format mismatch") } data := make([]byte, len(w.Data)+1) data[0] = w.Format copy(data[1:], w.Data) return data, nil } // MarshalDataOnly marshals the data only. func (w *Wrapper) MarshalDataOnly(self Record, format uint8) ([]byte, error) { if w.Meta() == nil { return nil, errors.New("missing meta") } if w.Meta().Deleted > 0 { return nil, nil } if format != dsd.AUTO && format != w.Format { return nil, errors.New("could not dump model, wrapped object format mismatch") } return w.Data, nil } // MarshalRecord marshals the data, format and metadata. func (w *Wrapper) MarshalRecord(r Record) ([]byte, error) { // Duplication necessary, as the version from Base would call Base.Marshal instead of Wrapper.Marshal if w.Meta() == nil { return nil, errors.New("missing meta") } // version c := container.New([]byte{1}) // meta metaSection, err := dsd.Dump(w.meta, dsd.GenCode) if err != nil { return nil, err } c.AppendAsBlock(metaSection) // data dataSection, err := w.Marshal(r, dsd.AUTO) if err != nil { return nil, err } c.Append(dataSection) return c.CompileData(), nil } // IsWrapped returns whether the record is a Wrapper. func (w *Wrapper) IsWrapped() bool { return true } // Unwrap unwraps data into a record. func Unwrap(wrapped, r Record) error { wrapper, ok := wrapped.(*Wrapper) if !ok { return fmt.Errorf("cannot unwrap %T", wrapped) } err := dsd.LoadAsFormat(wrapper.Data, wrapper.Format, r) if err != nil { return fmt.Errorf("failed to unwrap %T: %w", r, err) } r.SetKey(wrapped.Key()) r.SetMeta(wrapped.Meta()) return nil } // GetAccessor returns an accessor for this record, if available. func (w *Wrapper) GetAccessor(self Record) accessor.Accessor { if w.Format == dsd.JSON && len(w.Data) > 0 { return accessor.NewJSONBytesAccessor(&w.Data) } return nil }