Skip to content
Snippets Groups Projects
payload.go 4.15 KiB
package payload

import (
	"sync"

	"github.com/iotaledger/goshimmer/packages/binary/drng/payload/header"
	"github.com/iotaledger/goshimmer/packages/binary/marshalutil"
	"github.com/iotaledger/goshimmer/packages/binary/tangle/model/message/payload"
	"github.com/iotaledger/hive.go/stringify"
)

type Payload struct {
	header header.Header
	data   []byte

	bytes      []byte
	bytesMutex sync.RWMutex
}

func New(header header.Header, data []byte) *Payload {
	return &Payload{
		header: header,
		data:   data,
	}
}

func (p *Payload) SubType() header.Type {
	return p.header.PayloadType()
}

func (payload *Payload) Instance() uint32 {
	return payload.header.Instance()
}

func (payload *Payload) Data() []byte {
	return payload.data
}

// Parse is a wrapper for simplified unmarshaling in a byte stream using the marshalUtil package.
func Parse(marshalUtil *marshalutil.MarshalUtil) (*Payload, error) {
	if payload, err := marshalUtil.Parse(func(data []byte) (interface{}, error, int) { return FromBytes(data) }); err != nil {
		return &Payload{}, err
	} else {
		return payload.(*Payload), nil
	}
}

// FromBytes parses the marshaled version of a Payload into an object.
// It either returns a new Payload or fills an optionally provided Payload with the parsed information.
func FromBytes(bytes []byte, optionalTargetObject ...*Payload) (result *Payload, err error, consumedBytes int) {
	// determine the target object that will hold the unmarshaled information
	switch len(optionalTargetObject) {
	case 0:
		result = &Payload{}
	case 1:
		result = optionalTargetObject[0]
	default:
		panic("too many arguments in call to OutputFromBytes")
	}

	// initialize helper
	marshalUtil := marshalutil.New(bytes)

	// read information that are required to identify the payload from the outside
	if _, err = marshalUtil.ReadUint32(); err != nil {
		return
	}

	len, err := marshalUtil.ReadUint32()
	if err != nil {
		return
	}

	// parse header
	if result.header, err = header.Parse(marshalUtil); err != nil {
		return
	}

	// parse data
	if result.data, err = marshalUtil.ReadBytes(int(len - header.Length)); err != nil {
		return
	}

	// return the number of bytes we processed
	consumedBytes = marshalUtil.ReadOffset()

	// store bytes, so we don't have to marshal manually
	result.bytes = bytes[:consumedBytes]

	return
}

func (payload *Payload) Bytes() (bytes []byte) {
	// acquire lock for reading bytes
	payload.bytesMutex.RLock()

	// return if bytes have been determined already
	if bytes = payload.bytes; bytes != nil {
		defer payload.bytesMutex.RUnlock()
		return
	}

	// switch to write lock
	payload.bytesMutex.RUnlock()
	payload.bytesMutex.Lock()
	defer payload.bytesMutex.Unlock()

	// return if bytes have been determined in the mean time
	if bytes = payload.bytes; bytes != nil {
		return
	}
	// initialize helper
	marshalUtil := marshalutil.New()

	// marshal the payload specific information
	marshalUtil.WriteUint32(Type)
	marshalUtil.WriteUint32(uint32(len(payload.data) + header.Length))
	marshalUtil.WriteBytes(payload.header.Bytes())
	marshalUtil.WriteBytes(payload.data[:])

	bytes = marshalUtil.Bytes()

	return
}

func (payload *Payload) String() string {
	return stringify.Struct("Payload",
		stringify.StructField("type", uint64(payload.SubType())),
		stringify.StructField("instance", uint64(payload.Instance())),
		stringify.StructField("data", payload.Data()),
	)
}

// region Payload implementation ///////////////////////////////////////////////////////////////////////////////////////

var Type = payload.Type(111)

func (payload *Payload) GetType() payload.Type {
	return Type
}

func (payload *Payload) MarshalBinary() (bytes []byte, err error) {
	return payload.Bytes(), nil
}

func (payload *Payload) UnmarshalBinary(data []byte) (err error) {
	_, err, _ = FromBytes(data, payload)

	return
}

func init() {
	payload.RegisterType(Type, func(data []byte) (payload payload.Payload, err error) {
		payload = &Payload{}
		err = payload.UnmarshalBinary(data)

		return
	})
}

// define contract (ensure that the struct fulfills the corresponding interface)
var _ payload.Payload = &Payload{}

// // endregion ///////////////////////////////////////////////////////////////////////////////////////////////////////////