This commit is contained in:
bluepython508 committed 2026-10-22 12:27:19 +01:00
1 parent 3530afb854
commit 6903fcff27
2146 files changed
+112256 -190703

No files matched your search

+94 -82
View File
@@ -1,104 +1,116 @@
# Do not delete linter settings. Linters like gocritic can be enabled on the command line.
linters-settings:
depguard:
rules:
prevent_unmaintained_packages:
list-mode: strict
files:
- $all
- "!$test"
allow:
- $gostd
- github.com/x448/float16
deny:
- pkg: io/ioutil
desc: "replaced by io and os packages since Go 1.16: https://tip.golang.org/doc/go1.16#ioutil"
dupl:
threshold: 100
funlen:
lines: 100
statements: 50
goconst:
ignore-tests: true
min-len: 2
min-occurrences: 3
gocritic:
enabled-tags:
- diagnostic
- experimental
- opinionated
- performance
- style
disabled-checks:
- commentedOutCode
- dupImport # https://github.com/go-critic/go-critic/issues/845
- ifElseChain
- octalLiteral
- paramTypeCombine
- whyNoLint
gofmt:
simplify: false
goimports:
local-prefixes: github.com/fxamacker/cbor
golint:
min-confidence: 0
govet:
check-shadowing: true
lll:
line-length: 140
maligned:
suggest-new: true
misspell:
locale: US
staticcheck:
checks: ["all"]
version: "2"
linters:
disable-all: true
default: none
enable:
- asciicheck
- bidichk
- depguard
- errcheck
- exportloopref
- forbidigo
- goconst
- gocritic
- gocyclo
- gofmt
- goimports
- goprintffuncname
- gosec
- gosimple
- govet
- ineffassign
- misspell
- nilerr
- revive
- staticcheck
- stylecheck
- typecheck
- unconvert
- unused
settings:
depguard:
rules:
prevent_unmaintained_packages:
list-mode: strict
files:
- $all
- '!$test'
allow:
- $gostd
- github.com/x448/float16
deny:
- pkg: io/ioutil
desc: 'replaced by io and os packages since Go 1.16: https://tip.golang.org/doc/go1.16#ioutil'
dupl:
threshold: 100
funlen:
lines: 100
statements: 50
goconst:
min-len: 2
min-occurrences: 3
gocritic:
disabled-checks:
- commentedOutCode
- dupImport
- ifElseChain
- octalLiteral
- paramTypeCombine
- whyNoLint
enabled-tags:
- diagnostic
- experimental
- opinionated
- performance
- style
govet:
enable:
- shadow
lll:
line-length: 140
misspell:
locale: US
staticcheck:
checks:
- all
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
rules:
- path: decode.go
text: string ` overflows ` has (\d+) occurrences, make it a constant
- path: decode.go
text: string ` \(range is \[` has (\d+) occurrences, make it a constant
- path: decode.go
text: string `, ` has (\d+) occurrences, make it a constant
- path: decode.go
text: string ` overflows Go's int64` has (\d+) occurrences, make it a constant
- path: decode.go
text: string `\]\)` has (\d+) occurrences, make it a constant
- path: valid.go
text: string ` for type ` has (\d+) occurrences, make it a constant
- path: valid.go
text: 'string `cbor: ` has (\d+) occurrences, make it a constant'
- linters:
- goconst
path: (.+)_test\.go
paths:
- third_party$
- builtin$
- examples$
issues:
# max-issues-per-linter default is 50. Set to 0 to disable limit.
max-issues-per-linter: 0
# max-same-issues default is 3. Set to 0 to disable limit.
max-same-issues: 0
exclude-rules:
- path: decode.go
text: "string ` overflows ` has (\\d+) occurrences, make it a constant"
- path: decode.go
text: "string ` \\(range is \\[` has (\\d+) occurrences, make it a constant"
- path: decode.go
text: "string `, ` has (\\d+) occurrences, make it a constant"
- path: decode.go
text: "string ` overflows Go's int64` has (\\d+) occurrences, make it a constant"
- path: decode.go
text: "string `\\]\\)` has (\\d+) occurrences, make it a constant"
- path: valid.go
text: "string ` for type ` has (\\d+) occurrences, make it a constant"
- path: valid.go
text: "string `cbor: ` has (\\d+) occurrences, make it a constant"
formatters:
enable:
- gofmt
- goimports
settings:
gofmt:
simplify: false
goimports:
local-prefixes:
- github.com/fxamacker/cbor
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
+15 -14
View File
@@ -683,7 +683,7 @@ because RFC 8949 treats CBOR data item with remaining bytes as malformed.
Other useful functions:
- `Diagnose`, `DiagnoseFirst` produce human-readable [Extended Diagnostic Notation](https://www.rfc-editor.org/rfc/rfc8610.html#appendix-G) from CBOR data.
- `UnmarshalFirst` decodes first CBOR data item and return any remaining bytes.
- `Wellformed` returns true if the CBOR data item is well-formed.
- `Wellformed` returns nil error if the CBOR data item is well-formed.
Interfaces identical or comparable to Go `encoding` packages include:
`Marshaler`, `Unmarshaler`, `BinaryMarshaler`, and `BinaryUnmarshaler`.
@@ -702,28 +702,29 @@ Default limits may need to be increased for systems handling very large data (e.
## Status
[v2.9.0](https://github.com/fxamacker/cbor/releases/tag/v2.9.0) (Jul 13, 2025) improved interoperability/transcoding between CBOR & JSON, refactored tests, and improved docs.
- Add opt-in support for `encoding.TextMarshaler` and `encoding.TextUnmarshaler` to encode and decode from CBOR text string.
- Add opt-in support for `json.Marshaler` and `json.Unmarshaler` via user-provided transcoding function.
- Update docs for TimeMode, Tag, RawTag, and add example for Embedded JSON Tag for CBOR.
v2.9.2 (Sunday, May 3, 2026) refactors and hardens the streaming encoder by adding stricter checks for encoding to CBOR indefinite-length data. This prevents improper use of this library from producing malformed CBOR indefinite-length data that would be rejected by the decoder.
v2.9.0 passed fuzz tests and is production quality.
This release includes other bugfixes, defensive checks, and added more tests.
The minimum version of Go required to build:
- v2.8.0 and newer releases require go 1.20+.
- v2.7.1 and older releases require go 1.17+.
v2.9.2 passed fuzz tests (billions of executions) and is production quality.
For more details, see [release notes](https://github.com/fxamacker/cbor/releases).
For more details, see [v2.9.2 release notes](https://github.com/fxamacker/cbor/releases).
### Prior Releases
[v2.8.0](https://github.com/fxamacker/cbor/releases/tag/v2.8.0) (March 30, 2025) is a small release primarily to add `omitzero` option to struct field tags and fix bugs. It passed fuzz tests (billions of executions) and is production quality.
Releases and commits tend to be on Sundays because my work schedule didn't leave time to work on this during weekdays (sometimes consecutive weekends and consecutive Christmas breaks, too). Monday morning releases were to allow more fuzzing to run overnight before clicking the release button.
[v2.7.0](https://github.com/fxamacker/cbor/releases/tag/v2.7.0) (June 23, 2024) adds features and improvements that help large projects (e.g. Kubernetes) use CBOR as an alternative to JSON and Protocol Buffers. Other improvements include speedups, improved memory use, bug fixes, new serialization options, etc. It passed fuzz tests (5+ billion executions) and is production quality.
[v2.9.1](https://github.com/fxamacker/cbor/releases/tag/v2.9.1) (Monday, Mar 30, 2026) includes important bugfixes, defensive checks, improved code quality, and more tests. Although not public, the fuzzer was also improved by adding more fuzz tests. It passed fuzz tests (billions of executions) and is production quality.
[v2.6.0](https://github.com/fxamacker/cbor/releases/tag/v2.6.0) (February 2024) adds important new features, optimizations, and bug fixes. It is especially useful to systems that need to convert data between CBOR and JSON. New options and optimizations improve handling of bignum, integers, maps, and strings.
[v2.9.0](https://github.com/fxamacker/cbor/releases/tag/v2.9.0) (Sunday, Jul 13, 2025) improved interoperability/transcoding between CBOR & JSON, refactored tests, and improved docs. It passed fuzz tests (billions of executions) and is production quality.
[v2.5.0](https://github.com/fxamacker/cbor/releases/tag/v2.5.0) was released on Sunday, August 13, 2023 with new features and important bug fixes. It is fuzz tested and production quality after extended beta [v2.5.0-beta](https://github.com/fxamacker/cbor/releases/tag/v2.5.0-beta) (Dec 2022) -> [v2.5.0](https://github.com/fxamacker/cbor/releases/tag/v2.5.0) (Aug 2023).
[v2.8.0](https://github.com/fxamacker/cbor/releases/tag/v2.8.0) (Sunday, March 30, 2025) is a small release primarily to add `omitzero` option to struct field tags and fix bugs. It passed fuzz tests (billions of executions) and is production quality.
[v2.7.0](https://github.com/fxamacker/cbor/releases/tag/v2.7.0) (Monday, June 23, 2024) adds features and improvements that help large projects (e.g. Kubernetes) use CBOR as an alternative to JSON and Protocol Buffers. Other improvements include speedups, improved memory use, bug fixes, new serialization options, etc. It passed fuzz tests (5+ billion executions) and is production quality.
[v2.6.0](https://github.com/fxamacker/cbor/releases/tag/v2.6.0) (Sunday, Feb 11, 2024) adds important new features, optimizations, and bug fixes. It is especially useful to systems that need to convert data between CBOR and JSON. New options and optimizations improve handling of bignum, integers, maps, and strings.
[v2.5.0](https://github.com/fxamacker/cbor/releases/tag/v2.5.0) (Sunday, August 13, 2023) adds new features and important bug fixes. It is fuzz tested and production quality after extended beta [v2.5.0-beta](https://github.com/fxamacker/cbor/releases/tag/v2.5.0-beta) (Dec 2022) -> [v2.5.0](https://github.com/fxamacker/cbor/releases/tag/v2.5.0) (Aug 2023).
__IMPORTANT__: 👉 Before upgrading from v2.4 or older release, please read the notable changes highlighted in the release notes. v2.5.0 is a large release with bug fixes to error handling for extraneous data in `Unmarshal`, etc. that should be reviewed before upgrading.
+153 -110
View File
@@ -92,94 +92,126 @@ func newTypeInfo(t reflect.Type) *typeInfo {
}
type decodingStructType struct {
fields fields
fieldIndicesByName map[string]int
err error
toArray bool
fields decodingFields
fieldIndicesByName map[string]int // Only populated if toArray is false
fieldIndicesByIntKey map[int64]int // Only populated if toArray is false
err error
toArray bool
}
// The stdlib errors.Join was introduced in Go 1.20, and we still support Go 1.17, so instead,
// here's a very basic implementation of an aggregated error.
type multierror []error
func (m multierror) Error() string {
var sb strings.Builder
for i, err := range m {
sb.WriteString(err.Error())
if i < len(m)-1 {
sb.WriteString(", ")
}
}
return sb.String()
}
func getDecodingStructType(t reflect.Type) *decodingStructType {
func getDecodingStructType(t reflect.Type) (*decodingStructType, error) {
if v, _ := decodingStructTypeCache.Load(t); v != nil {
return v.(*decodingStructType)
structType := v.(*decodingStructType)
if structType.err != nil {
return nil, structType.err
}
return structType, nil
}
flds, structOptions := getFields(t)
toArray := hasToArrayOption(structOptions)
var errs []error
for i := 0; i < len(flds); i++ {
if flds[i].keyAsInt {
nameAsInt, numErr := strconv.Atoi(flds[i].name)
if numErr != nil {
errs = append(errs, errors.New("cbor: failed to parse field name \""+flds[i].name+"\" to int ("+numErr.Error()+")"))
break
}
flds[i].nameAsInt = int64(nameAsInt)
}
flds[i].typInfo = getTypeInfo(flds[i].typ)
if toArray {
return getDecodingStructToArrayType(t, flds)
}
fieldIndicesByName := make(map[string]int, len(flds))
for i, fld := range flds {
if _, ok := fieldIndicesByName[fld.name]; ok {
errs = append(errs, fmt.Errorf("cbor: two or more fields of %v have the same name %q", t, fld.name))
continue
}
fieldIndicesByName[fld.name] = i
}
var fieldIndicesByIntKey map[int64]int
var err error
{
var multi multierror
for _, each := range errs {
if each != nil {
multi = append(multi, each)
decFlds := make(decodingFields, len(flds))
for i, f := range flds {
// nameAsInt is set in getFields() except for fields with an unparsable tagged name.
// Atoi() is called here to catch and save parsing errors.
if f.keyAsInt && f.nameAsInt == 0 {
if _, numErr := strconv.Atoi(f.name); numErr != nil {
structType := &decodingStructType{
err: errors.New("cbor: failed to parse field name \"" + f.name + "\" to int (" + numErr.Error() + ")"),
}
decodingStructTypeCache.Store(t, structType)
return nil, structType.err
}
}
if len(multi) == 1 {
err = multi[0]
} else if len(multi) > 1 {
err = multi
if f.keyAsInt {
if fieldIndicesByIntKey == nil {
fieldIndicesByIntKey = make(map[int64]int, len(flds))
}
// The duplication check is only a safeguard, since getFields() already deduplicates fields.
if _, ok := fieldIndicesByIntKey[f.nameAsInt]; ok {
structType := &decodingStructType{
err: fmt.Errorf("cbor: two or more fields of %v have the same keyasint value %d", t, f.nameAsInt),
}
decodingStructTypeCache.Store(t, structType)
return nil, structType.err
}
fieldIndicesByIntKey[f.nameAsInt] = i
} else {
// The duplication check is only a safeguard, since getFields() already deduplicates fields.
if _, ok := fieldIndicesByName[f.name]; ok {
structType := &decodingStructType{
err: fmt.Errorf("cbor: two or more fields of %v have the same name %q", t, f.name),
}
decodingStructTypeCache.Store(t, structType)
return nil, structType.err
}
fieldIndicesByName[f.name] = i
}
decFlds[i] = &decodingField{
field: *f,
typInfo: getTypeInfo(f.typ),
}
}
structType := &decodingStructType{
fields: flds,
fieldIndicesByName: fieldIndicesByName,
err: err,
toArray: toArray,
fields: decFlds,
fieldIndicesByName: fieldIndicesByName,
fieldIndicesByIntKey: fieldIndicesByIntKey,
}
decodingStructTypeCache.Store(t, structType)
return structType
return structType, nil
}
func getDecodingStructToArrayType(t reflect.Type, flds fields) (*decodingStructType, error) {
decFlds := make(decodingFields, len(flds))
for i, f := range flds {
// nameAsInt is set in getFields() except for fields with an unparsable tagged name.
// Atoi() is called here to catch and save parsing errors.
if f.keyAsInt && f.nameAsInt == 0 {
if _, numErr := strconv.Atoi(f.name); numErr != nil {
structType := &decodingStructType{
err: errors.New("cbor: failed to parse field name \"" + f.name + "\" to int (" + numErr.Error() + ")"),
}
decodingStructTypeCache.Store(t, structType)
return nil, structType.err
}
}
decFlds[i] = &decodingField{
field: *f,
typInfo: getTypeInfo(f.typ),
}
}
structType := &decodingStructType{
fields: decFlds,
toArray: true,
}
decodingStructTypeCache.Store(t, structType)
return structType, nil
}
type encodingStructType struct {
fields fields
bytewiseFields fields
lengthFirstFields fields
omitEmptyFieldsIdx []int
fields encodingFields
bytewiseFields encodingFields // Only populated if toArray is false
lengthFirstFields encodingFields // Only populated if toArray is false
omitEmptyFieldsIdx []int // Only populated if toArray is false
err error
toArray bool
}
func (st *encodingStructType) getFields(em *encMode) fields {
func (st *encodingStructType) getFields(em *encMode) encodingFields {
switch em.sort {
case SortNone, SortFastShuffle:
return st.fields
@@ -191,7 +223,7 @@ func (st *encodingStructType) getFields(em *encMode) fields {
}
type bytewiseFieldSorter struct {
fields fields
fields encodingFields
}
func (x *bytewiseFieldSorter) Len() int {
@@ -203,11 +235,11 @@ func (x *bytewiseFieldSorter) Swap(i, j int) {
}
func (x *bytewiseFieldSorter) Less(i, j int) bool {
return bytes.Compare(x.fields[i].cborName, x.fields[j].cborName) <= 0
return bytes.Compare(x.fields[i].cborName, x.fields[j].cborName) < 0
}
type lengthFirstFieldSorter struct {
fields fields
fields encodingFields
}
func (x *lengthFirstFieldSorter) Len() int {
@@ -222,13 +254,16 @@ func (x *lengthFirstFieldSorter) Less(i, j int) bool {
if len(x.fields[i].cborName) != len(x.fields[j].cborName) {
return len(x.fields[i].cborName) < len(x.fields[j].cborName)
}
return bytes.Compare(x.fields[i].cborName, x.fields[j].cborName) <= 0
return bytes.Compare(x.fields[i].cborName, x.fields[j].cborName) < 0
}
func getEncodingStructType(t reflect.Type) (*encodingStructType, error) {
if v, _ := encodingStructTypeCache.Load(t); v != nil {
structType := v.(*encodingStructType)
return structType, structType.err
if structType.err != nil {
return nil, structType.err
}
return structType, nil
}
flds, structOptions := getFields(t)
@@ -237,111 +272,119 @@ func getEncodingStructType(t reflect.Type) (*encodingStructType, error) {
return getEncodingStructToArrayType(t, flds)
}
var err error
var hasKeyAsInt bool
var hasKeyAsStr bool
var omitEmptyIdx []int
encFlds := make(encodingFields, len(flds))
e := getEncodeBuffer()
for i := 0; i < len(flds); i++ {
defer putEncodeBuffer(e)
for i, f := range flds {
encFlds[i] = &encodingField{field: *f}
ef := encFlds[i]
// Get field's encodeFunc
flds[i].ef, flds[i].ief, flds[i].izf = getEncodeFunc(flds[i].typ)
if flds[i].ef == nil {
err = &UnsupportedTypeError{t}
break
ef.ef, ef.ief, ef.izf = getEncodeFunc(f.typ)
if ef.ef == nil {
structType := &encodingStructType{err: &UnsupportedTypeError{t}}
encodingStructTypeCache.Store(t, structType)
return nil, structType.err
}
// Encode field name
if flds[i].keyAsInt {
nameAsInt, numErr := strconv.Atoi(flds[i].name)
if numErr != nil {
err = errors.New("cbor: failed to parse field name \"" + flds[i].name + "\" to int (" + numErr.Error() + ")")
break
if f.keyAsInt {
if f.nameAsInt == 0 {
// nameAsInt is set in getFields() except for fields with an unparsable tagged name.
// Atoi() is called here to catch and save parsing errors.
if _, numErr := strconv.Atoi(f.name); numErr != nil {
structType := &encodingStructType{
err: errors.New("cbor: failed to parse field name \"" + f.name + "\" to int (" + numErr.Error() + ")"),
}
encodingStructTypeCache.Store(t, structType)
return nil, structType.err
}
}
flds[i].nameAsInt = int64(nameAsInt)
nameAsInt := f.nameAsInt
if nameAsInt >= 0 {
encodeHead(e, byte(cborTypePositiveInt), uint64(nameAsInt))
encodeHead(e, byte(cborTypePositiveInt), uint64(nameAsInt)) //nolint:gosec
} else {
n := nameAsInt*(-1) - 1
encodeHead(e, byte(cborTypeNegativeInt), uint64(n))
encodeHead(e, byte(cborTypeNegativeInt), uint64(n)) //nolint:gosec
}
flds[i].cborName = make([]byte, e.Len())
copy(flds[i].cborName, e.Bytes())
ef.cborName = make([]byte, e.Len())
copy(ef.cborName, e.Bytes())
e.Reset()
hasKeyAsInt = true
} else {
encodeHead(e, byte(cborTypeTextString), uint64(len(flds[i].name)))
flds[i].cborName = make([]byte, e.Len()+len(flds[i].name))
n := copy(flds[i].cborName, e.Bytes())
copy(flds[i].cborName[n:], flds[i].name)
encodeHead(e, byte(cborTypeTextString), uint64(len(f.name)))
ef.cborName = make([]byte, e.Len()+len(f.name))
n := copy(ef.cborName, e.Bytes())
copy(ef.cborName[n:], f.name)
e.Reset()
// If cborName contains a text string, then cborNameByteString contains a
// string that has the byte string major type but is otherwise identical to
// cborName.
flds[i].cborNameByteString = make([]byte, len(flds[i].cborName))
copy(flds[i].cborNameByteString, flds[i].cborName)
ef.cborNameByteString = make([]byte, len(ef.cborName))
copy(ef.cborNameByteString, ef.cborName)
// Reset encoded CBOR type to byte string, preserving the "additional
// information" bits:
flds[i].cborNameByteString[0] = byte(cborTypeByteString) |
getAdditionalInformation(flds[i].cborNameByteString[0])
ef.cborNameByteString[0] = byte(cborTypeByteString) |
getAdditionalInformation(ef.cborNameByteString[0])
hasKeyAsStr = true
}
// Check if field can be omitted when empty
if flds[i].omitEmpty {
if f.omitEmpty {
omitEmptyIdx = append(omitEmptyIdx, i)
}
}
putEncodeBuffer(e)
if err != nil {
structType := &encodingStructType{err: err}
encodingStructTypeCache.Store(t, structType)
return structType, structType.err
}
// Sort fields by canonical order
bytewiseFields := make(fields, len(flds))
copy(bytewiseFields, flds)
bytewiseFields := make(encodingFields, len(encFlds))
copy(bytewiseFields, encFlds)
sort.Sort(&bytewiseFieldSorter{bytewiseFields})
lengthFirstFields := bytewiseFields
if hasKeyAsInt && hasKeyAsStr {
lengthFirstFields = make(fields, len(flds))
copy(lengthFirstFields, flds)
lengthFirstFields = make(encodingFields, len(encFlds))
copy(lengthFirstFields, encFlds)
sort.Sort(&lengthFirstFieldSorter{lengthFirstFields})
}
structType := &encodingStructType{
fields: flds,
fields: encFlds,
bytewiseFields: bytewiseFields,
lengthFirstFields: lengthFirstFields,
omitEmptyFieldsIdx: omitEmptyIdx,
}
encodingStructTypeCache.Store(t, structType)
return structType, structType.err
return structType, nil
}
func getEncodingStructToArrayType(t reflect.Type, flds fields) (*encodingStructType, error) {
for i := 0; i < len(flds); i++ {
// Get field's encodeFunc
flds[i].ef, flds[i].ief, flds[i].izf = getEncodeFunc(flds[i].typ)
if flds[i].ef == nil {
encFlds := make(encodingFields, len(flds))
for i, f := range flds {
encFlds[i] = &encodingField{field: *f}
encFlds[i].ef, encFlds[i].ief, encFlds[i].izf = getEncodeFunc(f.typ)
if encFlds[i].ef == nil {
structType := &encodingStructType{err: &UnsupportedTypeError{t}}
encodingStructTypeCache.Store(t, structType)
return structType, structType.err
return nil, structType.err
}
}
structType := &encodingStructType{
fields: flds,
fields: encFlds,
toArray: true,
}
encodingStructTypeCache.Store(t, structType)
return structType, structType.err
return structType, nil
}
func getEncodeFunc(t reflect.Type) (encodeFunc, isEmptyFunc, isZeroFunc) {
+211 -229
View File
@@ -16,7 +16,6 @@ import (
"math/big"
"reflect"
"strconv"
"strings"
"time"
"unicode/utf8"
@@ -326,14 +325,14 @@ func (dmkm DupMapKeyMode) valid() bool {
return dmkm >= 0 && dmkm < maxDupMapKeyMode
}
// IndefLengthMode specifies whether to allow indefinite length items.
// IndefLengthMode specifies whether to allow indefinite-length items.
type IndefLengthMode int
const (
// IndefLengthAllowed allows indefinite length items.
// IndefLengthAllowed allows indefinite-length items.
IndefLengthAllowed IndefLengthMode = iota
// IndefLengthForbidden disallows indefinite length items.
// IndefLengthForbidden disallows indefinite-length items.
IndefLengthForbidden
maxIndefLengthMode
@@ -378,6 +377,7 @@ const (
// - 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
@@ -811,7 +811,7 @@ type DecOptions struct {
// Default is 128*1024=131072 and it can be set to [16, 2147483647]
MaxMapPairs int
// IndefLength specifies whether to allow indefinite length CBOR items.
// IndefLength specifies whether to allow indefinite-length CBOR items.
IndefLength IndefLengthMode
// TagsMd specifies whether to allow CBOR tags (major type 6).
@@ -1055,7 +1055,7 @@ func (opts DecOptions) decMode() (*decMode, error) { //nolint:gocritic // ignore
}
if !opts.ExtraReturnErrors.valid() {
return nil, errors.New("cbor: invalid ExtraReturnErrors " + strconv.Itoa(int(opts.ExtraReturnErrors)))
return nil, errors.New("cbor: invalid ExtraReturnErrors " + strconv.Itoa(int(opts.ExtraReturnErrors))) //nolint:gosec
}
if opts.DefaultMapType != nil && opts.DefaultMapType.Kind() != reflect.Map {
@@ -1149,8 +1149,8 @@ func (opts DecOptions) decMode() (*decMode, error) { //nolint:gocritic // ignore
unrecognizedTagToAny: opts.UnrecognizedTagToAny,
timeTagToAny: opts.TimeTagToAny,
simpleValues: simpleValues,
nanDec: opts.NaN,
infDec: opts.Inf,
nan: opts.NaN,
inf: opts.Inf,
byteStringToTime: opts.ByteStringToTime,
byteStringExpectedFormat: opts.ByteStringExpectedFormat,
bignumTag: opts.BignumTag,
@@ -1230,8 +1230,8 @@ type decMode struct {
unrecognizedTagToAny UnrecognizedTagToAnyMode
timeTagToAny TimeTagToAnyMode
simpleValues *SimpleValueRegistry
nanDec NaNMode
infDec InfMode
nan NaNMode
inf InfMode
byteStringToTime ByteStringToTimeMode
byteStringExpectedFormat ByteStringExpectedFormatMode
bignumTag BignumTagMode
@@ -1272,8 +1272,8 @@ func (dm *decMode) DecOptions() DecOptions {
UnrecognizedTagToAny: dm.unrecognizedTagToAny,
TimeTagToAny: dm.timeTagToAny,
SimpleValues: simpleValues,
NaN: dm.nanDec,
Inf: dm.infDec,
NaN: dm.nan,
Inf: dm.inf,
ByteStringToTime: dm.byteStringToTime,
ByteStringExpectedFormat: dm.byteStringExpectedFormat,
BignumTag: dm.bignumTag,
@@ -1583,11 +1583,11 @@ func (d *decoder) parseToValue(v reflect.Value, tInfo *typeInfo) error { //nolin
_, ai, val := d.getHead()
switch ai {
case additionalInformationAsFloat16:
f := float64(float16.Frombits(uint16(val)).Float32())
f := float64(float16.Frombits(uint16(val)).Float32()) //nolint:gosec
return fillFloat(t, f, v)
case additionalInformationAsFloat32:
f := float64(math.Float32frombits(uint32(val)))
f := float64(math.Float32frombits(uint32(val))) //nolint:gosec
return fillFloat(t, f, v)
case additionalInformationAsFloat64:
@@ -1595,10 +1595,10 @@ func (d *decoder) parseToValue(v reflect.Value, tInfo *typeInfo) error { //nolin
return fillFloat(t, f, v)
default: // ai <= 24
if d.dm.simpleValues.rejected[SimpleValue(val)] {
if d.dm.simpleValues.rejected[SimpleValue(val)] { //nolint:gosec
return &UnacceptableDataItemError{
CBORType: t.String(),
Message: "simple value " + strconv.FormatInt(int64(val), 10) + " is not recognized",
Message: "simple value " + strconv.FormatInt(int64(val), 10) + " is not recognized", //nolint:gosec
}
}
@@ -1677,20 +1677,23 @@ func (d *decoder) parseToValue(v reflect.Value, tInfo *typeInfo) error { //nolin
return d.parseToValue(v, tInfo)
case cborTypeArray:
if tInfo.nonPtrKind == reflect.Slice {
switch tInfo.nonPtrKind {
case reflect.Slice:
return d.parseArrayToSlice(v, tInfo)
} else if tInfo.nonPtrKind == reflect.Array {
case reflect.Array:
return d.parseArrayToArray(v, tInfo)
} else if tInfo.nonPtrKind == reflect.Struct {
case reflect.Struct:
return d.parseArrayToStruct(v, tInfo)
}
d.skip()
return &UnmarshalTypeError{CBORType: t.String(), GoType: tInfo.nonPtrType.String()}
case cborTypeMap:
if tInfo.nonPtrKind == reflect.Struct {
switch tInfo.nonPtrKind {
case reflect.Struct:
return d.parseMapToStruct(v, tInfo)
} else if tInfo.nonPtrKind == reflect.Map {
case reflect.Map:
return d.parseMapToMap(v, tInfo)
}
d.skip()
@@ -1745,8 +1748,8 @@ func (d *decoder) parseToTime() (time.Time, bool, error) {
// Read tag number
_, _, tagNum := d.getHead()
if tagNum != 0 && tagNum != 1 {
d.skip() // skip tag content
return time.Time{}, false, errors.New("cbor: wrong tag number for time.Time, got " + strconv.Itoa(int(tagNum)) + ", expect 0 or 1")
d.skip() // skip tag content
return time.Time{}, false, errors.New("cbor: wrong tag number for time.Time, got " + strconv.Itoa(int(tagNum)) + ", expect 0 or 1") //nolint:gosec
}
}
} else {
@@ -1766,6 +1769,7 @@ func (d *decoder) parseToTime() (time.Time, bool, error) {
}
return t, true, nil
}
d.skip()
return time.Time{}, false, &UnmarshalTypeError{CBORType: t.String(), GoType: typeTime.String()}
case cborTypeTextString:
@@ -1815,10 +1819,10 @@ func (d *decoder) parseToTime() (time.Time, bool, error) {
var f float64
switch ai {
case additionalInformationAsFloat16:
f = float64(float16.Frombits(uint16(val)).Float32())
f = float64(float16.Frombits(uint16(val)).Float32()) //nolint:gosec
case additionalInformationAsFloat32:
f = float64(math.Float32frombits(uint32(val)))
f = float64(math.Float32frombits(uint32(val))) //nolint:gosec
case additionalInformationAsFloat64:
f = math.Float64frombits(val)
@@ -1832,9 +1836,17 @@ func (d *decoder) parseToTime() (time.Time, bool, error) {
return time.Time{}, true, nil
}
seconds, fractional := math.Modf(f)
if seconds > math.MaxInt64 || seconds < math.MinInt64 {
return time.Time{}, false, &UnmarshalTypeError{
CBORType: t.String(),
GoType: typeTime.String(),
errorMsg: fmt.Sprintf("%v overflows Go's int64", f),
}
}
return time.Unix(int64(seconds), int64(fractional*1e9)), true, nil
default:
d.skip()
return time.Time{}, false, &UnmarshalTypeError{CBORType: t.String(), GoType: typeTime.String()}
}
}
@@ -2145,14 +2157,14 @@ func (d *decoder) parse(skipSelfDescribedTag bool) (any, error) { //nolint:gocyc
case cborTypePrimitives:
_, ai, val := d.getHead()
if ai <= 24 && d.dm.simpleValues.rejected[SimpleValue(val)] {
if ai <= 24 && d.dm.simpleValues.rejected[SimpleValue(val)] { //nolint:gosec
return nil, &UnacceptableDataItemError{
CBORType: t.String(),
Message: "simple value " + strconv.FormatInt(int64(val), 10) + " is not recognized",
Message: "simple value " + strconv.FormatInt(int64(val), 10) + " is not recognized", //nolint:gosec
}
}
if ai < 20 || ai == 24 {
return SimpleValue(val), nil
return SimpleValue(val), nil //nolint:gosec
}
switch ai {
@@ -2165,11 +2177,11 @@ func (d *decoder) parse(skipSelfDescribedTag bool) (any, error) { //nolint:gocyc
return nil, nil
case additionalInformationAsFloat16:
f := float64(float16.Frombits(uint16(val)).Float32())
f := float64(float16.Frombits(uint16(val)).Float32()) //nolint:gosec
return f, nil
case additionalInformationAsFloat32:
f := float64(math.Float32frombits(uint32(val)))
f := float64(math.Float32frombits(uint32(val))) //nolint:gosec
return f, nil
case additionalInformationAsFloat64:
@@ -2202,16 +2214,16 @@ func (d *decoder) parse(skipSelfDescribedTag bool) (any, error) { //nolint:gocyc
func (d *decoder) parseByteString() ([]byte, bool) {
_, _, val, indefiniteLength := d.getHeadWithIndefiniteLengthFlag()
if !indefiniteLength {
b := d.data[d.off : d.off+int(val)]
d.off += int(val)
b := d.data[d.off : d.off+int(val)] //nolint:gosec
d.off += int(val) //nolint:gosec
return b, false
}
// Process indefinite length string chunks.
// Process indefinite-length string chunks.
b := []byte{}
for !d.foundBreak() {
_, _, val = d.getHead()
b = append(b, d.data[d.off:d.off+int(val)]...)
d.off += int(val)
b = append(b, d.data[d.off:d.off+int(val)]...) //nolint:gosec
d.off += int(val) //nolint:gosec
}
return b, true
}
@@ -2253,7 +2265,7 @@ func (d *decoder) applyByteStringTextConversion(
default:
// If this happens, there is a bug: the decoder has pushed an invalid
// "expected later encoding" tag to the stack.
panic(fmt.Sprintf("unrecognized expected later encoding tag: %d", d.expectedLaterEncodingTags))
panic(fmt.Sprintf("unrecognized expected later encoding tag: %d", d.expectedLaterEncodingTags[len(d.expectedLaterEncodingTags)-1]))
}
case reflect.Slice:
@@ -2300,19 +2312,19 @@ func (d *decoder) applyByteStringTextConversion(
func (d *decoder) parseTextString() ([]byte, error) {
_, _, val, indefiniteLength := d.getHeadWithIndefiniteLengthFlag()
if !indefiniteLength {
b := d.data[d.off : d.off+int(val)]
d.off += int(val)
b := d.data[d.off : d.off+int(val)] //nolint:gosec
d.off += int(val) //nolint:gosec
if d.dm.utf8 == UTF8RejectInvalid && !utf8.Valid(b) {
return nil, &SemanticError{"cbor: invalid UTF-8 string"}
}
return b, nil
}
// Process indefinite length string chunks.
// Process indefinite-length string chunks.
b := []byte{}
for !d.foundBreak() {
_, _, val = d.getHead()
x := d.data[d.off : d.off+int(val)]
d.off += int(val)
x := d.data[d.off : d.off+int(val)] //nolint:gosec
d.off += int(val) //nolint:gosec
if d.dm.utf8 == UTF8RejectInvalid && !utf8.Valid(x) {
for !d.foundBreak() {
d.skip() // Skip remaining chunk on error
@@ -2327,7 +2339,7 @@ func (d *decoder) parseTextString() ([]byte, error) {
func (d *decoder) parseArray() ([]any, error) {
_, _, val, indefiniteLength := d.getHeadWithIndefiniteLengthFlag()
hasSize := !indefiniteLength
count := int(val)
count := int(val) //nolint:gosec
if !hasSize {
count = d.numOfItemsUntilBreak() // peek ahead to get array size to preallocate slice for better performance
}
@@ -2349,7 +2361,7 @@ func (d *decoder) parseArray() ([]any, error) {
func (d *decoder) parseArrayToSlice(v reflect.Value, tInfo *typeInfo) error {
_, _, val, indefiniteLength := d.getHeadWithIndefiniteLengthFlag()
hasSize := !indefiniteLength
count := int(val)
count := int(val) //nolint:gosec
if !hasSize {
count = d.numOfItemsUntilBreak() // peek ahead to get array size to preallocate slice for better performance
}
@@ -2371,7 +2383,7 @@ func (d *decoder) parseArrayToSlice(v reflect.Value, tInfo *typeInfo) error {
func (d *decoder) parseArrayToArray(v reflect.Value, tInfo *typeInfo) error {
_, _, val, indefiniteLength := d.getHeadWithIndefiniteLengthFlag()
hasSize := !indefiniteLength
count := int(val)
count := int(val) //nolint:gosec
gi := 0
vLen := v.Len()
var err error
@@ -2400,7 +2412,7 @@ func (d *decoder) parseArrayToArray(v reflect.Value, tInfo *typeInfo) error {
func (d *decoder) parseMap() (any, error) {
_, _, val, indefiniteLength := d.getHeadWithIndefiniteLengthFlag()
hasSize := !indefiniteLength
count := int(val)
count := int(val) //nolint:gosec
m := make(map[any]any)
var k, e any
var err, lastErr error
@@ -2465,7 +2477,7 @@ func (d *decoder) parseMap() (any, error) {
func (d *decoder) parseMapToMap(v reflect.Value, tInfo *typeInfo) error { //nolint:gocyclo
_, _, val, indefiniteLength := d.getHeadWithIndefiniteLengthFlag()
hasSize := !indefiniteLength
count := int(val)
count := int(val) //nolint:gosec
if v.IsNil() {
mapsize := count
if !hasSize {
@@ -2566,9 +2578,9 @@ func (d *decoder) parseMapToMap(v reflect.Value, tInfo *typeInfo) error { //noli
}
func (d *decoder) parseArrayToStruct(v reflect.Value, tInfo *typeInfo) error {
structType := getDecodingStructType(tInfo.nonPtrType)
if structType.err != nil {
return structType.err
structType, structTypeErr := getDecodingStructType(tInfo.nonPtrType)
if structTypeErr != nil {
return structTypeErr
}
if !structType.toArray {
@@ -2584,7 +2596,7 @@ func (d *decoder) parseArrayToStruct(v reflect.Value, tInfo *typeInfo) error {
start := d.off
_, _, val, indefiniteLength := d.getHeadWithIndefiniteLengthFlag()
hasSize := !indefiniteLength
count := int(val)
count := int(val) //nolint:gosec
if !hasSize {
count = d.numOfItemsUntilBreak() // peek ahead to get array size
}
@@ -2637,11 +2649,72 @@ func (d *decoder) parseArrayToStruct(v reflect.Value, tInfo *typeInfo) error {
return err
}
// parseMapToStruct needs to be fast so gocyclo can be ignored for now.
// skipMapEntriesFromIndex skips remaining map entries starting from index i.
func (d *decoder) skipMapEntriesFromIndex(i, count int, hasSize bool) {
for ; (hasSize && i < count) || (!hasSize && !d.foundBreak()); i++ {
d.skip()
d.skip()
}
}
// skipMapForDupKey skips the current map value and all remaining map entries,
// then returns a DupMapKeyError for the given key at map index i.
func (d *decoder) skipMapForDupKey(dupKey any, i, count int, hasSize bool) error {
// Skip the value of the duplicate key.
d.skip()
// Skip all remaining map entries.
d.skipMapEntriesFromIndex(i+1, count, hasSize)
return &DupMapKeyError{dupKey, i}
}
// skipMapForUnknownField skips the current map value and all remaining map entries,
// then returns a UnknownFieldError for the given key at map index i.
func (d *decoder) skipMapForUnknownField(i, count int, hasSize bool) error {
// Skip the value of the unknown key.
d.skip()
// Skip all remaining map entries.
d.skipMapEntriesFromIndex(i+1, count, hasSize)
return &UnknownFieldError{i}
}
// decodeToStructField decodes the next CBOR value into the struct field f in v.
// If the field cannot be resolved, the CBOR value is skipped.
func (d *decoder) decodeToStructField(v reflect.Value, f *decodingField, tInfo *typeInfo) error {
var fv reflect.Value
if len(f.idx) == 1 {
fv = v.Field(f.idx[0])
} else {
var err error
fv, err = getFieldValue(v, f.idx, func(v reflect.Value) (reflect.Value, error) {
// Return a new value for embedded field null pointer to point to, or return error.
if !v.CanSet() {
return reflect.Value{}, errors.New("cbor: cannot set embedded pointer to unexported struct: " + v.Type().String())
}
v.Set(reflect.New(v.Type().Elem()))
return v, nil
})
if !fv.IsValid() {
d.skip()
return err
}
}
err := d.parseToValue(fv, f.typInfo)
if err != nil {
if typeError, ok := err.(*UnmarshalTypeError); ok {
typeError.StructFieldName = tInfo.nonPtrType.String() + "." + f.name
}
return err
}
return nil
}
func (d *decoder) parseMapToStruct(v reflect.Value, tInfo *typeInfo) error { //nolint:gocyclo
structType := getDecodingStructType(tInfo.nonPtrType)
if structType.err != nil {
return structType.err
structType, structTypeErr := getDecodingStructType(tInfo.nonPtrType)
if structTypeErr != nil {
return structTypeErr
}
if structType.toArray {
@@ -2654,14 +2727,12 @@ func (d *decoder) parseMapToStruct(v reflect.Value, tInfo *typeInfo) error { //n
}
}
var err, lastErr error
// Get CBOR map size
_, _, val, indefiniteLength := d.getHeadWithIndefiniteLengthFlag()
hasSize := !indefiniteLength
count := int(val)
count := int(val) //nolint:gosec
// Keeps track of matched struct fields
// Keep track of matched struct fields to detect duplicate map keys.
var foundFldIdx []bool
{
const maxStackFields = 128
@@ -2675,99 +2746,80 @@ func (d *decoder) parseMapToStruct(v reflect.Value, tInfo *typeInfo) error { //n
}
}
// Keeps track of CBOR map keys to detect duplicate map key
keyCount := 0
var mapKeys map[any]struct{}
// Keep track of unmatched CBOR map keys to detect duplicate map keys.
var unmatchedMapKeys map[any]struct{}
errOnUnknownField := (d.dm.extraReturnErrors & ExtraDecErrorUnknownField) > 0
var err error
MapEntryLoop:
for j := 0; (hasSize && j < count) || (!hasSize && !d.foundBreak()); j++ {
var f *field
// If duplicate field detection is enabled and the key at index j did not match any
// field, k will hold the map key.
var k any
caseInsensitive := d.dm.fieldNameMatching == FieldNameMatchingPreferCaseSensitive
for i := 0; (hasSize && i < count) || (!hasSize && !d.foundBreak()); i++ {
t := d.nextCBORType()
if t == cborTypeTextString || (t == cborTypeByteString && d.dm.fieldNameByteString == FieldNameByteStringAllowed) {
// Reclassify disallowed byte string keys so they fall to the default case.
// keyType is only used for branch control.
keyType := t
if t == cborTypeByteString && d.dm.fieldNameByteString != FieldNameByteStringAllowed {
keyType = 0xff
}
switch keyType {
case cborTypeTextString, cborTypeByteString:
var keyBytes []byte
if t == cborTypeTextString {
keyBytes, lastErr = d.parseTextString()
if lastErr != nil {
var parseErr error
keyBytes, parseErr = d.parseTextString()
if parseErr != nil {
if err == nil {
err = lastErr
err = parseErr
}
d.skip() // skip value
d.skip() // Skip value
continue
}
} else { // cborTypeByteString
keyBytes, _ = d.parseByteString()
}
// Check for exact match on field name.
if i, ok := structType.fieldIndicesByName[string(keyBytes)]; ok {
fld := structType.fields[i]
// Find matching struct field (exact match, then case-insensitive fallback).
if fldIdx, ok := findStructFieldByKey(structType, keyBytes, caseInsensitive); ok {
fld := structType.fields[fldIdx]
if !foundFldIdx[i] {
f = fld
foundFldIdx[i] = true
} else if d.dm.dupMapKey == DupMapKeyEnforcedAPF {
err = &DupMapKeyError{fld.name, j}
d.skip() // skip value
j++
// skip the rest of the map
for ; (hasSize && j < count) || (!hasSize && !d.foundBreak()); j++ {
d.skip()
d.skip()
switch checkDupField(d.dm, foundFldIdx, fldIdx) {
case mapActionParseValueAndContinue:
if fieldErr := d.decodeToStructField(v, fld, tInfo); fieldErr != nil && err == nil {
err = fieldErr
}
return err
} else {
// discard repeated match
continue
case mapActionSkipAllAndReturnError:
return d.skipMapForDupKey(string(keyBytes), i, count, hasSize)
case mapActionSkipValueAndContinue:
d.skip()
continue MapEntryLoop
continue
}
}
// Find field with case-insensitive match
if f == nil && d.dm.fieldNameMatching == FieldNameMatchingPreferCaseSensitive {
keyLen := len(keyBytes)
keyString := string(keyBytes)
for i := 0; i < len(structType.fields); i++ {
fld := structType.fields[i]
if len(fld.name) == keyLen && strings.EqualFold(fld.name, keyString) {
if !foundFldIdx[i] {
f = fld
foundFldIdx[i] = true
} else if d.dm.dupMapKey == DupMapKeyEnforcedAPF {
err = &DupMapKeyError{keyString, j}
d.skip() // skip value
j++
// skip the rest of the map
for ; (hasSize && j < count) || (!hasSize && !d.foundBreak()); j++ {
d.skip()
d.skip()
}
return err
} else {
// discard repeated match
d.skip()
continue MapEntryLoop
}
break
}
}
// No matching struct field found.
if unmatchedErr := handleUnmatchedMapKey(d, string(keyBytes), i, count, hasSize, &unmatchedMapKeys); unmatchedErr != nil {
return unmatchedErr
}
if d.dm.dupMapKey == DupMapKeyEnforcedAPF && f == nil {
k = string(keyBytes)
}
} else if t <= cborTypeNegativeInt { // uint/int
case cborTypePositiveInt, cborTypeNegativeInt:
var nameAsInt int64
if t == cborTypePositiveInt {
_, _, val := d.getHead()
nameAsInt = int64(val)
if val > math.MaxInt64 {
if err == nil {
err = &UnmarshalTypeError{
CBORType: t.String(),
GoType: reflect.TypeOf(int64(0)).String(),
errorMsg: strconv.FormatUint(val, 10) + " overflows Go's int64",
}
}
d.skip() // skip value
continue
}
nameAsInt = int64(val) //nolint:gosec
} else {
_, _, val := d.getHead()
if val > math.MaxInt64 {
@@ -2781,39 +2833,35 @@ MapEntryLoop:
d.skip() // skip value
continue
}
nameAsInt = int64(-1) ^ int64(val)
nameAsInt = int64(-1) ^ int64(val) //nolint:gosec
}
// Find field
for i := 0; i < len(structType.fields); i++ {
fld := structType.fields[i]
if fld.keyAsInt && fld.nameAsInt == nameAsInt {
if !foundFldIdx[i] {
f = fld
foundFldIdx[i] = true
} else if d.dm.dupMapKey == DupMapKeyEnforcedAPF {
err = &DupMapKeyError{nameAsInt, j}
d.skip() // skip value
j++
// skip the rest of the map
for ; (hasSize && j < count) || (!hasSize && !d.foundBreak()); j++ {
d.skip()
d.skip()
}
return err
} else {
// discard repeated match
d.skip()
continue MapEntryLoop
// Find field by integer key
if fldIdx, ok := structType.fieldIndicesByIntKey[nameAsInt]; ok {
fld := structType.fields[fldIdx]
switch checkDupField(d.dm, foundFldIdx, fldIdx) {
case mapActionParseValueAndContinue:
if fieldErr := d.decodeToStructField(v, fld, tInfo); fieldErr != nil && err == nil {
err = fieldErr
}
break
continue
case mapActionSkipAllAndReturnError:
return d.skipMapForDupKey(nameAsInt, i, count, hasSize)
case mapActionSkipValueAndContinue:
d.skip()
continue
}
}
if d.dm.dupMapKey == DupMapKeyEnforcedAPF && f == nil {
k = nameAsInt
// No matching struct field found.
if unmatchedErr := handleUnmatchedMapKey(d, nameAsInt, i, count, hasSize, &unmatchedMapKeys); unmatchedErr != nil {
return unmatchedErr
}
} else {
default:
// CBOR map keys that can't be matched to any struct field.
if err == nil {
err = &UnmarshalTypeError{
CBORType: t.String(),
@@ -2821,97 +2869,31 @@ MapEntryLoop:
errorMsg: "map key is of type " + t.String() + " and cannot be used to match struct field name",
}
}
var otherKey any
if d.dm.dupMapKey == DupMapKeyEnforcedAPF {
// parse key
k, lastErr = d.parse(true)
if lastErr != nil {
var parseErr error
otherKey, parseErr = d.parse(true)
if parseErr != nil {
d.skip() // skip value
continue
}
// Detect if CBOR map key can be used as Go map key.
if !isHashableValue(reflect.ValueOf(k)) {
if !isHashableValue(reflect.ValueOf(otherKey)) {
d.skip() // skip value
continue
}
} else {
d.skip() // skip key
}
}
if f == nil {
if errOnUnknownField {
err = &UnknownFieldError{j}
d.skip() // Skip value
j++
// skip the rest of the map
for ; (hasSize && j < count) || (!hasSize && !d.foundBreak()); j++ {
d.skip()
d.skip()
}
return err
}
// Two map keys that match the same struct field are immediately considered
// duplicates. This check detects duplicates between two map keys that do
// not match a struct field. If unknown field errors are enabled, then this
// check is never reached.
if d.dm.dupMapKey == DupMapKeyEnforcedAPF {
if mapKeys == nil {
mapKeys = make(map[any]struct{}, 1)
}
mapKeys[k] = struct{}{}
newKeyCount := len(mapKeys)
if newKeyCount == keyCount {
err = &DupMapKeyError{k, j}
d.skip() // skip value
j++
// skip the rest of the map
for ; (hasSize && j < count) || (!hasSize && !d.foundBreak()); j++ {
d.skip()
d.skip()
}
return err
}
keyCount = newKeyCount
}
d.skip() // Skip value
continue
}
// Get field value by index
var fv reflect.Value
if len(f.idx) == 1 {
fv = v.Field(f.idx[0])
} else {
fv, lastErr = getFieldValue(v, f.idx, func(v reflect.Value) (reflect.Value, error) {
// Return a new value for embedded field null pointer to point to, or return error.
if !v.CanSet() {
return reflect.Value{}, errors.New("cbor: cannot set embedded pointer to unexported struct: " + v.Type().String())
}
v.Set(reflect.New(v.Type().Elem()))
return v, nil
})
if lastErr != nil && err == nil {
err = lastErr
}
if !fv.IsValid() {
d.skip()
continue
}
}
if lastErr = d.parseToValue(fv, f.typInfo); lastErr != nil {
if err == nil {
if typeError, ok := lastErr.(*UnmarshalTypeError); ok {
typeError.StructFieldName = tInfo.nonPtrType.String() + "." + f.name
err = typeError
} else {
err = lastErr
}
if unmatchedErr := handleUnmatchedMapKey(d, otherKey, i, count, hasSize, &unmatchedMapKeys); unmatchedErr != nil {
return unmatchedErr
}
}
}
return err
}
@@ -2958,15 +2940,15 @@ func (d *decoder) skip() {
switch t {
case cborTypeByteString, cborTypeTextString:
d.off += int(val)
d.off += int(val) //nolint:gosec
case cborTypeArray:
for i := 0; i < int(val); i++ {
for i := 0; i < int(val); i++ { //nolint:gosec
d.skip()
}
case cborTypeMap:
for i := 0; i < int(val)*2; i++ {
for i := 0; i < int(val)*2; i++ { //nolint:gosec
d.skip()
}
+98
View File
@@ -0,0 +1,98 @@
// Copyright (c) Faye Amacker. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.
package cbor
import "strings"
// mapAction represents the next action during decoding a CBOR map to a Go struct.
type mapAction int
const (
mapActionParseValueAndContinue mapAction = iota // The caller should process the map value.
mapActionSkipValueAndContinue // The caller should skip the map value.
mapActionSkipAllAndReturnError // The caller should skip the rest of the map and return an error.
)
// checkDupField checks if a struct field at index i has already been matched and returns the next action.
// If not matched, it marks the field as matched and returns mapActionParseValueAndContinue.
// If matched and DupMapKeyEnforcedAPF is specified in the given dm, it returns mapActionSkipAllAndReturnError.
// If matched and DupMapKeyEnforcedAPF is not specified in the given dm, it returns mapActionSkipValueAndContinue.
func checkDupField(dm *decMode, foundFldIdx []bool, i int) mapAction {
if !foundFldIdx[i] {
foundFldIdx[i] = true
return mapActionParseValueAndContinue
}
if dm.dupMapKey == DupMapKeyEnforcedAPF {
return mapActionSkipAllAndReturnError
}
return mapActionSkipValueAndContinue
}
// findStructFieldByKey finds a struct field matching keyBytes by name.
// It tries an exact match first. If no exact match is found and
// caseInsensitive is true, it falls back to a case-insensitive search.
// findStructFieldByKey returns the field index and true, or -1 and false.
func findStructFieldByKey(
structType *decodingStructType,
keyBytes []byte,
caseInsensitive bool,
) (int, bool) {
if fldIdx, ok := structType.fieldIndicesByName[string(keyBytes)]; ok {
return fldIdx, true
}
if caseInsensitive {
return findFieldCaseInsensitive(structType.fields, string(keyBytes))
}
return -1, false
}
// findFieldCaseInsensitive returns the index of the first field whose name
// case-insensitively matches key, or -1 and false if no field matches.
func findFieldCaseInsensitive(flds decodingFields, key string) (int, bool) {
keyLen := len(key)
for i, f := range flds {
if f.keyAsInt {
continue
}
if len(f.name) == keyLen && strings.EqualFold(f.name, key) {
return i, true
}
}
return -1, false
}
// handleUnmatchedMapKey handles a map entry whose key does not match any struct
// field. It can return UnknownFieldError or DupMapKeyError.
// handleUnmatchedMapKey consumes the CBOR value, so the caller doesn't need to skip any values.
// If an error is returned, the caller should abort parsing the map and return the error.
// If no error is returned, the caller should continue to process the next map pair.
func handleUnmatchedMapKey(
d *decoder,
key any,
i int,
count int,
hasSize bool,
// *map[any]struct{} is used here because we use lazy initialization for uks
uks *map[any]struct{}, //nolint:gocritic
) error {
errOnUnknownField := (d.dm.extraReturnErrors & ExtraDecErrorUnknownField) > 0
if errOnUnknownField {
return d.skipMapForUnknownField(i, count, hasSize)
}
if d.dm.dupMapKey == DupMapKeyEnforcedAPF {
if *uks == nil {
*uks = make(map[any]struct{})
}
if _, dup := (*uks)[key]; dup {
return d.skipMapForDupKey(key, i, count, hasSize)
}
(*uks)[key] = struct{}{}
}
// Skip value.
d.skip()
return nil
}
+11 -14
View File
@@ -51,11 +51,8 @@ const (
maxByteStringEncoding
)
func (bse ByteStringEncoding) valid() error {
if bse >= maxByteStringEncoding {
return errors.New("cbor: invalid ByteStringEncoding " + strconv.Itoa(int(bse)))
}
return nil
func (bse ByteStringEncoding) valid() bool {
return bse < maxByteStringEncoding
}
// DiagOptions specifies Diag options.
@@ -104,8 +101,8 @@ func (opts DiagOptions) DiagMode() (DiagMode, error) {
}
func (opts DiagOptions) diagMode() (*diagMode, error) {
if err := opts.ByteStringEncoding.valid(); err != nil {
return nil, err
if !opts.ByteStringEncoding.valid() {
return nil, errors.New("cbor: invalid ByteStringEncoding " + strconv.Itoa(int(opts.ByteStringEncoding)))
}
decMode, err := DecOptions{
@@ -360,7 +357,7 @@ func (di *diagnose) item() error { //nolint:gocyclo
case cborTypeArray:
_, _, val := di.d.getHead()
count := int(val)
count := int(val) //nolint:gosec
di.w.WriteByte('[')
for i := 0; i < count; i++ {
@@ -376,7 +373,7 @@ func (di *diagnose) item() error { //nolint:gocyclo
case cborTypeMap:
_, _, val := di.d.getHead()
count := int(val)
count := int(val) //nolint:gosec
di.w.WriteByte('{')
for i := 0; i < count; i++ {
@@ -477,8 +474,8 @@ func (di *diagnose) item() error { //nolint:gocyclo
func (di *diagnose) writeU16(val rune) {
di.w.WriteString("\\u")
var in [2]byte
in[0] = byte(val >> 8)
in[1] = byte(val)
in[0] = byte(val >> 8) //nolint:gosec
in[1] = byte(val) //nolint:gosec
sz := hex.EncodedLen(len(in))
di.w.Grow(sz)
dst := di.w.Bytes()[di.w.Len() : di.w.Len()+sz]
@@ -608,7 +605,7 @@ func (di *diagnose) encodeTextString(val string, quote byte) error {
c, size := utf8.DecodeRuneInString(val[i:])
switch {
case c == utf8.RuneError:
case c == utf8.RuneError && size == 1:
return &SemanticError{"cbor: invalid UTF-8 string"}
case c < utf16SurrSelf:
@@ -631,7 +628,7 @@ func (di *diagnose) encodeFloat(ai byte, val uint64) error {
f64 := float64(0)
switch ai {
case additionalInformationAsFloat16:
f16 := float16.Frombits(uint16(val))
f16 := float16.Frombits(uint16(val)) //nolint:gosec
switch {
case f16.IsNaN():
di.w.WriteString("NaN")
@@ -647,7 +644,7 @@ func (di *diagnose) encodeFloat(ai byte, val uint64) error {
}
case additionalInformationAsFloat32:
f32 := math.Float32frombits(uint32(val))
f32 := math.Float32frombits(uint32(val)) //nolint:gosec
switch {
case f32 != f32:
di.w.WriteString("NaN")
+15 -5
View File
@@ -56,20 +56,30 @@ modes won't accidentally change at runtime after they're created.
Modes are intended to be reused and are safe for concurrent use.
EncMode and DecMode Interfaces
EncMode, UserBufferEncMode, and DecMode Interfaces
// EncMode interface uses immutable options and is safe for concurrent use.
type EncMode interface {
Marshal(v interface{}) ([]byte, error)
Marshal(v any) ([]byte, error)
NewEncoder(w io.Writer) *Encoder
EncOptions() EncOptions // returns copy of options
EncOptions() EncOptions
}
// UserBufferEncMode extends EncMode with MarshalToBuffer, which supports
// user specified buffer rather than encoding into the built-in buffer pool.
type UserBufferEncMode interface {
EncMode
MarshalToBuffer(v any, buf *bytes.Buffer) error
}
// DecMode interface uses immutable options and is safe for concurrent use.
type DecMode interface {
Unmarshal(data []byte, v interface{}) error
Unmarshal(data []byte, v any) error
UnmarshalFirst(data []byte, v any) (rest []byte, err error)
Valid(data []byte) error // Deprecated: use Wellformed instead.
Wellformed(data []byte) error
NewDecoder(r io.Reader) *Decoder
DecOptions() DecOptions // returns copy of options
DecOptions() DecOptions
}
Using Default Encoding Mode
+32 -23
View File
@@ -30,7 +30,7 @@ import (
// If value implements the Marshaler interface, Marshal calls its
// MarshalCBOR method.
//
// If value implements encoding.BinaryMarshaler, Marhsal calls its
// If value implements encoding.BinaryMarshaler, Marshal calls its
// MarshalBinary method and encode it as CBOR byte string.
//
// Boolean values encode as CBOR booleans (type 7).
@@ -343,7 +343,7 @@ const (
// non-UTC timezone then a "localtime - UTC" numeric offset will be included as specified in RFC3339.
// NOTE: User applications can avoid including the RFC3339 numeric offset by:
// - providing a time.Time value set to UTC, or
// - using the TimeUnix, TimeUnixMicro, or TimeUnixDynamic option instead of TimeRFC3339.
// - using the TimeUnix, TimeUnixMicro, TimeUnixDynamic, or TimeRFC3339NanoUTC option.
TimeRFC3339
// TimeRFC3339Nano causes time.Time to encode to a CBOR time (tag 0) with a text string content
@@ -351,9 +351,13 @@ const (
// non-UTC timezone then a "localtime - UTC" numeric offset will be included as specified in RFC3339.
// NOTE: User applications can avoid including the RFC3339 numeric offset by:
// - providing a time.Time value set to UTC, or
// - using the TimeUnix, TimeUnixMicro, or TimeUnixDynamic option instead of TimeRFC3339Nano.
// - using the TimeUnix, TimeUnixMicro, TimeUnixDynamic, or TimeRFC3339NanoUTC option.
TimeRFC3339Nano
// TimeRFC3339NanoUTC causes time.Time to encode to a CBOR time (tag 0) with a text string content
// representing UTC time using nanosecond precision in RFC3339 format.
TimeRFC3339NanoUTC
maxTimeMode
)
@@ -436,7 +440,7 @@ const (
// FieldNameToTextString encodes struct fields to CBOR text string (major type 3).
FieldNameToTextString FieldNameMode = iota
// FieldNameToTextString encodes struct fields to CBOR byte string (major type 2).
// FieldNameToByteString encodes struct fields to CBOR byte string (major type 2).
FieldNameToByteString
maxFieldNameMode
@@ -567,7 +571,7 @@ type EncOptions struct {
// RFC3339 format gets tag number 0, and numeric epoch time tag number 1.
TimeTag EncTagMode
// IndefLength specifies whether to allow indefinite length CBOR items.
// IndefLength specifies whether to allow indefinite-length CBOR items.
IndefLength IndefLengthMode
// NilContainers specifies how to encode nil slices and maps.
@@ -1132,10 +1136,11 @@ func encodeFloat(e *bytes.Buffer, em *encMode, v reflect.Value) error {
if fopt == ShortestFloat16 {
var f16 float16.Float16
p := float16.PrecisionFromfloat32(f32)
if p == float16.PrecisionExact {
switch p {
case float16.PrecisionExact:
// Roundtrip float32->float16->float32 test isn't needed.
f16 = float16.Fromfloat32(f32)
} else if p == float16.PrecisionUnknown {
case float16.PrecisionUnknown:
// Try roundtrip float32->float16->float32 to determine if float32 can fit into float16.
f16 = float16.Fromfloat32(f32)
if f16.Float32() == f32 {
@@ -1293,10 +1298,10 @@ func encodeByteString(e *bytes.Buffer, em *encMode, v reflect.Value) error {
if slen == 0 {
return e.WriteByte(byte(cborTypeByteString))
}
encodeHead(e, byte(cborTypeByteString), uint64(slen))
encodeHead(e, byte(cborTypeByteString), uint64(slen)) //nolint:gosec
if vk == reflect.Array {
for i := 0; i < slen; i++ {
e.WriteByte(byte(v.Index(i).Uint()))
e.WriteByte(byte(v.Index(i).Uint())) //nolint:gosec
}
return nil
}
@@ -1333,7 +1338,7 @@ func (ae arrayEncodeFunc) encode(e *bytes.Buffer, em *encMode, v reflect.Value)
if alen == 0 {
return e.WriteByte(byte(cborTypeArray))
}
encodeHead(e, byte(cborTypeArray), uint64(alen))
encodeHead(e, byte(cborTypeArray), uint64(alen)) //nolint:gosec
for i := 0; i < alen; i++ {
if err := ae.f(e, em, v.Index(i)); err != nil {
return err
@@ -1364,7 +1369,7 @@ func (me mapEncodeFunc) encode(e *bytes.Buffer, em *encMode, v reflect.Value) er
return e.WriteByte(byte(cborTypeMap))
}
encodeHead(e, byte(cborTypeMap), uint64(mlen))
encodeHead(e, byte(cborTypeMap), uint64(mlen)) //nolint:gosec
if em.sort == SortNone || em.sort == SortFastShuffle || mlen <= 1 {
return me.e(e, em, v, nil)
}
@@ -1427,7 +1432,7 @@ func (x *bytewiseKeyValueSorter) Swap(i, j int) {
func (x *bytewiseKeyValueSorter) Less(i, j int) bool {
kvi, kvj := x.kvs[i], x.kvs[j]
return bytes.Compare(x.data[kvi.offset:kvi.valueOffset], x.data[kvj.offset:kvj.valueOffset]) <= 0
return bytes.Compare(x.data[kvi.offset:kvi.valueOffset], x.data[kvj.offset:kvj.valueOffset]) < 0
}
type lengthFirstKeyValueSorter struct {
@@ -1448,7 +1453,7 @@ func (x *lengthFirstKeyValueSorter) Less(i, j int) bool {
if keyLengthDifference := (kvi.valueOffset - kvi.offset) - (kvj.valueOffset - kvj.offset); keyLengthDifference != 0 {
return keyLengthDifference < 0
}
return bytes.Compare(x.data[kvi.offset:kvi.valueOffset], x.data[kvj.offset:kvj.valueOffset]) <= 0
return bytes.Compare(x.data[kvi.offset:kvi.valueOffset], x.data[kvj.offset:kvj.valueOffset]) < 0
}
var keyValuePool = sync.Pool{}
@@ -1535,8 +1540,8 @@ func encodeStruct(e *bytes.Buffer, em *encMode, v reflect.Value) (err error) {
// Head is rewritten later if actual encoded field count is different from struct field count.
encodedHeadLen := encodeHead(e, byte(cborTypeMap), uint64(len(flds)))
kvbegin := e.Len()
kvcount := 0
kvBeginOffset := e.Len()
kvCount := 0
for offset := 0; offset < len(flds); offset++ {
f := flds[(start+offset)%len(flds)]
@@ -1582,10 +1587,10 @@ func encodeStruct(e *bytes.Buffer, em *encMode, v reflect.Value) (err error) {
return err
}
kvcount++
kvCount++
}
if len(flds) == kvcount {
if len(flds) == kvCount {
// Encoded element count in head is the same as actual element count.
return nil
}
@@ -1593,8 +1598,8 @@ func encodeStruct(e *bytes.Buffer, em *encMode, v reflect.Value) (err error) {
// Overwrite the bytes that were reserved for the head before encoding the map entries.
var actualHeadLen int
{
headbuf := *bytes.NewBuffer(e.Bytes()[kvbegin-encodedHeadLen : kvbegin-encodedHeadLen : kvbegin])
actualHeadLen = encodeHead(&headbuf, byte(cborTypeMap), uint64(kvcount))
headbuf := *bytes.NewBuffer(e.Bytes()[kvBeginOffset-encodedHeadLen : kvBeginOffset-encodedHeadLen : kvBeginOffset])
actualHeadLen = encodeHead(&headbuf, byte(cborTypeMap), uint64(kvCount))
}
if actualHeadLen == encodedHeadLen {
@@ -1607,8 +1612,8 @@ func encodeStruct(e *bytes.Buffer, em *encMode, v reflect.Value) (err error) {
// encoded. The encoded entries are offset to the right by the number of excess reserved
// bytes. Shift the entries left to remove the gap.
excessReservedBytes := encodedHeadLen - actualHeadLen
dst := e.Bytes()[kvbegin-excessReservedBytes : e.Len()-excessReservedBytes]
src := e.Bytes()[kvbegin:e.Len()]
dst := e.Bytes()[kvBeginOffset-excessReservedBytes : e.Len()-excessReservedBytes]
src := e.Bytes()[kvBeginOffset:e.Len()]
copy(dst, src)
// After shifting, the excess bytes are at the end of the output buffer and they are
@@ -1633,7 +1638,7 @@ func encodeTime(e *bytes.Buffer, em *encMode, v reflect.Value) error {
}
if em.timeTag == EncTagRequired {
tagNumber := 1
if em.time == TimeRFC3339 || em.time == TimeRFC3339Nano {
if em.time == TimeRFC3339 || em.time == TimeRFC3339Nano || em.time == TimeRFC3339NanoUTC {
tagNumber = 0
}
encodeHead(e, byte(cborTypeTag), uint64(tagNumber))
@@ -1650,7 +1655,7 @@ func encodeTime(e *bytes.Buffer, em *encMode, v reflect.Value) error {
case TimeUnixDynamic:
t = t.UTC().Round(time.Microsecond)
secs, nsecs := t.Unix(), uint64(t.Nanosecond())
secs, nsecs := t.Unix(), uint64(t.Nanosecond()) //nolint:gosec
if nsecs == 0 {
return encodeInt(e, em, reflect.ValueOf(secs))
}
@@ -1661,6 +1666,10 @@ func encodeTime(e *bytes.Buffer, em *encMode, v reflect.Value) error {
s := t.Format(time.RFC3339)
return encodeString(e, em, reflect.ValueOf(s))
case TimeRFC3339NanoUTC:
s := t.UTC().Format(time.RFC3339Nano)
return encodeString(e, em, reflect.ValueOf(s))
default: // TimeRFC3339Nano
s := t.Format(time.RFC3339Nano)
return encodeString(e, em, reflect.ValueOf(s))
+1 -1
View File
@@ -93,6 +93,6 @@ func (sv *SimpleValue) unmarshalCBOR(data []byte) error {
// It is safe to cast val to uint8 here because
// - data is already verified to be well-formed CBOR simple value and
// - val is <= math.MaxUint8.
*sv = SimpleValue(val)
*sv = SimpleValue(val) //nolint:gosec
return nil
}
+140 -39
View File
@@ -6,6 +6,7 @@ package cbor
import (
"bytes"
"errors"
"fmt"
"io"
"reflect"
)
@@ -157,11 +158,32 @@ func (dec *Decoder) overwriteBuf(newBuf []byte) {
dec.off = 0
}
// indefDataItem tracks open indefinite-length data item during encoding.
// typ is the CBOR type of the indefinite-length data item.
// count is the number of items written so far.
// For indefinite-length maps, count must be even (key/value pairs) when
// the container is closed.
type indefDataItem struct {
typ cborType
count int
}
// IndefiniteLengthMapOddItemCountError indicates that EndIndefinite was
// called on an open indefinite-length map with an odd number of items.
type IndefiniteLengthMapOddItemCountError struct {
count int
}
func (e *IndefiniteLengthMapOddItemCountError) Error() string {
return fmt.Sprintf("cbor: cannot end indefinite-length map with %d item(s)", e.count)
}
// Encoder writes CBOR values to io.Writer.
type Encoder struct {
w io.Writer
em *encMode
indefTypes []cborType
w io.Writer
em *encMode
indefs []indefDataItem
scratch [1]byte // reused for single-byte writes (indefinite-length head and break code)
}
// NewEncoder returns a new encoder that writes to w using the default encoding options.
@@ -171,89 +193,166 @@ func NewEncoder(w io.Writer) *Encoder {
// Encode writes the CBOR encoding of v.
func (enc *Encoder) Encode(v any) error {
if len(enc.indefTypes) > 0 && v != nil {
indefType := enc.indefTypes[len(enc.indefTypes)-1]
if indefType == cborTypeTextString {
k := reflect.TypeOf(v).Kind()
if k != reflect.String {
return errors.New("cbor: cannot encode item type " + k.String() + " for indefinite-length text string")
}
} else if indefType == cborTypeByteString {
t := reflect.TypeOf(v)
k := t.Kind()
if (k != reflect.Array && k != reflect.Slice) || t.Elem().Kind() != reflect.Uint8 {
return errors.New("cbor: cannot encode item type " + k.String() + " for indefinite-length byte string")
}
if len(enc.indefs) > 0 {
err := validateIndefiniteLengthChunkByType(enc.indefs[len(enc.indefs)-1].typ, v)
if err != nil {
return err
}
}
buf := getEncodeBuffer()
err := encode(buf, enc.em, reflect.ValueOf(v))
// Validate the encoded chunk against the indefinite-length data item using a byte-based check.
// This reliably detects chunks from cbor.Marshaler, registered tags, StringToByteString, etc.,
// which may produce a chunk inconsistent with the parent's major type.
// Applies only to indefinite-length byte/text string parents (RFC 8949 Section 3.2.3).
if err == nil && len(enc.indefs) > 0 {
err = validateIndefiniteLengthChunkByData(enc.indefs[len(enc.indefs)-1].typ, buf.Bytes(), v)
}
if err == nil {
_, err = enc.w.Write(buf.Bytes())
}
putEncodeBuffer(buf)
return err
if err != nil {
return err
}
if len(enc.indefs) > 0 {
enc.indefs[len(enc.indefs)-1].count++
}
return nil
}
// StartIndefiniteByteString starts byte string encoding of indefinite length.
// StartIndefiniteByteString starts indefinite-length byte string encoding.
// Subsequent calls of (*Encoder).Encode() encodes definite length byte strings
// ("chunks") as one contiguous string until EndIndefinite is called.
func (enc *Encoder) StartIndefiniteByteString() error {
return enc.startIndefinite(cborTypeByteString)
}
// StartIndefiniteTextString starts text string encoding of indefinite length.
// StartIndefiniteTextString starts indefinite-length text string encoding.
// Subsequent calls of (*Encoder).Encode() encodes definite length text strings
// ("chunks") as one contiguous string until EndIndefinite is called.
func (enc *Encoder) StartIndefiniteTextString() error {
return enc.startIndefinite(cborTypeTextString)
}
// StartIndefiniteArray starts array encoding of indefinite length.
// StartIndefiniteArray starts indefinite-length array encoding.
// Subsequent calls of (*Encoder).Encode() encodes elements of the array
// until EndIndefinite is called.
func (enc *Encoder) StartIndefiniteArray() error {
return enc.startIndefinite(cborTypeArray)
}
// StartIndefiniteMap starts array encoding of indefinite length.
// StartIndefiniteMap starts indefinite-length map encoding.
// Subsequent calls of (*Encoder).Encode() encodes elements of the map
// until EndIndefinite is called.
func (enc *Encoder) StartIndefiniteMap() error {
return enc.startIndefinite(cborTypeMap)
}
// EndIndefinite closes last opened indefinite length value.
// EndIndefinite closes last opened indefinite-length value.
// It returns *IndefiniteLengthMapOddItemCountError without writing the
// "break" code if the open indefinite-length map has an odd number of
// items; the encoder state is unchanged so the caller can write the
// missing value and retry.
func (enc *Encoder) EndIndefinite() error {
if len(enc.indefTypes) == 0 {
if len(enc.indefs) == 0 {
return errors.New("cbor: cannot encode \"break\" code outside indefinite length values")
}
_, err := enc.w.Write([]byte{cborBreakFlag})
if err == nil {
enc.indefTypes = enc.indefTypes[:len(enc.indefTypes)-1]
}
return err
}
var cborIndefHeader = map[cborType][]byte{
cborTypeByteString: {cborByteStringWithIndefiniteLengthHead},
cborTypeTextString: {cborTextStringWithIndefiniteLengthHead},
cborTypeArray: {cborArrayWithIndefiniteLengthHead},
cborTypeMap: {cborMapWithIndefiniteLengthHead},
// Verify that indefinite-length map has even number of elements
top := enc.indefs[len(enc.indefs)-1]
if top.typ == cborTypeMap && top.count%2 != 0 {
return &IndefiniteLengthMapOddItemCountError{count: top.count}
}
// Write break code
enc.scratch[0] = cborBreakFlag
_, err := enc.w.Write(enc.scratch[:])
if err != nil {
return err
}
enc.indefs = enc.indefs[:len(enc.indefs)-1]
// Increment parent container's item count because the child
// (indefinite-length data item) is fully written to the stream.
if len(enc.indefs) > 0 {
enc.indefs[len(enc.indefs)-1].count++
}
return nil
}
func (enc *Encoder) startIndefinite(typ cborType) error {
if enc.em.indefLength == IndefLengthForbidden {
return &IndefiniteLengthError{typ}
}
_, err := enc.w.Write(cborIndefHeader[typ])
if err == nil {
enc.indefTypes = append(enc.indefTypes, typ)
// Verify that new indefinite-length data item is not a chunk in indefinite-length byte/text string.
if len(enc.indefs) > 0 {
parent := enc.indefs[len(enc.indefs)-1].typ
if parent == cborTypeByteString || parent == cborTypeTextString {
return errors.New("cbor: cannot encode indefinite-length " + typ.String() +
" as chunk of indefinite-length " + parent.String())
}
}
return err
// Write indefinite-length head.
enc.scratch[0] = byte(typ) | additionalInformationAsIndefiniteLengthFlag
_, err := enc.w.Write(enc.scratch[:])
if err != nil {
return err
}
enc.indefs = append(enc.indefs, indefDataItem{typ: typ})
return nil
}
// validateIndefiniteLengthChunkByType rejects chunks based solely on their Go type.
func validateIndefiniteLengthChunkByType(indefiniteLengthCborType cborType, v any) error {
if indefiniteLengthCborType != cborTypeByteString &&
indefiniteLengthCborType != cborTypeTextString {
return nil
}
if v == nil {
return errors.New("cbor: cannot encode nil for indefinite-length " + indefiniteLengthCborType.String())
}
return nil
}
// validateIndefiniteLengthChunkByData checks that chunk is a definite-length
// CBOR data item with a matching major type.
// No-op for indefinite-length array/map, where any data item is valid.
func validateIndefiniteLengthChunkByData(indefiniteLengthCborType cborType, chunk []byte, v any) error {
if indefiniteLengthCborType != cborTypeByteString &&
indefiniteLengthCborType != cborTypeTextString {
return nil
}
if len(chunk) == 0 {
return errors.New("cbor: cannot encode item type " + reflect.TypeOf(v).Kind().String() +
" for indefinite-length " + indefiniteLengthCborType.String())
}
t, ai := parseInitialByte(chunk[0])
if t != indefiniteLengthCborType {
return errors.New("cbor: cannot encode item type " + reflect.TypeOf(v).Kind().String() +
" for indefinite-length " + indefiniteLengthCborType.String())
}
if ai == additionalInformationAsIndefiniteLengthFlag {
return errors.New("cbor: cannot encode indefinite-length " + indefiniteLengthCborType.String() +
" as chunk of indefinite-length " + indefiniteLengthCborType.String())
}
return nil
}
// RawMessage is a raw encoded CBOR value.
@@ -262,7 +361,9 @@ type RawMessage []byte
// MarshalCBOR returns m or CBOR nil if m is nil.
func (m RawMessage) MarshalCBOR() ([]byte, error) {
if len(m) == 0 {
return cborNil, nil
b := make([]byte, len(cborNil))
copy(b, cborNil)
return b, nil
}
return m, nil
}
+53 -18
View File
@@ -6,28 +6,44 @@ package cbor
import (
"reflect"
"sort"
"strconv"
"strings"
)
// field holds shared struct field metadata returned by getFields().
type field struct {
name string
nameAsInt int64 // used to decoder to match field name with CBOR int
cborName []byte
cborNameByteString []byte // major type 2 name encoding iff cborName has major type 3
idx []int
typ reflect.Type
ef encodeFunc
ief isEmptyFunc
izf isZeroFunc
typInfo *typeInfo // used to decoder to reuse type info
tagged bool // used to choose dominant field (at the same level tagged fields dominate untagged fields)
omitEmpty bool // used to skip empty field
omitZero bool // used to skip zero field
keyAsInt bool // used to encode/decode field name as int
name string
nameAsInt int64 // used to match field name with CBOR int
idx []int
typ reflect.Type // used during cache building only
keyAsInt bool // used to encode/decode field name as int
tagged bool // used to choose dominant field (at the same level tagged fields dominate untagged fields)
omitEmpty bool // used to skip empty field
omitZero bool // used to skip zero field
}
type fields []*field
// encodingField extends field with encoding-specific data.
type encodingField struct {
field
cborName []byte
cborNameByteString []byte // major type 2 name encoding if cborName has major type 3
ef encodeFunc
ief isEmptyFunc
izf isZeroFunc
}
type encodingFields []*encodingField
// decodingField extends field with decoding-specific data.
type decodingField struct {
field
typInfo *typeInfo // used by decoder to reuse type info
}
type decodingFields []*decodingField
// indexFieldSorter sorts fields by field idx at each level, breaking ties with idx depth.
type indexFieldSorter struct {
fields fields
@@ -48,7 +64,7 @@ func (x *indexFieldSorter) Less(i, j int) bool {
return iIdx[k] < jIdx[k]
}
}
return len(iIdx) <= len(jIdx)
return len(iIdx) < len(jIdx)
}
// nameLevelAndTagFieldSorter sorts fields by field name, idx depth, and presence of tag.
@@ -69,6 +85,10 @@ func (x *nameLevelAndTagFieldSorter) Less(i, j int) bool {
if fi.name != fj.name {
return fi.name < fj.name
}
// Fields with the same name but different keyAsInt are in separate namespaces.
if fi.keyAsInt != fj.keyAsInt {
return fi.keyAsInt
}
if len(fi.idx) != len(fj.idx) {
return len(fi.idx) < len(fj.idx)
}
@@ -117,22 +137,37 @@ func getFields(t reflect.Type) (flds fields, structOptions string) {
}
}
// Normalize keyasint field names to their canonical integer string form.
// This ensures that "01", "+1", and "1" are treated as the same key
// during deduplication.
for _, f := range flds {
if f.keyAsInt {
nameAsInt, err := strconv.Atoi(f.name)
if err != nil {
continue // Leave invalid names for callers to report.
}
f.nameAsInt = int64(nameAsInt)
f.name = strconv.Itoa(nameAsInt)
}
}
sort.Sort(&nameLevelAndTagFieldSorter{flds})
// Keep visible fields.
j := 0 // index of next unique field
for i := 0; i < len(flds); {
name := flds[i].name
keyAsInt := flds[i].keyAsInt
if i == len(flds)-1 || // last field
name != flds[i+1].name || // field i has unique field name
name != flds[i+1].name || flds[i+1].keyAsInt != keyAsInt || // field i has unique (name, keyAsInt)
len(flds[i].idx) < len(flds[i+1].idx) || // field i is at a less nested level than field i+1
(flds[i].tagged && !flds[i+1].tagged) { // field i is tagged while field i+1 is not
flds[j] = flds[i]
j++
}
// Skip fields with the same field name.
for i++; i < len(flds) && name == flds[i].name; i++ { //nolint:revive
// Skip fields with the same (name, keyAsInt).
for i++; i < len(flds) && name == flds[i].name && keyAsInt == flds[i].keyAsInt; i++ { //nolint:revive
}
}
if j != len(flds) {
+5
View File
@@ -155,6 +155,7 @@ type TagSet interface {
Add(opts TagOptions, contentType reflect.Type, num uint64, nestedNum ...uint64) error
// Remove removes given tag content type from TagSet.
// Remove is a no-op if contentType is nil.
Remove(contentType reflect.Type)
tagProvider
@@ -245,7 +246,11 @@ func (t *syncTagSet) Add(opts TagOptions, contentType reflect.Type, num uint64,
}
// Remove removes given tag content type from TagSet.
// Remove is a no-op if contentType is nil.
func (t *syncTagSet) Remove(contentType reflect.Type) {
if contentType == nil {
return
}
for contentType.Kind() == reflect.Pointer {
contentType = contentType.Elem()
}
+10 -10
View File
@@ -54,7 +54,7 @@ func (e *MaxMapPairsError) Error() string {
return "cbor: exceeded max number of key-value pairs " + strconv.Itoa(e.maxMapPairs) + " for CBOR map"
}
// IndefiniteLengthError indicates found disallowed indefinite length items.
// IndefiniteLengthError indicates found disallowed indefinite-length items.
type IndefiniteLengthError struct {
t cborType
}
@@ -113,7 +113,7 @@ func (d *decoder) wellformedInternal(depth int, checkBuiltinTags bool) (int, err
}
return d.wellformedIndefiniteString(t, depth, checkBuiltinTags)
}
valInt := int(val)
valInt := int(val) //nolint:gosec
if valInt < 0 {
// Detect integer overflow
return 0, errors.New("cbor: " + t.String() + " length " + strconv.FormatUint(val, 10) + " is too large, causing integer overflow")
@@ -136,7 +136,7 @@ func (d *decoder) wellformedInternal(depth int, checkBuiltinTags bool) (int, err
return d.wellformedIndefiniteArrayOrMap(t, depth, checkBuiltinTags)
}
valInt := int(val)
valInt := int(val) //nolint:gosec
if valInt < 0 {
// Detect integer overflow
return 0, errors.New("cbor: " + t.String() + " length " + strconv.FormatUint(val, 10) + " is too large, it would cause integer overflow")
@@ -212,7 +212,7 @@ func (d *decoder) wellformedInternal(depth int, checkBuiltinTags bool) (int, err
return depth, nil
}
// wellformedIndefiniteString checks indefinite length byte/text string's well-formedness and returns max depth and error.
// wellformedIndefiniteString checks indefinite-length byte/text string's well-formedness and returns max depth and error.
func (d *decoder) wellformedIndefiniteString(t cborType, depth int, checkBuiltinTags bool) (int, error) {
var err error
for {
@@ -223,7 +223,7 @@ func (d *decoder) wellformedIndefiniteString(t cborType, depth int, checkBuiltin
d.off++
break
}
// Peek ahead to get next type and indefinite length status.
// Peek ahead to get next type and indefinite-length status.
nt, ai := parseInitialByte(d.data[d.off])
if t != nt {
return 0, &SyntaxError{"cbor: wrong element type " + nt.String() + " for indefinite-length " + t.String()}
@@ -238,7 +238,7 @@ func (d *decoder) wellformedIndefiniteString(t cborType, depth int, checkBuiltin
return depth, nil
}
// wellformedIndefiniteArrayOrMap checks indefinite length array/map's well-formedness and returns max depth and error.
// wellformedIndefiniteArrayOrMap checks indefinite-length array/map's well-formedness and returns max depth and error.
func (d *decoder) wellformedIndefiniteArrayOrMap(t cborType, depth int, checkBuiltinTags bool) (int, error) {
var err error
maxDepth := depth
@@ -326,7 +326,7 @@ func (d *decoder) wellformedHead() (t cborType, ai byte, val uint64, err error)
val = uint64(binary.BigEndian.Uint16(d.data[d.off : d.off+argumentSize]))
d.off += argumentSize
if t == cborTypePrimitives {
if err := d.acceptableFloat(float64(float16.Frombits(uint16(val)).Float32())); err != nil {
if err := d.acceptableFloat(float64(float16.Frombits(uint16(val)).Float32())); err != nil { //nolint:gosec
return 0, 0, 0, err
}
}
@@ -341,7 +341,7 @@ func (d *decoder) wellformedHead() (t cborType, ai byte, val uint64, err error)
val = uint64(binary.BigEndian.Uint32(d.data[d.off : d.off+argumentSize]))
d.off += argumentSize
if t == cborTypePrimitives {
if err := d.acceptableFloat(float64(math.Float32frombits(uint32(val)))); err != nil {
if err := d.acceptableFloat(float64(math.Float32frombits(uint32(val)))); err != nil { //nolint:gosec
return 0, 0, 0, err
}
}
@@ -379,12 +379,12 @@ func (d *decoder) wellformedHead() (t cborType, ai byte, val uint64, err error)
func (d *decoder) acceptableFloat(f float64) error {
switch {
case d.dm.nanDec == NaNDecodeForbidden && math.IsNaN(f):
case d.dm.nan == NaNDecodeForbidden && math.IsNaN(f):
return &UnacceptableDataItemError{
CBORType: cborTypePrimitives.String(),
Message: "floating-point NaN",
}
case d.dm.infDec == InfDecodeForbidden && math.IsInf(f, 0):
case d.dm.inf == InfDecodeForbidden && math.IsInf(f, 0):
return &UnacceptableDataItemError{
CBORType: cborTypePrimitives.String(),
Message: "floating-point infinity",