Compare commits

..

6 Commits

Author SHA1 Message Date
Faye Amacker
948c054444
Merge pull request #648 from fxamacker/fxamacker/port-pr-647-to-2.7-branch
Some checks failed
ci / test ${{matrix.os}} go-${{ matrix.go-version }} (1.17, macos-latest) (push) Has been cancelled
ci / test ${{matrix.os}} go-${{ matrix.go-version }} (1.17, ubuntu-latest) (push) Has been cancelled
ci / test ${{matrix.os}} go-${{ matrix.go-version }} (1.17, windows-latest) (push) Has been cancelled
ci / test ${{matrix.os}} go-${{ matrix.go-version }} (1.19, macos-latest) (push) Has been cancelled
ci / test ${{matrix.os}} go-${{ matrix.go-version }} (1.19, ubuntu-latest) (push) Has been cancelled
ci / test ${{matrix.os}} go-${{ matrix.go-version }} (1.19, windows-latest) (push) Has been cancelled
ci / test ${{matrix.os}} go-${{ matrix.go-version }} (1.20, macos-latest) (push) Has been cancelled
ci / test ${{matrix.os}} go-${{ matrix.go-version }} (1.20, ubuntu-latest) (push) Has been cancelled
ci / test ${{matrix.os}} go-${{ matrix.go-version }} (1.20, windows-latest) (push) Has been cancelled
ci / test ${{matrix.os}} go-${{ matrix.go-version }} (1.21, macos-latest) (push) Has been cancelled
ci / test ${{matrix.os}} go-${{ matrix.go-version }} (1.21, ubuntu-latest) (push) Has been cancelled
ci / test ${{matrix.os}} go-${{ matrix.go-version }} (1.21, windows-latest) (push) Has been cancelled
ci / test ${{matrix.os}} go-${{ matrix.go-version }} (1.22, macos-latest) (push) Has been cancelled
ci / test ${{matrix.os}} go-${{ matrix.go-version }} (1.22, ubuntu-latest) (push) Has been cancelled
ci / test ${{matrix.os}} go-${{ matrix.go-version }} (1.22, windows-latest) (push) Has been cancelled
Port PR 647 to release-2.7 branch to optimize internal calls to UnmarshalCBOR()
2025-03-29 20:05:46 -05:00
Faye Amacker
608e40e88e Optimize calls to UnmarshalCBOR() for RawTag, etc.
Currently, unreleased changes in PR #636 and #645 cause the
input data to be checked twice when UnmarshalCBOR() is
called internally by Unmarshal() for:
- ByteString
- RawTag
- SimpleValue

UnmarshalCBOR() checks input data because it can be called by
user apps providing bad data. However, the codec already checks
input data before internally calling UnmarshalCBOR() so the
2nd check is redundant.

This commit avoids redundant check on the input data by having
Unmarshal() call the private unmarshalCBOR() if implemented
by ByteString, RawTag, SimpleValue, etc.:
- Internally, the codec calls the private unmarshalCBOR() to
  avoid the redundant check on input data.
- Externally, UnmarshalCBOR() is available as a wrapper that
  checks input data before calling the private unmarshalCBOR().

UnmarshalCBOR() for ByteString, RawTag, and SimpleValue are marked
as deprecated and Unmarshal() should be used instead.
2025-03-28 11:04:15 -05:00
Faye Amacker
efbaf6c3a0
Merge pull request #636 from fxamacker/fxamacker/check-wellformedness-in-UnmarshalCBOR
Update error handling in RawTag.UnmarshalCBOR(), etc. to match cbor.Unmarshal()
2025-03-17 23:01:56 -05:00
Faye Amacker
c3ffc7a1de Check CBOR well-formedness in ByteString.UnmarshalCBOR
When ByteString.UnmarshalCBOR() is called by codec
(normal case), the codec will first check if data is well-formed
before calling ByteString.UnmarshalCBOR(data).

However, it can also be called by user app (not intended use)
and user apps might not check if data is well-formed.  In
such cases, this function can panic if given malformed data.

This commit updates ByteString.UnmarshalCBOR() to check for
well-formedness inside the function, so it behaves the same
whether it is called by codec internally or by user app.

Unfortunately, this approach means the same data is checked twice
for the normal case of the codec using
Unmarshal(data, *ByteString).

This can be revisited and maybe optimized in the future.
2025-03-16 20:23:25 -05:00
Faye Amacker
8a96f6d88c Check CBOR well-formedness in SimpleValue.UnmarshalCBOR
When SimpleValue.UnmarshalCBOR() is called by codec
(normal case), the codec will first check if data is well-formed
before calling SimpleValue.UnmarshalCBOR(data).

However, it can also be called by user app (not intended use)
and user apps might not check if data is well-formed.  In
such cases, this function can panic if given malformed data.

This commit updates SimpleValue.UnmarshalCBOR() to check for
well-formedness inside the function, so it behaves the same
whether it is called by codec internally or by user app.

Unfortunately, this approach means the same data is checked twice
for the normal case of the codec using
Unmarshal(data, *SimpleValue).

This can be revisited and maybe optimized in the future.
2025-03-16 19:55:18 -05:00
Faye Amacker
19b7fb6109 Check CBOR well-formedness in RawTag.UnmarshalCBOR
When RawTag.UnmarshalCBOR() is called by codec (normal case),
the codec will first check if data is well-formed before
calling RawTag.UnmarshalCBOR(data).

However, it can also be called by user app (not intended use)
and user apps might not check if data is well-formed.  In
such cases, this function can panic if given malformed data.

This commit updates RawTag.UnmarshalCBOR() to check for
well-formedness inside the function, so it behaves the same
whether it is called by codec internally or by user app.

Unfortunately, this approach means the same data is checked twice
for the normal case of the codec using Unmarshal(data, *RawTag).
This can be revisited and maybe optimized in the future.
2025-03-16 19:50:38 -05:00
24 changed files with 563 additions and 336 deletions

View File

@ -38,7 +38,7 @@ jobs:
- name: Checkout code
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Install Go
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
with:
go-version: 1.21
check-latest: true

View File

@ -26,10 +26,10 @@ jobs:
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
go-version: [1.17, 1.18, 1.19, '1.20', 1.21, 1.22, 1.23]
go-version: [1.17, 1.19, '1.20', 1.21, 1.22]
steps:
- name: Install Go
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
with:
go-version: ${{ matrix.go-version }}
check-latest: true

View File

@ -32,14 +32,14 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6
uses: github/codeql-action/init@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3.25.10
with:
languages: ${{ matrix.language }}
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6
uses: github/codeql-action/autobuild@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3.25.10
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6
uses: github/codeql-action/analyze@23acc5c183826b7a8a97bce3cecc52db901f8251 # v3.25.10

View File

@ -37,11 +37,11 @@ jobs:
with:
fetch-depth: 1
- name: Set up Go
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
with:
go-version: 1.21.x
check-latest: true
- name: Install latest from golang.org
run: go install golang.org/x/vuln/cmd/govulncheck@4ea4418106cea3bb2c9aa098527c924e9e1fbbb4 # v1.1.3
run: go install golang.org/x/vuln/cmd/govulncheck@5507063454b1b8c930db99818a88b52f1f143418 # v1.0.4
- name: Run govulncheck
run: govulncheck -show=traces ./...

View File

@ -37,7 +37,7 @@ jobs:
fetch-depth: 1
- name: Setup Go
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
with:
go-version: ${{ env.GO_VERSION }}
check-latest: true

View File

@ -38,11 +38,38 @@ func (bs ByteString) MarshalCBOR() ([]byte, error) {
// UnmarshalCBOR decodes CBOR byte string (major type 2) to ByteString.
// Decoding CBOR null and CBOR undefined sets ByteString to be empty.
//
// Deprecated: No longer used by this codec; kept for compatibility
// with user apps that directly call this function.
func (bs *ByteString) UnmarshalCBOR(data []byte) error {
if bs == nil {
return errors.New("cbor.ByteString: UnmarshalCBOR on nil pointer")
}
d := decoder{data: data, dm: defaultDecMode}
// Check well-formedness of CBOR data item.
// ByteString.UnmarshalCBOR() is exported, so
// the codec needs to support same behavior for:
// - Unmarshal(data, *ByteString)
// - ByteString.UnmarshalCBOR(data)
err := d.wellformed(false, false)
if err != nil {
return err
}
return bs.unmarshalCBOR(data)
}
// unmarshalCBOR decodes CBOR byte string (major type 2) to ByteString.
// Decoding CBOR null and CBOR undefined sets ByteString to be empty.
// This function assumes data is well-formed, and does not perform bounds checking.
// This function is called by Unmarshal().
func (bs *ByteString) unmarshalCBOR(data []byte) error {
if bs == nil {
return errors.New("cbor.ByteString: UnmarshalCBOR on nil pointer")
}
// Decoding CBOR null and CBOR undefined to ByteString resets data.
// This behavior is similar to decoding CBOR null and CBOR undefined to []byte.
if len(data) == 1 && (data[0] == 0xf6 || data[0] == 0xf7) {

View File

@ -3,7 +3,11 @@
package cbor
import "testing"
import (
"io"
"strings"
"testing"
)
func TestByteString(t *testing.T) {
type s1 struct {
@ -99,3 +103,110 @@ func TestByteString(t *testing.T) {
dm, _ := DecOptions{}.DecMode()
testRoundTrip(t, testCases, em, dm)
}
func TestUnmarshalByteStringOnBadData(t *testing.T) {
testCases := []struct {
name string
data []byte
errMsg string
}{
// Empty data
{
name: "nil data",
data: nil,
errMsg: io.EOF.Error(),
},
{
name: "empty data",
data: []byte{},
errMsg: io.EOF.Error(),
},
// Wrong CBOR types
{
name: "uint type",
data: hexDecode("01"),
errMsg: "cbor: cannot unmarshal positive integer into Go value of type cbor.ByteString",
},
{
name: "int type",
data: hexDecode("20"),
errMsg: "cbor: cannot unmarshal negative integer into Go value of type cbor.ByteString",
},
{
name: "string type",
data: hexDecode("60"),
errMsg: "cbor: cannot unmarshal UTF-8 text string into Go value of type cbor.ByteString",
},
{
name: "array type",
data: hexDecode("80"),
errMsg: "cbor: cannot unmarshal array into Go value of type cbor.ByteString",
},
{
name: "map type",
data: hexDecode("a0"),
errMsg: "cbor: cannot unmarshal map into Go value of type cbor.ByteString",
},
{
name: "tag type",
data: hexDecode("c074323031332d30332d32315432303a30343a30305a"),
errMsg: "cbor: cannot unmarshal tag into Go value of type cbor.ByteString",
},
{
name: "float type",
data: hexDecode("f90000"),
errMsg: "cbor: cannot unmarshal primitives into Go value of type cbor.ByteString",
},
// Truncated CBOR data
{
name: "truncated head",
data: hexDecode("18"),
errMsg: io.ErrUnexpectedEOF.Error(),
},
// Truncated CBOR byte string
{
name: "truncated byte string",
data: hexDecode("44010203"),
errMsg: io.ErrUnexpectedEOF.Error(),
},
// Extraneous CBOR data
{
name: "extraneous data",
data: hexDecode("c074323031332d30332d32315432303a30343a30305a00"),
errMsg: "cbor: 1 bytes of extraneous data starting at index 22",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Test ByteString.UnmarshalCBOR(data)
{
var v ByteString
err := v.UnmarshalCBOR(tc.data)
if err == nil {
t.Errorf("UnmarshalCBOR(%x) didn't return error", tc.data)
}
if !strings.HasPrefix(err.Error(), tc.errMsg) {
t.Errorf("UnmarshalCBOR(%x) returned error %q, want %q", tc.data, err.Error(), tc.errMsg)
}
}
// Test Unmarshal(data, *ByteString), which calls ByteString.unmarshalCBOR() under the hood
{
var v ByteString
err := Unmarshal(tc.data, &v)
if err == nil {
t.Errorf("Unmarshal(%x) didn't return error", tc.data)
}
if !strings.HasPrefix(err.Error(), tc.errMsg) {
t.Errorf("Unmarshal(%x) returned error %q, want %q", tc.data, err.Error(), tc.errMsg)
}
}
})
}
}

View File

@ -31,6 +31,7 @@ type specialType int
const (
specialTypeNone specialType = iota
specialTypeUnmarshalerIface
specialTypeUnexportedUnmarshalerIface
specialTypeEmptyIface
specialTypeIface
specialTypeTag
@ -69,6 +70,8 @@ func newTypeInfo(t reflect.Type) *typeInfo {
tInfo.spclType = specialTypeTag
} else if t == typeTime {
tInfo.spclType = specialTypeTime
} else if reflect.PtrTo(t).Implements(typeUnexportedUnmarshaler) {
tInfo.spclType = specialTypeUnexportedUnmarshalerIface
} else if reflect.PtrTo(t).Implements(typeUnmarshaler) {
tInfo.spclType = specialTypeUnmarshalerIface
}

View File

@ -151,6 +151,10 @@ type Unmarshaler interface {
UnmarshalCBOR([]byte) error
}
type unmarshaler interface {
unmarshalCBOR([]byte) error
}
// InvalidUnmarshalError describes an invalid argument passed to Unmarshal.
type InvalidUnmarshalError struct {
s string
@ -960,6 +964,10 @@ const (
defaultMaxMapPairs = 131072
minMaxMapPairs = 16
maxMaxMapPairs = 2147483647
defaultMaxNestedLevels = 32
minMaxNestedLevels = 4
maxMaxNestedLevels = 65535
)
var defaultSimpleValues = func() *SimpleValueRegistry {
@ -1382,7 +1390,8 @@ func (d *decoder) parseToValue(v reflect.Value, tInfo *typeInfo) error { //nolin
registeredType := d.dm.tags.getTypeFromTagNum(tagNums)
if registeredType != nil {
if implements(registeredType, tInfo.nonPtrType) {
if registeredType.Implements(tInfo.nonPtrType) ||
reflect.PtrTo(registeredType).Implements(tInfo.nonPtrType) {
v.Set(reflect.New(registeredType))
v = v.Elem()
tInfo = getTypeInfo(registeredType)
@ -1455,6 +1464,9 @@ func (d *decoder) parseToValue(v reflect.Value, tInfo *typeInfo) error { //nolin
case specialTypeUnmarshalerIface:
return d.parseToUnmarshaler(v)
case specialTypeUnexportedUnmarshalerIface:
return d.parseToUnexportedUnmarshaler(v)
}
}
@ -1800,6 +1812,26 @@ func (d *decoder) parseToUnmarshaler(v reflect.Value) error {
return errors.New("cbor: failed to assert " + v.Type().String() + " as cbor.Unmarshaler")
}
// parseToUnexportedUnmarshaler parses CBOR data to value implementing unmarshaler interface.
// It assumes data is well-formed, and does not perform bounds checking.
func (d *decoder) parseToUnexportedUnmarshaler(v reflect.Value) error {
if d.nextCBORNil() && v.Kind() == reflect.Ptr && v.IsNil() {
d.skip()
return nil
}
if v.Kind() != reflect.Ptr && v.CanAddr() {
v = v.Addr()
}
if u, ok := v.Interface().(unmarshaler); ok {
start := d.off
d.skip()
return u.unmarshalCBOR(d.data[start:d.off])
}
d.skip()
return errors.New("cbor: failed to assert " + v.Type().String() + " as cbor.unmarshaler")
}
// parse parses CBOR data and returns value in default Go type.
// It assumes data is well-formed, and does not perform bounds checking.
func (d *decoder) parse(skipSelfDescribedTag bool) (interface{}, error) { //nolint:gocyclo
@ -2964,13 +2996,14 @@ func (d *decoder) nextCBORNil() bool {
}
var (
typeIntf = reflect.TypeOf([]interface{}(nil)).Elem()
typeTime = reflect.TypeOf(time.Time{})
typeBigInt = reflect.TypeOf(big.Int{})
typeUnmarshaler = reflect.TypeOf((*Unmarshaler)(nil)).Elem()
typeBinaryUnmarshaler = reflect.TypeOf((*encoding.BinaryUnmarshaler)(nil)).Elem()
typeString = reflect.TypeOf("")
typeByteSlice = reflect.TypeOf([]byte(nil))
typeIntf = reflect.TypeOf([]interface{}(nil)).Elem()
typeTime = reflect.TypeOf(time.Time{})
typeBigInt = reflect.TypeOf(big.Int{})
typeUnmarshaler = reflect.TypeOf((*Unmarshaler)(nil)).Elem()
typeUnexportedUnmarshaler = reflect.TypeOf((*unmarshaler)(nil)).Elem()
typeBinaryUnmarshaler = reflect.TypeOf((*encoding.BinaryUnmarshaler)(nil)).Elem()
typeString = reflect.TypeOf("")
typeByteSlice = reflect.TypeOf([]byte(nil))
)
func fillNil(_ cborType, v reflect.Value) error {

View File

@ -1,19 +0,0 @@
// Copyright (c) Faye Amacker. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.
//go:build !tinygo
package cbor
import "reflect"
const (
defaultMaxNestedLevels = 32
minMaxNestedLevels = 4
maxMaxNestedLevels = 65535
)
func implements(concreteType reflect.Type, interfaceType reflect.Type) bool {
return concreteType.Implements(interfaceType) ||
reflect.PtrTo(concreteType).Implements(interfaceType)
}

View File

@ -1,109 +0,0 @@
// Copyright (c) Faye Amacker. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.
//go:build !tinygo
package cbor
import (
"reflect"
"testing"
)
func TestUnmarshalDeepNesting(t *testing.T) {
// Construct this object rather than embed such a large constant in the code
type TestNode struct {
Value int
Child *TestNode
}
n := &TestNode{Value: 0}
root := n
for i := 0; i < 65534; i++ {
child := &TestNode{Value: i}
n.Child = child
n = child
}
em, err := EncOptions{}.EncMode()
if err != nil {
t.Errorf("EncMode() returned error %v", err)
}
data, err := em.Marshal(root)
if err != nil {
t.Errorf("Marshal() deeply nested object returned error %v", err)
}
// Try unmarshal it
dm, err := DecOptions{MaxNestedLevels: 65535}.DecMode()
if err != nil {
t.Errorf("DecMode() returned error %v", err)
}
var readback TestNode
err = dm.Unmarshal(data, &readback)
if err != nil {
t.Errorf("Unmarshal() of deeply nested object returned error: %v", err)
}
if !reflect.DeepEqual(root, &readback) {
t.Errorf("Unmarshal() of deeply nested object did not match\nGot: %#v\n Want: %#v\n",
&readback, root)
}
}
func TestUnmarshalRegisteredTagToInterface(t *testing.T) {
var err error
tags := NewTagSet()
err = tags.Add(TagOptions{EncTag: EncTagRequired, DecTag: DecTagRequired}, reflect.TypeOf(C{}), 279)
if err != nil {
t.Error(err)
}
err = tags.Add(TagOptions{EncTag: EncTagRequired, DecTag: DecTagRequired}, reflect.TypeOf(D{}), 280)
if err != nil {
t.Error(err)
}
encMode, _ := PreferredUnsortedEncOptions().EncModeWithTags(tags)
decMode, _ := DecOptions{}.DecModeWithTags(tags)
v1 := A1{Field: &C{Field: 5}}
data1, err := encMode.Marshal(v1)
if err != nil {
t.Fatalf("Marshal(%+v) returned error %v", v1, err)
}
v2 := A2{Fields: []B{&C{Field: 5}, &D{Field: "a"}}}
data2, err := encMode.Marshal(v2)
if err != nil {
t.Fatalf("Marshal(%+v) returned error %v", v2, err)
}
testCases := []struct {
name string
data []byte
unmarshalToObj interface{}
wantValue interface{}
}{
{
name: "interface type",
data: data1,
unmarshalToObj: &A1{},
wantValue: &v1,
},
{
name: "slice of interface type",
data: data2,
unmarshalToObj: &A2{},
wantValue: &v2,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err = decMode.Unmarshal(tc.data, tc.unmarshalToObj)
if err != nil {
t.Errorf("Unmarshal(0x%x) returned error %v", tc.data, err)
}
if !reflect.DeepEqual(tc.unmarshalToObj, tc.wantValue) {
t.Errorf("Unmarshal(0x%x) = %v, want %v", tc.data, tc.unmarshalToObj, tc.wantValue)
}
})
}
}

View File

@ -4786,6 +4786,44 @@ func TestUnmarshalIntoMapError(t *testing.T) {
}
}
func TestUnmarshalDeepNesting(t *testing.T) {
// Construct this object rather than embed such a large constant in the code
type TestNode struct {
Value int
Child *TestNode
}
n := &TestNode{Value: 0}
root := n
for i := 0; i < 65534; i++ {
child := &TestNode{Value: i}
n.Child = child
n = child
}
em, err := EncOptions{}.EncMode()
if err != nil {
t.Errorf("EncMode() returned error %v", err)
}
data, err := em.Marshal(root)
if err != nil {
t.Errorf("Marshal() deeply nested object returned error %v", err)
}
// Try unmarshal it
dm, err := DecOptions{MaxNestedLevels: 65535}.DecMode()
if err != nil {
t.Errorf("DecMode() returned error %v", err)
}
var readback TestNode
err = dm.Unmarshal(data, &readback)
if err != nil {
t.Errorf("Unmarshal() of deeply nested object returned error: %v", err)
}
if !reflect.DeepEqual(root, &readback) {
t.Errorf("Unmarshal() of deeply nested object did not match\nGot: %#v\n Want: %#v\n",
&readback, root)
}
}
func TestStructToArrayError(t *testing.T) {
type coseHeader struct {
Alg int `cbor:"1,keyasint,omitempty"`
@ -4995,8 +5033,8 @@ func TestDecModeDefaultMaxNestedLevel(t *testing.T) {
t.Errorf("DecMode() returned error %v", err)
} else {
maxNestedLevels := dm.DecOptions().MaxNestedLevels
if maxNestedLevels != defaultMaxNestedLevels {
t.Errorf("DecOptions().MaxNestedLevels = %d, want %v", maxNestedLevels, defaultMaxNestedLevels)
if maxNestedLevels != 32 {
t.Errorf("DecOptions().MaxNestedLevels = %d, want %v", maxNestedLevels, 32)
}
}
}
@ -7698,7 +7736,7 @@ type A2 struct {
Fields []B
}
func TestUnmarshalRegisteredTagToConcreteType(t *testing.T) {
func TestUnmarshalRegisteredTagToInterface(t *testing.T) {
var err error
tags := NewTagSet()
err = tags.Add(TagOptions{EncTag: EncTagRequired, DecTag: DecTagRequired}, reflect.TypeOf(C{}), 279)
@ -7719,18 +7757,36 @@ func TestUnmarshalRegisteredTagToConcreteType(t *testing.T) {
t.Fatalf("Marshal(%+v) returned error %v", v1, err)
}
v2 := A2{Fields: []B{&C{Field: 5}, &D{Field: "a"}}}
data2, err := encMode.Marshal(v2)
if err != nil {
t.Fatalf("Marshal(%+v) returned error %v", v2, err)
}
testCases := []struct {
name string
data []byte
unmarshalToObj interface{}
wantValue interface{}
}{
{
name: "interface type",
data: data1,
unmarshalToObj: &A1{},
wantValue: &v1,
},
{
name: "concrete type",
data: data1,
unmarshalToObj: &A1{Field: &C{}},
wantValue: &v1,
},
{
name: "slice of interface type",
data: data2,
unmarshalToObj: &A2{},
wantValue: &v2,
},
}
for _, tc := range testCases {

View File

@ -1,24 +0,0 @@
// Copyright (c) Faye Amacker. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.
//go:build tinygo
package cbor
import "reflect"
const (
defaultMaxNestedLevels = 16 // was 32 for non-tinygo (24+ for tinygo v0.33 panics tests)
minMaxNestedLevels = 4 // same as non-tinygo
maxMaxNestedLevels = 65535 // same as non-tinygo (to allow testing)
)
// tinygo v0.33 doesn't implement Type.AssignableTo() and it panics.
// Type.AssignableTo() is used under the hood for Type.Implements().
//
// More details in https://github.com/tinygo-org/tinygo/issues/4277.
//
// implements() always returns false until tinygo implements Type.AssignableTo().
func implements(concreteType reflect.Type, interfaceType reflect.Type) bool {
return false
}

View File

@ -1,111 +0,0 @@
// Copyright (c) Faye Amacker. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.
//go:build tinygo
package cbor
import (
"reflect"
"testing"
)
// TestUnmarshalDeepNesting tests marshaling and unmarshaling of deeply nesting objects.
// tinygo v0.33 fails with roughly 24+ levels of nested objects.
func TestUnmarshalDeepNesting(t *testing.T) {
// Construct this object rather than embed such a large constant in the code.
type TestNode struct {
Value int
Child *TestNode
}
n := &TestNode{Value: 0}
root := n
const tinygoNestedLevels = 24
for i := 1; i < tinygoNestedLevels; i++ {
child := &TestNode{Value: i}
n.Child = child
n = child
}
em, err := EncOptions{}.EncMode()
if err != nil {
t.Errorf("EncMode() returned error %v", err)
}
data, err := em.Marshal(root)
if err != nil {
t.Errorf("Marshal() deeply nested object returned error %v", err)
}
// Try unmarshal it
dm, err := DecOptions{MaxNestedLevels: tinygoNestedLevels}.DecMode()
if err != nil {
t.Errorf("DecMode() returned error %v", err)
}
var readback TestNode
err = dm.Unmarshal(data, &readback)
if err != nil {
t.Errorf("Unmarshal() of deeply nested object returned error: %v", err)
}
if !reflect.DeepEqual(root, &readback) {
t.Errorf("Unmarshal() of deeply nested object did not match\nGot: %#v\n Want: %#v\n",
&readback, root)
}
}
func TestUnmarshalRegisteredTagToInterface(t *testing.T) {
var err error
tags := NewTagSet()
err = tags.Add(TagOptions{EncTag: EncTagRequired, DecTag: DecTagRequired}, reflect.TypeOf(C{}), 279)
if err != nil {
t.Error(err)
}
err = tags.Add(TagOptions{EncTag: EncTagRequired, DecTag: DecTagRequired}, reflect.TypeOf(D{}), 280)
if err != nil {
t.Error(err)
}
encMode, _ := PreferredUnsortedEncOptions().EncModeWithTags(tags)
decMode, _ := DecOptions{}.DecModeWithTags(tags)
v1 := A1{Field: &C{Field: 5}}
data1, err := encMode.Marshal(v1)
if err != nil {
t.Fatalf("Marshal(%+v) returned error %v", v1, err)
}
v2 := A2{Fields: []B{&C{Field: 5}, &D{Field: "a"}}}
data2, err := encMode.Marshal(v2)
if err != nil {
t.Fatalf("Marshal(%+v) returned error %v", v2, err)
}
testCases := []struct {
name string
data []byte
unmarshalToObj interface{}
wantValue interface{}
}{
{
name: "interface type",
data: data1,
unmarshalToObj: &A1{},
wantValue: &v1,
},
{
name: "slice of interface type",
data: data2,
unmarshalToObj: &A2{},
wantValue: &v2,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err = decMode.Unmarshal(tc.data, tc.unmarshalToObj)
if err == nil {
t.Errorf("Unmarshal(0x%x) returned no error, expect error", tc.data)
} else if _, ok := err.(*UnmarshalTypeError); !ok {
t.Errorf("Unmarshal(0x%x) returned wrong error type %T, want (*UnmarshalTypeError)", tc.data, err)
}
})
}
}

View File

@ -1,7 +1,7 @@
// Copyright (c) Faye Amacker. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.
//go:build go1.20 && !tinygo
//go:build go1.20
package cbor

View File

@ -1,7 +1,7 @@
// Copyright (c) Faye Amacker. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.
//go:build !go1.20 || tinygo
//go:build !go1.20
package cbor

View File

@ -319,7 +319,7 @@ func TestInvalidTypeMarshal(t *testing.T) {
{"map of channel cannot be marshaled", make(map[string]chan bool), "cbor: unsupported type: map[string]chan bool"},
{"struct of channel cannot be marshaled", s1{}, "cbor: unsupported type: cbor.s1"},
{"struct of channel cannot be marshaled", s2{}, "cbor: unsupported type: cbor.s2"},
{"function cannot be marshaled", func(i int) int { return i * i }, "cbor: unsupported type: func"},
{"function cannot be marshaled", func(i int) int { return i * i }, "cbor: unsupported type: func(int) int"},
{"complex cannot be marshaled", complex(100, 8), "cbor: unsupported type: complex128"},
}
em, err := EncOptions{Sort: SortCanonical}.EncMode()
@ -334,7 +334,7 @@ func TestInvalidTypeMarshal(t *testing.T) {
t.Errorf("Marshal(%v) didn't return an error, want error %q", tc.value, tc.wantErrorMsg)
} else if _, ok := err.(*UnsupportedTypeError); !ok {
t.Errorf("Marshal(%v) error type %T, want *UnsupportedTypeError", tc.value, err)
} else if !strings.HasPrefix(err.Error(), tc.wantErrorMsg) {
} else if err.Error() != tc.wantErrorMsg {
t.Errorf("Marshal(%v) error %q, want %q", tc.value, err.Error(), tc.wantErrorMsg)
} else if b != nil {
t.Errorf("Marshal(%v) = 0x%x, want nil", tc.value, b)
@ -346,7 +346,7 @@ func TestInvalidTypeMarshal(t *testing.T) {
t.Errorf("Marshal(%v) didn't return an error, want error %q", tc.value, tc.wantErrorMsg)
} else if _, ok := err.(*UnsupportedTypeError); !ok {
t.Errorf("Marshal(%v) error type %T, want *UnsupportedTypeError", tc.value, err)
} else if !strings.HasPrefix(err.Error(), tc.wantErrorMsg) {
} else if err.Error() != tc.wantErrorMsg {
t.Errorf("Marshal(%v) error %q, want %q", tc.value, err.Error(), tc.wantErrorMsg)
} else if b != nil {
t.Errorf("Marshal(%v) = 0x%x, want nil", tc.value, b)

View File

@ -45,6 +45,9 @@ func (sv SimpleValue) MarshalCBOR() ([]byte, error) {
}
// UnmarshalCBOR decodes CBOR simple value (major type 7) to SimpleValue.
//
// Deprecated: No longer used by this codec; kept for compatibility
// with user apps that directly call this function.
func (sv *SimpleValue) UnmarshalCBOR(data []byte) error {
if sv == nil {
return errors.New("cbor.SimpleValue: UnmarshalCBOR on nil pointer")
@ -52,6 +55,29 @@ func (sv *SimpleValue) UnmarshalCBOR(data []byte) error {
d := decoder{data: data, dm: defaultDecMode}
// Check well-formedness of CBOR data item.
// SimpleValue.UnmarshalCBOR() is exported, so
// the codec needs to support same behavior for:
// - Unmarshal(data, *SimpleValue)
// - SimpleValue.UnmarshalCBOR(data)
err := d.wellformed(false, false)
if err != nil {
return err
}
return sv.unmarshalCBOR(data)
}
// unmarshalCBOR decodes CBOR simple value (major type 7) to SimpleValue.
// This function assumes data is well-formed, and does not perform bounds checking.
// This function is called by Unmarshal().
func (sv *SimpleValue) unmarshalCBOR(data []byte) error {
if sv == nil {
return errors.New("cbor.SimpleValue: UnmarshalCBOR on nil pointer")
}
d := decoder{data: data, dm: defaultDecMode}
typ, ai, val := d.getHead()
if typ != cborTypePrimitives {

View File

@ -5,7 +5,9 @@ package cbor
import (
"bytes"
"io"
"reflect"
"strings"
"testing"
)
@ -51,6 +53,125 @@ func TestUnmarshalSimpleValue(t *testing.T) {
})
}
func TestUnmarshalSimpleValueOnBadData(t *testing.T) {
testCases := []struct {
name string
data []byte
errMsg string
}{
// Empty data
{
name: "nil data",
data: nil,
errMsg: io.EOF.Error(),
},
{
name: "empty data",
data: []byte{},
errMsg: io.EOF.Error(),
},
// Wrong CBOR types
{
name: "uint type",
data: hexDecode("01"),
errMsg: "cbor: cannot unmarshal positive integer into Go value of type SimpleValue",
},
{
name: "int type",
data: hexDecode("20"),
errMsg: "cbor: cannot unmarshal negative integer into Go value of type SimpleValue",
},
{
name: "byte string type",
data: hexDecode("40"),
errMsg: "cbor: cannot unmarshal byte string into Go value of type SimpleValue",
},
{
name: "string type",
data: hexDecode("60"),
errMsg: "cbor: cannot unmarshal UTF-8 text string into Go value of type SimpleValue",
},
{
name: "array type",
data: hexDecode("80"),
errMsg: "cbor: cannot unmarshal array into Go value of type SimpleValue",
},
{
name: "map type",
data: hexDecode("a0"),
errMsg: "cbor: cannot unmarshal map into Go value of type SimpleValue",
},
{
name: "tag type",
data: hexDecode("c074323031332d30332d32315432303a30343a30305a"),
errMsg: "cbor: cannot unmarshal tag into Go value of type SimpleValue",
},
{
name: "float type",
data: hexDecode("f90000"),
errMsg: "cbor: cannot unmarshal primitives into Go value of type SimpleValue",
},
// Truncated CBOR data
{
name: "truncated head",
data: hexDecode("18"),
errMsg: io.ErrUnexpectedEOF.Error(),
},
// Truncated CBOR simple value
{
name: "truncated simple value",
data: hexDecode("f8"),
errMsg: io.ErrUnexpectedEOF.Error(),
},
// Invalid simple value
{
name: "invalid simple value",
data: hexDecode("f800"),
errMsg: "cbor: invalid simple value 0 for type primitives",
},
// Extraneous CBOR data
{
name: "extraneous data",
data: hexDecode("f4f5"),
errMsg: "cbor: 1 bytes of extraneous data starting at index 1",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Test SimpleValue.UnmarshalCBOR(data)
{
var v SimpleValue
err := v.UnmarshalCBOR(tc.data)
if err == nil {
t.Errorf("UnmarshalCBOR(%x) didn't return error", tc.data)
}
if !strings.HasPrefix(err.Error(), tc.errMsg) {
t.Errorf("UnmarshalCBOR(%x) returned error %q, want %q", tc.data, err.Error(), tc.errMsg)
}
}
// Test Unmarshal(data, *SimpleValue), which calls SimpleValue.unmarshalCBOR() under the hood
{
var v SimpleValue
err := Unmarshal(tc.data, &v)
if err == nil {
t.Errorf("Unmarshal(%x) didn't return error", tc.data)
}
if !strings.HasPrefix(err.Error(), tc.errMsg) {
t.Errorf("Unmarshal(%x) returned error %q, want %q", tc.data, err.Error(), tc.errMsg)
}
}
})
}
}
func testUnmarshalInvalidSimpleValueToEmptyInterface(t *testing.T, data []byte) {
var v interface{}
if err := Unmarshal(data, v); err == nil {

View File

@ -692,7 +692,7 @@ func TestEncoderError(t *testing.T) {
wantErrorMsg string
}{
{"channel cannot be marshaled", make(chan bool), "cbor: unsupported type: chan bool"},
{"function cannot be marshaled", func(i int) int { return i * i }, "cbor: unsupported type: func"},
{"function cannot be marshaled", func(i int) int { return i * i }, "cbor: unsupported type: func(int) int"},
{"complex cannot be marshaled", complex(100, 8), "cbor: unsupported type: complex128"},
}
var w bytes.Buffer
@ -705,7 +705,7 @@ func TestEncoderError(t *testing.T) {
t.Errorf("Encode(%v) didn't return an error, want error %q", tc.value, tc.wantErrorMsg)
} else if _, ok := err.(*UnsupportedTypeError); !ok {
t.Errorf("Encode(%v) error type %T, want *UnsupportedTypeError", tc.value, err)
} else if !strings.HasPrefix(err.Error(), tc.wantErrorMsg) {
} else if err.Error() != tc.wantErrorMsg {
t.Errorf("Encode(%v) error %q, want %q", tc.value, err.Error(), tc.wantErrorMsg)
}
})

26
tag.go
View File

@ -23,11 +23,37 @@ type RawTag struct {
}
// UnmarshalCBOR sets *t with tag number and raw tag content copied from data.
//
// Deprecated: No longer used by this codec; kept for compatibility
// with user apps that directly call this function.
func (t *RawTag) UnmarshalCBOR(data []byte) error {
if t == nil {
return errors.New("cbor.RawTag: UnmarshalCBOR on nil pointer")
}
d := decoder{data: data, dm: defaultDecMode}
// Check if data is a well-formed CBOR data item.
// RawTag.UnmarshalCBOR() is exported, so
// the codec needs to support same behavior for:
// - Unmarshal(data, *RawTag)
// - RawTag.UnmarshalCBOR(data)
err := d.wellformed(false, false)
if err != nil {
return err
}
return t.unmarshalCBOR(data)
}
// unmarshalCBOR sets *t with tag number and raw tag content copied from data.
// This function assumes data is well-formed, and does not perform bounds checking.
// This function is called by Unmarshal().
func (t *RawTag) unmarshalCBOR(data []byte) error {
if t == nil {
return errors.New("cbor.RawTag: UnmarshalCBOR on nil pointer")
}
// Decoding CBOR null and undefined to cbor.RawTag is no-op.
if len(data) == 1 && (data[0] == 0xf6 || data[0] == 0xf7) {
return nil

View File

@ -1536,3 +1536,125 @@ func TestEncodeBuiltinTag(t *testing.T) {
})
}
}
func TestUnmarshalRawTagOnBadData(t *testing.T) {
testCases := []struct {
name string
data []byte
errMsg string
}{
// Empty data
{
name: "nil data",
data: nil,
errMsg: io.EOF.Error(),
},
{
name: "empty data",
data: []byte{},
errMsg: io.EOF.Error(),
},
// Wrong CBOR types
{
name: "uint type",
data: hexDecode("01"),
errMsg: "cbor: cannot unmarshal positive integer into Go value of type cbor.RawTag",
},
{
name: "int type",
data: hexDecode("20"),
errMsg: "cbor: cannot unmarshal negative integer into Go value of type cbor.RawTag",
},
{
name: "byte string type",
data: hexDecode("40"),
errMsg: "cbor: cannot unmarshal byte string into Go value of type cbor.RawTag",
},
{
name: "string type",
data: hexDecode("60"),
errMsg: "cbor: cannot unmarshal UTF-8 text string into Go value of type cbor.RawTag",
},
{
name: "array type",
data: hexDecode("80"),
errMsg: "cbor: cannot unmarshal array into Go value of type cbor.RawTag",
},
{
name: "map type",
data: hexDecode("a0"),
errMsg: "cbor: cannot unmarshal map into Go value of type cbor.RawTag",
},
{
name: "primitive type",
data: hexDecode("f4"),
errMsg: "cbor: cannot unmarshal primitives into Go value of type cbor.RawTag",
},
{
name: "float type",
data: hexDecode("f90000"),
errMsg: "cbor: cannot unmarshal primitives into Go value of type cbor.RawTag",
},
// Truncated CBOR data
{
name: "truncated head",
data: hexDecode("18"),
errMsg: io.ErrUnexpectedEOF.Error(),
},
// Truncated CBOR tag data
{
name: "truncated tag number",
data: hexDecode("d8"),
errMsg: io.ErrUnexpectedEOF.Error(),
},
{
name: "tag number not followed by tag content",
data: hexDecode("da"),
errMsg: io.ErrUnexpectedEOF.Error(),
},
{
name: "truncated tag content",
data: hexDecode("c074323031332d30332d32315432303a30343a3030"),
errMsg: io.ErrUnexpectedEOF.Error(),
},
// Extraneous CBOR data
{
name: "extraneous data",
data: hexDecode("c074323031332d30332d32315432303a30343a30305a00"),
errMsg: "cbor: 1 bytes of extraneous data starting at index 22",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Test RawTag.UnmarshalCBOR(data)
{
var v RawTag
err := v.UnmarshalCBOR(tc.data)
if err == nil {
t.Errorf("UnmarshalCBOR(%x) didn't return error", tc.data)
}
if !strings.HasPrefix(err.Error(), tc.errMsg) {
t.Errorf("UnmarshalCBOR(%x) returned error %q, want %q", tc.data, err.Error(), tc.errMsg)
}
}
// Test Unmarshal(data, *RawTag), which calls RawTag.unmarshalCBOR() under the hood
{
var v RawTag
err := Unmarshal(tc.data, &v)
if err == nil {
t.Errorf("Unmarshal(%x) didn't return error", tc.data)
}
if !strings.HasPrefix(err.Error(), tc.errMsg) {
t.Errorf("Unmarshal(%x) returned error %q, want %q", tc.data, err.Error(), tc.errMsg)
}
}
})
}
}

View File

@ -1,34 +0,0 @@
// Copyright (c) Faye Amacker. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.
//go:build !tinygo
package cbor
import "testing"
func Test32Depth(t *testing.T) {
testCases := []struct {
name string
data []byte
wantDepth int
}{
{"32-level array", hexDecode("82018181818181818181818181818181818181818181818181818181818181818101"), 32},
{"32-level indefinite length array", hexDecode("9f018181818181818181818181818181818181818181818181818181818181818101ff"), 32},
{"32-level map", hexDecode("a1018181818181818181818181818181818181818181818181818181818181818101"), 32},
{"32-level indefinite length map", hexDecode("bf018181818181818181818181818181818181818181818181818181818181818101ff"), 32},
{"32-level tag", hexDecode("d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d86474323031332d30332d32315432303a30343a30305a"), 32}, // 100(100(...("2013-03-21T20:04:00Z")))
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
d := decoder{data: tc.data, dm: defaultDecMode}
depth, err := d.wellformedInternal(0, false)
if err != nil {
t.Errorf("wellformed(0x%x) returned error %v", tc.data, err)
}
if depth != tc.wantDepth {
t.Errorf("wellformed(0x%x) returned depth %d, want %d", tc.data, depth, tc.wantDepth)
}
})
}
}

View File

@ -5,7 +5,6 @@ package cbor
import (
"bytes"
"strconv"
"testing"
)
@ -103,11 +102,11 @@ func TestDepth(t *testing.T) {
{"tagged map and array", hexDecode("d864a26161016162d865820203"), 2}, // 100({"a": 1, "b": 101([2, 3])})
{"tagged map and array", hexDecode("d864a26161016162d865d866820203"), 3}, // 100({"a": 1, "b": 101(102([2, 3]))})
{"nested tag", hexDecode("d864d865d86674323031332d30332d32315432303a30343a30305a"), 2}, // 100(101(102("2013-03-21T20:04:00Z")))
{"16-level array", hexDecode("820181818181818181818181818181818101"), 16},
{"16-level indefinite length array", hexDecode("9f0181818181818181818181818181818101ff"), 16},
{"16-level map", hexDecode("a10181818181818181818181818181818101"), 16},
{"16-level indefinite length map", hexDecode("bf0181818181818181818181818181818101ff"), 16},
{"16-level tag", hexDecode("d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d86474323031332d30332d32315432303a30343a30305a"), 16}, // 100(100(...("2013-03-21T20:04:00Z")))
{"32-level array", hexDecode("82018181818181818181818181818181818181818181818181818181818181818101"), 32},
{"32-level indefinite length array", hexDecode("9f018181818181818181818181818181818181818181818181818181818181818101ff"), 32},
{"32-level map", hexDecode("a1018181818181818181818181818181818181818181818181818181818181818101"), 32},
{"32-level indefinite length map", hexDecode("bf018181818181818181818181818181818181818181818181818181818181818101ff"), 32},
{"32-level tag", hexDecode("d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d86474323031332d30332d32315432303a30343a30305a"), 32}, // 100(100(...("2013-03-21T20:04:00Z")))
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
@ -146,31 +145,31 @@ func TestDepthError(t *testing.T) {
name: "33-level array",
data: hexDecode("8201818181818181818181818181818181818181818181818181818181818181818101"),
opts: DecOptions{},
wantErrorMsg: "cbor: exceeded max nested level " + strconv.Itoa(defaultMaxNestedLevels),
wantErrorMsg: "cbor: exceeded max nested level 32",
},
{
name: "33-level indefinite length array",
data: hexDecode("9f01818181818181818181818181818181818181818181818181818181818181818101ff"),
opts: DecOptions{},
wantErrorMsg: "cbor: exceeded max nested level " + strconv.Itoa(defaultMaxNestedLevels),
wantErrorMsg: "cbor: exceeded max nested level 32",
},
{
name: "33-level map",
data: hexDecode("a101818181818181818181818181818181818181818181818181818181818181818101"),
opts: DecOptions{},
wantErrorMsg: "cbor: exceeded max nested level " + strconv.Itoa(defaultMaxNestedLevels),
wantErrorMsg: "cbor: exceeded max nested level 32",
},
{
name: "33-level indefinite length map",
data: hexDecode("bf01818181818181818181818181818181818181818181818181818181818181818101ff"),
opts: DecOptions{},
wantErrorMsg: "cbor: exceeded max nested level " + strconv.Itoa(defaultMaxNestedLevels),
wantErrorMsg: "cbor: exceeded max nested level 32",
},
{
name: "33-level tag",
data: hexDecode("d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d86474323031332d30332d32315432303a30343a30305a"),
opts: DecOptions{},
wantErrorMsg: "cbor: exceeded max nested level " + strconv.Itoa(defaultMaxNestedLevels),
wantErrorMsg: "cbor: exceeded max nested level 32",
},
}
for _, tc := range testCases {