Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ Types of changes
- `Fixed` for any bug fixes.
- `Security` in case of vulnerabilities.

## [0.3.2]

- `Fixed` trailing separator when decoding integer with COMP-3 codec.
- `Fixed` invalid COMP-3 buffers can be decoded as hex string.

## [0.3.1]

- `Fixed` accept empty values and buffers in the COMP-3 codec.
Expand Down
84 changes: 61 additions & 23 deletions pkg/posimap/core/codec/comp3.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package codec

import (
"encoding/hex"
"errors"
"fmt"
"io"
Expand Down Expand Up @@ -71,8 +72,6 @@ const (
)

func (c *Comp3) Decode(buffer api.Buffer, offset int) (any, error) {
result := &strings.Builder{}

bytes, err := buffer.Slice(offset, c.size)
if err != nil && !errors.Is(err, io.EOF) {
return nil, fmt.Errorf("%w", err) // return error if buffer is too short
Expand All @@ -82,33 +81,35 @@ func (c *Comp3) Decode(buffer api.Buffer, offset int) (any, error) {
return "", nil
}

return c.decode(bytes)
}

func (c *Comp3) decode(bytes []byte) (string, error) {
result := make([]rune, 0, c.length)

for byteIndex, byteVal := range bytes {
if byteIndex*2 == c.intDigits {
result.WriteRune(c.sep)
if byteIndex*2 == c.intDigits && c.decDigits > 0 {
result = append(result, c.sep)
}

if byteIndex == c.size-1 {
high := (byteVal & highNibbleMask) >> nibbleShift

if byteIndex*2 < c.intDigits+c.decDigits {
result.WriteRune(convertNibbleToRune(high))
result = append(result, convertHighNibbleToRune(byteVal))
}

sign := handleSign(byteVal)

return sign + result.String(), nil
return handleSign(string(result), byteVal, bytes), nil
}

result.WriteRune(convertNibbleToRune((byteVal & highNibbleMask) >> nibbleShift))
result = append(result, convertHighNibbleToRune(byteVal))

if byteIndex*2+1 == c.intDigits {
result.WriteRune(c.sep)
if byteIndex*2+1 == c.intDigits && c.decDigits > 0 {
result = append(result, c.sep)
}

result.WriteRune(convertNibbleToRune(byteVal & lowNibbleMask))
result = append(result, convertLowNibbleToRune(byteVal))
}

return result.String(), ErrBufferTooShort // should never happen because short buffer is handled by buffer interface
return string(result), ErrBufferTooShort // should never happen because short buffer is handled by buffer interface
}

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

if bytes := c.detectInvalidBuffer(value); bytes != nil {
if err := buffer.Write(offset, bytes); err != nil {
return fmt.Errorf("%w", err)
}

return nil
}

nibbleSign, str, err := c.detectSignAndAddLeadingZeroes(value)
if err != nil {
return err
Expand All @@ -140,6 +149,28 @@ func (c *Comp3) Encode(buffer api.Buffer, offset int, value any) error {
return c.encode(buffer, offset, str, nibbleSign)
}

func (c *Comp3) detectInvalidBuffer(value any) []byte {
str, ok := value.(string)
if !ok {
return nil
}

if len(str) == 0 {
return nil
}

if str[0] == '!' {
bytes, err := hex.DecodeString(str[1:])
if err != nil {
return nil
}

return bytes
}

return nil
}

func (c *Comp3) detectSignAndAddLeadingZeroes(value any) (byte, string, error) {
str, ok := value.(string)
if !ok {
Expand Down Expand Up @@ -236,6 +267,14 @@ const (
alphaNibbleOffset = 10
)

func convertHighNibbleToRune(byteVal byte) rune {
return convertNibbleToRune((byteVal & highNibbleMask) >> nibbleShift)
}

func convertLowNibbleToRune(byteVal byte) rune {
return convertNibbleToRune(byteVal & lowNibbleMask)
}

func convertNibbleToRune(nibble byte) rune {
if nibble <= maxDecimalNibble {
return rune('0' + nibble)
Expand All @@ -260,21 +299,20 @@ func convertRuneToNibble(runeVal rune) (byte, error) {
return 0, fmt.Errorf("%w: invalid rune %q", ErrInvalidComp3Nibble, runeVal)
}

func handleSign(byteVal byte) string {
func handleSign(result string, byteVal byte, buffer []byte) string {
signNibble := byteVal & lowNibbleMask

if signNibble != signNibblePositive && signNibble != signNibbleNegative && signNibble != signNibbleZero {
return "#"
}

switch signNibble {
case signNibbleNegative:
return "-"
return "-" + result
case signNibblePositive:
return "+"
return "+" + result
case signNibbleZero:
return result
}

return ""
// return buffer value as hex string in case of invalid sign nibble
return "!" + hex.EncodeToString(buffer)
}

func isNull(bytes []byte) bool {
Expand Down
36 changes: 34 additions & 2 deletions pkg/posimap/core/codec/comp3_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func TestComp3_Decode(t *testing.T) {
intDigits: 5,
decDigits: 2,
sep: '.',
expected: "#12345.67",
expected: "!1234567a", // invalid sign nibble leads to byte sequence as hex string
},
{
name: "invalid digit nibble",
Expand Down Expand Up @@ -137,6 +137,30 @@ func TestComp3_Decode(t *testing.T) {
sep: ',',
expected: "", // null value is decoded as empty string
},
{
name: "only integer part",
data: []byte{0x12, 0x34, 0x56, 0x0C},
intDigits: 6,
decDigits: 0,
sep: '.',
expected: "+123456", // no trailing separator
},
{
name: "only integer part odd digits",
data: []byte{0x12, 0x34, 0x5C},
intDigits: 5,
decDigits: 0,
sep: '.',
expected: "+12345", // no trailing separator
},
{
name: "no sign",
data: []byte{0x12, 0x34, 0x5F},
intDigits: 5,
decDigits: 0,
sep: '.',
expected: "12345", // no trailing separator
},
}

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

if value != testcase.expected {
t.Errorf("[%s] expected [% 02X], got [% 02X]", testcase.name, testcase.expected, buf.Bytes())
t.Errorf("[%s] expected [%s], got [%s]", testcase.name, testcase.expected, value)
}
})
}
Expand Down Expand Up @@ -299,6 +323,14 @@ func TestComp3_Encode(t *testing.T) {
sep: '.', // even if the expected separator is '.', accept ',' in input
expected: []byte{0x12, 0x34, 0x56, 0x7C},
},
{
name: "invalid buffer",
value: "!1234567a", // invalid sign nibble leads to byte sequence as hex string
intDigits: 5,
decDigits: 2,
sep: '.',
expected: []byte{0x12, 0x34, 0x56, 0x7A},
},
}

for _, testcase := range tests {
Expand Down