diff --git a/packages/binary/drng/payload/collectiveBeacon/collective_beacon.go b/packages/binary/drng/payload/collectiveBeacon/collective_beacon.go new file mode 100644 index 0000000000000000000000000000000000000000..56013c99d24394ab267f043024a9efcd26563e18 --- /dev/null +++ b/packages/binary/drng/payload/collectiveBeacon/collective_beacon.go @@ -0,0 +1,197 @@ +package collectiveBeacon + +import ( + "sync" + + "github.com/iotaledger/hive.go/stringify" + + drngPayload "github.com/iotaledger/goshimmer/packages/binary/drng/payload" + "github.com/iotaledger/goshimmer/packages/binary/drng/payload/header" + "github.com/iotaledger/goshimmer/packages/binary/marshalutil" + "github.com/iotaledger/goshimmer/packages/binary/tangle/model/transaction/payload" +) + +type Payload struct { + //objectstorage.StorableObjectFlags + + header header.Header + + round uint64 // round of the current beacon + prevSignature []byte // collective signature of the previous beacon + signature []byte // collective signature of the current beacon + dpk []byte // distributed public key + bytes []byte + bytesMutex sync.RWMutex +} + +func New(instanceID uint32, round uint64, prevSignature, signature, dpk []byte) *Payload { + return &Payload{ + header: header.New(header.CollectiveBeaconType(), instanceID), + round: round, + prevSignature: prevSignature, + signature: signature, + dpk: dpk, + } +} + +func (p *Payload) SubType() header.Type { + return p.header.PayloadType() +} + +func (payload *Payload) Instance() uint32 { + return payload.header.Instance() +} + +func (payload *Payload) Round() uint64 { + return payload.round +} + +func (payload *Payload) PrevSignature() []byte { + return payload.prevSignature +} + +func (payload *Payload) Signature() []byte { + return payload.signature +} + +func (payload *Payload) DistributedPK() []byte { + return payload.dpk +} + +// 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 + } + if _, err = marshalUtil.ReadUint32(); err != nil { + return + } + + // parse header + if result.header, err = header.Parse(marshalUtil); err != nil { + return + } + + // parse round + if result.round, err = marshalUtil.ReadUint64(); err != nil { + return + } + + // parse prevSignature + if result.prevSignature, err = marshalUtil.ReadBytes(SignatureSize); err != nil { + return + } + + // parse current signature + if result.signature, err = marshalUtil.ReadBytes(SignatureSize); err != nil { + return + } + + // parse distributed public key + if result.dpk, err = marshalUtil.ReadBytes(PublicKeySize); 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 + } + + // marshal fields + payloadLength := header.Length + marshalutil.UINT64_SIZE + SignatureSize*2 + PublicKeySize + marshalUtil := marshalutil.New(marshalutil.UINT32_SIZE + marshalutil.UINT32_SIZE + payloadLength) + marshalUtil.WriteUint32(drngPayload.Type) + marshalUtil.WriteUint32(uint32(payloadLength)) + marshalUtil.WriteBytes(payload.header.Bytes()) + marshalUtil.WriteUint64(payload.Round()) + marshalUtil.WriteBytes(payload.PrevSignature()) + marshalUtil.WriteBytes(payload.Signature()) + marshalUtil.WriteBytes(payload.DistributedPK()) + + bytes = marshalUtil.Bytes() + + // store result + payload.bytes = bytes + + return +} + +func (payload *Payload) String() string { + return stringify.Struct("Payload", + stringify.StructField("type", payload.SubType()), + stringify.StructField("instance", payload.Instance()), + stringify.StructField("round", payload.Round()), + stringify.StructField("prevSignature", payload.PrevSignature()), + stringify.StructField("signature", payload.Signature()), + stringify.StructField("distributedPK", payload.DistributedPK()), + ) +} + +// region Payload implementation /////////////////////////////////////////////////////////////////////////////////////// + +func (payload *Payload) GetType() payload.Type { + return drngPayload.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(drngPayload.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 /////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/packages/binary/drng/payload/collectiveBeacon/collective_beacon_test.go b/packages/binary/drng/payload/collectiveBeacon/collective_beacon_test.go new file mode 100644 index 0000000000000000000000000000000000000000..aaf92bf39ede92656814977e47ee12d92bf98f9f --- /dev/null +++ b/packages/binary/drng/payload/collectiveBeacon/collective_beacon_test.go @@ -0,0 +1,32 @@ +package collectiveBeacon + +import ( + "testing" + + "github.com/iotaledger/goshimmer/packages/binary/drng/payload/header" + "github.com/iotaledger/goshimmer/packages/binary/marshalutil" + "github.com/stretchr/testify/require" +) + +func TestParse(t *testing.T) { + header := header.New(header.CollectiveBeaconType(), 0) + payload := New(header.Instance(), + 0, + []byte("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"), // prevSignature + []byte("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"), // signature + []byte("CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC")) // distributed PK + bytes := payload.Bytes() + + marshalUtil := marshalutil.New(bytes) + parsedpayload, err := marshalUtil.Parse(func(data []byte) (interface{}, error, int) { return FromBytes(data) }) + require.NoError(t, err) + + cb := parsedpayload.(*Payload) + + require.Equal(t, payload.SubType(), cb.SubType()) + require.Equal(t, payload.Instance(), cb.Instance()) + require.Equal(t, payload.Round(), cb.Round()) + require.Equal(t, payload.PrevSignature(), cb.PrevSignature()) + require.Equal(t, payload.Signature(), cb.Signature()) + require.Equal(t, payload.DistributedPK(), cb.DistributedPK()) +} diff --git a/packages/binary/drng/payload/collectiveBeacon/common.go b/packages/binary/drng/payload/collectiveBeacon/common.go new file mode 100644 index 0000000000000000000000000000000000000000..4d6a78058f050438e3daef9b3c8124b34b56404b --- /dev/null +++ b/packages/binary/drng/payload/collectiveBeacon/common.go @@ -0,0 +1,8 @@ +package collectiveBeacon + +const ( + // BLS Signature size in bytes + SignatureSize = 32 + // BLS Public Key size in bytes + PublicKeySize = 32 +) diff --git a/packages/binary/drng/payload/header/header.go b/packages/binary/drng/payload/header/header.go new file mode 100644 index 0000000000000000000000000000000000000000..789c0a454f7c07cb0c2e38a71cf0cbd49f887d75 --- /dev/null +++ b/packages/binary/drng/payload/header/header.go @@ -0,0 +1,99 @@ +package header + +import ( + "github.com/iotaledger/goshimmer/packages/binary/marshalutil" +) + +type Type = byte + +type payloadType struct { + CollectiveBeacon Type +} + +var drngTypes = &payloadType{ + CollectiveBeacon: Type(1), +} + +const Length = 5 + +func CollectiveBeaconType() Type { + return drngTypes.CollectiveBeacon +} + +type Header struct { + payloadType Type // message type + instanceID uint32 // identifier of the dRAND instance +} + +func New(payloadType Type, instanceID uint32) Header { + return Header{ + payloadType: payloadType, + instanceID: instanceID, + } +} + +func (h Header) PayloadType() Type { + return h.payloadType +} + +func (h Header) Instance() uint32 { + return h.instanceID +} + +// Parse is a wrapper for simplified unmarshaling in a byte stream using the marshalUtil package. +func Parse(marshalUtil *marshalutil.MarshalUtil) (Header, error) { + if header, err := marshalUtil.Parse(func(data []byte) (interface{}, error, int) { return FromBytes(data) }); err != nil { + return Header{}, err + } else { + return header.(Header), nil + } +} + +// FromBytes unmarshals a header from a sequence of bytes. +// It either creates a new header or fills the optionally provided object with the parsed information. +func FromBytes(bytes []byte, optionalTargetObject ...*Header) (result Header, err error, consumedBytes int) { + // determine the target object that will hold the unmarshaled information + var targetObject *Header + switch len(optionalTargetObject) { + case 0: + targetObject = &result + case 1: + targetObject = optionalTargetObject[0] + default: + panic("too many arguments in call to FromBytes") + } + + // initialize helper + marshalUtil := marshalutil.New(bytes) + + // read payload type from bytes + if targetObject.payloadType, err = marshalUtil.ReadByte(); err != nil { + return + } + + // read instance ID from bytes + if targetObject.instanceID, err = marshalUtil.ReadUint32(); err != nil { + return + } + + // copy result if we have provided a target object + result = *targetObject + + // return the number of bytes we processed + consumedBytes = marshalUtil.ReadOffset() + + return +} + +func (header *Header) Bytes() (bytes []byte) { + // initialize helper + marshalUtil := marshalutil.New() + + // marshal the payload specific information + marshalUtil.WriteByte(header.PayloadType()) + marshalUtil.WriteUint32(header.Instance()) + + bytes = marshalUtil.Bytes() + + return +} diff --git a/packages/binary/drng/payload/header/header_test.go b/packages/binary/drng/payload/header/header_test.go new file mode 100644 index 0000000000000000000000000000000000000000..b5778db91e87a77464d55aa9d79ec44fa4aa9a8d --- /dev/null +++ b/packages/binary/drng/payload/header/header_test.go @@ -0,0 +1,19 @@ +package header + +import ( + "testing" + + "github.com/iotaledger/goshimmer/packages/binary/marshalutil" + "github.com/stretchr/testify/require" +) + +func TestParse(t *testing.T) { + header := New(CollectiveBeaconType(), 0) + bytes := header.Bytes() + + marshalUtil := marshalutil.New(bytes) + parsedHeader, err := Parse(marshalUtil) + require.NoError(t, err) + + require.Equal(t, header, parsedHeader) +} diff --git a/packages/binary/drng/payload/payload.go b/packages/binary/drng/payload/payload.go new file mode 100644 index 0000000000000000000000000000000000000000..9637cbcba98b222c302bacdf548ec539ffee1520 --- /dev/null +++ b/packages/binary/drng/payload/payload.go @@ -0,0 +1,152 @@ +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/transaction/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 +} + +// 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 + } + + // 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", payload.SubType()), + stringify.StructField("instance", 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 /////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/packages/binary/drng/payload/payload_test.go b/packages/binary/drng/payload/payload_test.go new file mode 100644 index 0000000000000000000000000000000000000000..16aa567b99b065d3dc9f1f335c9751e30f6e7f43 --- /dev/null +++ b/packages/binary/drng/payload/payload_test.go @@ -0,0 +1,26 @@ +package payload + +import ( + "testing" + + "github.com/iotaledger/goshimmer/packages/binary/drng/payload/header" + "github.com/iotaledger/goshimmer/packages/binary/marshalutil" + "github.com/stretchr/testify/require" +) + +func TestParse(t *testing.T) { + header := header.New(header.CollectiveBeaconType(), 0) + data := []byte("test") + payload := New(header, data) + bytes := payload.Bytes() + + marshalUtil := marshalutil.New(bytes) + parsedpayload, err := marshalUtil.Parse(func(data []byte) (interface{}, error, int) { return FromBytes(data) }) + require.NoError(t, err) + + cb := parsedpayload.(*Payload) + + require.Equal(t, payload.SubType(), cb.SubType()) + require.Equal(t, payload.Instance(), cb.Instance()) + require.Equal(t, payload.Data(), cb.Data()) +}