diff --git a/pkg/transformers/json/transformer.go b/pkg/transformers/json/transformer.go index fa032167df..cf26667972 100644 --- a/pkg/transformers/json/transformer.go +++ b/pkg/transformers/json/transformer.go @@ -187,7 +187,7 @@ func (ts *transformerService) transformTimeField(payload map[string]interface{}) return 0, err } - return t.UnixNano(), nil + return transformers.ToUnixNano(t.UnixNano()), nil } } diff --git a/pkg/transformers/senml/transformer.go b/pkg/transformers/senml/transformer.go index 1e253df632..cce7f31f5c 100644 --- a/pkg/transformers/senml/transformer.go +++ b/pkg/transformers/senml/transformer.go @@ -15,6 +15,8 @@ const ( JSON = "application/senml+json" // CBOR represents SenML in CBOR format content type. CBOR = "application/senml+cbor" + + maxRelativeTime = 1 << 28 ) var ( @@ -59,8 +61,16 @@ func (t transformer) Transform(msg *messaging.Message) (interface{}, error) { // Use reception timestamp if SenML messsage Time is missing t := v.Time if t == 0 { - // Convert the Unix timestamp in nanoseconds to float64 - t = float64(msg.GetCreated()) / float64(1e9) + t = float64(msg.GetCreated()) + } + + // If time is below 2**28 it is relative to the current time + // https://datatracker.ietf.org/doc/html/rfc8428#section-4.5.3 + if t >= maxRelativeTime { + t = transformers.ToUnixNano(t) + } + if v.UpdateTime >= maxRelativeTime { + v.UpdateTime = transformers.ToUnixNano(v.UpdateTime) } msgs[i] = Message{ diff --git a/pkg/transformers/senml/transformer_test.go b/pkg/transformers/senml/transformer_test.go index de57634168..defed27368 100644 --- a/pkg/transformers/senml/transformer_test.go +++ b/pkg/transformers/senml/transformer_test.go @@ -17,7 +17,7 @@ import ( func TestTransformJSON(t *testing.T) { // Following hex-encoded bytes correspond to the content of: - // [{-2: "base-name", -3: 100.0, -4: "base-unit", -1: 10, -5: 10.0, -6: 100.0, 0: "name", 1: "unit", 6: 300.0, 7: 150.0, 2: 42.0, 5: 10.0}] + // [{"bn":"base-name","bt":100,"bu":"base-unit","bver":10,"bv":10,"bs":100,"n":"name","u":"unit","t":300,"ut":150,"v":42,"s":10}] // For more details for mapping SenML labels to integers, please take a look here: https://tools.ietf.org/html/rfc8428#page-19. jsonBytes, err := hex.DecodeString("5b7b22626e223a22626173652d6e616d65222c226274223a3130302c226275223a22626173652d756e6974222c2262766572223a31302c226276223a31302c226273223a3130302c226e223a226e616d65222c2275223a22756e6974222c2274223a3330302c227574223a3135302c2276223a34322c2273223a31307d5d") assert.Nil(t, err, "Decoding JSON expected to succeed") diff --git a/pkg/transformers/transformer.go b/pkg/transformers/transformer.go index 73ca7dd466..aa53887678 100644 --- a/pkg/transformers/transformer.go +++ b/pkg/transformers/transformer.go @@ -10,3 +10,23 @@ type Transformer interface { // Transform Magistrala message to any other format. Transform(msg *messaging.Message) (interface{}, error) } + +type number interface { + uint64 | int64 | float64 +} + +// ToUnixNano converts time to UnixNano time format. +func ToUnixNano[N number](t N) N { + switch { + case t == 0: + return 0 + case t >= 1e18: // Check if the value is in nanoseconds + return t + case t >= 1e15 && t < 1e18: // Check if the value is in milliseconds + return t * 1e3 + case t >= 1e12 && t < 1e15: // Check if the value is in microseconds + return t * 1e6 + default: // Assume it's in seconds (Unix time) + return t * 1e9 + } +} diff --git a/pkg/transformers/transformer_test.go b/pkg/transformers/transformer_test.go new file mode 100644 index 0000000000..bcaa4125b5 --- /dev/null +++ b/pkg/transformers/transformer_test.go @@ -0,0 +1,140 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package transformers_test + +import ( + "testing" + "time" + + "github.com/absmach/magistrala/pkg/transformers" +) + +var now = time.Now() + +func TestInt64ToUnixNano(t *testing.T) { + cases := []struct { + desc string + time int64 + want int64 + }{ + { + desc: "empty", + time: 0, + want: 0, + }, + { + desc: "unix", + time: now.Unix(), + want: now.Unix() * int64(time.Second), + }, + { + desc: "unix milli", + time: now.UnixMilli(), + want: now.UnixMilli() * int64(time.Millisecond), + }, + { + desc: "unix micro", + time: now.UnixMicro(), + want: now.UnixMicro() * int64(time.Microsecond), + }, + { + desc: "unix nano", + time: now.UnixNano(), + want: now.UnixNano(), + }, + { + desc: "1e9 nano", + time: time.Unix(1e9, 0).Unix(), + want: time.Unix(1e9, 0).UnixNano(), + }, + { + desc: "1e10 nano", + time: time.Unix(1e10, 0).Unix(), + want: time.Unix(1e10, 0).UnixNano(), + }, + { + desc: "1e12 nano", + time: time.UnixMilli(1e12).Unix(), + want: time.UnixMilli(1e12).UnixNano(), + }, + { + desc: "1e13 nano", + time: time.UnixMilli(1e13).Unix(), + want: time.UnixMilli(1e13).UnixNano(), + }, + { + desc: "1e15 nano", + time: time.UnixMicro(1e15).Unix(), + want: time.UnixMicro(1e15).UnixNano(), + }, + { + desc: "1e16 nano", + time: time.UnixMicro(1e16).Unix(), + want: time.UnixMicro(1e16).UnixNano(), + }, + } + + for _, c := range cases { + t.Run(c.desc, func(t *testing.T) { + got := transformers.ToUnixNano(c.time) + if got != c.want { + t.Errorf("ToUnixNano(%d) = %d; want %d", c.time, got, c.want) + } + t.Logf("ToUnixNano(%d) = %d; want %d", c.time, got, c.want) + }) + } +} + +func TestFloat64ToUnixNano(t *testing.T) { + cases := []struct { + desc string + time float64 + want float64 + }{ + { + desc: "empty", + time: 0, + want: 0, + }, + { + desc: "unix", + time: float64(now.Unix()), + want: float64(now.Unix() * int64(time.Second)), + }, + { + desc: "unix milli", + time: float64(now.UnixMilli()), + want: float64(now.UnixMilli() * int64(time.Millisecond)), + }, + { + desc: "unix micro", + time: float64(now.UnixMicro()), + want: float64(now.UnixMicro() * int64(time.Microsecond)), + }, + { + desc: "unix nano", + time: float64(now.UnixNano()), + want: float64(now.UnixNano()), + }, + } + + for _, c := range cases { + t.Run(c.desc, func(t *testing.T) { + got := transformers.ToUnixNano(c.time) + if got != c.want { + t.Errorf("ToUnixNano(%f) = %f; want %f", c.time, got, c.want) + } + t.Logf("ToUnixNano(%f) = %f; want %f", c.time, got, c.want) + }) + } +} + +func BenchmarkToUnixNano(b *testing.B) { + for i := 0; i < b.N; i++ { + transformers.ToUnixNano(now.Unix()) + transformers.ToUnixNano(now.UnixMilli()) + transformers.ToUnixNano(now.UnixMicro()) + transformers.ToUnixNano(now.UnixNano()) + } +}