Skip to content
36 changes: 24 additions & 12 deletions pkg/common/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
"time"

"github.com/go-playground/validator"
"github.com/truvami/decoder/internal/logger"
"go.uber.org/zap"
)

func HexStringToBytes(hexString string) ([]byte, error) {
Expand Down Expand Up @@ -154,21 +156,28 @@ func Decode(payloadHex *string, config *PayloadConfig) (any, error) {
errs := []error{}

if len(config.Tags) != 0 {
var index uint8 = 3
var payloadLength = uint8(len(payloadBytes))
for index+2 < payloadLength {
var found = false
var index = 3
var payloadLength = len(payloadBytes)
for index < payloadLength {
if payloadLength-index < 2 {
return nil, fmt.Errorf("incomplete TLV header at offset %d: need 2 bytes but only %d remain", index, payloadLength-index)
}

var tag = payloadBytes[index]
index++
var length = payloadBytes[index]
index++
var length = int(payloadBytes[index+1])
index += 2

if index+length > payloadLength {
return nil, fmt.Errorf("TLV tag 0x%02x at offset %d declares length %d, but only %d bytes remain", tag, index-2, length, payloadLength-index)
}

var found bool
for _, tagConfig := range config.Tags {
if tagConfig.Tag == tag {
found = true
config.Features = append(config.Features, tagConfig.Feature)

value, err := extractFieldValue(payloadBytes, int(index), int(length), false, tagConfig.Hex)
value, err := extractFieldValue(payloadBytes, index, length, false, tagConfig.Hex)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -198,11 +207,14 @@ func Decode(payloadHex *string, config *PayloadConfig) (any, error) {
}
}
}
if found {
index += length
} else {
return nil, fmt.Errorf("unknown tag %x", tag)
if !found {
tagHex := fmt.Sprintf("0x%02x", tag)
unknownTLVTagsTotal.WithLabelValues(tagHex).Inc()
if logger.Logger != nil {
logger.Logger.Warn("skipping unknown tag", zap.String("tag", tagHex), zap.Int("length", length))
}
}
index += length
}

return targetValue.Interface(), errors.Join(errs...)
Expand Down
60 changes: 46 additions & 14 deletions pkg/common/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,11 @@ func TestDecode(t *testing.T) {
payload: "ffffff0000",
config: tagConfig,
expected: Port2Payload{
Time: nil,
Time: Uint32Ptr(0),
Power: nil,
Sensor: nil,
},
expectedErr: "validation failed for Time",
},
{
payload: "ffffff000400000000",
Expand All @@ -141,16 +142,19 @@ func TestDecode(t *testing.T) {
expectedErr: "validation failed for Time",
},
{
payload: "ffffff040100",
config: tagConfig,
expected: nil,
expectedErr: "unknown tag 4",
payload: "ffffff040100",
config: tagConfig,
expected: Port2Payload{
Time: nil,
Power: nil,
Sensor: nil,
},
},
{
payload: "ffffff010200",
config: tagConfig,
expected: nil,
expectedErr: "field out of bounds",
expectedErr: "TLV tag",
},
{
payload: "ffffff030100",
Expand All @@ -163,8 +167,15 @@ func TestDecode(t *testing.T) {
for _, test := range tests {
t.Run(test.payload, func(t *testing.T) {
decodedData, err := Decode(StringPtr(test.payload), &test.config)
if err != nil && !strings.Contains(err.Error(), test.expectedErr) {
t.Fatalf("expected %s received %s", test.expectedErr, err)
if test.expectedErr != "" {
if err == nil {
t.Fatalf("expected error containing %q but got nil", test.expectedErr)
}
if !strings.Contains(err.Error(), test.expectedErr) {
t.Fatalf("expected error containing %q received %s", test.expectedErr, err)
}
} else if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if !reflect.DeepEqual(decodedData, test.expected) {
t.Fatalf("expected: %+v received: %+v", test.expected, decodedData)
Expand Down Expand Up @@ -239,8 +250,15 @@ func TestExtractFieldValue(t *testing.T) {
for _, test := range tests {
t.Run(fmt.Sprintf("%v_%v_%v", test.payload, test.start, test.length), func(t *testing.T) {
result, err := extractFieldValue(test.payload, test.start, test.length, test.optional, test.hexadecimal)
if err != nil && err.Error() != test.expectedErr {
t.Fatalf("expected: %s received: %s", test.expectedErr, err.Error())
if test.expectedErr != "" {
if err == nil {
t.Fatalf("expected error %q but got nil", test.expectedErr)
}
if err.Error() != test.expectedErr {
t.Fatalf("expected: %s received: %s", test.expectedErr, err.Error())
}
} else if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if !reflect.DeepEqual(result, test.expected) {
t.Fatalf("expected: %v received: %v", test.expected, result)
Expand Down Expand Up @@ -373,8 +391,15 @@ func TestConvertFieldValue(t *testing.T) {
for _, test := range tests {
t.Run(fmt.Sprintf("%v_%v", test.value, test.fieldType), func(t *testing.T) {
result, err := convertFieldValue(test.value, test.fieldType, nil)
if err != nil && err.Error() != test.expectedErr {
t.Fatalf("expected: %s received: %s", test.expectedErr, err.Error())
if test.expectedErr != "" {
if err == nil {
t.Fatalf("expected error %q but got nil", test.expectedErr)
}
if err.Error() != test.expectedErr {
t.Fatalf("expected: %s received: %s", test.expectedErr, err.Error())
}
} else if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if !reflect.DeepEqual(result, test.expected) {
t.Fatalf("expected: %v received: %v", test.expected, result)
Expand Down Expand Up @@ -450,8 +475,15 @@ func TestInsertFieldBytes(t *testing.T) {

for _, test := range tests {
set, bytes, err := insertFieldBytes(test.value, test.length, test.transform)
if err != nil && err.Error() != test.expectedErr {
t.Fatalf("expected: %s received: %s", test.expectedErr, err.Error())
if test.expectedErr != "" {
if err == nil {
t.Fatalf("expected error %q but got nil", test.expectedErr)
}
if err.Error() != test.expectedErr {
t.Fatalf("expected: %s received: %s", test.expectedErr, err.Error())
}
} else if err != nil {
t.Fatalf("unexpected error: %s", err)
}
if !set && err == nil {
t.Fatalf("expected set to be true when error is nil")
Expand Down
13 changes: 13 additions & 0 deletions pkg/common/metrics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package common

import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)

var (
unknownTLVTagsTotal = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "truvami_common_unknown_tlv_tags_total",
Help: "The total number of unknown TLV tags encountered during decoding",
}, []string{"tag"})
)
15 changes: 11 additions & 4 deletions pkg/decoder/tagxl/v1/decoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,17 @@ func TestDecode(t *testing.T) {
expectedErr: "port not supported: port 151 tag ff",
},
{
port: 151,
payload: "4c0501ff020000",
expected: Port151Payload{},
expectedErr: "unknown tag ff",
port: 151,
payload: "4c0501ff020000",
expected: Port151Payload{},
},
{
port: 151,
payload: "4c0d0345020a92ff03aabbcc4e0107",
expected: Port151Payload{
Battery: helpers.Float32Ptr(2.706),
DataRate: helpers.DataRatePtr(decoder.DataRateTagXLADR),
},
},
{
port: 151,
Expand Down
Loading