Skip to content

Commit 191dff8

Browse files
fix(common): skip unknown TLV tags instead of failing (#181)
1 parent 847a2fb commit 191dff8

4 files changed

Lines changed: 94 additions & 30 deletions

File tree

pkg/common/helpers.go

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import (
1111
"time"
1212

1313
"github.com/go-playground/validator"
14+
"github.com/truvami/decoder/internal/logger"
15+
"go.uber.org/zap"
1416
)
1517

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

156158
if len(config.Tags) != 0 {
157-
var index uint8 = 3
158-
var payloadLength = uint8(len(payloadBytes))
159-
for index+2 < payloadLength {
160-
var found = false
159+
var index = 3
160+
var payloadLength = len(payloadBytes)
161+
for index < payloadLength {
162+
if payloadLength-index < 2 {
163+
return nil, fmt.Errorf("incomplete TLV header at offset %d: need 2 bytes but only %d remain", index, payloadLength-index)
164+
}
165+
161166
var tag = payloadBytes[index]
162-
index++
163-
var length = payloadBytes[index]
164-
index++
167+
var length = int(payloadBytes[index+1])
168+
index += 2
165169

170+
if index+length > payloadLength {
171+
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)
172+
}
173+
174+
var found bool
166175
for _, tagConfig := range config.Tags {
167176
if tagConfig.Tag == tag {
168177
found = true
169178
config.Features = append(config.Features, tagConfig.Feature)
170179

171-
value, err := extractFieldValue(payloadBytes, int(index), int(length), false, tagConfig.Hex)
180+
value, err := extractFieldValue(payloadBytes, index, length, false, tagConfig.Hex)
172181
if err != nil {
173182
return nil, err
174183
}
@@ -198,11 +207,14 @@ func Decode(payloadHex *string, config *PayloadConfig) (any, error) {
198207
}
199208
}
200209
}
201-
if found {
202-
index += length
203-
} else {
204-
return nil, fmt.Errorf("unknown tag %x", tag)
210+
if !found {
211+
tagHex := fmt.Sprintf("0x%02x", tag)
212+
unknownTLVTagsTotal.WithLabelValues(tagHex).Inc()
213+
if logger.Logger != nil {
214+
logger.Logger.Warn("skipping unknown tag", zap.String("tag", tagHex), zap.Int("length", length))
215+
}
205216
}
217+
index += length
206218
}
207219

208220
return targetValue.Interface(), errors.Join(errs...)

pkg/common/helpers_test.go

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -125,10 +125,11 @@ func TestDecode(t *testing.T) {
125125
payload: "ffffff0000",
126126
config: tagConfig,
127127
expected: Port2Payload{
128-
Time: nil,
128+
Time: Uint32Ptr(0),
129129
Power: nil,
130130
Sensor: nil,
131131
},
132+
expectedErr: "validation failed for Time",
132133
},
133134
{
134135
payload: "ffffff000400000000",
@@ -141,16 +142,19 @@ func TestDecode(t *testing.T) {
141142
expectedErr: "validation failed for Time",
142143
},
143144
{
144-
payload: "ffffff040100",
145-
config: tagConfig,
146-
expected: nil,
147-
expectedErr: "unknown tag 4",
145+
payload: "ffffff040100",
146+
config: tagConfig,
147+
expected: Port2Payload{
148+
Time: nil,
149+
Power: nil,
150+
Sensor: nil,
151+
},
148152
},
149153
{
150154
payload: "ffffff010200",
151155
config: tagConfig,
152156
expected: nil,
153-
expectedErr: "field out of bounds",
157+
expectedErr: "TLV tag",
154158
},
155159
{
156160
payload: "ffffff030100",
@@ -163,8 +167,15 @@ func TestDecode(t *testing.T) {
163167
for _, test := range tests {
164168
t.Run(test.payload, func(t *testing.T) {
165169
decodedData, err := Decode(StringPtr(test.payload), &test.config)
166-
if err != nil && !strings.Contains(err.Error(), test.expectedErr) {
167-
t.Fatalf("expected %s received %s", test.expectedErr, err)
170+
if test.expectedErr != "" {
171+
if err == nil {
172+
t.Fatalf("expected error containing %q but got nil", test.expectedErr)
173+
}
174+
if !strings.Contains(err.Error(), test.expectedErr) {
175+
t.Fatalf("expected error containing %q received %s", test.expectedErr, err)
176+
}
177+
} else if err != nil {
178+
t.Fatalf("unexpected error: %s", err)
168179
}
169180
if !reflect.DeepEqual(decodedData, test.expected) {
170181
t.Fatalf("expected: %+v received: %+v", test.expected, decodedData)
@@ -239,8 +250,15 @@ func TestExtractFieldValue(t *testing.T) {
239250
for _, test := range tests {
240251
t.Run(fmt.Sprintf("%v_%v_%v", test.payload, test.start, test.length), func(t *testing.T) {
241252
result, err := extractFieldValue(test.payload, test.start, test.length, test.optional, test.hexadecimal)
242-
if err != nil && err.Error() != test.expectedErr {
243-
t.Fatalf("expected: %s received: %s", test.expectedErr, err.Error())
253+
if test.expectedErr != "" {
254+
if err == nil {
255+
t.Fatalf("expected error %q but got nil", test.expectedErr)
256+
}
257+
if err.Error() != test.expectedErr {
258+
t.Fatalf("expected: %s received: %s", test.expectedErr, err.Error())
259+
}
260+
} else if err != nil {
261+
t.Fatalf("unexpected error: %s", err)
244262
}
245263
if !reflect.DeepEqual(result, test.expected) {
246264
t.Fatalf("expected: %v received: %v", test.expected, result)
@@ -373,8 +391,15 @@ func TestConvertFieldValue(t *testing.T) {
373391
for _, test := range tests {
374392
t.Run(fmt.Sprintf("%v_%v", test.value, test.fieldType), func(t *testing.T) {
375393
result, err := convertFieldValue(test.value, test.fieldType, nil)
376-
if err != nil && err.Error() != test.expectedErr {
377-
t.Fatalf("expected: %s received: %s", test.expectedErr, err.Error())
394+
if test.expectedErr != "" {
395+
if err == nil {
396+
t.Fatalf("expected error %q but got nil", test.expectedErr)
397+
}
398+
if err.Error() != test.expectedErr {
399+
t.Fatalf("expected: %s received: %s", test.expectedErr, err.Error())
400+
}
401+
} else if err != nil {
402+
t.Fatalf("unexpected error: %s", err)
378403
}
379404
if !reflect.DeepEqual(result, test.expected) {
380405
t.Fatalf("expected: %v received: %v", test.expected, result)
@@ -450,8 +475,15 @@ func TestInsertFieldBytes(t *testing.T) {
450475

451476
for _, test := range tests {
452477
set, bytes, err := insertFieldBytes(test.value, test.length, test.transform)
453-
if err != nil && err.Error() != test.expectedErr {
454-
t.Fatalf("expected: %s received: %s", test.expectedErr, err.Error())
478+
if test.expectedErr != "" {
479+
if err == nil {
480+
t.Fatalf("expected error %q but got nil", test.expectedErr)
481+
}
482+
if err.Error() != test.expectedErr {
483+
t.Fatalf("expected: %s received: %s", test.expectedErr, err.Error())
484+
}
485+
} else if err != nil {
486+
t.Fatalf("unexpected error: %s", err)
455487
}
456488
if !set && err == nil {
457489
t.Fatalf("expected set to be true when error is nil")

pkg/common/metrics.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package common
2+
3+
import (
4+
"github.com/prometheus/client_golang/prometheus"
5+
"github.com/prometheus/client_golang/prometheus/promauto"
6+
)
7+
8+
var (
9+
unknownTLVTagsTotal = promauto.NewCounterVec(prometheus.CounterOpts{
10+
Name: "truvami_common_unknown_tlv_tags_total",
11+
Help: "The total number of unknown TLV tags encountered during decoding",
12+
}, []string{"tag"})
13+
)

pkg/decoder/tagxl/v1/decoder_test.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,10 +117,17 @@ func TestDecode(t *testing.T) {
117117
expectedErr: "port not supported: port 151 tag ff",
118118
},
119119
{
120-
port: 151,
121-
payload: "4c0501ff020000",
122-
expected: Port151Payload{},
123-
expectedErr: "unknown tag ff",
120+
port: 151,
121+
payload: "4c0501ff020000",
122+
expected: Port151Payload{},
123+
},
124+
{
125+
port: 151,
126+
payload: "4c0d0345020a92ff03aabbcc4e0107",
127+
expected: Port151Payload{
128+
Battery: helpers.Float32Ptr(2.706),
129+
DataRate: helpers.DataRatePtr(decoder.DataRateTagXLADR),
130+
},
124131
},
125132
{
126133
port: 151,

0 commit comments

Comments
 (0)