Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add 2 more options for decoding CBOR integers to interface{} #457

Merged
merged 1 commit into from
Dec 28, 2023
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
82 changes: 66 additions & 16 deletions decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,21 +264,40 @@ func (tm TagsMode) valid() bool {
return tm >= 0 && tm < maxTagsMode
}

// IntDecMode specifies which Go int type (int64 or uint64) should
// be used when decoding CBOR int (major type 0 and 1) to Go interface{}.
// IntDecMode specifies which Go type (int64, uint64, or big.Int) should
// be used when decoding CBOR integers (major type 0 and 1) to Go interface{}.
type IntDecMode int

const (
// IntDecConvertNone affects how CBOR int (major type 0 and 1) decodes to Go interface{}.
// It makes CBOR positive int (major type 0) decode to uint64 value, and
// CBOR negative int (major type 1) decode to int64 value.
// IntDecConvertNone affects how CBOR integers (major type 0 and 1) decode to Go interface{}.
// It decodes CBOR unsigned integer (major type 0) to:
// - uint64
// It decodes CBOR negative integer (major type 1) to:
// - int64 if value fits
// - big.Int or *big.Int (see BigIntDecMode) if value doesn't fit into int64
IntDecConvertNone IntDecMode = iota

// IntDecConvertSigned affects how CBOR int (major type 0 and 1) decodes to Go interface{}.
// It makes CBOR positive/negative int (major type 0 and 1) decode to int64 value.
// If value overflows int64, UnmarshalTypeError is returned.
// IntDecConvertSigned affects how CBOR integers (major type 0 and 1) decode to Go interface{}.
// It decodes CBOR integers (major type 0 and 1) to:
// - int64 if value fits
// - big.Int or *big.Int (see BigIntDecMode) if value < math.MinInt64
// - return UnmarshalTypeError if value > math.MaxInt64
// Deprecated: IntDecConvertSigned should not be used.
// Please use other options, such as IntDecConvertSignedOrError, IntDecConvertSignedOrBigInt, IntDecConvertNone.
IntDecConvertSigned

// IntDecConvertSignedOrFail affects how CBOR integers (major type 0 and 1) decode to Go interface{}.
// It decodes CBOR integers (major type 0 and 1) to:
// - int64 if value fits
// - return UnmarshalTypeError if value doesn't fit into int64
IntDecConvertSignedOrFail

// IntDecConvertSigned affects how CBOR integers (major type 0 and 1) decode to Go interface{}.
// It makes CBOR integers (major type 0 and 1) decode to:
// - int64 if value fits
// - big.Int or *big.Int (see BigIntDecMode) if value doesn't fit into int64
IntDecConvertSignedOrBigInt

maxIntDec
)

Expand Down Expand Up @@ -1194,32 +1213,63 @@ func (d *decoder) parse(skipSelfDescribedTag bool) (interface{}, error) { //noli
switch t {
case cborTypePositiveInt:
_, _, val := d.getHead()
if d.dm.intDec == IntDecConvertNone {

switch d.dm.intDec {
case IntDecConvertNone:
return val, nil
}
if val > math.MaxInt64 {
return nil, &UnmarshalTypeError{
CBORType: t.String(),
GoType: reflect.TypeOf(int64(0)).String(),
errorMsg: strconv.FormatUint(val, 10) + " overflows Go's int64",

case IntDecConvertSigned, IntDecConvertSignedOrFail:
if val > math.MaxInt64 {
return nil, &UnmarshalTypeError{
CBORType: t.String(),
GoType: reflect.TypeOf(int64(0)).String(),
errorMsg: strconv.FormatUint(val, 10) + " overflows Go's int64",
}
}

return int64(val), nil

case IntDecConvertSignedOrBigInt:
if val > math.MaxInt64 {
bi := new(big.Int).SetUint64(val)
if d.dm.bigIntDec == BigIntDecodePointer {
return bi, nil
}
return *bi, nil
}

return int64(val), nil

default:
// not reachable
}
return int64(val), nil

case cborTypeNegativeInt:
_, _, val := d.getHead()

if val > math.MaxInt64 {
// CBOR negative integer value overflows Go int64, use big.Int instead.
bi := new(big.Int).SetUint64(val)
bi.Add(bi, big.NewInt(1))
bi.Neg(bi)

if d.dm.intDec == IntDecConvertSignedOrFail {
return nil, &UnmarshalTypeError{
CBORType: t.String(),
GoType: reflect.TypeOf(int64(0)).String(),
errorMsg: bi.String() + " overflows Go's int64",
}
}

if d.dm.bigIntDec == BigIntDecodePointer {
return bi, nil
}
return *bi, nil
}

nValue := int64(-1) ^ int64(val)
return nValue, nil

case cborTypeByteString:
return d.parseByteString(), nil
case cborTypeTextString:
Expand Down
173 changes: 169 additions & 4 deletions decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4704,8 +4704,168 @@ func TestDecModeInvalidIntDec(t *testing.T) {
}
}

func TestIntDec(t *testing.T) {
dm, err := DecOptions{IntDec: IntDecConvertSigned}.DecMode()
func TestIntDecConvertNone(t *testing.T) {
dm, err := DecOptions{
IntDec: IntDecConvertNone,
BigIntDec: BigIntDecodePointer,
}.DecMode()
if err != nil {
t.Errorf("DecMode() returned an error %+v", err)
}

testCases := []struct {
name string
cborData []byte
wantObj interface{}
}{
{
name: "CBOR pos int",
cborData: hexDecode("1a000f4240"),
wantObj: uint64(1000000),
},
{
name: "CBOR pos int overflows int64",
cborData: hexDecode("1b8000000000000000"), // math.MaxInt64+1
wantObj: uint64(math.MaxInt64 + 1),
},
{
name: "CBOR neg int",
cborData: hexDecode("3903e7"),
wantObj: int64(-1000),
},
{
name: "CBOR neg int overflows int64",
cborData: hexDecode("3b8000000000000000"), // math.MinInt64-1
wantObj: new(big.Int).Sub(big.NewInt(math.MinInt64), big.NewInt(1)),
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
var v interface{}
err := dm.Unmarshal(tc.cborData, &v)
if err == nil {
if !reflect.DeepEqual(v, tc.wantObj) {
t.Errorf("Unmarshal(0x%x) return %v (%T), want %v (%T)", tc.cborData, v, v, tc.wantObj, tc.wantObj)
}
} else {
t.Errorf("Unmarshal(0x%x) returned error %q", tc.cborData, err)
}
})
}
}

func TestIntDecConvertSigned(t *testing.T) {
dm, err := DecOptions{
IntDec: IntDecConvertSigned,
BigIntDec: BigIntDecodePointer,
}.DecMode()
if err != nil {
t.Errorf("DecMode() returned an error %+v", err)
}

testCases := []struct {
name string
cborData []byte
wantObj interface{}
wantErrorMsg string
}{
{
name: "CBOR pos int",
cborData: hexDecode("1a000f4240"),
wantObj: int64(1000000),
},
{
name: "CBOR pos int overflows int64",
cborData: hexDecode("1b8000000000000000"), // math.MaxInt64+1
wantErrorMsg: "9223372036854775808 overflows Go's int64",
},
{
name: "CBOR neg int",
cborData: hexDecode("3903e7"),
wantObj: int64(-1000),
},
{
name: "CBOR neg int overflows int64",
cborData: hexDecode("3b8000000000000000"), // math.MinInt64-1
wantObj: new(big.Int).Sub(big.NewInt(math.MinInt64), big.NewInt(1)),
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
var v interface{}
err := dm.Unmarshal(tc.cborData, &v)
if err == nil {
if tc.wantErrorMsg != "" {
t.Errorf("Unmarshal(0x%x) didn't return an error, want %q", tc.cborData, tc.wantErrorMsg)
} else if !reflect.DeepEqual(v, tc.wantObj) {
t.Errorf("Unmarshal(0x%x) return %v (%T), want %v (%T)", tc.cborData, v, v, tc.wantObj, tc.wantObj)
}
} else {
if tc.wantErrorMsg == "" {
t.Errorf("Unmarshal(0x%x) returned error %q", tc.cborData, err)
} else if !strings.Contains(err.Error(), tc.wantErrorMsg) {
t.Errorf("Unmarshal(0x%x) returned error %q, want %q", tc.cborData, err.Error(), tc.wantErrorMsg)
}
}
})
}
}

func TestIntDecConvertSignedOrBigInt(t *testing.T) {
dm, err := DecOptions{
IntDec: IntDecConvertSignedOrBigInt,
BigIntDec: BigIntDecodePointer,
}.DecMode()
if err != nil {
t.Errorf("DecMode() returned an error %+v", err)
}

testCases := []struct {
name string
cborData []byte
wantObj interface{}
}{
{
name: "CBOR pos int",
cborData: hexDecode("1a000f4240"),
wantObj: int64(1000000),
},
{
name: "CBOR pos int overflows int64",
cborData: hexDecode("1b8000000000000000"),
wantObj: new(big.Int).Add(big.NewInt(math.MaxInt64), big.NewInt(1)),
},
{
name: "CBOR neg int",
cborData: hexDecode("3903e7"),
wantObj: int64(-1000),
},
{
name: "CBOR neg int overflows int64",
cborData: hexDecode("3b8000000000000000"),
wantObj: new(big.Int).Sub(big.NewInt(math.MinInt64), big.NewInt(1)),
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
var v interface{}
err := dm.Unmarshal(tc.cborData, &v)
if err == nil {
if !reflect.DeepEqual(v, tc.wantObj) {
t.Errorf("Unmarshal(0x%x) return %v (%T), want %v (%T)", tc.cborData, v, v, tc.wantObj, tc.wantObj)
}
} else {
t.Errorf("Unmarshal(0x%x) returned error %q", tc.cborData, err)
}
})
}
}

func TestIntDecConvertSignedOrError(t *testing.T) {
dm, err := DecOptions{
IntDec: IntDecConvertSignedOrFail,
BigIntDec: BigIntDecodePointer,
}.DecMode()
if err != nil {
t.Errorf("DecMode() returned an error %+v", err)
}
Expand All @@ -4723,14 +4883,19 @@ func TestIntDec(t *testing.T) {
},
{
name: "CBOR pos int overflows int64",
cborData: hexDecode("1bffffffffffffffff"),
wantErrorMsg: "18446744073709551615 overflows Go's int64",
cborData: hexDecode("1b8000000000000000"), // math.MaxInt64+1
wantErrorMsg: "9223372036854775808 overflows Go's int64",
},
{
name: "CBOR neg int",
cborData: hexDecode("3903e7"),
wantObj: int64(-1000),
},
{
name: "CBOR neg int overflows int64",
cborData: hexDecode("3b8000000000000000"), // math.MinInt64-1
wantErrorMsg: "-9223372036854775809 overflows Go's int64",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
Expand Down