Skip to content

Commit 1a188bd

Browse files
authored
fix: handling of invalid comp3 buffers (#46)
* fix: trailing dot after comp-3 decoding * fix: handle invalid buffers * fix: invalid COMP-3 buffers can be decoded as hex string
1 parent eeeb721 commit 1a188bd

File tree

3 files changed

+100
-25
lines changed

3 files changed

+100
-25
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ Types of changes
1414
- `Fixed` for any bug fixes.
1515
- `Security` in case of vulnerabilities.
1616

17+
## [0.3.2]
18+
19+
- `Fixed` trailing separator when decoding integer with COMP-3 codec.
20+
- `Fixed` invalid COMP-3 buffers can be decoded as hex string.
21+
1722
## [0.3.1]
1823

1924
- `Fixed` accept empty values and buffers in the COMP-3 codec.

pkg/posimap/core/codec/comp3.go

Lines changed: 61 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package codec
1919

2020
import (
21+
"encoding/hex"
2122
"errors"
2223
"fmt"
2324
"io"
@@ -71,8 +72,6 @@ const (
7172
)
7273

7374
func (c *Comp3) Decode(buffer api.Buffer, offset int) (any, error) {
74-
result := &strings.Builder{}
75-
7675
bytes, err := buffer.Slice(offset, c.size)
7776
if err != nil && !errors.Is(err, io.EOF) {
7877
return nil, fmt.Errorf("%w", err) // return error if buffer is too short
@@ -82,33 +81,35 @@ func (c *Comp3) Decode(buffer api.Buffer, offset int) (any, error) {
8281
return "", nil
8382
}
8483

84+
return c.decode(bytes)
85+
}
86+
87+
func (c *Comp3) decode(bytes []byte) (string, error) {
88+
result := make([]rune, 0, c.length)
89+
8590
for byteIndex, byteVal := range bytes {
86-
if byteIndex*2 == c.intDigits {
87-
result.WriteRune(c.sep)
91+
if byteIndex*2 == c.intDigits && c.decDigits > 0 {
92+
result = append(result, c.sep)
8893
}
8994

9095
if byteIndex == c.size-1 {
91-
high := (byteVal & highNibbleMask) >> nibbleShift
92-
9396
if byteIndex*2 < c.intDigits+c.decDigits {
94-
result.WriteRune(convertNibbleToRune(high))
97+
result = append(result, convertHighNibbleToRune(byteVal))
9598
}
9699

97-
sign := handleSign(byteVal)
98-
99-
return sign + result.String(), nil
100+
return handleSign(string(result), byteVal, bytes), nil
100101
}
101102

102-
result.WriteRune(convertNibbleToRune((byteVal & highNibbleMask) >> nibbleShift))
103+
result = append(result, convertHighNibbleToRune(byteVal))
103104

104-
if byteIndex*2+1 == c.intDigits {
105-
result.WriteRune(c.sep)
105+
if byteIndex*2+1 == c.intDigits && c.decDigits > 0 {
106+
result = append(result, c.sep)
106107
}
107108

108-
result.WriteRune(convertNibbleToRune(byteVal & lowNibbleMask))
109+
result = append(result, convertLowNibbleToRune(byteVal))
109110
}
110111

111-
return result.String(), ErrBufferTooShort // should never happen because short buffer is handled by buffer interface
112+
return string(result), ErrBufferTooShort // should never happen because short buffer is handled by buffer interface
112113
}
113114

114115
func (c *Comp3) Encode(buffer api.Buffer, offset int, value any) error {
@@ -121,6 +122,14 @@ func (c *Comp3) Encode(buffer api.Buffer, offset int, value any) error {
121122
return nil
122123
}
123124

125+
if bytes := c.detectInvalidBuffer(value); bytes != nil {
126+
if err := buffer.Write(offset, bytes); err != nil {
127+
return fmt.Errorf("%w", err)
128+
}
129+
130+
return nil
131+
}
132+
124133
nibbleSign, str, err := c.detectSignAndAddLeadingZeroes(value)
125134
if err != nil {
126135
return err
@@ -140,6 +149,28 @@ func (c *Comp3) Encode(buffer api.Buffer, offset int, value any) error {
140149
return c.encode(buffer, offset, str, nibbleSign)
141150
}
142151

152+
func (c *Comp3) detectInvalidBuffer(value any) []byte {
153+
str, ok := value.(string)
154+
if !ok {
155+
return nil
156+
}
157+
158+
if len(str) == 0 {
159+
return nil
160+
}
161+
162+
if str[0] == '!' {
163+
bytes, err := hex.DecodeString(str[1:])
164+
if err != nil {
165+
return nil
166+
}
167+
168+
return bytes
169+
}
170+
171+
return nil
172+
}
173+
143174
func (c *Comp3) detectSignAndAddLeadingZeroes(value any) (byte, string, error) {
144175
str, ok := value.(string)
145176
if !ok {
@@ -236,6 +267,14 @@ const (
236267
alphaNibbleOffset = 10
237268
)
238269

270+
func convertHighNibbleToRune(byteVal byte) rune {
271+
return convertNibbleToRune((byteVal & highNibbleMask) >> nibbleShift)
272+
}
273+
274+
func convertLowNibbleToRune(byteVal byte) rune {
275+
return convertNibbleToRune(byteVal & lowNibbleMask)
276+
}
277+
239278
func convertNibbleToRune(nibble byte) rune {
240279
if nibble <= maxDecimalNibble {
241280
return rune('0' + nibble)
@@ -260,21 +299,20 @@ func convertRuneToNibble(runeVal rune) (byte, error) {
260299
return 0, fmt.Errorf("%w: invalid rune %q", ErrInvalidComp3Nibble, runeVal)
261300
}
262301

263-
func handleSign(byteVal byte) string {
302+
func handleSign(result string, byteVal byte, buffer []byte) string {
264303
signNibble := byteVal & lowNibbleMask
265304

266-
if signNibble != signNibblePositive && signNibble != signNibbleNegative && signNibble != signNibbleZero {
267-
return "#"
268-
}
269-
270305
switch signNibble {
271306
case signNibbleNegative:
272-
return "-"
307+
return "-" + result
273308
case signNibblePositive:
274-
return "+"
309+
return "+" + result
310+
case signNibbleZero:
311+
return result
275312
}
276313

277-
return ""
314+
// return buffer value as hex string in case of invalid sign nibble
315+
return "!" + hex.EncodeToString(buffer)
278316
}
279317

280318
func isNull(bytes []byte) bool {

pkg/posimap/core/codec/comp3_test.go

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ func TestComp3_Decode(t *testing.T) {
7171
intDigits: 5,
7272
decDigits: 2,
7373
sep: '.',
74-
expected: "#12345.67",
74+
expected: "!1234567a", // invalid sign nibble leads to byte sequence as hex string
7575
},
7676
{
7777
name: "invalid digit nibble",
@@ -137,6 +137,30 @@ func TestComp3_Decode(t *testing.T) {
137137
sep: ',',
138138
expected: "", // null value is decoded as empty string
139139
},
140+
{
141+
name: "only integer part",
142+
data: []byte{0x12, 0x34, 0x56, 0x0C},
143+
intDigits: 6,
144+
decDigits: 0,
145+
sep: '.',
146+
expected: "+123456", // no trailing separator
147+
},
148+
{
149+
name: "only integer part odd digits",
150+
data: []byte{0x12, 0x34, 0x5C},
151+
intDigits: 5,
152+
decDigits: 0,
153+
sep: '.',
154+
expected: "+12345", // no trailing separator
155+
},
156+
{
157+
name: "no sign",
158+
data: []byte{0x12, 0x34, 0x5F},
159+
intDigits: 5,
160+
decDigits: 0,
161+
sep: '.',
162+
expected: "12345", // no trailing separator
163+
},
140164
}
141165

142166
for _, testcase := range tests {
@@ -152,7 +176,7 @@ func TestComp3_Decode(t *testing.T) {
152176
}
153177

154178
if value != testcase.expected {
155-
t.Errorf("[%s] expected [% 02X], got [% 02X]", testcase.name, testcase.expected, buf.Bytes())
179+
t.Errorf("[%s] expected [%s], got [%s]", testcase.name, testcase.expected, value)
156180
}
157181
})
158182
}
@@ -299,6 +323,14 @@ func TestComp3_Encode(t *testing.T) {
299323
sep: '.', // even if the expected separator is '.', accept ',' in input
300324
expected: []byte{0x12, 0x34, 0x56, 0x7C},
301325
},
326+
{
327+
name: "invalid buffer",
328+
value: "!1234567a", // invalid sign nibble leads to byte sequence as hex string
329+
intDigits: 5,
330+
decDigits: 2,
331+
sep: '.',
332+
expected: []byte{0x12, 0x34, 0x56, 0x7A},
333+
},
302334
}
303335

304336
for _, testcase := range tests {

0 commit comments

Comments
 (0)