|
@@ -13,50 +13,67 @@ import (
|
|
"github.com/segmentio/ksuid"
|
|
"github.com/segmentio/ksuid"
|
|
)
|
|
)
|
|
|
|
|
|
-const btcEpoch = 1231006505 // Bitcoin genesis, 2009-01-03T18:15:05Z
|
|
|
|
-
|
|
|
|
-type RawEvent struct {
|
|
|
|
- _ struct{} `cbor:",toarray" json:"-"`
|
|
|
|
- Type uint16
|
|
|
|
- SequentialID uint64
|
|
|
|
- TimestampS uint32
|
|
|
|
- TimestampMS uint16
|
|
|
|
- UniqueID []byte
|
|
|
|
- Payload interface{}
|
|
|
|
|
|
+const (
|
|
|
|
+ btcEpoch = 1231006505 // Bitcoin genesis, 2009-01-03T18:15:05Z
|
|
|
|
+ V1MessageVersion = 1 // Current version for messages on the bus
|
|
|
|
+ DefaultMessageVersion = V1MessageVersion
|
|
|
|
+)
|
|
|
|
+
|
|
|
|
+type Envelop struct {
|
|
|
|
+ _ struct{} `json:"-"`
|
|
|
|
+ Type uint16 `cbor:"event_type"`
|
|
|
|
+ SequentialID uint64 `cbor:"sequential_id"`
|
|
|
|
+ TimestampS uint32 `cbor:"timestamp_s"`
|
|
|
|
+ TimestampMS uint16 `cbor:"timestamp_ms"`
|
|
|
|
+ UniqueID [16]byte `cbor:"unique_id"`
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+type RawEvent[T any] struct {
|
|
|
|
+ _ struct{} `json:"-"`
|
|
|
|
+ Version uint8
|
|
|
|
+ Envelop Envelop
|
|
|
|
+ Payload T
|
|
}
|
|
}
|
|
|
|
|
|
-func NewRawEvent(type_ uint16, payload interface{}) (*RawEvent, error) {
|
|
|
|
|
|
+func NewRawEvent[T any](type_ uint16, payload T) (*RawEvent[T], error) {
|
|
now := time.Now()
|
|
now := time.Now()
|
|
ksuid, err := ksuid.NewRandomWithTime(now)
|
|
ksuid, err := ksuid.NewRandomWithTime(now)
|
|
if err != nil {
|
|
if err != nil {
|
|
return nil, err
|
|
return nil, err
|
|
}
|
|
}
|
|
- return &RawEvent{
|
|
|
|
- Type: type_,
|
|
|
|
- SequentialID: 0,
|
|
|
|
- TimestampS: uint32(now.Unix() - btcEpoch),
|
|
|
|
- TimestampMS: uint16(now.UnixMilli() % 1000),
|
|
|
|
- UniqueID: ksuid[4:],
|
|
|
|
- Payload: payload,
|
|
|
|
|
|
+
|
|
|
|
+ unique_id := [16]byte{}
|
|
|
|
+ copy(unique_id[:], ksuid[4:])
|
|
|
|
+
|
|
|
|
+ return &RawEvent[T]{
|
|
|
|
+ Version: DefaultMessageVersion,
|
|
|
|
+ Envelop: Envelop{
|
|
|
|
+ Type: type_,
|
|
|
|
+ SequentialID: 0,
|
|
|
|
+ TimestampS: uint32(now.Unix() - btcEpoch),
|
|
|
|
+ TimestampMS: uint16(now.UnixMilli() % 1000),
|
|
|
|
+ UniqueID: unique_id,
|
|
|
|
+ },
|
|
|
|
+ Payload: payload,
|
|
}, nil
|
|
}, nil
|
|
}
|
|
}
|
|
|
|
|
|
-func (rawEvent RawEvent) Check() (*Event, error) {
|
|
|
|
- timestampS, ok := overflow.Add64(btcEpoch, int64(rawEvent.TimestampS))
|
|
|
|
- if !ok || rawEvent.TimestampMS > 999 {
|
|
|
|
|
|
+func (rawEvent RawEvent[T]) Check() (*Event[T], error) {
|
|
|
|
+ timestampS, ok := overflow.Add64(btcEpoch, int64(rawEvent.Envelop.TimestampS))
|
|
|
|
+ if !ok || rawEvent.Envelop.TimestampMS > 999 {
|
|
return nil, fmt.Errorf("timestamp overflow")
|
|
return nil, fmt.Errorf("timestamp overflow")
|
|
}
|
|
}
|
|
- timestampNS := int64(rawEvent.TimestampMS) * 1000000
|
|
|
|
|
|
+ timestampNS := int64(rawEvent.Envelop.TimestampMS) * 1000000
|
|
timestamp := time.Unix(timestampS, timestampNS)
|
|
timestamp := time.Unix(timestampS, timestampNS)
|
|
|
|
|
|
- ksuid, err := ksuid.FromParts(timestamp, rawEvent.UniqueID)
|
|
|
|
|
|
+ ksuid, err := ksuid.FromParts(timestamp, rawEvent.Envelop.UniqueID[:])
|
|
if err != nil {
|
|
if err != nil {
|
|
return nil, err
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
|
|
- event := Event{
|
|
|
|
- Type: EventType(rawEvent.Type),
|
|
|
|
- SequentialID: rawEvent.SequentialID,
|
|
|
|
|
|
+ event := Event[T]{
|
|
|
|
+ Type: EventType(rawEvent.Envelop.Type),
|
|
|
|
+ SequentialID: rawEvent.Envelop.SequentialID,
|
|
Timestamp: timestamp,
|
|
Timestamp: timestamp,
|
|
UniqueID: ksuid,
|
|
UniqueID: ksuid,
|
|
Payload: rawEvent.Payload,
|
|
Payload: rawEvent.Payload,
|
|
@@ -64,18 +81,18 @@ func (rawEvent RawEvent) Check() (*Event, error) {
|
|
return &event, nil
|
|
return &event, nil
|
|
}
|
|
}
|
|
|
|
|
|
-func (event RawEvent) Equal(other RawEvent) bool {
|
|
|
|
- if event.Type != other.Type ||
|
|
|
|
- event.SequentialID != other.SequentialID ||
|
|
|
|
- event.TimestampS != other.TimestampS ||
|
|
|
|
- event.TimestampMS != other.TimestampMS ||
|
|
|
|
- !bytes.Equal(event.UniqueID, other.UniqueID) {
|
|
|
|
|
|
+func (event RawEvent[T]) Equal(other RawEvent[T]) bool {
|
|
|
|
+ if event.Envelop.Type != other.Envelop.Type ||
|
|
|
|
+ event.Envelop.SequentialID != other.Envelop.SequentialID ||
|
|
|
|
+ event.Envelop.TimestampS != other.Envelop.TimestampS ||
|
|
|
|
+ event.Envelop.TimestampMS != other.Envelop.TimestampMS ||
|
|
|
|
+ !bytes.Equal(event.Envelop.UniqueID[:], other.Envelop.UniqueID[:]) {
|
|
return false
|
|
return false
|
|
}
|
|
}
|
|
return reflect.DeepEqual(event.Payload, other.Payload)
|
|
return reflect.DeepEqual(event.Payload, other.Payload)
|
|
}
|
|
}
|
|
|
|
|
|
-func (event RawEvent) JSON() string {
|
|
|
|
|
|
+func (event RawEvent[T]) JSON() string {
|
|
output, err := json.Marshal(event)
|
|
output, err := json.Marshal(event)
|
|
if err != nil {
|
|
if err != nil {
|
|
panic(err)
|
|
panic(err)
|
|
@@ -83,48 +100,41 @@ func (event RawEvent) JSON() string {
|
|
return string(output)
|
|
return string(output)
|
|
}
|
|
}
|
|
|
|
|
|
-func (event RawEvent) MarshalJSON() ([]byte, error) {
|
|
|
|
|
|
+func (event RawEvent[T]) MarshalJSON() ([]byte, error) {
|
|
return json.Marshal([]interface{}{
|
|
return json.Marshal([]interface{}{
|
|
- event.Type,
|
|
|
|
- event.SequentialID,
|
|
|
|
- event.TimestampS,
|
|
|
|
- event.TimestampMS,
|
|
|
|
- event.UniqueID, // automatically Base64-encoded
|
|
|
|
- event.Payload,
|
|
|
|
- })
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-func (event RawEvent) MarshalCBOR() ([]byte, error) {
|
|
|
|
- return cbor.Marshal([]interface{}{
|
|
|
|
- event.Type,
|
|
|
|
- event.SequentialID,
|
|
|
|
- event.TimestampS,
|
|
|
|
- event.TimestampMS,
|
|
|
|
- event.UniqueID,
|
|
|
|
|
|
+ event.Version,
|
|
|
|
+ event.Envelop.Type,
|
|
|
|
+ event.Envelop.SequentialID,
|
|
|
|
+ event.Envelop.TimestampS,
|
|
|
|
+ event.Envelop.TimestampMS,
|
|
|
|
+ event.Envelop.UniqueID[:], // automatically Base64-encoded
|
|
event.Payload,
|
|
event.Payload,
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
|
|
-func (event *RawEvent) UnmarshalJSON(input []byte) error {
|
|
|
|
|
|
+func (event *RawEvent[T]) UnmarshalJSON(input []byte) error {
|
|
var err error
|
|
var err error
|
|
array := []interface{}{}
|
|
array := []interface{}{}
|
|
if err = json.Unmarshal(input, &array); err != nil {
|
|
if err = json.Unmarshal(input, &array); err != nil {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
- if len(array) != 6 {
|
|
|
|
|
|
+ if len(array) != 7 {
|
|
return fmt.Errorf("event must be an array of length %d, but got %d", 6, len(array))
|
|
return fmt.Errorf("event must be an array of length %d, but got %d", 6, len(array))
|
|
}
|
|
}
|
|
|
|
|
|
- event.Type = uint16(array[0].(float64))
|
|
|
|
- event.SequentialID = uint64(array[1].(float64))
|
|
|
|
- event.TimestampS = uint32(array[2].(float64))
|
|
|
|
- event.TimestampMS = uint16(array[3].(float64))
|
|
|
|
- event.UniqueID, err = base64.StdEncoding.DecodeString(array[4].(string))
|
|
|
|
|
|
+ event.Version = uint8(array[0].(float64))
|
|
|
|
+ event.Envelop.Type = uint16(array[1].(float64))
|
|
|
|
+ event.Envelop.SequentialID = uint64(array[2].(float64))
|
|
|
|
+ event.Envelop.TimestampS = uint32(array[3].(float64))
|
|
|
|
+ event.Envelop.TimestampMS = uint16(array[4].(float64))
|
|
|
|
+
|
|
|
|
+ unique_id, err := base64.StdEncoding.DecodeString(array[5].(string))
|
|
if err != nil {
|
|
if err != nil {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
|
|
+ copy(event.Envelop.UniqueID[:], unique_id)
|
|
|
|
|
|
- payload, ok := array[5].(map[string]interface{})
|
|
|
|
|
|
+ payload, ok := array[6].(T)
|
|
if !ok {
|
|
if !ok {
|
|
return fmt.Errorf("event payload must be a map")
|
|
return fmt.Errorf("event payload must be a map")
|
|
}
|
|
}
|
|
@@ -133,35 +143,46 @@ func (event *RawEvent) UnmarshalJSON(input []byte) error {
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
|
|
|
|
-func (event *RawEvent) UnmarshalCBOR(input []byte) error {
|
|
|
|
- var err error
|
|
|
|
- array := []interface{}{}
|
|
|
|
- if err = cbor.Unmarshal(input, &array); err != nil {
|
|
|
|
- return err
|
|
|
|
|
|
+func (event RawEvent[T]) EncodeCBOR() ([]byte, error) {
|
|
|
|
+ encoded_envelop, err := cbor.Marshal(event.Envelop)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, err
|
|
}
|
|
}
|
|
- if len(array) != 6 {
|
|
|
|
- return fmt.Errorf("event must be an array of length %d, but got %d", 6, len(array))
|
|
|
|
|
|
+
|
|
|
|
+ encoded_payload, err := cbor.Marshal(event.Payload)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, err
|
|
}
|
|
}
|
|
|
|
|
|
- event.Type = uint16(array[0].(uint64))
|
|
|
|
- event.SequentialID = uint64(array[1].(uint64))
|
|
|
|
- event.TimestampS = uint32(array[2].(uint64))
|
|
|
|
- event.TimestampMS = uint16(array[3].(uint64))
|
|
|
|
- event.UniqueID = array[4].([]byte)
|
|
|
|
|
|
+ partial := append([]byte{V1MessageVersion}, encoded_envelop...)
|
|
|
|
+ return append(partial, encoded_payload...), nil
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (event *RawEvent[T]) DecodeCBOR(input []byte) error {
|
|
|
|
+ var err error
|
|
|
|
|
|
- payloadIn, ok := array[5].(map[interface{}]interface{})
|
|
|
|
- if !ok {
|
|
|
|
- return fmt.Errorf("event payload must be a map")
|
|
|
|
|
|
+ if len(input) < 1 {
|
|
|
|
+ return fmt.Errorf("Empty message. Missing Version number")
|
|
}
|
|
}
|
|
- payloadOut := make(map[string]interface{})
|
|
|
|
- for k, v := range payloadIn {
|
|
|
|
- var key string
|
|
|
|
- if key, ok = k.(string); !ok {
|
|
|
|
- return fmt.Errorf("event payload must be a map with string keys, but got the key %v", k)
|
|
|
|
|
|
+
|
|
|
|
+ version_message := input[0]
|
|
|
|
+
|
|
|
|
+ switch version_message {
|
|
|
|
+ case V1MessageVersion:
|
|
|
|
+ decoder := cbor.NewDecoder(bytes.NewReader(input[1:]))
|
|
|
|
+
|
|
|
|
+ err = decoder.Decode(&event.Envelop)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return err
|
|
}
|
|
}
|
|
- payloadOut[key] = v
|
|
|
|
- }
|
|
|
|
- event.Payload = payloadOut
|
|
|
|
|
|
|
|
- return nil
|
|
|
|
|
|
+ err = decoder.Decode(&event.Payload)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return nil
|
|
|
|
+ default:
|
|
|
|
+ return fmt.Errorf("Unsupported version message %d", version_message)
|
|
|
|
+ }
|
|
}
|
|
}
|