Compare commits

...

468 Commits

Author SHA1 Message Date
Faye Amacker
a01272272f Use reflect.PtrTo for older Go versions 2024-09-08 11:52:21 -05:00
Faye Amacker
d50bec7bf5 Reduce DecOptions.MaxNestedLevels default (tinygo)
Currently, tests panic if compiled with tinygo v0.33 when
decoding data with 24+ nested levels.

This commit reduces default MaxNestedLevels to 16 (was 32).
To allow testing, etc. the user configurable limits
are unchanged (allows up to 65535).
2024-09-08 10:24:43 -05:00
Faye Amacker
e1bc59bbec Don't decode CBOR tag data to interface (tinygo only)
tinygo v0.33 doesn't implement Type.AssignableTo(), which is
needed under the hood when decoding registered CBOR tag data
to Go interface.

More details in https://github.com/tinygo-org/tinygo/issues/4277.

This commit returns error of UnmarshalTypeError type when
decoding registered CBOR tag data to Go interface.

This change can be reverted after tinygo implements
Type.AssignalbeTo().
2024-09-08 09:55:29 -05:00
Faye Amacker
950ea6de1b Modify test to reduce nested levels for tinygo
tinygo v0.33 fails with roughly 24+ levels of nested objects.

This commit modifies TestUnmarshalDeepNesting to reduce nested
levels to 24 when testing marshaling and unmarshaling deeply
nested object.
2024-09-07 21:19:23 -05:00
Faye Amacker
fb90c7bd35 Modify tests for tinygo Type.String() mismatch
Return value of tinygo's Type.String() for function type doesn't
match equivalent call using Go's reflect package.

More details at https://github.com/tinygo-org/tinygo/issues/4458

This commit modifies tests to handle function type string being
different for tinygo and Go.
2024-09-07 20:42:17 -05:00
Faye Amacker
7730daf1b0 Make tinygo use MapIter.Key() & MapIter.Value()
tinygo v0.33 doesn't support Value.SetIterKey() and
Value.SetIterValue(), which are in go1.20 and newer.

This commit adds tinygo build tag to use MapIter.Key() and
MapIter.Value() instead to iterate map elements using reflect.
2024-09-07 20:24:51 -05:00
Faye Amacker
456253cc1e
Merge pull request #580 from fxamacker/dependabot/github_actions/github/codeql-action-3.26.6
Bump github/codeql-action from 3.26.2 to 3.26.6
2024-09-01 08:53:45 -05:00
Faye Amacker
bacf09a76d
Merge pull request #573 from fxamacker/fxamacker/add-go1.23-to-ci.yml
Add go1.23 to ci.yml
2024-08-31 14:19:57 -05:00
dependabot[bot]
eb1949be1c
Bump github/codeql-action from 3.26.2 to 3.26.6
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.26.2 to 3.26.6.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](429e197704...4dd16135b6)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-30 09:43:54 +00:00
Faye Amacker
cf1667f5ec
Merge pull request #575 from fxamacker/dependabot/github_actions/github/codeql-action-3.26.2
Bump github/codeql-action from 3.26.0 to 3.26.2
2024-08-18 11:02:41 -05:00
dependabot[bot]
e98958b255
Bump github/codeql-action from 3.26.0 to 3.26.2
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.26.0 to 3.26.2.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](eb055d739a...429e197704)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-15 09:25:11 +00:00
Faye Amacker
01e8588c4d
Add go1.23 to ci.yml
Added go1.18 and go1.23 to ci.yml.
2024-08-13 22:03:30 -05:00
Faye Amacker
5057ba65b1
Merge pull request #572 from fxamacker/fxamacker/bump-govulncheck-to-1.1.3
Bump govulncheck from 1.0.4 to 1.1.3
2024-08-09 19:30:39 -05:00
Faye Amacker
b31319c4fe
Bump govulncheck from 1.0.4 to 1.1.3 2024-08-08 18:47:47 -05:00
Faye Amacker
caefd953a3
Merge pull request #571 from fxamacker/dependabot/github_actions/github/codeql-action-3.26.0
Bump github/codeql-action from 3.25.15 to 3.26.0
2024-08-07 17:52:59 -05:00
dependabot[bot]
f9b54ce197
Bump github/codeql-action from 3.25.15 to 3.26.0
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.25.15 to 3.26.0.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](afb54ba388...eb055d739a)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-07 09:43:12 +00:00
Faye Amacker
454574e60e
Merge pull request #569 from fxamacker/dependabot/github_actions/github/codeql-action-3.25.15
Bump github/codeql-action from 3.25.14 to 3.25.15
2024-08-01 08:20:32 -05:00
dependabot[bot]
14cb6f1803
Bump github/codeql-action from 3.25.14 to 3.25.15
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.25.14 to 3.25.15.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](5cf07d8b70...afb54ba388)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-29 09:28:11 +00:00
Faye Amacker
21b3069a86
Merge pull request #567 from fxamacker/dependabot/github_actions/github/codeql-action-3.25.14
Bump github/codeql-action from 3.25.12 to 3.25.14
2024-07-27 14:01:06 -05:00
dependabot[bot]
a0ff9415ea
Bump github/codeql-action from 3.25.12 to 3.25.14
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.25.12 to 3.25.14.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](4fa2a79536...5cf07d8b70)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-25 09:29:18 +00:00
Faye Amacker
ed92c2af72
Merge pull request #565 from fxamacker/dependabot/github_actions/github/codeql-action-3.25.12
Bump github/codeql-action from 3.25.11 to 3.25.12
2024-07-14 09:38:09 -05:00
Faye Amacker
909af06032
Merge pull request #564 from fxamacker/dependabot/github_actions/actions/setup-go-5.0.2
Bump actions/setup-go from 5.0.1 to 5.0.2
2024-07-13 08:52:49 -05:00
dependabot[bot]
f30292c5b4
Bump github/codeql-action from 3.25.11 to 3.25.12
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.25.11 to 3.25.12.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](b611370bb5...4fa2a79536)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-12 09:26:52 +00:00
dependabot[bot]
1790aa66a1
Bump actions/setup-go from 5.0.1 to 5.0.2
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 5.0.1 to 5.0.2.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](cdcb360436...0a12ed9d6a)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-11 09:41:43 +00:00
Faye Amacker
ac4aec762e
Merge pull request #562 from fxamacker/dependabot/github_actions/github/codeql-action-3.25.11
Bump github/codeql-action from 3.25.10 to 3.25.11
2024-07-04 09:21:22 -05:00
dependabot[bot]
39256aa197
Bump github/codeql-action from 3.25.10 to 3.25.11
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.25.10 to 3.25.11.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](23acc5c183...b611370bb5)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-01 09:17:10 +00:00
Faye Amacker
02b69dbb52
Merge pull request #560 from fxamacker/fxamacker/update-readme-for-v2.7.0
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
Update README.md for v2.7.0 release
2024-06-23 23:49:42 -05:00
Faye Amacker
21f77863e8
Update README.md for v2.7.0 release
Add Kubernetes to list of users, update Quick Start, etc.
2024-06-23 23:32:04 -05:00
Faye Amacker
04bb1e774f
Merge pull request #557 from fxamacker/dependabot/github_actions/actions/checkout-4.1.7
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
Bump actions/checkout from 4.1.6 to 4.1.7
2024-06-16 08:18:57 -05:00
Faye Amacker
d09460d306
Merge pull request #559 from fxamacker/dependabot/github_actions/github/codeql-action-3.25.10
Bump github/codeql-action from 3.25.7 to 3.25.10
2024-06-15 21:50:08 -05:00
dependabot[bot]
41bb70ad5f
Bump github/codeql-action from 3.25.7 to 3.25.10
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.25.7 to 3.25.10.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](f079b84933...23acc5c183)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-14 09:43:44 +00:00
dependabot[bot]
7bdba08ff1
Bump actions/checkout from 4.1.6 to 4.1.7
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.6 to 4.1.7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](a5ac7e51b4...692973e3d9)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-13 09:30:37 +00:00
Faye Amacker
968f91aa82
Merge pull request #556 from benluddy/shuffle-empty-struct
Fix panic using SortFastShuffle (unreleased new feaure) to encode a struct with no fields.
2024-06-10 21:09:31 -05:00
Ben Luddy
ffab76a44a
Fix panic using SortFastShuffle to encode a struct with no fields.
Signed-off-by: Ben Luddy <bluddy@redhat.com>
2024-06-10 11:02:51 -04:00
Faye Amacker
878cfefeff
Merge pull request #554 from fxamacker/fxamacker/rename-bytestringmode
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
Rename ByteSliceMode to ByteSliceLaterFormatMode, etc
2024-06-09 22:08:52 -05:00
Faye Amacker
8ac5347e02
Merge pull request #553 from fxamacker/fxamacker/add-userbufferencmode-interface
Allow user to specify buffer by adding `cbor.MarshalToBuffer()`, `UserBufferEncMode` interface, etc.
2024-06-09 21:31:15 -05:00
Faye Amacker
d1b239987b Rename ByteSliceMode to ByteSliceLaterFormatMode
Also renamed related options to be consistent.
2024-06-09 21:16:42 -05:00
Faye Amacker
ed38331613 Add UserBufferEncMode interface with MarshalToBuffer()
This commit adds features related to allowing user to specify
a buffer rather than using built-in buffer pool:

- cbor.MarshalToBuffer() uses codec's default options to encode
  to user provided buffer instead of using built-in buffer pool.

- UserBufferEncMode interface extends EncMode interface with
  MarshalToBuffer() so user can provide buffer for encoding
  instead of using built-in buffer pool.

- EncOptions.UserBufferEncMode() returns UserBufferEncMode

- EncOptions.UserBufferEncModeWithTags() returns UserBufferEncMode

- EncOptions.UserBufferEncModeWithSharedTags() returns UserBufferEncMode
2024-06-09 19:27:59 -05:00
Faye Amacker
181632811f
Merge pull request #552 from fxamacker/fxamacker/add-InadmissibleTagContentTypeError
Replace `*errors.errorString` with `InadmissibleTagContentTypeError`
2024-06-09 19:22:35 -05:00
Faye Amacker
58a5aa05a3
Merge pull request #550 from fxamacker/fxamacker/improve-bytestringformat-dec-mode
Improve byte string format decoding options
2024-06-09 19:22:10 -05:00
Faye Amacker
b84b471463 Add and use InadmissibleTagContentTypeError
Currently *errors.errorString is returned when unmarshalling
built-in tags (tag 0-3 and 21-23) with inadmissible content type.

This commit adds InadmissibleTagContentTypeError and returns this
error with the same error message as before when unmarshalling
build-in tags with inadmissible content type.
2024-06-09 17:10:50 -05:00
Faye Amacker
a0ec46203a Add ByteStringExpectedFormatError 2024-06-09 15:51:46 -05:00
Faye Amacker
979be2321d Refactor to rename unreleased decoding options
While at it, also improve docs.
2024-06-09 15:13:29 -05:00
Faye Amacker
20a50eb0f2
Merge pull request #548 from fxamacker/fxamacker/bump-golangci-lint-to-1.56.2
Bump golangci-lint to 1.56.2
2024-06-09 13:24:40 -05:00
Faye Amacker
7d4681afd9
Update safer-golangci-lint.yml 2024-06-09 12:37:06 -05:00
Faye Amacker
ca0b21f9d8
Update dependabot.yml 2024-06-09 12:28:14 -05:00
Faye Amacker
0a66977b12
Update ci.yml
Add go1.21 to ci.yml.  It was omitted because local tests use it and test matrix on GitHub is too large.

Very old versions like go1.17 can be removed from ci.yml after fxamacker/cbor v1.27.0 is released.
2024-06-09 12:25:48 -05:00
Faye Amacker
dc2b567281
Bump golangci-lint to 1.56.2 2024-06-09 12:04:01 -05:00
Faye Amacker
86cb9931f8
Merge pull request #547 from fxamacker/dependabot/github_actions/github/codeql-action-3.25.7
Bump github/codeql-action from 3.25.6 to 3.25.7
2024-06-08 18:22:50 -05:00
Faye Amacker
0e2d14e462
Merge pull request #546 from benluddy/builtin-tag-encode-dwim
Disable conflicting encode options when marshaling cbor.Tag.
2024-06-06 21:04:14 -05:00
Ben Luddy
0cabb4afed
Disable conflicting encode options when marshaling cbor.Tag.
Encode options, especially those that control the mapping from Go type to CBOR type, can result in
output containing tag validity errors. For tag numbers that are built in, it's possible to "do the
right thing" and override those options on a case-by-case basis. This can't and does not prevent tag
validity errors for unrecognized tag numbers.

Signed-off-by: Ben Luddy <bluddy@redhat.com>
2024-06-03 11:36:14 -04:00
dependabot[bot]
3bfdc48056
Bump github/codeql-action from 3.25.6 to 3.25.7
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.25.6 to 3.25.7.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](9fdb3e4972...f079b84933)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-03 09:25:25 +00:00
Faye Amacker
69326f1c86
Merge pull request #541 from fxamacker/dependabot/github_actions/github/codeql-action-3.25.6
Bump github/codeql-action from 3.25.5 to 3.25.6
2024-05-29 09:25:46 -05:00
Faye Amacker
22b6d0ceeb
Merge pull request #544 from fxamacker/fxamacker/refactor-more
Use "cbor:" prefixed error msg when decoding with non-default TimeTagToAnyMode setting
2024-05-28 23:12:39 -05:00
Faye Amacker
20cde11035 Use "cbor:" prefixed error msg for TimeTagToAnyMode 2024-05-28 20:44:41 -05:00
Faye Amacker
3f436a63b9 Refactor to remove more magic numbers 2024-05-28 14:12:25 -05:00
Faye Amacker
c8b278fba2
Merge pull request #543 from fxamacker/fxamacker/bump-golangci-lint-to-1.54.2
Bump golangci-lint from 1.53.3 to 1.54.2
2024-05-27 22:48:26 -05:00
Faye Amacker
3ffae58c48
Update .golangci.yml 2024-05-27 22:25:11 -05:00
Faye Amacker
f928cfee6a
bump golangci-lint from 1.53.3 to 1.54.2 2024-05-27 22:12:28 -05:00
Faye Amacker
f1dd9245cf
Merge pull request #542 from fxamacker/fxamacker/refactor
Refactor and improve code
2024-05-27 22:08:27 -05:00
Faye Amacker
7ed3916635 Add comment 2024-05-27 22:04:31 -05:00
Faye Amacker
b426c3e8c6 Lint 2024-05-27 21:21:12 -05:00
Faye Amacker
fdf5bd8378 Refactor to remove more magic numbers 2024-05-27 21:14:04 -05:00
Faye Amacker
c5c66ba45d Refactor to remove more magic numbers 2024-05-27 20:08:55 -05:00
Faye Amacker
0efea34486 Refactor to remove magic numbers of tag numbers 2024-05-27 19:30:09 -05:00
Faye Amacker
22175debe5 Extract CBOR vars & funcs to new file cbor.go 2024-05-27 18:52:21 -05:00
Faye Amacker
23ddf31ad6 Refactor to remove magic number 0xff (break flag) 2024-05-27 18:22:34 -05:00
Faye Amacker
6d25a5c624 Refactor to remove magic number 31 (indef length) 2024-05-27 18:03:56 -05:00
Faye Amacker
b75278bc2e Refactor CBOR head funcs to remove magic numbers 2024-05-27 16:06:38 -05:00
Faye Amacker
03575b4950 Refactor code and lint 2024-05-27 15:33:34 -05:00
dependabot[bot]
28c7a5e2d1
Bump github/codeql-action from 3.25.5 to 3.25.6
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.25.5 to 3.25.6.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](b7cec75265...9fdb3e4972)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-27 09:15:35 +00:00
Faye Amacker
842ac3ea85
Merge pull request #540 from benluddy/output-invalid-rfc3339
Fix invalid RFC 3339 in TimeTagToAny (unreleased new feature)
2024-05-26 16:13:07 -05:00
Faye Amacker
6017d7fddf
Merge pull request #539 from fxamacker/dependabot/github_actions/github/codeql-action-3.25.5
Bump github/codeql-action from 3.25.4 to 3.25.5
2024-05-26 09:17:18 -05:00
Faye Amacker
3f0a98e935
Merge pull request #538 from fxamacker/dependabot/github_actions/actions/checkout-4.1.6
Bump actions/checkout from 4.1.5 to 4.1.6
2024-05-25 15:24:27 -05:00
Ben Luddy
77c571acd0
Don't produce invalid RFC 3339 using TimeTagToAny.
TimeTagToAny can produce strings that are not valid RFC 3339 timestamps through its use
of (time.Time).Format, whose signature does not allow it to return errors.

Signed-off-by: Ben Luddy <bluddy@redhat.com>
2024-05-23 13:01:57 -04:00
dependabot[bot]
e98d2c0521
Bump github/codeql-action from 3.25.4 to 3.25.5
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.25.4 to 3.25.5.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](ccf74c9479...b7cec75265)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-20 09:34:06 +00:00
dependabot[bot]
e41bca2ac0
Bump actions/checkout from 4.1.5 to 4.1.6
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.5 to 4.1.6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](44c2b7a8a4...a5ac7e51b4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-20 09:33:58 +00:00
Faye Amacker
6d407ed3b2
Merge pull request #537 from benluddy/map-sort-refactor
Refactor sorted map encode to use fewer buffers for nested maps.
2024-05-19 19:11:06 -05:00
Ben Luddy
6396be350f
Refactor sorted map encode to use fewer buffers for nested maps.
Runs a bit faster, but more importantly, only needs a single buffer to encode nested, sorted maps
instead of using multiple temporary buffers.

                                                            │ before.txt  │             after.txt              │
                                                            │   sec/op    │   sec/op     vs base               │
MarshalCanonical/Go_map[string]string_to_CBOR_map_canonical   1.464µ ± 0%   1.395µ ± 0%  -4.68% (p=0.000 n=10)
MarshalCanonical/Go_map[int]int_to_CBOR_map_canonical         192.1n ± 0%   186.2n ± 1%  -3.10% (p=0.000 n=10)
geomean                                                       530.2n        509.6n       -3.89%

                                                            │ before.txt │               after.txt               │
                                                            │    B/op    │    B/op      vs base                  │
MarshalCanonical/Go_map[string]string_to_CBOR_map_canonical   88.00 ± 0%   112.00 ± 0%  +27.27% (p=0.000 n=10)
MarshalCanonical/Go_map[int]int_to_CBOR_map_canonical         3.000 ± 0%    3.000 ± 0%        ~ (p=1.000 n=10) ¹
geomean                                                       16.25         18.33       +12.82%
¹ all samples are equal

                                                            │ before.txt │              after.txt              │
                                                            │ allocs/op  │ allocs/op   vs base                 │
MarshalCanonical/Go_map[string]string_to_CBOR_map_canonical   2.000 ± 0%   2.000 ± 0%       ~ (p=1.000 n=10) ¹
MarshalCanonical/Go_map[int]int_to_CBOR_map_canonical         1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=10) ¹
geomean                                                       1.414        1.414       +0.00%
¹ all samples are equal

Signed-off-by: Ben Luddy <bluddy@redhat.com>
2024-05-19 19:56:11 -04:00
Faye Amacker
367b524e9a
Merge pull request #535 from fxamacker/dependabot/github_actions/actions/checkout-4.1.5
Bump actions/checkout from 4.1.4 to 4.1.5
2024-05-19 10:21:01 -05:00
Faye Amacker
4bd2d2d9e9
Merge pull request #534 from fxamacker/dependabot/github_actions/github/codeql-action-3.25.4
Bump github/codeql-action from 3.25.3 to 3.25.4
2024-05-18 15:25:03 -05:00
dependabot[bot]
9d050c4603
Bump actions/checkout from 4.1.4 to 4.1.5
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.4 to 4.1.5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](0ad4b8fada...44c2b7a8a4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-13 09:23:38 +00:00
dependabot[bot]
56a1dfc7f5
Bump github/codeql-action from 3.25.3 to 3.25.4
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.25.3 to 3.25.4.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](d39d31e687...ccf74c9479)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-13 09:23:34 +00:00
Faye Amacker
eb04c4e11c
Merge pull request #533 from benluddy/diagnose-allocs
Improve speed & memory use for Diagnose() and DiagnoseFirst()
2024-05-12 19:20:00 -05:00
Faye Amacker
933f0441c6
Merge pull request #527 from benluddy/bignum-options
Add option to reject decoding bignum tags and encoding big.Int.
2024-05-12 18:33:38 -05:00
Faye Amacker
710fe00ffa Lint 2024-05-12 18:22:04 -05:00
Faye Amacker
8964976b42
Merge branch 'master' into bignum-options 2024-05-12 17:25:19 -05:00
Faye Amacker
92fdcbb6e1
Merge pull request #526 from benluddy/binary-marshaler-unmarshaler-options
Add options to disable BinaryMarshaler/BinaryUnmarshaler support.
2024-05-12 16:18:38 -05:00
Faye Amacker
fc5f0b6873
Merge branch 'master' into binary-marshaler-unmarshaler-options 2024-05-12 16:13:05 -05:00
Faye Amacker
742c956d4e
Merge pull request #521 from benluddy/user-provided-buffer
Add a method for marshaling directly into a user-provided buffer.
2024-05-12 14:57:56 -05:00
Faye Amacker
55884b4487
Merge pull request #532 from fxamacker/dependabot/github_actions/actions/setup-go-5.0.1
Bump actions/setup-go from 4.1.0 to 5.0.1
2024-05-11 09:11:29 -05:00
Ben Luddy
fcbe98d114
Add options to disable BinaryMarshaler/BinaryUnmarshaler support.
By default, values whose type implements BinaryMarshaler encode to a byte string whose contents are
the result of calling MarshalBinary, and decoding a byte string into a BinaryUnmarshaler calls
UnmarshalBinary on the contents of the byte string. These options make it possible to disable both
behaviors.

Signed-off-by: Ben Luddy <bluddy@redhat.com>
2024-05-10 15:26:53 -04:00
Ben Luddy
045f722ef3
Add MarshalToBuffer accepting user-provided buffer to *encMode.
The application calling Marshal has more information about the object it wants to marshal than this
library does (e.g. what type is it, what is the 95th-percentile serialized size of similar objects,
etc.). It can use this information to increase the utilization of encode buffers beyond what is
possible with a shared buffer pool for all calls to Marshal.

To avoid the backwards-incompatible change of expanding the method set of the EncMode interface,
invoking this method from an external package will require a type assertion, and there is currently
no exported "optional interface" declaration to make that more convenient.

Signed-off-by: Ben Luddy <bluddy@redhat.com>
2024-05-10 14:44:00 -04:00
Ben Luddy
8077474e3b
Remove encoderBuffer type in favor of using bytes.Buffer directly.
With its only remaining field being an embedded bytes.Buffer, there's no reason to retain the
encoderBuffer type.

Signed-off-by: Ben Luddy <bluddy@redhat.com>
2024-05-10 14:43:57 -04:00
Ben Luddy
039409578b
Use local arrays instead of the scratch field of encoderBuffer.
There doesn't seem to be any performance advantage to the [16]byte scratch space on each
encoderBuffer versus stack-allocated local array variables. If anything, there may be a spatial
locality advantage to using the stack. B/op and allocs/op are unchanged, sec/op is somewhere between
a wash and very marginally better:

                                                            │ scratch-withbuffer.txt │         scratch-stack.txt          │
                                                            │         sec/op         │   sec/op     vs base               │
Marshal/Go_bool_to_CBOR_bool                                             46.77n ± 1%   47.19n ± 0%  +0.90% (p=0.000 n=10)
Marshal/Go_uint64_to_CBOR_positive_int                                   66.03n ± 1%   66.15n ± 0%       ~ (p=0.393 n=10)
Marshal/Go_int64_to_CBOR_negative_int                                    52.34n ± 1%   52.44n ± 4%       ~ (p=0.306 n=10)
Marshal/Go_float64_to_CBOR_float                                         62.95n ± 1%   63.70n ± 1%  +1.19% (p=0.000 n=10)
Marshal/Go_[]uint8_to_CBOR_bytes                                         76.89n ± 1%   76.22n ± 0%  -0.86% (p=0.000 n=10)
Marshal/Go_string_to_CBOR_text                                           79.52n ± 1%   78.86n ± 1%  -0.82% (p=0.000 n=10)
Marshal/Go_[]int_to_CBOR_array                                           294.6n ± 0%   292.8n ± 0%  -0.64% (p=0.000 n=10)
Marshal/Go_map[string]string_to_CBOR_map                                 950.4n ± 1%   923.9n ± 1%  -2.79% (p=0.000 n=10)
Marshal/Go_map[string]interface{}_to_CBOR_map                            2.300µ ± 1%   2.310µ ± 0%       ~ (p=0.137 n=10)
Marshal/Go_struct_to_CBOR_map                                            1.422µ ± 1%   1.409µ ± 1%  -0.88% (p=0.006 n=10)
Marshal/Go_map[int]interface{}_to_CBOR_map                               2.207µ ± 0%   2.196µ ± 1%  -0.50% (p=0.004 n=10)
Marshal/Go_struct_keyasint_to_CBOR_map                                   1.394µ ± 0%   1.397µ ± 0%       ~ (p=0.050 n=10)
Marshal/Go_[]interface{}_to_CBOR_map                                     1.676µ ± 0%   1.632µ ± 1%  -2.63% (p=0.000 n=10)
Marshal/Go_struct_toarray_to_CBOR_array                                  1.370µ ± 1%   1.357µ ± 1%  -0.99% (p=0.000 n=10)
MarshalCanonical/Go_map[string]string_to_CBOR_map                        947.9n ± 1%   918.3n ± 1%  -3.12% (p=0.000 n=10)
MarshalCanonical/Go_map[string]string_to_CBOR_map_canonical              1.533µ ± 1%   1.503µ ± 2%  -1.96% (p=0.002 n=10)
MarshalCanonical/Go_struct_to_CBOR_map                                   307.9n ± 0%   309.0n ± 1%  +0.34% (p=0.014 n=10)
MarshalCanonical/Go_struct_to_CBOR_map_canonical                         309.9n ± 0%   309.4n ± 1%       ~ (p=1.000 n=10)
MarshalCanonical/Go_map[int]int_to_CBOR_map                              196.7n ± 0%   198.2n ± 0%  +0.76% (p=0.000 n=10)
MarshalCanonical/Go_map[int]int_to_CBOR_map_canonical                    197.6n ± 1%   198.6n ± 1%  +0.51% (p=0.005 n=10)
MarshalCOSE/128-Bit_Symmetric_Key                                        340.3n ± 0%   341.6n ± 0%  +0.38% (p=0.001 n=10)
MarshalCOSE/256-Bit_Symmetric_Key                                        344.0n ± 0%   344.1n ± 1%       ~ (p=0.540 n=10)
MarshalCOSE/ECDSA_P256_256-Bit_Key                                       433.2n ± 0%   438.1n ± 1%  +1.12% (p=0.000 n=10)
MarshalCWTClaims                                                         296.5n ± 0%   298.9n ± 1%  +0.83% (p=0.003 n=10)
MarshalSenML                                                             1.522µ ± 0%   1.524µ ± 1%       ~ (p=0.697 n=10)
MarshalSenMLShortestFloat16                                              1.534µ ± 1%   1.557µ ± 1%  +1.53% (p=0.001 n=10)
MarshalWebAuthn                                                          406.5n ± 0%   409.4n ± 1%  +0.71% (p=0.007 n=10)
MarshalCOSEMAC                                                           319.1n ± 0%   319.6n ± 1%       ~ (p=0.446 n=10)
MarshalCOSEMACWithTag                                                    399.8n ± 0%   397.9n ± 0%  -0.49% (p=0.007 n=10)
geomean                                                                  404.3n        403.5n       -0.22%

Signed-off-by: Ben Luddy <bluddy@redhat.com>
2024-05-10 14:25:19 -04:00
Ben Luddy
7f27a44147
Add option to reject decoding bignum tags and encoding big.Int.
Signed-off-by: Ben Luddy <bluddy@redhat.com>
2024-05-10 14:22:46 -04:00
Ben Luddy
4db77a21fe
Avoid buffer allocations for byte string diagnostic encodings.
The "Encoder" types for the various byte string diagnostic encodings (encoding/hex, encoding/base32,
and encoding/base64) include sizable internal buffers (1KiB at time of writing), and a new encoder
is created to produce the diagnostic encoding of every byte string in the input. This results in a
lot of extra allocations, especially for inputs containing many small byte strings.

                                        │  before.txt  │              after.txt              │
                                        │    sec/op    │   sec/op     vs base                │
Diagnose/byte_string_base16_encoding      315.90n ± 0%   92.34n ± 1%  -70.77% (p=0.000 n=10)
Diagnose/byte_string_base32_encoding      325.55n ± 0%   96.98n ± 0%  -70.21% (p=0.000 n=10)
Diagnose/byte_string_base32hex_encoding   325.45n ± 0%   96.97n ± 0%  -70.21% (p=0.000 n=10)
Diagnose/byte_string_base64url_encoding   336.50n ± 0%   96.50n ± 0%  -71.32% (p=0.000 n=10)
geomean                                    325.8n        95.68n       -70.63%

                                        │  before.txt  │             after.txt              │
                                        │     B/op     │    B/op     vs base                │
Diagnose/byte_string_base16_encoding      1344.00 ± 0%   80.00 ± 0%  -94.05% (p=0.000 n=10)
Diagnose/byte_string_base32_encoding      1344.00 ± 0%   80.00 ± 0%  -94.05% (p=0.000 n=10)
Diagnose/byte_string_base32hex_encoding   1344.00 ± 0%   80.00 ± 0%  -94.05% (p=0.000 n=10)
Diagnose/byte_string_base64url_encoding   1344.00 ± 0%   80.00 ± 0%  -94.05% (p=0.000 n=10)
geomean                                   1.313Ki        80.00       -94.05%

                                        │ before.txt │             after.txt              │
                                        │ allocs/op  │ allocs/op   vs base                │
Diagnose/byte_string_base16_encoding      5.000 ± 0%   2.000 ± 0%  -60.00% (p=0.000 n=10)
Diagnose/byte_string_base32_encoding      5.000 ± 0%   2.000 ± 0%  -60.00% (p=0.000 n=10)
Diagnose/byte_string_base32hex_encoding   5.000 ± 0%   2.000 ± 0%  -60.00% (p=0.000 n=10)
Diagnose/byte_string_base64url_encoding   5.000 ± 0%   2.000 ± 0%  -60.00% (p=0.000 n=10)
geomean                                   5.000        2.000       -60.00%

Signed-off-by: Ben Luddy <bluddy@redhat.com>
2024-05-08 13:08:16 -04:00
Ben Luddy
601260eb3b
Avoid string and slice allocations in diag for \uxxxx escapes.
│ before.txt  │              after.txt              │
                                          │   sec/op    │   sec/op     vs base                │
Diagnose/escaped_character_in_text_string   180.3n ± 0%   109.2n ± 2%  -39.41% (p=0.000 n=10)

                                          │ before.txt  │             after.txt              │
                                          │    B/op     │    B/op     vs base                │
Diagnose/escaped_character_in_text_string   192.00 ± 0%   72.00 ± 0%  -62.50% (p=0.000 n=10)

                                          │ before.txt │             after.txt              │
                                          │ allocs/op  │ allocs/op   vs base                │
Diagnose/escaped_character_in_text_string   5.000 ± 0%   2.000 ± 0%  -60.00% (p=0.000 n=10)

Signed-off-by: Ben Luddy <bluddy@redhat.com>
2024-05-08 13:08:16 -04:00
Ben Luddy
40e7a4b4f0
Remove unnecessary error checking from diagnose.
The write methods of *bytes.Buffer are guaranteed to return a nil error, so it is impossible to have
test coverage for the if statements handling non-nil errors.

Signed-off-by: Ben Luddy <bluddy@redhat.com>
2024-05-08 13:08:16 -04:00
Ben Luddy
5029ed5882
Add benchmarks for Diagnose.
Signed-off-by: Ben Luddy <bluddy@redhat.com>
2024-05-08 13:08:16 -04:00
dependabot[bot]
94c093d081
Bump actions/setup-go from 4.1.0 to 5.0.1
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 4.1.0 to 5.0.1.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](93397bea11...cdcb360436)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-06 09:49:17 +00:00
Faye Amacker
5a131e1c31
Merge pull request #531 from fxamacker/fxamacker/refactor-to-reuse-code
Refactor to reuse functions and improve code coverage
2024-05-05 18:49:52 -05:00
Faye Amacker
f83a7d00b4 Refactor to combine encodeHead() & encodedHeadLen()
This commit removes encodedHeadLen() and modifies encodeHead()
to return encoded head length to simplify code.
2024-05-05 18:46:04 -05:00
Faye Amacker
ca791949b1
Merge pull request #476 from benluddy/stdlib-json-byteslice-compatibility
Support automatic conversion between text and binary string representations
2024-05-05 17:29:15 -05:00
Faye Amacker
d3153aa3f0 Refactor to remove encodeFixedLengthStruct()
This commit removes encodeFixedLengthStruct() and reuses
encodeStruct() to simplify code.

Previously, encodeStruct() used extra buffer to encode elements
to get actual encoded element count.  To avoid this overhead,
encodeFixedLengthStruct() was created to encode fixed
length struct (struct without any "omitempty" fields) since
encoded element count is always known in this use case.

With PR #519 (https://github.com/fxamacker/cbor/pull/519),
encodeStruct() doesn't use extra buffer any more, and
encodeFixedLengthStruct() isn't necessary.
2024-05-04 20:27:01 -05:00
Faye Amacker
28a8572444
Merge pull request #519 from benluddy/struct-encode-directly
Encode structs directly to output buffer.
2024-05-04 18:11:39 -05:00
Faye Amacker
99b92cf3e8
Merge pull request #530 from fxamacker/dependabot/github_actions/actions/checkout-4.1.4
Bump actions/checkout from 4.1.3 to 4.1.4
2024-05-04 08:34:11 -05:00
Faye Amacker
4eb87787b7
Merge pull request #529 from fxamacker/dependabot/github_actions/github/codeql-action-3.25.3
Bump github/codeql-action from 3.25.1 to 3.25.3
2024-05-03 07:47:19 -05:00
Ben Luddy
83e9c2bff8
Support auto conversion of byte strings to and from text encodings.
These options improve interoperability with programs that use JSON to encode and decode objects to
and from both struct types and empty interface values.

Signed-off-by: Ben Luddy <bluddy@redhat.com>
2024-04-29 12:53:24 -04:00
Ben Luddy
d981dece35
Encode structs directly to output buffer.
For variable-length structs (structs with omitempty fields), encoding to the unused capacity at the
end of the output buffer while counting nonempty items is cheaper than using a separate temporary
buffer (no pool interactions and better spatial locality). Copying the items can be avoided entirely
by reserving space in the output buffer for the head if the encoded length of the head can be
predicted before checking optional fields.

                                                                     │ before.txt  │              after.txt              │
                                                                     │   sec/op    │   sec/op     vs base                │
Marshal/Go_struct_to_CBOR_map                                          1.404µ ± 0%   1.408µ ± 1%        ~ (p=0.170 n=10)
Marshal/Go_struct_many_fields_all_omitempty_all_empty_to_CBOR_map      443.8n ± 0%   430.6n ± 0%   -2.99% (p=0.000 n=10)
Marshal/Go_struct_some_fields_all_omitempty_all_empty_to_CBOR_map      181.7n ± 0%   163.5n ± 0%  -10.04% (p=0.000 n=10)
Marshal/Go_struct_many_fields_all_omitempty_all_nonempty_to_CBOR_map   813.5n ± 0%   784.8n ± 0%   -3.53% (p=0.000 n=10)
Marshal/Go_struct_some_fields_all_omitempty_all_nonempty_to_CBOR_map   300.8n ± 0%   275.4n ± 0%   -8.43% (p=0.000 n=10)
Marshal/Go_struct_many_fields_one_omitempty_to_CBOR_map                763.8n ± 0%   727.7n ± 0%   -4.73% (p=0.000 n=10)
Marshal/Go_struct_some_fields_one_omitempty_to_CBOR_map                284.2n ± 0%   257.6n ± 0%   -9.36% (p=0.000 n=10)
Marshal/Go_struct_keyasint_to_CBOR_map                                 1.422µ ± 0%   1.414µ ± 1%   -0.56% (p=0.029 n=10)
Marshal/Go_struct_toarray_to_CBOR_array                                1.341µ ± 1%   1.338µ ± 1%        ~ (p=0.340 n=10)
MarshalCanonical/Go_struct_to_CBOR_map                                 386.4n ± 0%   392.4n ± 0%   +1.57% (p=0.000 n=10)
MarshalCanonical/Go_struct_to_CBOR_map_canonical                       386.9n ± 0%   384.8n ± 0%   -0.52% (p=0.001 n=10)
geomean                                                                560.5n        540.4n        -3.59%

                                                                     │ before.txt │              after.txt              │
                                                                     │    B/op    │    B/op     vs base                 │
Marshal/Go_struct_to_CBOR_map                                          208.0 ± 0%   208.0 ± 0%       ~ (p=1.000 n=10) ¹
Marshal/Go_struct_many_fields_all_omitempty_all_empty_to_CBOR_map      1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=10) ¹
Marshal/Go_struct_some_fields_all_omitempty_all_empty_to_CBOR_map      1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=10) ¹
Marshal/Go_struct_many_fields_all_omitempty_all_nonempty_to_CBOR_map   176.0 ± 0%   176.0 ± 0%       ~ (p=1.000 n=10) ¹
Marshal/Go_struct_some_fields_all_omitempty_all_nonempty_to_CBOR_map   48.00 ± 0%   48.00 ± 0%       ~ (p=1.000 n=10) ¹
Marshal/Go_struct_many_fields_one_omitempty_to_CBOR_map                160.0 ± 0%   160.0 ± 0%       ~ (p=1.000 n=10) ¹
Marshal/Go_struct_some_fields_one_omitempty_to_CBOR_map                48.00 ± 0%   48.00 ± 0%       ~ (p=1.000 n=10) ¹
Marshal/Go_struct_keyasint_to_CBOR_map                                 192.0 ± 0%   192.0 ± 0%       ~ (p=1.000 n=10) ¹
Marshal/Go_struct_toarray_to_CBOR_array                                192.0 ± 0%   192.0 ± 0%       ~ (p=1.000 n=10) ¹
MarshalCanonical/Go_struct_to_CBOR_map                                 64.00 ± 0%   64.00 ± 0%       ~ (p=1.000 n=10) ¹
MarshalCanonical/Go_struct_to_CBOR_map_canonical                       64.00 ± 0%   64.00 ± 0%       ~ (p=1.000 n=10) ¹
geomean                                                                46.18        46.18       +0.00%
¹ all samples are equal

                                                                     │ before.txt │              after.txt              │
                                                                     │ allocs/op  │ allocs/op   vs base                 │
Marshal/Go_struct_to_CBOR_map                                          1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=10) ¹
Marshal/Go_struct_many_fields_all_omitempty_all_empty_to_CBOR_map      1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=10) ¹
Marshal/Go_struct_some_fields_all_omitempty_all_empty_to_CBOR_map      1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=10) ¹
Marshal/Go_struct_many_fields_all_omitempty_all_nonempty_to_CBOR_map   1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=10) ¹
Marshal/Go_struct_some_fields_all_omitempty_all_nonempty_to_CBOR_map   1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=10) ¹
Marshal/Go_struct_many_fields_one_omitempty_to_CBOR_map                1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=10) ¹
Marshal/Go_struct_some_fields_one_omitempty_to_CBOR_map                1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=10) ¹
Marshal/Go_struct_keyasint_to_CBOR_map                                 1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=10) ¹
Marshal/Go_struct_toarray_to_CBOR_array                                1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=10) ¹
MarshalCanonical/Go_struct_to_CBOR_map                                 1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=10) ¹
MarshalCanonical/Go_struct_to_CBOR_map_canonical                       1.000 ± 0%   1.000 ± 0%       ~ (p=1.000 n=10) ¹
geomean                                                                1.000        1.000       +0.00%
¹ all samples are equal

Signed-off-by: Ben Luddy <bluddy@redhat.com>
2024-04-29 11:49:33 -04:00
dependabot[bot]
53f2a01d30
Bump actions/checkout from 4.1.3 to 4.1.4
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.3 to 4.1.4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](1d96c772d1...0ad4b8fada)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-29 09:44:31 +00:00
dependabot[bot]
ef2ff186b1
Bump github/codeql-action from 3.25.1 to 3.25.3
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.25.1 to 3.25.3.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](c7f9125735...d39d31e687)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-29 09:44:24 +00:00
Faye Amacker
da82dfa419
Merge pull request #524 from benluddy/bytestring-to-time
Add a decoding option to allow decoding byte string into time.Time.
2024-04-28 13:39:04 -05:00
Faye Amacker
7cf590f077
Merge pull request #523 from fxamacker/dependabot/github_actions/github/codeql-action-3.25.1
Bump github/codeql-action from 3.24.10 to 3.25.1
2024-04-27 07:56:48 -05:00
Faye Amacker
9d8bb08bd0
Merge pull request #522 from fxamacker/dependabot/github_actions/actions/checkout-4.1.3
Bump actions/checkout from 4.1.2 to 4.1.3
2024-04-25 21:44:24 -05:00
Suriyan S
692ac0e89b
Add a decoding option to allow decoding byte string into time.Time.
Co-authored-by: Ben Luddy <bluddy@redhat.com>
Signed-off-by: Ben Luddy <bluddy@redhat.com>
2024-04-24 13:16:26 -04:00
dependabot[bot]
8d62d4c5c2
Bump github/codeql-action from 3.24.10 to 3.25.1
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.24.10 to 3.25.1.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](4355270be1...c7f9125735)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-22 09:31:06 +00:00
dependabot[bot]
be41d80f79
Bump actions/checkout from 4.1.2 to 4.1.3
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.2 to 4.1.3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](9bb56186c3...1d96c772d1)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-22 09:30:57 +00:00
Faye Amacker
8ef865cdae
Merge pull request #515 from benluddy/sortmode-determinism
Add SortMode to encode struct fields in a less predictable order.
2024-04-20 18:38:00 -05:00
Faye Amacker
69a85b8597
Merge pull request #513 from benluddy/float-nan-inf
Allow rejection of NaN and Inf float values on encode and decode.
2024-04-20 16:22:58 -05:00
Ben Luddy
cdc2c42a47
Allow rejection of NaN and Inf float values on encode and decode.
Signed-off-by: Ben Luddy <bluddy@redhat.com>
2024-04-14 21:45:02 -04:00
Faye Amacker
8ea912ae02
Merge pull request #518 from fxamacker/dependabot/github_actions/github/codeql-action-3.24.10
Bump github/codeql-action from 3.24.9 to 3.24.10
2024-04-14 16:40:56 -05:00
Ben Luddy
4bbff65cd6
Add shuffled SortMode for encoding struct fields less predictably.
The motivation is to prevent programs that consume the output from assuming (incorrectly) that it
was encoded deterministically, even when implementation details make the output apparently stable.

Signed-off-by: Ben Luddy <bluddy@redhat.com>
2024-04-12 17:04:44 -04:00
dependabot[bot]
9d209eb628
Bump github/codeql-action from 3.24.9 to 3.24.10
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.24.9 to 3.24.10.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](1b1aada464...4355270be1)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-08 09:37:58 +00:00
Faye Amacker
9f099e88c1
Merge pull request #481 from benluddy/simplevalue-govalues
Add decode option to allow rejecting inputs that contain certain simple values.
2024-04-07 18:10:08 -05:00
Faye Amacker
4330f5904d
Merge pull request #516 from benluddy/skip-sort-for-single-element-maps
Bypass sorting overhead for single-entry maps.
2024-04-07 15:08:00 -05:00
Ben Luddy
30de9af692
Bypass sorting overhead for single-entry maps.
The map[int]int case is a single-entry map. This optimization is an easy win for a case that is not
extraordinarily rare.

MarshalCanonical/Go_map[string]string_to_CBOR_map             895.9n ± 2%   899.3n ± 1%        ~ (p=0.912 n=10)
MarshalCanonical/Go_map[string]string_to_CBOR_map_canonical   1.763µ ± 4%   1.763µ ± 2%        ~ (p=0.753 n=10)
MarshalCanonical/Go_struct_to_CBOR_map                        317.0n ± 2%   311.1n ± 0%   -1.88% (p=0.000 n=10)
MarshalCanonical/Go_struct_to_CBOR_map_canonical              315.5n ± 1%   314.6n ± 4%        ~ (p=0.239 n=10)
MarshalCanonical/Go_map[int]int_to_CBOR_map                   194.8n ± 2%   196.2n ± 1%        ~ (p=0.839 n=10)
MarshalCanonical/Go_map[int]int_to_CBOR_map_canonical         326.3n ± 3%   190.9n ± 3%  -41.47% (p=0.000 n=10)
geomean                                                       464.5n        424.0n        -8.71%

Signed-off-by: Ben Luddy <bluddy@redhat.com>
2024-04-04 14:05:10 -04:00
Ben Luddy
28078a7180
Add option to configure how simple values are decoded.
Applications can use the decode option SimpleValues to override the default unmarshal behavior on a
per-simple-value basis. The option is open to future extension, and currently supports the choice
between the default backwards-compatible behavior (true as true, false as false, null as nil, and
undefined as nil, and cbor.SimpleValue(N) for each currently-unregistered but well-formed simple
value number N) and outright rejection with the new error type UnacceptableDataItemError.

Signed-off-by: Ben Luddy <bluddy@redhat.com>
2024-04-02 14:52:27 -04:00
Faye Amacker
3cec62b9f7
Merge pull request #506 from ssuriyan7/rfc3339-timestring
Add decoding option to specific how to decode tag 0 or 1 into any
2024-03-31 20:06:39 -05:00
Suriyan S
219a19cc05 Add decoding option to specific how to decode tag 0 or 1 into any
Adds a decoding option to specify how to decode CBOR tag 0 or 1 data item into an empty interface.
Based on the specified mode, Unmarshal can return a time.Time value or a time string in a specific format.

Signed-off-by: Suriyan Subbarayan suriyansub710@gmail.com
2024-03-29 22:14:30 -04:00
Faye Amacker
571b811773
Merge pull request #511 from fxamacker/dependabot/github_actions/github/codeql-action-3.24.9
Bump github/codeql-action from 3.24.7 to 3.24.9
2024-03-25 08:40:14 -05:00
dependabot[bot]
5199357a77
Bump github/codeql-action from 3.24.7 to 3.24.9
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.24.7 to 3.24.9.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](3ab4101902...1b1aada464)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-25 09:46:58 +00:00
Faye Amacker
103732aa81
Merge pull request #510 from fxamacker/dependabot/github_actions/github/codeql-action-3.24.7
Bump github/codeql-action from 3.24.6 to 3.24.7
2024-03-20 08:20:19 -05:00
Faye Amacker
291fd8364d
Merge pull request #509 from fxamacker/dependabot/github_actions/actions/checkout-4.1.2
Bump actions/checkout from 4.1.1 to 4.1.2
2024-03-18 08:56:25 -05:00
dependabot[bot]
942ae0f39d
Bump github/codeql-action from 3.24.6 to 3.24.7
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.24.6 to 3.24.7.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](8a470fddaf...3ab4101902)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-18 09:03:46 +00:00
dependabot[bot]
0c3e99532e
Bump actions/checkout from 4.1.1 to 4.1.2
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.1 to 4.1.2.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](b4ffde65f4...9bb56186c3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-18 09:03:40 +00:00
Faye Amacker
a35fe34aac
Merge pull request #503 from benluddy/decode-into-time-interface-option-side-effects
Decouple time.Time parsing from empty interface behavior.
2024-03-13 20:24:25 -05:00
Ben Luddy
865ac9603f
Decouple time.Time parsing from empty interface behavior.
Due to an implementation detail, options controlling decodes into empty interface values could in
some cases affect the behavior of decoding into time.Time. That is fixed. This patch also clarifies
the documentation and reconciles a couple edge-case behaviors:

1. In TimeTag mode DecTagIgnored, decoding a null or undefined simple value enclosed in a tag other
than 0 or 1 to time.Time is now a no-op. This is the same as the existing behavior when decoding an
untagged null or undefined.

2. The content type enclosed by tags 0 and 1 were not being validated if enclosed within an
unrecognized tag. This has been fixed.

Signed-off-by: Ben Luddy <bluddy@redhat.com>
2024-03-13 09:05:49 -04:00
Faye Amacker
4a6e6d1e02
Merge pull request #504 from fxamacker/fxamacker/enable-more-linters
Enable more linters in .golangci.yml
2024-03-10 17:52:33 -05:00
Faye Amacker
ab7d670cd0
Update pull_request_template.md
Remove hardcoded number of linters since
that can change.
2024-03-09 19:47:37 -06:00
Faye Amacker
f2351b634e
Update ci.yml to use go1.22 2024-03-09 18:04:32 -06:00
Faye Amacker
20ae8ef1c6
Enable more linters in .golangci.yml 2024-03-09 17:55:37 -06:00
Faye Amacker
57f786f744
Merge pull request #502 from fxamacker/dependabot/github_actions/github/codeql-action-3.24.6
Bump github/codeql-action from 3.24.5 to 3.24.6
2024-03-06 22:09:46 -06:00
dependabot[bot]
c059e241ab
Bump github/codeql-action from 3.24.5 to 3.24.6
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.24.5 to 3.24.6.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](47b3d888fe...8a470fddaf)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-04 09:05:34 +00:00
Faye Amacker
06d25daa2c
Merge pull request #498 from fxamacker/dependabot/github_actions/github/codeql-action-3.24.5
Bump github/codeql-action from 3.24.0 to 3.24.5
2024-03-03 08:14:17 -06:00
dependabot[bot]
8eee285d8a
Bump github/codeql-action from 3.24.0 to 3.24.5
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.24.0 to 3.24.5.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](e8893c57a1...47b3d888fe)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-26 09:48:45 +00:00
Faye Amacker
cfbd0ff46d
Merge pull request #485 from fxamacker/fxamacker/check-marshaler-data-wellformedness
Check well-formedness of data from MarshalCBOR
2024-02-25 12:26:07 -06:00
Faye Amacker
00109c1a54
Merge pull request #495 from x448/patch-6
Remove OpenSSF Scorecard until scorecard bug is fixed
2024-02-20 21:01:19 -06:00
Montgomery Edwards⁴⁴⁸
5a6886bba6
Remove OpenSSF Scorecard until score is fixed
Remove OpenSSF Scorecard badge from README.md until
this OpenSSF scorecard bug is fixed:
https://github.com/ossf/scorecard/issues/3891

The fix is in progress at OpenSSF Scorecard:
https://github.com/ossf/scorecard/pull/3893
2024-02-20 20:44:23 -06:00
Faye Amacker
a3a1d711f1 Use custom DecModes to check Marshaler data
This commit checks Marshaler data for well-formedness
(and very limited partial validation) by reusing one
of four DecModes created during startup.  These modes
are safe for parallel use by different Marshalers.
2024-02-19 18:21:28 -06:00
Faye Amacker
17164a1354 Refactor to use consts for max nested level settings 2024-02-19 16:52:54 -06:00
Faye Amacker
afbafc4054
Merge pull request #492 from benluddy/dupmapkey-same-struct-field
Treat map keys matching the same struct field as duplicates.
2024-02-19 15:54:00 -06:00
Faye Amacker
79596076c5
Merge pull request #493 from fxamacker/fxamacker/bump-govulncheck-to-v1.0.4
Bump govulncheck from v1.0.1 to v1.0.4
2024-02-18 22:39:11 -06:00
Faye Amacker
0b4149dac9
Bump govulncheck to v1.0.4
Update govulncheck from 1.0.1 to 1.0.4
in govulncheck.yml.
2024-02-18 15:35:05 -06:00
Ben Luddy
1d28086023
Treat map keys matching the same struct field as duplicates.
This resolves the case where two map keys could match the same struct field without being treated as
duplicate map keys. The DupMapKey documentation has been updated accordingly.

A map key that matches a previously-matched field will no longer quietly fall back to a
case-insensitive match and is now always treated as a duplicate key. Case-sensitive matching of map
key to struct field name is now a map lookup to improve the quadratic worst-case:

                                                                   │ manykeys-before.txt │         manykeys-after.txt          │
                                                                   │       sec/op        │   sec/op     vs base                │
UnmarshalMapToStruct/default_options/many_fields_one_key_per_field          24.137µ ± 4%   5.240µ ± 1%  -78.29% (p=0.000 n=10)

                                                                   │ manykeys-before.txt │        manykeys-after.txt         │
                                                                   │        B/op         │   B/op    vs base                 │
UnmarshalMapToStruct/default_options/many_fields_one_key_per_field            112.0 ± 0%   0.0 ± 0%  -100.00% (p=0.000 n=10)

                                                                   │ manykeys-before.txt │         manykeys-after.txt          │
                                                                   │      allocs/op      │ allocs/op   vs base                 │
UnmarshalMapToStruct/default_options/many_fields_one_key_per_field            1.000 ± 0%   0.000 ± 0%  -100.00% (p=0.000 n=10)

This fix cleared the way to optimize the way duplicate keys are tracked, especially for inputs that
contain no unknown fields:

                                                                               │  before.txt  │              after.txt              │
                                                                               │    sec/op    │   sec/op     vs base                │
UnmarshalMapToStruct/default_options/all_known_fields                             642.7n ± 3%   735.4n ± 0%  +14.42% (p=0.000 n=10)
UnmarshalMapToStruct/default_options/all_known_duplicate_fields                  1611.5n ± 1%   521.1n ± 1%  -67.66% (p=0.000 n=10)
UnmarshalMapToStruct/default_options/all_unknown_fields                           1.731µ ± 3%   1.174µ ± 0%  -32.18% (p=0.000 n=10)
UnmarshalMapToStruct/default_options/all_unknown_duplicate_fields                 1.726µ ± 2%   1.076µ ± 0%  -37.66% (p=0.000 n=10)
UnmarshalMapToStruct/reject_unknown/all_known_fields                              638.3n ± 1%   725.5n ± 1%  +13.66% (p=0.000 n=10)
UnmarshalMapToStruct/reject_unknown/all_known_duplicate_fields                    476.1n ± 1%   514.7n ± 0%   +8.11% (p=0.000 n=10)
UnmarshalMapToStruct/reject_unknown/all_unknown_fields                            448.1n ± 1%   398.8n ± 1%  -11.00% (p=0.000 n=10)
UnmarshalMapToStruct/reject_unknown/all_unknown_duplicate_fields                  447.9n ± 0%   399.9n ± 1%  -10.71% (p=0.000 n=10)
UnmarshalMapToStruct/reject_duplicate/all_known_fields                           1825.5n ± 0%   726.0n ± 0%  -60.23% (p=0.000 n=10)
UnmarshalMapToStruct/reject_duplicate/all_known_duplicate_fields                  744.3n ± 0%   440.8n ± 0%  -40.78% (p=0.000 n=10)
UnmarshalMapToStruct/reject_duplicate/all_unknown_fields                          2.885µ ± 1%   3.329µ ± 0%  +15.37% (p=0.000 n=10)
UnmarshalMapToStruct/reject_duplicate/all_unknown_duplicate_fields                844.8n ± 0%   738.7n ± 1%  -12.56% (p=0.000 n=10)
UnmarshalMapToStruct/reject_unknown_and_duplicate/all_known_fields               1828.5n ± 0%   723.4n ± 0%  -60.44% (p=0.000 n=10)
UnmarshalMapToStruct/reject_unknown_and_duplicate/all_known_duplicate_fields      744.3n ± 0%   436.4n ± 0%  -41.36% (p=0.000 n=10)
UnmarshalMapToStruct/reject_unknown_and_duplicate/all_unknown_fields              643.4n ± 0%   435.3n ± 0%  -32.34% (p=0.000 n=10)
UnmarshalMapToStruct/reject_unknown_and_duplicate/all_unknown_duplicate_fields    642.1n ± 0%   435.6n ± 1%  -32.15% (p=0.000 n=10)
geomean                                                                           936.7n        748.3n       -29.34%

                                                                               │ before.txt  │                after.txt                 │
                                                                               │    B/op     │    B/op      vs base                     │
UnmarshalMapToStruct/default_options/all_known_fields                             16.00 ± 0%     0.00 ± 0%  -100.00% (p=0.000 n=10)
UnmarshalMapToStruct/default_options/all_known_duplicate_fields                   16.00 ± 0%     0.00 ± 0%  -100.00% (p=0.000 n=10)
UnmarshalMapToStruct/default_options/all_unknown_fields                           16.00 ± 0%     0.00 ± 0%  -100.00% (p=0.000 n=10)
UnmarshalMapToStruct/default_options/all_unknown_duplicate_fields                 16.00 ± 0%     0.00 ± 0%  -100.00% (p=0.000 n=10)
UnmarshalMapToStruct/reject_unknown/all_known_fields                              16.00 ± 0%     0.00 ± 0%  -100.00% (p=0.000 n=10)
UnmarshalMapToStruct/reject_unknown/all_known_duplicate_fields                    24.00 ± 0%     0.00 ± 0%  -100.00% (p=0.000 n=10)
UnmarshalMapToStruct/reject_unknown/all_unknown_fields                           24.000 ± 0%    8.000 ± 0%   -66.67% (p=0.000 n=10)
UnmarshalMapToStruct/reject_unknown/all_unknown_duplicate_fields                 24.000 ± 0%    8.000 ± 0%   -66.67% (p=0.000 n=10)
UnmarshalMapToStruct/reject_duplicate/all_known_fields                            800.0 ± 0%      0.0 ± 0%  -100.00% (p=0.000 n=10)
UnmarshalMapToStruct/reject_duplicate/all_known_duplicate_fields                 648.00 ± 0%    40.00 ± 0%   -93.83% (p=0.000 n=10)
UnmarshalMapToStruct/reject_duplicate/all_unknown_fields                          800.0 ± 0%   1285.0 ± 0%   +60.62% (p=0.000 n=10)
UnmarshalMapToStruct/reject_duplicate/all_unknown_duplicate_fields                648.0 ± 0%    248.0 ± 0%   -61.73% (p=0.000 n=10)
UnmarshalMapToStruct/reject_unknown_and_duplicate/all_known_fields                800.0 ± 0%      0.0 ± 0%  -100.00% (p=0.000 n=10)
UnmarshalMapToStruct/reject_unknown_and_duplicate/all_known_duplicate_fields     648.00 ± 0%    40.00 ± 0%   -93.83% (p=0.000 n=10)
UnmarshalMapToStruct/reject_unknown_and_duplicate/all_unknown_fields             616.00 ± 0%    24.00 ± 0%   -96.10% (p=0.000 n=10)
UnmarshalMapToStruct/reject_unknown_and_duplicate/all_unknown_duplicate_fields   616.00 ± 0%    24.00 ± 0%   -96.10% (p=0.000 n=10)
geomean                                                                           113.6                     ?                       ¹ ²
¹ summaries must be >0 to compute geomean
² ratios must be >0 to compute geomean

                                                                               │ before.txt │                after.txt                │
                                                                               │ allocs/op  │ allocs/op   vs base                     │
UnmarshalMapToStruct/default_options/all_known_fields                            1.000 ± 0%   0.000 ± 0%  -100.00% (p=0.000 n=10)
UnmarshalMapToStruct/default_options/all_known_duplicate_fields                  1.000 ± 0%   0.000 ± 0%  -100.00% (p=0.000 n=10)
UnmarshalMapToStruct/default_options/all_unknown_fields                          1.000 ± 0%   0.000 ± 0%  -100.00% (p=0.000 n=10)
UnmarshalMapToStruct/default_options/all_unknown_duplicate_fields                1.000 ± 0%   0.000 ± 0%  -100.00% (p=0.000 n=10)
UnmarshalMapToStruct/reject_unknown/all_known_fields                             1.000 ± 0%   0.000 ± 0%  -100.00% (p=0.000 n=10)
UnmarshalMapToStruct/reject_unknown/all_known_duplicate_fields                   2.000 ± 0%   0.000 ± 0%  -100.00% (p=0.000 n=10)
UnmarshalMapToStruct/reject_unknown/all_unknown_fields                           2.000 ± 0%   1.000 ± 0%   -50.00% (p=0.000 n=10)
UnmarshalMapToStruct/reject_unknown/all_unknown_duplicate_fields                 2.000 ± 0%   1.000 ± 0%   -50.00% (p=0.000 n=10)
UnmarshalMapToStruct/reject_duplicate/all_known_fields                           15.00 ± 0%    0.00 ± 0%  -100.00% (p=0.000 n=10)
UnmarshalMapToStruct/reject_duplicate/all_known_duplicate_fields                 5.000 ± 0%   2.000 ± 0%   -60.00% (p=0.000 n=10)
UnmarshalMapToStruct/reject_duplicate/all_unknown_fields                         15.00 ± 0%   17.00 ± 0%   +13.33% (p=0.000 n=10)
UnmarshalMapToStruct/reject_duplicate/all_unknown_duplicate_fields               5.000 ± 0%   5.000 ± 0%         ~ (p=1.000 n=10) ¹
UnmarshalMapToStruct/reject_unknown_and_duplicate/all_known_fields               15.00 ± 0%    0.00 ± 0%  -100.00% (p=0.000 n=10)
UnmarshalMapToStruct/reject_unknown_and_duplicate/all_known_duplicate_fields     5.000 ± 0%   2.000 ± 0%   -60.00% (p=0.000 n=10)
UnmarshalMapToStruct/reject_unknown_and_duplicate/all_unknown_fields             4.000 ± 0%   2.000 ± 0%   -50.00% (p=0.000 n=10)
UnmarshalMapToStruct/reject_unknown_and_duplicate/all_unknown_duplicate_fields   4.000 ± 0%   2.000 ± 0%   -50.00% (p=0.000 n=10)
geomean                                                                          3.043                    ?                       ² ³
¹ all samples are equal
² summaries must be >0 to compute geomean
³ ratios must be >0 to compute geomean

Signed-off-by: Ben Luddy <bluddy@redhat.com>
2024-02-14 10:04:21 -05:00
Ben Luddy
cd8b95e0b9
Add more tests and benchmarks for unknown and duplicate fields.
Signed-off-by: Ben Luddy <bluddy@redhat.com>
2024-02-14 09:27:11 -05:00
Faye Amacker
1f67e1e4b1
Merge pull request #489 from fxamacker/fxamacker/require-go-1.17-in-go.mod
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
Update go.mod to require go 1.17.
Update Quick Start to define CBOR data item and CBOR sequence.
2024-02-11 22:21:59 -06:00
Faye Amacker
793a0586a5
Update README.md for cbor v2.6.0
This library can encode and decode CBOR (RFC 8949) and CBOR Sequences (RFC 8742).

- CBOR data item is a single piece of CBOR data and its structure may contain zero, one, or more nested data items.

- CBOR sequence is a concatenation of 0 or more encoded CBOR data items.
2024-02-11 21:05:41 -06:00
Faye Amacker
4778e468f6
Update go.mod to require go 1.17
A recent optimization bumped go to 1.20 in go.mod but go 1.17 is still supported.

This commit is to reduce confusion about the minimum Go version supported by this CBOR library.

fxamacker/cbor v2.5.0 supports 1.12+.
fxamacker/cbor v2.6.0 will support 1.17+.
2024-02-11 15:12:44 -06:00
Faye Amacker
d81df7aca4
Merge pull request #487 from fxamacker/dependabot/github_actions/github/codeql-action-3.24.0
Bump github/codeql-action from 3.23.0 to 3.24.0
2024-02-11 14:17:02 -06:00
dependabot[bot]
ade4c60343
Bump github/codeql-action from 3.23.0 to 3.24.0
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.23.0 to 3.24.0.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](e5f05b81d5...e8893c57a1)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-05 09:37:07 +00:00
Faye Amacker
accf57c32e Check well-formedness of data from MarshalCBOR
MarshalerError is returned if CBOR data item returned from
MarshalCBOR() fails either:
- well-formedness check, or
- tag validation for builtin tags 0-3
2024-02-04 20:46:52 -06:00
Faye Amacker
2da2d67628
Merge pull request #484 from fxamacker/fxamacker/test-new-decoding-option-in-decoption-to-decmode-roundtrip
Add `UnrecognizedTagToAny` option to `TestDecOptions`
2024-02-04 18:34:16 -06:00
Faye Amacker
d68ecb46eb Add UnrecognizedTagToAny option to TestDecOptions 2024-02-04 18:01:04 -06:00
Faye Amacker
dd2eb91f36
Merge pull request #475 from ssuriyan7/return-tag-content-option
Add option to specify how to decode unrecognized CBOR tag to `any`
2024-02-04 17:55:08 -06:00
Faye Amacker
c8f2df8366
Merge pull request #480 from benluddy/options-mode-options-roundtrip-fix
Fix EncOption/DecOption unset fields on mode regurgitation.
2024-02-04 17:28:00 -06:00
Suriyan S
2c841f6f07 Add decoding option for return type of data with an unknown tag
Adds a decoding option to specify the preferred return type when unmarshalling a data with an unknown tag.
If this option is not set, Unmarshal returns a value of type Tag{}. This ensures backward compatibility.
If the option is set, Unmarshal returns the unmarshalled content.

Signed-off-by: Suriyan Subbarayan suriyansub710@gmail.com
2024-02-03 18:03:49 -05:00
Ben Luddy
7c1f8f3e33
Fix EncOption/DecOption unset fields on mode regurgitation.
Signed-off-by: Ben Luddy <bluddy@redhat.com>
2024-01-29 23:33:39 -05:00
Faye Amacker
25223c7753
Merge pull request #468 from dinhxuanvu/reflect
Improve memory allocs and speed of encoding maps by using Go 1.18+ features
2024-01-28 13:27:28 -06:00
Vu Dinh
380c17ea0b Use MapIter.SetKey/SetValue and sync.Pool to improve memory allocation
Since go 1.18, the reflect package introduces MapIter.SetKey and
MapIter.SetValue that will do fewer memory allocation for map
iteration which is frequently used for CBOR encode operation. Plus,
usage of sync.Pool will further reduce memory allocation by reusing
the shared memory in the pool. Lastly, the Value.SetZero method
(available since go 1.20) is helpful to release memory allocation
to the GC when is no longer needed.

Signed-off-by: Vu Dinh <vudinh@outlook.com>
2024-01-23 18:33:34 -05:00
Faye Amacker
e5eaf7abb9
Merge pull request #473 from fxamacker/fxamacker/refactor-map-encode-to-prepare-for-go-verion-bump
Refactor map encoding to prep for Go version bump
2024-01-22 18:37:51 -06:00
Faye Amacker
23ec2c5c76
Merge pull request #472 from benluddy/structfieldname-bytestring
Add options to support byte string map keys as struct field names
2024-01-22 18:37:04 -06:00
Ben Luddy
a29413c8fe
Add option to control the output CBOR type of struct field names.
Signed-off-by: Ben Luddy <bluddy@redhat.com>
2024-01-22 11:06:33 -05:00
Faye Amacker
cce0e5247a
Merge pull request #469 from fxamacker/dependabot/github_actions/github/codeql-action-3.23.0
Bump github/codeql-action from 3.22.12 to 3.23.0
2024-01-21 20:24:47 -06:00
Faye Amacker
49189749df Refactor map encoding to prep for go version bump
This refactor helps reduce duplicate code in
upcoming PRs.
2024-01-21 17:20:14 -06:00
Ben Luddy
55afd6c840
Add option to allow decoding byte strings as struct field names.
Signed-off-by: Ben Luddy <bluddy@redhat.com>
2024-01-18 14:27:25 -05:00
dependabot[bot]
2ad8fb0e23
Bump github/codeql-action from 3.22.12 to 3.23.0
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.22.12 to 3.23.0.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](012739e508...e5f05b81d5)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-15 09:23:54 +00:00
Faye Amacker
cd0553c333
Merge pull request #465 from benluddy/go-string-as-cbor-byte-string
New options for encoding Go strings to and from CBOR byte strings
2024-01-08 20:24:58 -06:00
Ben Luddy
ae20110f26
Add option to permit decoding CBOR byte strings into Go strings.
The unchanged default behavior is to produce an UnmarshalTypeError when decoding a CBOR byte string
into a Go string.

Signed-off-by: Ben Luddy <bluddy@redhat.com>
2024-01-08 16:24:01 -05:00
Ben Luddy
ebc84ccb9c
Add option to decode CBOR byte string into interface{} as Go string.
Signed-off-by: Ben Luddy <bluddy@redhat.com>
2024-01-08 16:24:01 -05:00
Ben Luddy
30aefa6c0b
Add option to encode Go strings to CBOR byte strings.
The StringType encode option supports use cases that must not produce invalid CBOR (even if
well-formed) and must be able to encode Go strings that do not contain valid UTF-8 sequences,
without the overhead of sanitizing all input Go values. The default value of the option is
TextStringType and encodes Go strings to CBOR major type 3, which is identical to the preexisting
behavior.

Signed-off-by: Ben Luddy <bluddy@redhat.com>
2024-01-08 16:23:59 -05:00
Faye Amacker
83ec0aa90b
Merge pull request #466 from fxamacker/fxamacker/fix-lint
Add another test and fix lint errors
2024-01-07 14:17:06 -06:00
Faye Amacker
c5c410b12c Add another test and fix lint errors 2024-01-07 13:31:52 -06:00
Faye Amacker
0cf56c3dab
Merge pull request #453 from dinhxuanvu/json-omitempty
Add encoding option to specify how omitempty fields are encoded
2024-01-07 13:09:56 -06:00
Vu Dinh
f95914be2b Address feedbacks
Signed-off-by: Vu Dinh <vudinh@outlook.com>
2024-01-04 12:46:44 -05:00
Faye Amacker
f4ccee0f52
Merge pull request #464 from fxamacker/fxamacker/refactor-simple-value
Fix cbor.SimpleValue encoding and decoding
2024-01-01 19:51:27 -06:00
Faye Amacker
bfcaa814f5 Fix cbor.SimpleValue encoding and decoding
This commit resolves two issues:

1.  Encoding cbor.SimpleValue with values 24..31 should fail because
CBOR simple values 24..31 are reserved and they MUST NOT be encoded
according to RFC 8949.

This commit makes encoder return UnsupportedValueError when encoding
cbor.SimpleValue with values 24..31 because that would not be
a well-formed CBOR data item.

2. Decoding other CBOR types to cbor.SimpleValue should fail because
cbor.SimpleValue represents CBOR simple value (major type 7) which is
different from CBOR integers and shouldn't be used interchangeably.

This commit makes decoder return UnmarshalTypeError when decoding
other CBOR types to cbor.SimpleValue.
2024-01-01 18:59:19 -06:00
Faye Amacker
95e432d948
Merge pull request #461 from fxamacker/fxamacker/fix-decode-cbor-null-to-pointer
Fix panic when decoding CBOR nil to `*cbor.SimpleValue`
2024-01-01 15:55:41 -06:00
Faye Amacker
cdc043aeb0 Fix decoding CBOR nil to *cbor.SimpleValue
Unmarshalling CBOR nil or CBOR undefined into a Go pointer should
always set the pointer to nil.

This commit fixes crash bug when unmarshalling CBOR nil or CBOR
undefined into *cbor.SimpleValue by setting the pointer to nil.

Also, added more tests for decoding to uninitialized and initialized
pointer values.

Separately (not part of this commit), the fuzzer was updated to
attempt unmarshaling to *cbor.SimpleValue.
2024-01-01 15:48:51 -06:00
Faye Amacker
5ff9771183
Merge pull request #458 from fxamacker/fxamacker/refactor-tests
Refactor tests to improve consistency and readability
2023-12-30 10:49:25 -06:00
Faye Amacker
1767c18df2 Refactor tests for readability 2023-12-29 15:20:44 -06:00
Faye Amacker
8d3ade149f Refactor tests for readability 2023-12-28 22:16:30 -06:00
Faye Amacker
dee1f16979 Refactor tests for readability 2023-12-28 21:00:16 -06:00
Faye Amacker
4206c1d5f0 Refactor tests for readability 2023-12-28 20:34:09 -06:00
Faye Amacker
a4efacaf49 Refactor tests for readability 2023-12-28 20:18:34 -06:00
Faye Amacker
9997d3c964
Merge pull request #455 from fxamacker/dependabot/github_actions/github/codeql-action-3.22.12
Bump github/codeql-action from 2.22.8 to 3.22.12
2023-12-28 15:23:18 -06:00
Faye Amacker
0e952c78ef
Merge pull request #457 from fxamacker/fxamacker/add-option-to-IntDecMode
Add 2 more options for decoding CBOR integers to any
2023-12-28 14:54:11 -06:00
Faye Amacker
b5a6ef08c6 Add 2 options for decoding CBOR integers to any
Added 2 new decoding options to IntDecMode which specifies how to
decode CBOR integers (major type 0 and 1) to Go interface{}.

- IntDecConvertSignedOrFail makes CBOR integers decode to:
  * int64 if value fits
  * return UnmarshalTypeError if value doesn't fit into int64

- IntDecConvertSignedOrBigInt makes CBOR integers decode:
  * int64 if value fits
  * big.Int or *big.Int if value doesn't fit into int64

Including default setting (IntDecConvertNone), there are
now 4 options for decoding CBOR unsigned or CBOR negative
integers into interface{}.

Deprecated IntDecConvertSigned option in favor of other options
for more consistent handling of values that don't git into int64.

Also updated comments for existing IntDecMode options.
2023-12-28 14:47:15 -06:00
Faye Amacker
ef38c6f940
Merge pull request #456 from fxamacker/fxamacker/add-decode-option-for-bigint
Add option to decode CBOR bignum to `interface{}` as `*big.Int`
2023-12-27 19:28:32 -06:00
Faye Amacker
e87f89128b Add option to decode bignum to any as *big.Int
Added decoding option BigIntDecMode which specifies how to decode
CBOR bignum to Go interface{}.

- BigIntDecodeValue (default) makes CBOR bignum decode to big.Int.
- BigIntDecodePointer makes CBOR bignum decode to *big.Int.
2023-12-27 14:39:39 -06:00
dependabot[bot]
0591f47901
Bump github/codeql-action from 2.22.8 to 3.22.12
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2.22.8 to 3.22.12.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](407ffafae6...012739e508)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-25 09:06:01 +00:00
Vu Dinh
0d142f9697 Add encoding option to specify how omitempty fields are encoded
The current behavior in the library is to omits if field value
would encode as empty JSON value. This behavior is different from
the bahavior in golang encoding/json which omits if field value
is an empty Go value (defined as false, 0, a nil pointer, a nil
interface value, and any empty array, slice, map, or string).
The OmitEmptyMode = 1 is added to encoding option so that cbor
would decode omitempty fields similarly to encoding/json in go.

Signed-off-by: Vu Dinh <vudinh@outlook.com>
2023-12-19 10:21:41 -05:00
Faye Amacker
a1d4c29efb
Merge pull request #448 from benluddy/doc-unmarshal-unrecognized-tag-to-empty-interface
Document behavior of Unmarshal of unrecognized tags into interface{}
2023-12-07 20:33:35 -06:00
Ben Luddy
18b6e98360
Note behavior of Unmarshal of unrecognized tags into interface{}.
Signed-off-by: Ben Luddy <bluddy@redhat.com>
2023-12-07 16:42:53 -05:00
Faye Amacker
6bbaf8b8e6
Merge pull request #443 from fxamacker/dependabot/github_actions/github/codeql-action-2.22.8
Bump github/codeql-action from 2.22.5 to 2.22.8
2023-12-02 16:45:36 -06:00
dependabot[bot]
35569805ea
Bump github/codeql-action from 2.22.5 to 2.22.8
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2.22.5 to 2.22.8.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](74483a38d3...407ffafae6)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-27 09:58:27 +00:00
Faye Amacker
2af3c5b62e
Merge pull request #437 from fxamacker/fxamacker/enable-more-linters
Enable more linters
2023-11-11 20:39:37 -06:00
Faye Amacker
a0ca59b719 Enable more linters 2023-11-11 19:18:43 -06:00
Montgomery Edwards⁴⁴⁸
25c007e389
Pin actions/checkout and actions/setup-go (#436)
Pin actions/checkout, actions/setup-go, etc.
2023-11-05 22:44:45 -06:00
Faye Amacker
ad0cc0cc70
Merge pull request #434 from fxamacker/fxamacker/refactor-option-valid-check
Refactor valid() to reject negative values for integer modes
2023-11-04 20:44:59 -05:00
Faye Amacker
ad9dc1c161 Make valid() reject negative values for modes
For modes of integer type, make valid() reject negative values
since 0 is default and non-negative values are used.

Also reformat comments with gofmt.
2023-11-04 19:44:51 -05:00
Faye Amacker
4687659e60
Merge pull request #433 from benluddy/field-name-matching-decode-option
Add FieldNameMatching decode option.
2023-11-04 17:25:41 -05:00
Ben Luddy
9e247e09ac
Add FieldNameMatching decode option.
When decoding a CBOR map into a Go struct, FieldNameMatching controls how string
keys are matched to struct fields in the destination struct and allows users to
require case-sensitive matches. The default value of this option preserves the
existing behavior, which prefers case-sensitive matches but will fall back to a
case-insensitive match.

Signed-off-by: Ben Luddy <bluddy@redhat.com>
2023-11-03 13:56:35 -04:00
Faye Amacker
cd3843972f
Merge pull request #431 from fxamacker/fxamacker/update-readme-to-add-new-users
Update README to add FIDO Alliance, Let's Encrypt, Matrix.org
2023-10-15 21:16:53 -05:00
Faye Amacker
8b1571ad85
Add FIDO Alliance, Let's Encrypt, Matrix.org
Update README to add to "Who uses fxamacker/cbor":
- FIDO Alliance
- Let's Encrypt (ISRG)
- Matrix.org
2023-10-15 20:21:33 -05:00
Faye Amacker
86b6599ea7
Merge pull request #430 from fxamacker/fxamacker/bump-golangci-lint-to-1.53.3
Update CI and README
2023-10-15 20:11:09 -05:00
Faye Amacker
ec730926de
Update govulncheck.yml
Bump go-version to 1.21
2023-10-15 19:47:09 -05:00
Faye Amacker
1563ca952d
Update ci.yml
Use quotes around '1.20' to prevent it from being treated as 1.2.
2023-10-15 19:29:18 -05:00
Faye Amacker
6e30978d46
Update govulncheck.yml
Bump govulncheck from 1.0.0 to 1.0.1.
Bump actions/checkout from v3 to v4.
2023-10-15 19:23:14 -05:00
Faye Amacker
30dd2787af
Update codeql-analysis.yml
Bump actions/checkout from v3 to v4.
2023-10-15 19:19:56 -05:00
Faye Amacker
f5536c48f3
Update ci-go-cover.yml
Bump actions/checkout from v3 to v4.
Bump go-version from 1.19 to 1.21.
2023-10-15 19:18:35 -05:00
Faye Amacker
84675af243
Update ci.yml
Add go 1.21, remove go 1.18 (keep 1.17).
Bump actions/checkout from v3 to v4.
2023-10-15 19:17:01 -05:00
Faye Amacker
3a10b8e32f
Update safer-golangci-lint.yml
Bump actions/checkout from v3 to v4 .
2023-10-15 19:06:22 -05:00
Faye Amacker
1d9df2e2ba
Update README.md
Remove "Used by" count because GitHub repo setting was recently updated to display stats for current version (v2) instead of v1.
2023-10-15 19:02:34 -05:00
Faye Amacker
f7c4f8fad8
Bump golangci-lint to 1.53.3 2023-10-15 18:24:26 -05:00
Faye Amacker
f8bac85356
Update README for changes in cbor v2.5.0 (#427)
Improve security comparison by adding example decoding 181 bytes with encoding/gob and getting fatal "out of memory" error.

Add example encoding 3-level nested Go struct with fxamacker/cbor to 1 byte CBOR.

Update and clarify "Versions and API Changes" section.

Update "Status" section to mention extended 2.5.0-beta -> 2.5.0 period.

Add to Quick Start:  "NOTE: Unmarshal returns ExtraneousDataError if there are remaining bytes, but new funcs UnmarshalFirst and DiagnoseFirst do not."
2023-09-03 22:48:50 -05:00
Faye Amacker
17fd221bad
Merge pull request #426 from fxamacker/fxamacker/update-readme-for-cbor-extraneous-data
Update README:
- Quick Start: mention CBOR settings can be used for trade-offs between speed, security, encoding size, etc.

- Quick Start: mention new functions in v2.5.0 that handle remaining bytes (extraneous data) as non-error:
  - func UnmarshalFirst(data []byte, v interface{}) (rest []byte, err error)
  - func DiagnoseFirst(data []byte) (string, []byte, error) 

- Status: more explicitly mention changes should be reviewed before upgrading from v2.4 (or older) to v2.5.0 due to error handling bug fixes related to extraneous data (e.g. PR 380) and other improvements.

- CBOR Security:  CBOR decoder has configurable limits and option to detect duplicate map keys. By comparison, encoding/gob is not designed to be hardened against adversarial inputs.
2023-09-02 14:20:58 -05:00
Faye Amacker
53ee4c33fa
Update CBOR Security in README
CBOR decoder has configurable limits and option to detect duplicate map keys while encoding/gob is not designed to be hardened against adversarial inputs.
2023-09-02 14:00:03 -05:00
Faye Amacker
5599246229
Update README.md for CBOR extraneous data
Update CBOR Quick Start to mention CBOR settings can be used for trade-offs between speed, security, encoding size, etc.

Update "Quick Start" to mention new functions in v2.5.0 that return remaining bytes (extraneous data):
- UnmarshalFirst()
- DiagnoseFirst()

Update "Status" to more explicitly mention changes should be reviewed before upgrading from v2.4 (or older) to v2.5.0 due to error handling bug fixes related to extraneous data, etc.
2023-09-02 12:23:05 -05:00
Faye Amacker
3b32167103
Update README.md for v2.5.0 (#424)
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.18, macos-latest) (push) Has been cancelled
ci / test ${{matrix.os}} go-${{ matrix.go-version }} (1.18, ubuntu-latest) (push) Has been cancelled
ci / test ${{matrix.os}} go-${{ matrix.go-version }} (1.18, 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
Updated sections:
- Status
- Limitations
- Acknowledgements
2023-08-13 22:11:13 -05:00
Faye Amacker
9cab20e621
Update CONTRIBUTING.md (#423)
Cleanup for v2.5.0.
2023-08-13 13:34:54 -05:00
Faye Amacker
5792a94ab2
Update README.md for v2.5.0 (#422)
Update README.md for v2.5.0
- Remove "Disclaimers" section.
- Update "Acknowledgments" section.
- Update Quick Start.
2023-08-13 10:17:13 -05:00
Faye Amacker
837230c62b
Update intro and cbor quick start in README (#421)
Update README for v2.5.0
* Rewrite Quick Start
* Update intro to show Struct Tags and CBOR Security earlier.
* Removed sections: "Comparison", "Usage", "Why fxamacker/cbor", "CBOR Options", "API", etc.
* Removed files: CBOR_GOLANG.md, CBOR_BENCHMARKS.md, etc.
* Update CODE_OF_CONDUCT.md
* Add openssf scorecard badge but did not link to detailed report.
* Make Acknowledgements section placeholder because new contributors weren't included for years.
2023-08-06 23:27:38 -05:00
Faye Amacker
66971779ea
Merge pull request #419 from fxamacker/fxamacker/fix-first-sentence-in-readme
Update README.md
2023-08-02 00:08:29 -05:00
Faye Amacker
1c816eaa0c
Update README.md
Primarily to fix first sentence but made a few more.
2023-08-02 00:00:13 -05:00
Faye Amacker
7f7139cf20
Merge pull request #418 from fxamacker/fxamacker/update-readme-for-v2.5.0-rc1
Update README for CBOR codec v2.5.0-rc1
2023-07-30 23:59:29 -05:00
Faye Amacker
1a5815e46a
Update README for cbor v2.5.0-rc1
Update top section of README for CBOR codec.

Also started removing content that is time consuming to maintain or redundant.

Some more content needs to be removed or shortened.
2023-07-30 23:47:56 -05:00
Faye Amacker
969aa363d7
Merge pull request #417 from fxamacker/remove-ignored-UTF8-setting-in-diagMode
Remove ignored UTF8 setting in diagMode()
2023-07-30 15:33:20 -05:00
Faye Amacker
09ebf1b757 Remove ignored UTF8 setting in diagMode()
Unreleased functions Diagnose() and DiagnoseFirst() added in v2.5.0-beta3
were ignoring the setting of DecOptions.UTF8 = DecodeInvalidUTF8 in
the underlying decoder.

Given this, removing the ignored code is enough to make the new
diagnostic functions match the default behavior of CBOR decoder
which is to reject invalid UTF-8 in CBOR text strings.

For now, it doesn't make sense to print invalid UTF-8 text
because the Diagnostic Notation described by RFC 8949 and the
Extended Diagnostic Notation in Appendix G of RFC 8610 don't specify
how to represent invalid UTF-8.
2023-07-30 14:21:24 -05:00
Faye Amacker
886456287d
Bump CI govulncheck to v1.0.0 (#414)
* Bump CI govulncheck to v1.0.0.
* Remove scheduled trigger (move it to private repo).
2023-07-15 12:42:12 -05:00
Faye Amacker
48c838c15d
Merge pull request #413 from fxamacker/fxamacker/update-readme-v2.5.0-beta5
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.18, macos-latest) (push) Has been cancelled
ci / test ${{matrix.os}} go-${{ matrix.go-version }} (1.18, ubuntu-latest) (push) Has been cancelled
ci / test ${{matrix.os}} go-${{ matrix.go-version }} (1.18, 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
Update README.md for cbor v2.5.0-beta5
2023-06-25 21:45:30 -05:00
Faye Amacker
1a406c5571
Update README.md for v2.5.0-beta5
fxamacker/cbor v2.5.0-beta5 is fuzz tested and production quality but docs needs to be updated before v2.5.0 release.

Feature freeze until v2.5.0 release.
2023-06-25 21:33:06 -05:00
Faye Amacker
56509bf36d
Merge pull request #412 from fxamacker/fxamacker/add-decoder-buffered
Add `Decoder.Buffered` to return `io.Reader` for remaining bytes in the buffer.
2023-06-25 20:00:38 -05:00
Faye Amacker
a2dfeeb0d6 Add Decoder.Buffered() to return buffered data
Buffered returns an io.Reader for data remaining in Decoder's buffer.
The returned reader is valid until the next call to Decode or Skip.

This basically matches the Decoder.Buffered() in Go's encoding/json.
2023-06-25 17:59:52 -05:00
Faye Amacker
e63ce52c22
Update govulncheck.yml 2023-06-25 17:39:05 -05:00
Faye Amacker
ebceec861f
Update ci-go-cover.yml
Revert temp changes that were used for regenerating coverage badge (>98%) for older releases.

Master branch and newer releases use >96% coverage badge due to Diagnose() functions reducing coverage a bit.
2023-06-25 15:36:25 -05:00
Faye Amacker
a61ef50579
Update ci-go-cover.yml
Temp CI change to regenerate code coverage badge for old releases that passed with 98% coverage.

Master branch passes with 96% coverage because adding Diagnose() functions dropped coverage a bit.  Name of badge URL includes coverage percent, so this doesn't affect master.
2023-06-25 14:07:17 -05:00
Faye Amacker
340a5fb061
Merge pull request #411 from fxamacker/fxamacker/update-readme-for-v2.5.0-beta4
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.18, macos-latest) (push) Has been cancelled
ci / test ${{matrix.os}} go-${{ matrix.go-version }} (1.18, ubuntu-latest) (push) Has been cancelled
ci / test ${{matrix.os}} go-${{ matrix.go-version }} (1.18, 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
Update README.md for cbor v2.5.0-beta4
2023-06-18 00:16:59 -05:00
Faye Amacker
adcd2e4596
Update README.md for cbor v2.5.0-beta4
Add Whats New section to mention:
- Features already present in v2.4 are release quality.
- Newly added functions require more fuzzing & docs.
2023-06-18 00:10:47 -05:00
Faye Amacker
3ae40016f0
Merge pull request #410 from fxamacker/fxamacker/fix-diagnose-empty-data-error
Fix Diagnose to return io.EOF error on empty data.

This was detected while updating CBOR fuzz tests for v2.5.0-beta3.
2023-06-17 19:32:25 -05:00
Faye Amacker
37f6b99b2b Fix Diagnose to return io.EOF error on empty data 2023-06-17 19:11:56 -05:00
Faye Amacker
d5917a09fe
Merge pull request #408 from x448/patch-4
Bump safer-golangci-lint to 1.52.2
Update README to mention DDR4 in benchmark comparison.
2023-05-17 23:12:17 -05:00
Faye Amacker
95b9e5ba18
Update README.md
Specify DDR4 in benchmark system because faster RAM
(DDR5 or overclocked DDR4) makes a difference in this
specific comparison.
2023-05-17 23:09:32 -05:00
Faye Amacker
de6c7eb625
Update .golangci.yml
Remove deprecated linters.

Temporarily disable revive to reduce noise in
golangci-lint 1.52.2.
2023-05-17 22:50:22 -05:00
Montgomery Edwards⁴⁴⁸
2506a6c6d3
Bump safer-golangci-lint to 1.52.2
Bump golangci-lint to 1.52.2.
Bump Go to 1.20.
2023-05-17 22:26:44 -05:00
Faye Amacker
a52d9a50de
Merge pull request #407 from fxamacker/fxamacker/update-readme-for-v2.5.0-beta3
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.18, macos-latest) (push) Has been cancelled
ci / test ${{matrix.os}} go-${{ matrix.go-version }} (1.18, ubuntu-latest) (push) Has been cancelled
ci / test ${{matrix.os}} go-${{ matrix.go-version }} (1.18, 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
Update README.md for v2.5.0-beta3
2023-05-15 23:09:55 -05:00
Faye Amacker
0c1d336268
Update README.md for v2.5.0-beta3
Document new CBOR codec functions:
- UnmarshalFirst()
- Diagnose()
- DiagnoseFirst()

UnmarshalFirst() makes it easier to use CBOR Sequences (RFC 8742).

Diagnose() and DiagnoseFirst() produces human-readable Extended Diagnostic Notation (RFC 8610 Appendix G).
2023-05-15 23:05:32 -05:00
Faye Amacker
5b2c859943
Merge pull request #406 from fxamacker/fxamacker/update-readme-for-rfc8742
Update README for CBOR Sequences RFC 8742
2023-05-14 23:47:55 -05:00
Faye Amacker
1d6af356a2
Update README for CBOR Sequences RFC 8742
Update README to separately mention support for:
- CBOR (IETF RFC 8949, STD 94)
- CBOR Sequences (IETF RFC 8742)
2023-05-14 23:40:34 -05:00
Faye Amacker
cdd8c70006
Merge pull request #405 from fxamacker/fxamacker/increase-test-coverage
Add more tests for Diagnose and DiagnoseFirst
2023-05-14 19:56:28 -05:00
Faye Amacker
7caf1287d7 Add more tests for Diagnose and DiagnoseFirst 2023-05-14 19:40:49 -05:00
Montgomery Edwards⁴⁴⁸
32a24cd50d
Update ci-go-cover.yml (#404)
Reduced min coverage to 96% (was 98%) since the Diagnose()
and DiagnoseFirst() functions reduced code coverage because
of error path .

These two functions return diagnostic notation for debugging,
so CBOR encoding/decoding funcs are unaffected (still >= 98% coverage).

Added triggers:
- pull_request
- workflow_dispatch

Co-authored-by: Faye Amacker <33205765+fxamacker@users.noreply.github.com>
2023-05-14 19:30:50 -05:00
Faye Amacker
92d0a5ad59
Merge pull request #386 from ldclabs/feature/diagnostic-notation
Implements diagnostic notation (#384)
2023-05-14 16:27:42 -05:00
zensh
8d11408f7a Add DiagnoseFirst to DiagMode interface.
Signed-off-by: Yan Qing <txr1883@gmail.com>
2023-05-09 13:24:05 +08:00
zensh
b701e74365 1. Add DiagnoseFirst(); 2. Always notating embedded CBOR sequence.
Signed-off-by: Yan Qing <txr1883@gmail.com>
2023-05-09 13:09:15 +08:00
zensh
c1ad975620 Rename DiagOption "IndicateFloatPrecision" to "FloatPrecisionIndicator". 2023-05-09 10:40:25 +08:00
zensh
e98f29d2e0 Fix Github's committing error 2023-05-09 10:31:50 +08:00
0xZensh
35d20279d1
Improve diagnose.encodeFloat
Co-authored-by: Faye Amacker <33205765+fxamacker@users.noreply.github.com>
2023-05-09 10:22:45 +08:00
zensh
37f17365f4 Fix diagnose for invalid UTF-8 text string. 2023-05-09 10:15:54 +08:00
zensh
2650da0f5c Fix diagnose for indefinite-length string with no chunks. 2023-05-09 09:42:38 +08:00
Faye Amacker
d3fb9ddcf5
Merge pull request #395 from fxamacker/dependabot/github_actions/actions/setup-go-4
Bump actions/setup-go from 3 to 4
2023-05-07 23:56:42 -05:00
Faye Amacker
03bf60ebb2
Merge pull request #402 from fxamacker/fxamacker/add-unmarshalfirst-to-DecMode-interface
Add UnmarshalFirst to DecMode interface
2023-05-06 17:27:48 -05:00
Faye Amacker
bb06d38c87 Add UnmarshalFirst to DecMode interface
UnmarshalFirst parses the first CBOR data item using the decoding mode.
Any remaining bytes are returned in rest.
2023-05-06 17:09:14 -05:00
Faye Amacker
21a673817e
Merge pull request #398 from immesys/immesys/unmarshal-prefix
Add UnmarshalFirst
2023-05-06 16:42:01 -05:00
Michael Andersen
d3a8ced640
Add UnmarshalFirst
Signed-off-by: Michael Andersen <michael.andersen@antimatter.io>
2023-05-01 14:17:49 -07:00
zensh
72e9118b5d According to fxamacker' review: 1. check tag data item for validity; 2. update from valid() to wellformed().
Signed-off-by: Yan Qing <txr1883@gmail.com>
2023-05-01 22:16:09 +08:00
zensh
a1c306420e Improve code according to fxamacker' review.
Signed-off-by: Yan Qing <txr1883@gmail.com>
2023-05-01 21:33:33 +08:00
zensh
1eed832daa Add partial/incomplete CBOR Sequences test case.
Signed-off-by: Yan Qing <txr1883@gmail.com>
2023-05-01 21:33:33 +08:00
zensh
b73ccf5508 Improve diagnostic notation API
Signed-off-by: Yan Qing <txr1883@gmail.com>
2023-05-01 21:33:33 +08:00
zensh
babf8813a3 Fix UTF-8 text diagnostic encoding. 2023-05-01 21:33:33 +08:00
zensh
13fc53c982 Fix for linters.
Signed-off-by: Yan Qing <txr1883@gmail.com>
2023-05-01 21:33:33 +08:00
zensh
a4da8c8e09 Implements diagnostic notation (#384)
Signed-off-by: Yan Qing <txr1883@gmail.com>
2023-05-01 21:33:33 +08:00
Faye Amacker
3343ed2a12
Merge pull request #400 from fxamacker/fxamacker/add-wellformed-function
Deprecate Valid() and add Wellformed() to replace it
2023-04-30 20:10:03 -05:00
Faye Amacker
c9e9b2667e Deprecate Valid() and add Wellformed() to replace it
Valid() is deprecated and only calls WellFormed(), so it will
behave the same way between current v2.4 and upcoming v2.5 release.

Projects should begin replacing calls to Valid() with calls to WellFormed().

Although the old docs for Valid() correctly said it checks for well-formedness,
this refactor was done because Valid() is not an appropriate function name.

RFC 8949 distinctly defines what is "Valid" and what is "Well-formed".

Wellformed() checks whether data is a well-formed encoded CBOR data item and
that it complies with additional restrictions such as MaxNestedLevels,
MaxArrayElements, MaxMapPairs, etc.  If there are any remaining bytes
after the encoded CBOR data item, then ExtraneousDataError is returned.
2023-04-30 19:16:53 -05:00
dependabot[bot]
dc812de48a
Bump actions/setup-go from 3 to 4
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 3 to 4.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-20 09:59:44 +00:00
Faye Amacker
6dfffb1e17
Merge pull request #394 from fxamacker/fxamacker/update-readme-for-2.5.0-beta2
Update README for cbor v2.5.0-beta2:
- Update some intro text.
- Remove "What is CBOR" section for now.
- Add heading "Who uses fxamacker/cbor".
- Update "CBOR Security" section with updated benchmarks
  for decoding 10 bytes of malicious CBOR data.
2023-03-05 23:51:36 -06:00
Faye Amacker
0e36fc7eff
Update README for cbor v2.5.0-beta2
- Remove "What is CBOR" section for now.
- Add heading "Who uses fxamacker/cbor".
- Update "CBOR Security" with updated benchmarks.
  for decoding 10 bytes of malicious CBOR data.
2023-03-05 23:39:25 -06:00
Faye Amacker
65bc18c8d4
Merge pull request #393 from fxamacker/fxamacker/create-govulncheck-yml
Add a GitHub Actions workflow to run govulncheck.
According to govulncheck docs:

"Govulncheck reports known vulnerabilities that affect Go code. 
It uses static analysis of source code or a binary's symbol table 
to narrow down reports to only those that could affect the application.

By default, govulncheck makes requests to the Go vulnerability database
at https://vuln.go.dev. Requests to the vulnerability database contain only
module paths, not code or other properties of your program."
2023-03-04 18:53:31 -06:00
Faye Amacker
8329ecad97
Update govulncheck.yml 2023-03-04 18:40:12 -06:00
Faye Amacker
3afff60b46
Update govulncheck.yml 2023-03-04 18:15:46 -06:00
Faye Amacker
e0936c06c5
Update govulncheck.yml
Temporarily add '**.yml' to trigger workflow.
2023-03-04 18:09:37 -06:00
Faye Amacker
ffa80e2a14
Update govulncheck.yml
Fix typo by removing a "-".
2023-03-04 17:44:44 -06:00
Faye Amacker
5790ae0e0f
Create govulncheck.yml
Add a GitHub Actions workflow to run govulncheck.
According to govulncheck docs:

    "Govulncheck reports known vulnerabilities that affect Go code. 
    It uses static analysis of source code or a binary's symbol table 
    to narrow down reports to only those that could affect the application.

    By default, govulncheck makes requests to the Go vulnerability database
    at https://vuln.go.dev. Requests to the vulnerability database contain only
    module paths, not code or other properties of your program."
2023-03-04 17:36:19 -06:00
Faye Amacker
f9e6291e0c
Retry in decoder if Read() returns 0 bytes read with nil error (#387)
Some checks failed
cover ≥98% / Coverage (push) Has been cancelled
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.18, macos-latest) (push) Has been cancelled
ci / test ${{matrix.os}} go-${{ matrix.go-version }} (1.18, ubuntu-latest) (push) Has been cancelled
ci / test ${{matrix.os}} go-${{ matrix.go-version }} (1.18, 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
Previously, decoder did not properly handle the scenario of
Read() returning 0 bytes read with nil error.

Fix this by retrying the read on (0, nil).

While at it, also refactor the error handling and add
more tests to reach 100% code coverage for stream.go.
2023-02-20 13:56:04 -06:00
Faye Amacker
4b3a96322b
Merge pull request #389 from x448/patch-2
bump safer-golangci-lint.yml to 1.51.1
2023-02-05 22:02:54 -06:00
Montgomery Edwards⁴⁴⁸
3c7139309e
bump safer-golangci-lint.yml to 1.51.1
This bumps 
- Go to 1.19
- golangci-lint to 1.51.1
2023-02-05 15:48:35 -06:00
Faye Amacker
1977e0910c
Merge pull request #383 from x448/patch-1
Update fxamacker_cbor_banner.png for v2.5
2023-01-03 19:49:16 -06:00
Montgomery Edwards⁴⁴⁸
b94a6fd611
Update fxamacker_cbor_banner.png for v2.5
Replaced "v2.4.0" with "v2.5".
Added Tailscale and Fraunhofer AISEC as notable users.
Made some minor updates to the image.
2023-01-03 17:53:45 -06:00
Faye Amacker
9c7f31b724
Merge pull request #382 from fxamacker/fxamacker/return-encoder-buffer-to-pool
Some checks failed
cover ≥98% / Coverage (push) Has been cancelled
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.18, macos-latest) (push) Has been cancelled
ci / test ${{matrix.os}} go-${{ matrix.go-version }} (1.18, ubuntu-latest) (push) Has been cancelled
ci / test ${{matrix.os}} go-${{ matrix.go-version }} (1.18, 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
Return buffer to pool when using Encoder
2023-01-01 18:05:26 -06:00
Faye Amacker
52c6053236 Add benchmarks for NewEncoder().Encode() 2023-01-01 18:03:45 -06:00
Faye Amacker
3ad8ce5465 Return buffer to pool when using Encoder
This change makes Encoder.Encode() reuse and return
the buffer from a pool.

Benchmarks that create a new Encoder inside a loop
for each call to Encoder.Encode() will show improved
speed and less memory use.  Benchmarks that reuse
Encoder in each call to Encoder.Encode() will show
some reduced speed from the overhead of returning
the buffer to the pool.
2023-01-01 17:07:18 -06:00
Faye Amacker
f57ecbe43c
Merge pull request #381 from fxamacker/fxamacker/add-decoder-skip
Add Decoder.Skip() to skip CBOR data item in CBOR Sequences (RFC 8742)
2023-01-01 16:34:00 -06:00
Faye Amacker
0fb145e4ec Add Decoder.Skip() to skip CBOR data item
When decoding CBOR Sequences (RFC 8742), it can be
useful to skip to the next CBOR data item without decoding.

This change adds Decoder.Skip() which will skip
to next CBOR data item (if there is any) otherwise
it will return error such as io.EOF, io.UnexpectedEOF, etc.
2023-01-01 14:46:44 -06:00
Faye Amacker
25ddb46501
Merge pull request #380 from fxamacker/fxamacker/handle-extranenous-data
Fix handling of extra data in Unmarshal() & Valid()
2022-12-31 18:34:16 -06:00
Faye Amacker
940ff5df67 Fix handling extra data in Unmarshal() & Valid()
Previously, Unmarshal() and Valid() ignored extra
data (if any) after the current CBOR data item. This
matched the behavior of Decoder.Decode().

However, Unmarshal() and Valid() are typically used for
single CBOR data item, so extra data (if present)
should return ExtraneousDataError.

This is a bug fix because the previous behavior
of ignoring extra data didn't exactly match RFC 8949
regarding this and was also different from how
encoding/json handles this.

The behavior for Decoder.Decode() is unchanged because
extra data after reading a single CBOR data item
is expected for Decoder with io.Reader.

Special thanks to zensh for reporting this issue
and proposing a solution.
2022-12-31 18:04:49 -06:00
Faye Amacker
ecdf5458ad
Merge pull request #379 from fxamacker/fxamacker/fix-decoder-reader-error
Make Decoder.Decode() return io.UnexpectedEOF instead of io.EOF if CBOR data item is truncated
2022-12-31 15:56:12 -06:00
Faye Amacker
60589f05c1 Fix Decoder.Decode() error type
Previously, if CBOR data item was truncated and
the underlying reader reached EOF, Decoder.Decode()
returned io.EOF.

This bug fix makes it return io.UnexpectedEOF because
the CBOR data item was truncated when EOF was encountered.
2022-12-31 15:18:11 -06:00
Faye Amacker
3ef81e071c
Merge pull request #377 from fxamacker/fxamacker/refactor-nilcontainer-encoption
Refactor NilContainersMode option
2022-12-29 18:59:43 -06:00
Faye Amacker
d8c73c8619 Refactor NilContainersMode option
- Moved NilContainersMode from decode.go to encode.go
  because it is only an encoding option.
- Renamed NullForNil to NilContainerAsNull.
- Renamed EmptyForNil to NilContainerAsEmpty.
- Updated and added some comments.
2022-12-29 18:35:28 -06:00
Faye Amacker
75d03846c5
Merge pull request #352 from dedefer/add_nil_containers_encode_options
Add option to make nil containers encode as empty containers.

The newly added encoding option is likely to be renamed before next release.
2022-12-29 18:13:03 -06:00
Faye Amacker
7c3a5992b0
Merge pull request #376 from fxamacker/feature/bytestring
Define ByteString type to support CBOR byte string as map keys and other uses.

Go doesn't allow []byte as map key, so ByteString can be used to support data formats
having CBOR map with byte string keys. Another use for ByteString is to encode
invalid UTF-8 string as CBOR byte string.
2022-12-29 17:32:46 -06:00
Faye Amacker
950368c2bc Support decoding byte string map key into Go map
Added support for decoding CBOR map with byte string keys
to Go map with empty interface key type.

This supports decoding these CBOR map key types:
- byte string
- tagged byte string
- nested tagged byte string

Previous commit already added support for decoding CBOR maps
with byte string keys to empty Go interface value.
2022-12-28 20:05:45 -06:00
Faye Amacker
947577607f Add InvalidMapKeyTypeError
InvalidMapKeyTypeError is returned when attempting
to decode CBOR map key to a Go type that cannot
be used as map key.

This allows callers to check for this error without
requiring string comparison of the error text.

To maintain backward compatibility, the text
of InvalidMapKeyTypeError remains the same
as the plain error that used to be returned.
2022-12-28 18:10:14 -06:00
Faye Amacker
4131383332 Support CBOR byte string as map key by default
Also added support for:
- CBOR tag with byte string tag content as map key.
- nested CBOR tag with byte string tag content as map key.

This new feature can be disabled by specifying decoding option
MapKeyByteStringForbidden, which will return an error
when attempting to decode CBOR byte string as map key
into an empty Go interface value.
2022-12-28 17:35:37 -06:00
Faye Amacker
03ea397ad9 Make ByteString support any CBOR byte string
This commit makes ByteString support CBOR byte string (major type 2)
without being limited to map keys.

ByteString can be used when using a []byte is not possible or
convenient.  For example, Go doesn't allow []byte as map key, so
ByteString can be used to support data formats having CBOR map with byte
string keys. ByteString can also be used to encode invalid UTF-8 string
as CBOR byte string.

- Modified ByteString to use string type.
- Implemented cbor.Marshaller and cbor.Unmarshaller for ByteString.
- Simplified ByteString encoding and decoding.
- Renamed MapKeyByteStringFail to MapKeyByteStringForbidden.
- Renamed MapKeyByteStringWrap to MapKeyByteStringAllowed.
- Added more tests for ByteString.
2022-12-28 15:39:13 -06:00
Andrew Gaffney
3373cc415a Create optional byte string wrapper for map keys
This allows the parsing of CBOR data containing maps with byte string
keys, which is commonly seen in Cardano blockchain data. The default
behavior remains the legacy behavior (throw an error) for backward
compatibility.

Signed-off-by: Andrew Gaffney <andrew@agaffney.org>
2022-12-27 21:02:20 -06:00
Faye Amacker
1c59246e5a
Merge pull request #375 from creachadair/mjf/doc-example-fix
docs: fix EncMode example in the package comment
2022-11-19 11:57:35 -06:00
M. J. Fromberger
bd5a64aaea doc: fix EncMode example in the package comment
A few of the examples show calling the EncMode method with only one result (the
mode) rather than a mode and an error.

Fixes #374.

Signed-off-by: M. J. Fromberger <michael.j.fromberger@gmail.com>
2022-11-18 15:18:15 -08:00
Faye Amacker
7704fa5efa
Add support for unassigned/reserved CBOR simple values (#370)
Add a SimpleValue type which is distinct from Go's numeric types.

Add support for properly encoding and decoding all simple values,
including 252 unassigned/reserved simple values.

Improve support for simple values as map keys by making them
distinct from uint64 values 0-255.

CBOR simple values are a subset of major type 7
that is not floating-point.

Only 4 simple values were previously supported by this codec:
- false
- true
- null
- undefined

The other 252 simple values are unassigned or reserved by IANA.
2022-11-13 19:27:24 -06:00
Montgomery Edwards⁴⁴⁸
3ce4b029a2
Mention 1276 repos depend on fxamacker/cbor/v2 (#371)
Add Tailscale, TIBCO, National Cybersecurity Agency of France.
Link to list of 1276 repos that depend on  fxamacker/cbor/v2.
Mention 155 repos are using 1.x and suggest upgrading to v2.
Mention CBOR simple values unassigned/reserved by IANA.

This is done to counter GitHub showing "Used by 155" (old 1.x version)
instead of showing "1277" (current 2.x version) on this project's main page.

Co-authored-by: Faye Amacker <33205765+fxamacker@users.noreply.github.com>
2022-10-10 12:09:06 -05:00
Montgomery Edwards⁴⁴⁸
de03533319
Bump safer-golangci-lint.yml to 1.49.0.1 (#368)
Allow different version of Go from golangci-lint to be specified
because gofmt changed in Go 1.19:
* GO_VERSION
* GOFMT_VERSION - version of Go for gofmt and for building goimports
* GOTOOLS_VERSION - version of go tools
* GOIMPORTS_DGST - SHA-256 of goimports command built with Go 1.8.6 and go tools v0.1.12.
* Bump safer-golangci-lint.yml to 1.49.0.1 (custom)
* Update .golangci.yml
Co-authored-by: Faye Amacker <33205765+fxamacker@users.noreply.github.com>
2022-10-02 23:05:04 -05:00
Faye Amacker
552b0a1966
Update codeql-analysis.yml
Bump actions/checkout to v3.
Remove boilerplate comments.
2022-09-24 18:13:37 -05:00
Faye Amacker
1d6a271a47
Merge pull request #367 from x448/patch-29
Bump Go to 1.19 in ci-go-cover.yml
2022-09-24 18:02:21 -05:00
Montgomery Edwards⁴⁴⁸
024606ae4b
Bump Go to 1.19 in ci-go-cover.yml 2022-09-24 11:20:00 -05:00
Faye Amacker
4a6dee8970
Merge pull request #365 from x448/patch-28
bump safer-golangci-lint.yml to 1.46.2
2022-09-17 10:13:46 -05:00
Montgomery Edwards⁴⁴⁸
496e5fde13
bump safer-golangci-lint.yml to 1.46.2 2022-09-14 09:38:33 -05:00
Faye Amacker
76116911c6
Merge pull request #363 from fxamacker/fxamacker/bump-ci-test-go-version
Add Go 1.19 to ci.yml test matrix
2022-09-11 18:23:56 -05:00
Faye Amacker
a0d9a3c68a
Add Go 1.19 to CI test matrix
Test with 1.17, 1.18, and 1.19.
2022-09-11 16:00:01 -05:00
Faye Amacker
45e293b732
Merge pull request #361 from jdharms/patch-1
godoc.org link in Readme should point to v2
2022-08-27 10:38:47 -05:00
Daniel Harms
286237aeec
godoc.org link in Readme should point to v2
It's possible there's some reason for this link to not point to the v2 docs, but I'm guessing this was just never updated.  I keep clicking this link to bring up documentation and then having to realize I'm looking at an old version :)
2022-08-26 09:27:18 -04:00
Faye Amacker
5cd39e10d5
Mention security assessment by NCC Group
Microsoft Corporation had NCC Group produce a [security assessment (PDF)](https://github.com/veraison/go-cose/blob/v1.0.0-rc.1/reports/NCC_Microsoft-go-cose-Report_2022-05-26_v1.0.pdf) which includes portions of this library in its scope.
2022-07-16 11:38:12 -05:00
Faye Amacker
77950b0b5e
Mention security assessment by NCC Group
Microsoft Corporation had NCC Group do a security assessment
which included portions of fxamacker/cbor v2.4.0 in its scope.
2022-07-16 11:07:52 -05:00
Faye Amacker
ab3392fb63
Merge pull request #355 from immesys/immesys/increase-nested-levels
Allow MaxNestedLevels to be up to 65535
2022-07-04 12:19:50 -05:00
Michael Andersen
d356e3fd51
Allow MaxNestedLevels to be up to 65535 2022-07-03 11:35:00 -07:00
Faye Amacker
d99d9d4f42
Merge pull request #353 from deeglaze/typos
Fix typos
2022-06-29 21:21:27 -05:00
Dionna Glaze
7546d1a547 Fix typos
Signed-off-by: Dionna Glaze <dionnaglaze@google.com>
2022-06-29 18:58:14 +00:00
Danila Fomin
64ed7b735b add option to enforce nil container marshaling as empty containers 2022-06-06 17:05:08 +03:00
Faye Amacker
1603763bb8
Merge pull request #350 from x448/patch-27
Update pull_request_template.md
2022-05-29 10:34:59 -05:00
Montgomery Edwards⁴⁴⁸
7a9d6f4863
Update pull_request_template.md 2022-05-29 09:58:49 -05:00
Faye Amacker
9e37184aca
Merge pull request #347 from fxamacker/dependabot/github_actions/github/codeql-action-2
Bump github/codeql-action from 1 to 2
2022-05-16 07:50:04 -05:00
dependabot[bot]
81cb9ce0a1
Bump github/codeql-action from 1 to 2
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 1 to 2.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v1...v2)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-16 01:18:44 +00:00
Faye Amacker
7d034603ed
Merge pull request #346 from x448/patch-26
Create dependabot.yml
2022-05-15 19:28:23 -05:00
Montgomery Edwards⁴⁴⁸
628ec828ac
Create dependabot.yml 2022-05-15 15:30:44 -05:00
Faye Amacker
46a0ceba89
Merge pull request #344 from fxamacker/fix-ci-for-stream-mode
Fix ci for stream mode branch
2022-05-15 13:11:53 -05:00
Faye Amacker
3e7918c540 Fix ci for stream mode branch 2022-05-15 13:04:31 -05:00
Montgomery Edwards⁴⁴⁸
1f558eb84b
Remove default permissions from GitHub Actions workflows (#341)
Remove default permissions.
Grant only read permission.
2022-05-15 12:38:39 -05:00
Faye Amacker
5b9c681a83
Merge pull request #342 from fxamacker/fxamacker/add-decode-option-for-invalid-utf8-in-nonstreammode
Add decoding option to allow invalid UTF-8
2022-05-15 12:02:37 -05:00
Faye Amacker
ae9b14c967 Add decoding option to allow invalid UTF-8
Add DecOptions.UTF8 to specify how to decode invalid UTF-8:
- UTF8RejectInvalid (default) returns SemanticError on invalid UTF-8
- UTF8DecodeInvalid decodes invalid UTF-8

Since CBOR text containing invalid UTF-8 isn't valid, decoder by default
rejects it and returns error.

This option is backward compatible by default.

See RFC 8949 Section 5.3 (Validity of Items) for more info.
2022-05-15 11:23:00 -05:00
Montgomery Edwards⁴⁴⁸
8988cfe9ed
Update ci.yml (#340)
Revoke default permissions.
Grant minimum required permission for CI job.
Add Go version to test names:  name: test ${{matrix.os}} go-${{ matrix.go-version }}
2022-05-14 23:21:54 -05:00
Montgomery Edwards⁴⁴⁸
33643a4809
Update ci.yml (#339)
Update ci triggers and tidy up.
2022-05-14 12:24:18 -05:00
Faye Amacker
7be1c8e2d3
Merge pull request #335 from ldclabs/master
Reuse underlying array if RawMessage has sufficient capacity
2022-05-08 22:41:40 -05:00
0xZens
e661c6546a Reuse underlying array if RawMessage has sufficient capacity
This is a performance optimization for implementing cbor-patch.

Signed-off-by: Yan Qing <admin@zensh.com>
2022-05-07 20:59:55 +08:00
Faye Amacker
89cef41860
Update ci.yml (#334)
Bump actions/checkout to v3.
Bump actions/setup-go to v3.
Add Go 1.18, remove Go 1.16.
2022-05-04 21:45:35 -05:00
CodingVoid
109ed52791
Remove trailing whitespaces in .golangci.yml (#333) 2022-05-04 21:31:44 -05:00
Montgomery Edwards⁴⁴⁸
6cdde668b0
Update CBOR docs for v2.4.0 (#318)
Some checks failed
cover ≥98% / Coverage (push) Has been cancelled
ci / Test on ${{matrix.os}} (1.15.x, macos-latest) (push) Has been cancelled
ci / Test on ${{matrix.os}} (1.15.x, ubuntu-latest) (push) Has been cancelled
ci / Test on ${{matrix.os}} (1.15.x, windows-latest) (push) Has been cancelled
ci / Test on ${{matrix.os}} (1.16.x, macos-latest) (push) Has been cancelled
ci / Test on ${{matrix.os}} (1.16.x, ubuntu-latest) (push) Has been cancelled
ci / Test on ${{matrix.os}} (1.16.x, windows-latest) (push) Has been cancelled
ci / Test on ${{matrix.os}} (1.17.x, macos-latest) (push) Has been cancelled
ci / Test on ${{matrix.os}} (1.17.x, ubuntu-latest) (push) Has been cancelled
ci / Test on ${{matrix.os}} (1.17.x, windows-latest) (push) Has been cancelled
- v2.4.0 passed 3.4 billion execs fuzzing
- add gravitational/teleport as a user
- remove mention of very outdated cbor-fuzz which was replaced long ago
- bump minimum fuzzing to 1+ billion execs for each new release as discussed with @fxamacker
- fix broken links in the badges
- minor updates to README during review

Co-authored-by: Faye Amacker <33205765+fxamacker@users.noreply.github.com>
2022-01-03 18:01:31 -06:00
Faye Amacker
5ea68a6873
Merge pull request #308 from fxamacker/fxamacker/decode-registered-cbor-tag-to-registered-type
Add support for decoding registered CBOR tag to interface type
2021-12-28 20:06:15 -06:00
Faye Amacker
4d014d215b Update CBOR docs 2021-12-28 20:03:48 -06:00
Faye Amacker
8f68a02d37 Merge with master branch 2021-12-28 19:12:47 -06:00
Faye Amacker
466959a191
Merge pull request #316 from fxamacker/fxamacker/add-default-map-type
Add option to specify default Go map type when decoding CBOR map into interface{}
2021-12-28 18:29:35 -06:00
Faye Amacker
ee1a9c9b2a Add DecOptions.DefaultMapType
Add option to specify Go map type to decode to when
unmarshalling to interface{}.
2021-12-28 17:54:25 -06:00
Faye Amacker
bd3cab7bc0
Update CBOR docs
Some checks failed
cover ≥98% / Coverage (push) Has been cancelled
ci / Test on ${{matrix.os}} (1.15.x, macos-latest) (push) Has been cancelled
ci / Test on ${{matrix.os}} (1.15.x, ubuntu-latest) (push) Has been cancelled
ci / Test on ${{matrix.os}} (1.15.x, windows-latest) (push) Has been cancelled
ci / Test on ${{matrix.os}} (1.16.x, macos-latest) (push) Has been cancelled
ci / Test on ${{matrix.os}} (1.16.x, ubuntu-latest) (push) Has been cancelled
ci / Test on ${{matrix.os}} (1.16.x, windows-latest) (push) Has been cancelled
ci / Test on ${{matrix.os}} (1.17.x, macos-latest) (push) Has been cancelled
ci / Test on ${{matrix.os}} (1.17.x, ubuntu-latest) (push) Has been cancelled
ci / Test on ${{matrix.os}} (1.17.x, windows-latest) (push) Has been cancelled
2021-12-28 16:35:23 -06:00
Faye Amacker
033552cc15
Update CBOR docs 2021-12-28 16:28:07 -06:00
Faye Amacker
c89cf33f63
Merge pull request #315 from x448/patch-22
Update README.md
2021-12-26 21:31:29 -06:00
Montgomery Edwards⁴⁴⁸
3cafefc59d
Update README.md
Fix text padding in a table.
2021-12-26 21:26:55 -06:00
Montgomery Edwards⁴⁴⁸
c9b463feb0
Update README.md
Update CBOR Security comparison.
Update CBOR Performance comparison.
Add summary below the badges for pkg.go.dev.
Removed some text.
2021-12-26 20:37:49 -06:00
Faye Amacker
6844380335
Merge pull request #314 from x448/patch-21
Update CONTRIBUTING.md
2021-12-26 10:40:59 -06:00
Montgomery Edwards⁴⁴⁸
12eb3c7012
Update CONTRIBUTING.md
- Replace "How to Contribute" section
- Mention signing requirements in "Pull Requests" section
2021-12-25 13:16:10 -06:00
Faye Amacker
7c0cd90a24
Bump safer-golangci-lint.yml to 1.43.0 2021-12-05 21:40:05 -06:00
Faye Amacker
552c25b908 Fix lint error 2021-12-05 20:46:54 -06:00
Faye Amacker
3ab76234d6 Support decoding registered tag to interface type
Modify decoder to create new value of registered type and decode data
to that value when unmarshaling CBOR data with registered tag to
interface type.
2021-12-05 20:34:45 -06:00
Faye Amacker
1ca0c319b9
Merge pull request #307 from fxamacker/fxamcker/fix-lint-error
Add revive linter and fix lint errors
2021-12-05 14:05:07 -06:00
Faye Amacker
e0963b1c4b Add revive as a default lint checker 2021-12-05 13:53:37 -06:00
Faye Amacker
9662cc1930 Fix lint error about commented out code 2021-12-05 13:52:16 -06:00
Faye Amacker
b5ec710759 Fix revive lint errors 2021-12-05 13:42:27 -06:00
Faye Amacker
93087d12e5
Merge pull request #306 from x448/patch-20
Add Go 1.17 to ci.yml
2021-12-05 13:35:13 -06:00
Montgomery Edwards⁴⁴⁸
eaaab26c9a
Add Go 1.17 to ci.yml
Drop Go 1.14 in ci.yml given OS x Go test matrix size.
2021-12-05 11:12:30 -06:00
Faye Amacker
f70d0168fe
bump safer-golangci-lint.yml to 1.42.1 2021-09-29 23:42:46 -05:00
Ackermann Yuriy
e480037e4a
Fixed missing quotes in README (#298) 2021-09-14 20:11:15 -05:00
Faye Amacker
24c34a4f22
Add Netherlands (govt) and Taurus SA as users 2021-08-28 22:08:19 -05:00
Montgomery Edwards⁴⁴⁸
01c850ef0d
Bump safer-golangci-lint.yml to 1.42 (#296) 2021-08-22 13:04:58 -05:00
Stefan Tatschner
891c2b85a1
Update SEP URL (#294) 2021-08-13 07:41:04 -05:00
Faye Amacker
07ff84e9e9
Add CBOR RFC 8949 to Go docs
Update docs.go to mention:
- CBOR RFC 8949
- Core Deterministic Encoding
2021-07-04 22:45:15 -05:00
Faye Amacker
af2a3ad5c1
Update CBOR RFC 8949 docs
Add sections:
- What is CBOR?
- Why CBOR?
- What is fxamacker/cbor?
- Who uses fxamacker/cbor?
- Why fxamacker/cbor?
Add missing doc for DecOptions.ExtraReturnErrors.
Closes #289
2021-06-27 17:08:15 -05:00
Faye Amacker
709dce336b Make CBOR test using -0.0 ignore linter
New version of staticcheck complained when
test uses -0.0 because it is same as 0.0 in Go.
2021-06-20 13:25:31 -05:00
Montgomery Edwards⁴⁴⁸
9cd3bafb49
Bump safer-golangci-lint.yml to 1.14.1 (#292) 2021-06-20 12:50:35 -05:00
Montgomery Edwards⁴⁴⁸
a773b413e8
Add CBOR fuzzing badge (#290)
* Add CBOR fuzzing badge

* Add badge to show Go versions supported
2021-06-16 18:45:16 -05:00
Faye Amacker
765bad254c
Describe CBOR RFC 8949 and RFC 7049 2021-06-05 19:35:47 -05:00
Montgomery Edwards⁴⁴⁸
a7a2de6c7d
CBOR fuzzing passed 1+ billion execs (#287)
Add banner with fuzzing stats.
Add notable projects using fxamacker/cbor to banner.
2021-06-03 20:38:23 -05:00
Montgomery Edwards⁴⁴⁸
ffaad45036
Update ci.yml (#286)
Expand matrix to 3 os x 3 go versions.

closes #203
2021-06-02 20:12:42 -05:00
Montgomery Edwards⁴⁴⁸
3444832a9d
Update README.md (#284)
Update CBOR library fuzzing stats to 728+ million execs for v2.3.0.
Replace "import" with "go get" instruction as the mini-banner.
2021-05-31 10:45:21 -05:00
Montgomery Edwards⁴⁴⁸
7b5d933bcc
Update README.md (#283)
MaxArrayElements defaults to 131072, can be set to [16, 2147483647]
MaxMapPairs defaults to 131072, can be set to [16, 2147483647]
2021-05-30 21:17:08 -05:00
Montgomery Edwards⁴⁴⁸
0aba0a5594
Update README.md (#282) 2021-05-30 19:32:52 -05:00
Montgomery Edwards⁴⁴⁸
230e12c2a6
Update README.md (#281)
Some checks failed
cover ≥98% / Coverage (push) Has been cancelled
ci / Test on ${{matrix.os}} (macos-latest) (push) Has been cancelled
ci / Test on ${{matrix.os}} (ubuntu-latest) (push) Has been cancelled
Prepare for fxamacker/cbor release 2.3.0.
2021-05-30 16:51:52 -05:00
Faye Amacker
4a03f1c003
Allow decoding to struct field of interface type (#280)
Closes #260
Closes #275
2021-05-29 19:19:40 -05:00
Faye Amacker
3240b60c8b
Merge pull request #279 from x448/patch-10
Update README.md
2021-05-27 23:50:46 -05:00
Montgomery Edwards⁴⁴⁸
51d23f44d0
Update README.md 2021-05-27 23:46:20 -05:00
Faye Amacker
18c29e604a
Update README.md
Remove broken badge.
2021-05-26 22:11:46 -05:00
Faye Amacker
c4fbdd596e
Update README.md
Comment out broken badge.
2021-05-26 22:11:12 -05:00
Faye Amacker
21ed974ee0
Merge pull request #277 from x448/patch-9
Update README.md
2021-05-26 22:04:01 -05:00
Montgomery Edwards⁴⁴⁸
66ab2cbf9f
Update README.md 2021-05-26 22:00:44 -05:00
Faye Amacker
d026dee820
Update README.md
Make text fit one row in the first table.
2021-05-22 16:13:26 -05:00
Faye Amacker
e75c9ba7fe
Merge pull request #276 from x448/patch-8
Update README.md
2021-05-22 16:05:19 -05:00
Montgomery Edwards⁴⁴⁸
f733cc3f1f
Update README.md
Improve appearance of several tables in dark mode.
2021-05-22 15:57:00 -05:00
Faye Amacker
bdd38cd1c8
Create codeql-analysis.yml 2021-05-16 22:23:02 -05:00
Montgomery Edwards⁴⁴⁸
9638b67516
Update safer-golangci-lint.yml and .golangci.yml (#274)
Replace golint with revive.

Remove maligned because govet now includes that check.

Add nilerr and revive as optional linters.

Bump timeout to 5m.
2021-05-16 14:48:49 -05:00
Montgomery Edwards⁴⁴⁸
0100c5e900
Use golangci-lint more securely (#273)
Use golangci-lint more securely in GitHub Actions
workflow.

100% of the script for downloading, installing, and
running golangci-lint is embedded in the workflow
file.

The embedded SHA384 is used to verify the
downloaded golangci-lint tarball 
(golangci-lint-1.40.1-linux-amd64.tar.gz).
2021-05-16 13:38:30 -05:00
Faye Amacker
0481ab7a57
Update CBOR_GOLANG.md with RFC 8949
CBOR is defined in RFC 8949 Concise Binary Object Representation
(previously RFC 7049).
2021-05-14 02:03:03 -05:00
Faye Amacker
577ef242e5
Delete FUNDING.yml 2021-05-13 23:08:06 -05:00
Faye Amacker
0bfe9e76fc
Shorten docs for CBOR RFC 8949, RFC 7049 settings
Get rid of "settings for" prefix in the list of functions that provide
predefined CBOR settings such as "Core Deterministic Encoding".
2021-04-06 09:14:21 -05:00
Faye Amacker
33600bc627
Document CBOR RFC 8949 features
CBOR features already implemented before RFC 8949 include:
 - Core Deterministic Encoding
 - Preferred Serialization

Mention that "7049bis" is the draft leading up to RFC 8949 (Dec 2020).

Closes #265
2021-04-06 08:57:19 -05:00
Faye Amacker
813cd60e4a
Update README to mention CBOR RFC 8949 features
This CBOR library added RFC 8949 (Dec 2020) features before it was approved.

Examples include "Preferred Serialization" and "Core Deterministic Encoding".

Replace "draft RFC" with "RFC 8949", etc.
2021-04-01 22:13:33 -05:00
Faye Amacker
6cf31e9ea0
Fix encoding cbor.RawTag with empty content (#259)
Encoding cbor.RawTag with empty content returns CBOR representation of tag_number(null).

Closes #258
2020-11-18 22:34:17 -06:00
Faye Amacker
ee962ff86d
Encode uninitialized cbor.(Raw)Tag to CBOR null (#257)
Closes #256
2020-11-18 20:58:28 -06:00
Faye Amacker
c5db62bc08
Perform no-op when decoding null to time.Time (#255)
Decoding CBOR null (0xf6) and undefined (0xf7) to time.Time has no effect and returns no error.

See Unmarshal documentation.

Closes #254
2020-11-18 16:00:15 -06:00
Faye Amacker
054a0aee03
Perform no-op when decoding null to cbor.(Raw)Tag (#253)
Decoding CBOR null (0xf6) and undefined (0xf7) to cbor.Tag or cbor.RawTag has no effect and returns no error.

See Unmarshal documentation.

Closes #252
2020-11-14 17:53:16 -06:00
Faye Amacker
802583e0df
Increase test coverage (#251) 2020-11-14 14:51:44 -06:00
Bastian Müller
25f67fca98
Export valid function (#249)
Closes: #248 

Signed-off-by: Bastian Müller <bastian@turbolent.com>
2020-10-06 17:31:49 -05:00
Faye Amacker
bafca87fa6
Fix DecOptions.ExtraReturnErrors field type (#244)
DecOptions.ExtraReturnErrors field type (int) was inconsistent
with type (ExtraDecErrorCond) of its associated constants,
ExtraDecErrorNone and ExtraDecErrorUnknownField.

DecOptions.ExtraReturnErrors field type is changed to ExtraDecErrorCond
so that no type cast is needed when assiging ExtraDecErrorNone or
ExtraDecErrorUnknownField to DecOptions.ExtraReturnErrors.

Closes: #240
2020-08-19 21:19:30 -05:00
Faye Amacker
58b1cf4afc
Support omitempty for struct & BinaryMarshaler types (#243)
Struct field with "omitempty" option is omitted during encoding if:
- field is false
- field is 0
- field is empty string, array, slice, or map
- field is null pointer or interface
- field is nil or empty slice for cbor.RawMessage
- (new) field is nil or empty slice for types implementing cbor.BinaryMarshaler
- (new) field is empty struct

Currently "omitempty" option is a no-op for the following types:
- time.Time
- big.Int
- cbor.Tag
- cbor.RawTag
- types implementing cbor.Marshaler

Removed Go's copyright notice in encode.go.

Updated README and LICENSE to remove Go's copyright and license.

Closes #232
2020-08-18 13:19:54 -05:00
Faye Amacker
a26ad4a7e5
Refactor and remove Go's copyright notice in 1 file (#242)
Refactored getFields and other functions.

Removed Go's copyright notice in structfields.go.
2020-07-30 18:35:59 -05:00
Faye Amacker
58b82b5bfc
Add missing attribution (#239)
Added missing attribution in encode_test.go.

Refactored and renamed variables, functions, and types.

Modified documentation.

Closes #233
Closes #237
2020-05-25 22:19:12 -05:00
Montgomery Edwards⁴⁴⁸
b7f2e3ebea
Update README.md to remove my name (#238) 2020-05-22 18:52:04 -05:00
Faye Amacker
26582a92c4
Include Go's copyright notice to 2 files (#236)
Two files contain a small amount of code from the Go standard library.

Add Go's copyright notice to 2 files:
- encode.go
- structfields.go

Updates issue #233, #234
2020-05-21 18:38:46 -05:00
Montgomery Edwards⁴⁴⁸
40147c8a2a
Update README and LICENSE to add Go's copyright and license (#235)
Update README to add:

* "Portions (from Go) Copyright © 2009 The Go Authors"
*  "Go is licensed under BSD-style license.  See LICENSE for the full license text."
* "This library includes a small amount of code from Go's standard library."

Add Go's BSD-style license to LICENSE file.  Prefix Go's copyright notice
with "Portions (from Go) ".
2020-05-21 18:12:21 -05:00
Faye Amacker
bffaf0cda7
Add support for CBOR tags 2 and 3 <-> big.Int (#231)
By default, encode big.Int value to CBOR integer (if value fits),
otherwise encode to CBOR bignum (CBOR tag 2 or 3).

BigIntConvert is the CBOR encoding option for big.Int:

- BigIntConvertShortest: (default) encode big.Int to CBOR integer
  if value fits, otherwise encode to CBOR bignum.

- BigIntConvertNone: encode big.Int to CBOR bignum without
  converting it to another CBOR type.

Decoder can decode the following to big.Int:

- CBOR bignum (tags 2 and 3)

- CBOR integers (major type 0 and 1)

When decoding CBOR bignum to empty interface, decode to big.Int.

When decoding CBOR negative integer (major type 1) to empty interface:

- if value fits int64, decode to int64.

- if value doesn't fit int64, decode to big.Int.

Closes #209
2020-05-20 18:49:40 -05:00
Faye Amacker
28e39be4a8
Strip CBOR tag number 55799 when it is a prefix (#230)
Tag number 55799 is normally prefixed to data to indicate that it is
CBOR encoded.

This library strips tag number 55799 (when it is a prefix) to provide
the same decoding behavior as if CBOR data is not tagged.

* When decoding to empty interface or other Go types, tag number 55799
is skipped and tag content is decoded.

* When decoding to Unmarshaler, tag number 55799 is skipped and only
the tag content is provided to UnmarshalCBOR.

Closes #228
2020-05-11 16:20:21 -05:00
Faye Amacker
0b811b7c78
Increase MaxArrayElements and MaxMapPairs max limits (#226)
Increased MaxArrayElements and MaxMapPairs max limits
from 134217728 to 2147483647.

Default limits are unchanged.

Closes #207
2020-05-03 22:03:20 -05:00
Faye Amacker
37a0c7ee51
Add option to disallow unknown field during decoding (#225)
New decoding option ExtraReturnErrors specifies extra conditions that
should be treated as errors.  It's a bit set of ExtraDecErrorCond values.

ExtraDecErrorCond values:

* ExtraDecErrorNone: (default) no extra error condition.

* ExtraDecErrorUnknownField: error condition when destination Go struct
doesn't have a field matching CBOR map key.  It only affects decoding
CBOR maps to Go struct values.

Closes #178
2020-05-03 20:50:20 -05:00
Faye Amacker
fc263b46c6
Return registered type when decoding tag to interface{} (#224)
Unmarshaling to empty interface returns object of registered content
type when tag number matches.

Also allow Go types to register with tag number if they implement
Unmarshaler interface.

Closes #223
2020-04-29 16:40:22 -05:00
Faye Amacker
8c2d6eb841
Validate tagged time even if TimeTag = DecTagIgnored (#222)
Validate CBOR data type of tagged content if tag number is 0 or 1,
even when TimeTag = DecTagIgnored.

Closes #221
2020-04-28 21:55:22 -05:00
Faye Amacker
337e96fc01
Refactor CBOR time decoding (#220) 2020-04-27 17:05:37 -05:00
Faye Amacker
269b8fffb7
Reject registering tag with duplicate tag number (#219)
Make TagSet.Add return error when new tag number is already registered
with the same TagSet.

Closes #218
2020-04-27 15:02:14 -05:00
Faye Amacker
3f759bc591
Add options for decoding CBOR int to interface{} (#217)
New decoding option IntDec affects how CBOR integers (major type 0 and 1)
decodes to Go interface{}.

IntDec values:

* IntDecConvertNone: (default) make CBOR positive int (major type 0) decode
to uint64 value, and CBOR negative int (major type 1) decode to int64 value.

* IntDecConvertSigned: make CBOR positive/negative int (major type 0 and 1)
decode to int64 value.  If value overflows int64, UnmarshalTypeError is
returned.

This can reduce/simplify decoding code written by users. For example,
decoding COSE maps to interface{}.

Instead of users checking for Go int64 and uint64, they can just check
for Go int64.

Closes: #216
2020-04-20 13:06:35 -05:00
Faye Amacker
e2eaf2ab40
Fix gofmt error for doc.go (#213)
Remove some trailing spaces by running gofmt.
2020-04-06 13:23:02 -05:00
Montgomery Edwards⁴⁴⁸
a6ed6ff68e
Update docs (#211)
* Update doc.go

Provide links to Quick Start guide, list of struct tags, encoding options, decoding options, etc. 

Make it clear em.Marshal(v) and encoder.Encode(v) use encoding options
specified during creation of the encoding mode em.

* Update README.md

Quick Start: Both em.Marshal(v) and encoder.Encode(v) use encoding options specified during creation of encoding mode em.
2020-04-05 13:56:34 -05:00
Faye Amacker
11b0c76f00
Update example_test.go (#210)
Added comment to remove "/v2" suffix when not using Go modules.
2020-04-05 10:37:58 -05:00
Montgomery Edwards⁴⁴⁸
a0722026e1
Update README.md (#205)
Add "Struct Tags" subsection under "Options" section and include previously undocumented struct tags.
Mention if struct tag supports "cbor" vs  "cbor or json" for struct field key.
Update list of struct tags in "Features" section.
Update list of links at the end of "Quick Start" section.
Replace "Security Policy" in one-line TOC with "Options" because security policy is already available from 3 other places.

Closes #201
2020-03-30 13:33:34 -05:00
Faye Amacker
af2b3f70c4
Update README.md (#200)
Added CBOR tag validity to Standards section.
Added Tag Options section.

Closes: #150 
Closes: #199
2020-03-22 17:00:33 -05:00
Montgomery Edwards⁴⁴⁸
007fd9f039
Update linters.yml (#198)
Bump golangci-lint from 1.23.1 to 1.23.8 because ubuntu-latest image appears to use Go 1.14 now.

Cleanup parameters passed to golangci-lint by removing params already specified in separate config file.
2020-03-21 17:46:52 -05:00
Faye Amacker
4201f59659 Modify pull request template 2020-03-21 17:40:39 -05:00
Faye Amacker
2cb411a4fb Modify pull request template
Closes #197
2020-03-21 17:10:01 -05:00
Faye Amacker
661e8ddb2e Create pull request templates 2020-03-21 16:14:02 -05:00
Montgomery Edwards⁴⁴⁸
bd88e42a9d
Update Security Policy (#196)
Created SECURITY.md file because GitHub automatically uses it.

The text is mostly copied from "Security Policy" section of README.md with an exception added for vulnerabilities already known to the public (to encourage easier or anonymous reporting).

Update "Security Policy" section.  Link to SECURITY.md instead of providing details in the README.
2020-03-20 11:22:33 -05:00
Faye Amacker
5dc3bbd09d
Rename issue templates (#195) 2020-03-13 21:17:29 -05:00
Faye Amacker
45acf375fc Update issue templates 2020-03-13 20:24:59 -05:00
Faye Amacker
e0aaf60d9b
Create FUNDING.yml 2020-03-11 20:53:16 -05:00
Faye Amacker
e6e5d003a9
Update README.md
GitHub is showing weird errors about SQL Server and MARS TDS which isn't related to my code but it shows up as CI errors on two badges.  See if changing the README.md again resolves the issue.
2020-03-09 10:48:29 -05:00
Faye Amacker
aeb8b6d961
Update README.md
Fix typo.  Changed "Functions" to "Function" because the word "with" was removed in a prior commit yesterday.
2020-03-09 10:26:20 -05:00
Montgomery Edwards⁴⁴⁸
f880af2d19
Update README.md (#193)
7049bis looks like it's adding requested text regarding undefined tag content and specifically time values (including NaN and Infinity).  Document how these values are handled by this CBOR library.

Four CBOR time values are decoded as Go's "zero instant time" (month 1, year 1, 00:00:00 UTC).  CBOR Null, CBOR Undefined, CBOR float NaN, and CBOR float Infinity.

Go provides func (t Time) IsZero() bool to detect "zero instant time".

Move the Standard API & Interfaces paragraph above Struct tags paragraph in the intro section.  Mention it's designed to simplify concurrency and allow fast parallelism.  CBOR options can be used without creating unintentional runtime side-effects and without passing options around everywhere.

Make fxamacker/cbor easier to read by not putting it inside code block everywhere -- just bold it. Also add line break between tables.
2020-03-08 16:40:03 -05:00
Montgomery Edwards⁴⁴⁸
aab6bac7c1
Update README.md (#192)
Fix typo introduced in last commit.  "Stuct" -> "Struct".
2020-03-07 13:20:05 -06:00
Montgomery Edwards⁴⁴⁸
03702ef8fd
Update README.md (#191)
Update v2.2 passed 3.1+ billion execs of coverage-guided fuzzing on March 7, 2020 (thanks @fxamacker for running it nonstop on her workstation because my cloud server is too slow for it).

Update description of CBOR by mentioning it's an Internet Standard by IETF.

In several places, replace:
encoder.Encode, decoder.Decode 
with 
(*Encoder).Encode, (*Decoder).Decode
2020-03-07 12:56:01 -06:00
Montgomery Edwards⁴⁴⁸
82ec48fc3c
Update README.md (#190)
Thank Carsten for adding this library to cbor.io.
Reorganized the intro section by moving some CBOR library comparisons below the CBOR features table.  
Use click-to-expand for some CBOR performance comparisons already shown in the slideshow.
Replace two shields.io badges with static svg file (release version and license).
2020-03-06 19:27:14 -06:00
Faye Amacker
2c4c4e8dfd
Update README.md (#189)
Remove extra parenthesis from svg link that caused alt text to appear.
2020-03-04 18:26:05 -06:00
Faye Amacker
f4af62833a
Update README.md (#188)
Remove extra parenthesis for SVG link that caused alt text to appear in the Usage section.
2020-03-04 15:57:57 -06:00
Montgomery Edwards⁴⁴⁸
cf1e2b7c07
Update README.md (#187)
When users click on svg tables, they shouldn't jump to another location in the document.  Instead, display the svg independent of README.md so the user can select text from the svg table.
2020-03-03 20:55:58 -06:00
Montgomery Edwards⁴⁴⁸
247466bfba
Update README.md (#186)
Give credit to a recently updated fxamacker/cbor-fuzz for finding the bug fixed in v2.2.
Small update to Installation section to make "/v2" stand out more.
2020-03-02 11:16:32 -06:00
Montgomery Edwards⁴⁴⁸
3271ed46f6
Update README.md
Clicking on cbor_struct_tags_api.svg shouldn't jump to Usage Section because user may have been trying to click to copy text.
Update Quick Start to make it more clear about importing with "/v2".
2020-03-01 22:51:55 -06:00
Faye Amacker
42f66f2895
Change size comparison barchart svg to png 2020-03-01 22:16:12 -06:00
Montgomery Edwards⁴⁴⁸
d566288ac7
Update README to replace 2 markdown tables w/ svg (#184)
Replace Encoding Options markdown table with svg.  
Replace Decoding Options markdown table with svg.
They use font-family: SF Pro Text, Helvetica Neue, Segoe UI, Roboto, Liberation Sans, Arial, san-serif
2020-02-29 16:46:59 -06:00
Montgomery Edwards⁴⁴⁸
bc4681c7f3
docs: Replace "Struct Tags and API" png with svg 2020-02-26 23:36:41 -06:00
Montgomery Edwards⁴⁴⁸
a181f1cb9b
Replace CBOR Comparison .png with .svg and more
Replace barchart PNG with SVG.  Size is much smaller and it isn't fuzzy.  This SVG doesn't use font embedding to limit bloat.

Clarify some text, mention Section 8 of CBOR RFC 7049 (Security Considerations).  Mention using "/v2" in the import statement under Quick Start because at least one person appears to have skipped the Installation instructions (not their fault since Go modules and /v2 isn't intuitive).
2020-02-26 14:44:26 -06:00
Montgomery Edwards⁴⁴⁸
7f924808ee
Update README to begin workaround for go.dev bugs
Some checks failed
cover ≥98% / Coverage (push) Has been cancelled
ci / Test on ${{matrix.os}} (macos-latest) (push) Has been cancelled
ci / Test on ${{matrix.os}} (ubuntu-latest) (push) Has been cancelled
linters / Lint (push) Has been cancelled
Replaced a handful of markdown tables with SVG: "cbor speed/memory comparison", "cbor security comparison", "cbor features" and one more.

There's a lot of markdown tables remaining (encoding/decoding option tables, etc.) that are being rendered incorrectly on go.dev.

Mentioned the bug fix for v2.2, and CBOR  decoder settings: `MaxNestedLevels`, `MaxArrayElements`, `MaxMapPairs`, and `IndefLength`.

go.dev bugs filed about this are:
https://github.com/golang/go/issues/37284
https://github.com/golang/go/issues/37394

Closes #175
2020-02-24 22:40:25 -06:00
Faye Amacker
8518c6f4a8
Update benchmarks for v2.2 2020-02-23 20:29:37 -06:00
Montgomery Edwards⁴⁴⁸
fd7a760e7c
Update README.md to resolve go.dev issues
Try again to make some tables look less horrible on go.dev (this time it rendered nbsp as 6 visible chars: & n b s p ; in a table heading and previous workarounds didn't work for other problems).   https://github.com/golang/go/issues/37284

Also, add list of CBOR standards supported to the intro section.  Add Go syntax highlighting to code snippets.  Rearranged some content.
2020-02-23 14:15:50 -06:00
Faye Amacker
cb23445252
Add CBOR encoding and decoding options, update CTAP2
Add these 2 options to both EncOptions and DecOptions:

* IndefLength: IndefLengthAllowed, IndefLengthForbidden
* TagsMd: TagsAllowed, TagsForbidden

Add 3 more options to DecOptions:

* MaxMapArrayElements (default = 128x1024)
* MaxMapPairs (default = 128x1024)
* MaxNestingLevels (default = 32)

Update func CTAP2EncOptions() to return 2 additional presets:

* TagsMd = TagsForbidden
* IndefLength = IndefLengthForbidden

Closes: #155
Closes: #167
2020-02-23 00:45:36 -06:00
Faye Amacker
242c772f74
Merge pull request #169 from fxamacker/feat-issue-133
Support decoding CBOR byte string to Go byte array and also prevent potential decoding error
2020-02-22 13:31:18 -06:00
Faye Amacker
c7f2cc7b11 Prevent error decoding indef len array to Go array
There's no problem decoding to Go slice, which is the recommended and
default Go type for CBOR arrays.

This fix prevents potential errors that can happen when CBOR indefinite
length array has fewer elements than Go array.

This problem was detected by an updated version of fxamacker/cbor-fuzz
and exists in all prior versions of this library.

Closes: #170
2020-02-22 13:16:35 -06:00
Faye Amacker
52db071921 Support decoding CBOR byte string to Go byte array
Added support for decoding CBOR byte string (major type 2) to
Go byte array.  Go byte slices were already supported.

Decoding is handled the same way as encoding/json, which says:

"To unmarshal a JSON array into a Go array, Unmarshal decodes
JSON array elements into corresponding Go array elements. If the
Go array is smaller than the JSON array, the additional JSON array
elements are discarded. If the JSON array is smaller than the
Go array, the additional Go array elements are set to zero values."

Closes: #133
2020-02-21 15:44:01 -06:00
52 changed files with 20275 additions and 4700 deletions

36
.github/ISSUE_TEMPLATE/1-bug-report.md vendored Normal file
View File

@ -0,0 +1,36 @@
---
name: "\U0001F41E Bug report"
about: Create a report to help us improve
title: 'bug: '
labels: ''
assignees: ''
---
### What version of fxamacker/cbor are you using?
### Does this issue reproduce with the latest release?
### What OS and CPU architecture are you using (`go env`)?
<details><summary><code>go env</code> Output</summary><br><pre>
$ go env
</pre></details>
### What did you do?
<!--
If possible, provide steps and/or code to reproduce the problem.
-->
### What did you expect to see?
### What did you see instead?

View File

@ -0,0 +1,20 @@
---
name: "\U0001F4A1 Feature request"
about: Suggest an idea for this project
title: 'feature: '
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@ -0,0 +1,16 @@
---
name: "\U0001F4DA Docs, wiki, or website issue"
about: Report an issue regarding documentation, wiki, or website
title: 'docs: '
labels: ''
assignees: ''
---
### What is the URL of the content?
### Please describe the problem.
### Screenshot (if applicable).

View File

@ -0,0 +1,16 @@
---
name: "\U0001F513 Security issue disclosure"
about: Report a security issue in fxamacker/cbor
title: ''
labels: ''
assignees: ''
---
<!--
🛑 PLEASE DO NOT DISCLOSE THE ISSUE HERE BECAUSE IT IS PUBLIC.
Email security disclosures to: faye.github@gmail.com
-->

12
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,12 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "daily"

69
.github/pull_request_template.md vendored Normal file
View File

@ -0,0 +1,69 @@
<!--
Thank you for your interest in contributing to fxamacker/cbor!
-->
### Description
<!-- For code contributions, please complete all the items below this line. -->
<!-- For documentation-only contributions, please delete everything below this line. -->
#### PR Was Proposed and Welcomed in Currently Open Issue
- [ ] This PR was proposed and welcomed by maintainer(s) in issue #___
- [ ] Closes or Updates Issue #___
#### Checklist (for code PR only, ignore for docs PR)
- [ ] Include unit tests that cover the new code
- [ ] Pass all unit tests
- [ ] Pass all lint checks in CI (goimports, gosec, staticcheck, etc.)
- [ ] Sign each commit with your real name and email.
Last line of each commit message should be in this format:
Signed-off-by: Firstname Lastname <firstname.lastname@example.com>
- [ ] Certify the Developer's Certificate of Origin 1.1
(see next section).
#### Certify the Developer's Certificate of Origin 1.1
- [ ] By marking this item as completed, I certify
the Developer Certificate of Origin 1.1.
```
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
660 York Street, Suite 102,
San Francisco, CA 94110 USA
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
```

View File

@ -1,7 +1,7 @@
# Copyright 2020-present Montgomery Edwards⁴⁴⁸ (github.com/x448).
# Copyright 2020-2023 Montgomery Edwards⁴⁴⁸ (github.com/x448).
# This file is licensed under the MIT License. See LICENSE at https://github.com/x448/workflows for the full text.
#
# CI Go Cover 2020.1.28.
# CI Go Cover 2023.5.14.
# This GitHub Actions workflow checks if Go (Golang) code coverage satisfies the required minimum.
# The required minimum is specified in the workflow name to keep badge.svg and verified minimum in sync.
#
@ -14,18 +14,36 @@
# 1. Change workflow name from "cover 100%" to "cover ≥92.5%". Script will automatically use 92.5%.
# 2. Update README.md to use the new path to badge.svg because the path includes the workflow name.
name: cover ≥98%
on: [push]
name: cover ≥96%
# Remove default permissions.
permissions: {}
on:
workflow_dispatch:
pull_request:
push:
branches: [main, master]
jobs:
# Verify minimum coverage is reached using `go test -short -cover` on latest-ubuntu with default version of Go.
# The grep expression can't be too strict, it needed to be relaxed to work with different versions of Go.
cover:
name: Coverage
permissions:
contents: read
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Install Go
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
with:
go-version: 1.21
check-latest: true
- name: Install x448/float16
run: go get github.com/x448/float16@v0.8.4
- name: Go Coverage
run: |
go version

View File

@ -1,26 +1,54 @@
# GitHub Actions - CI for Go to build & test. See ci-go-cover.yml and linters.yml for code coverage and linters.
# GitHub Actions - CI for Go to build & test.
# https://github.com/fxamacker/cbor/workflows/ci.yml
# See ci-go-cover.yml for coverage and safer-golangci-lint.yml for linting.
name: ci
on: [push]
jobs:
# Revoke default permissions.
permissions: {}
on:
workflow_dispatch:
pull_request:
push:
branches:
- 'master'
- 'release*'
- 'feature/stream-mode'
tags:
- 'v*'
jobs:
# Test on various OS with default Go version.
tests:
name: Test on ${{matrix.os}}
name: test ${{matrix.os}} go-${{ matrix.go-version }}
runs-on: ${{ matrix.os }}
permissions:
contents: read
strategy:
matrix:
os: [macos-latest, ubuntu-latest]
os: [macos-latest, ubuntu-latest, windows-latest]
go-version: [1.17, 1.18, 1.19, '1.20', 1.21, 1.22, 1.23]
steps:
- name: Install Go
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
with:
go-version: ${{ matrix.go-version }}
check-latest: true
- name: Checkout code
uses: actions/checkout@v1
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
fetch-depth: 1
- name: Print Go version
run: go version
- name: Get dependencies
run: go get -v -t -d ./...
- name: Build project
run: go build ./...
- name: Run tests
run: |
go version
go test -short -race -v ./...
go test -race -v ./...

45
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@ -0,0 +1,45 @@
name: "CodeQL"
# Remove default permissions
permissions: {}
on:
push:
branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
schedule:
- cron: '30 5 * * 4'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'go' ]
steps:
- name: Checkout repository
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6
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
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6

47
.github/workflows/govulncheck.yml vendored Normal file
View File

@ -0,0 +1,47 @@
# GitHub Actions workflow for govulncheck.
# This file is licensed under MIT License.
# https://github.com/fxamacker/cbor
name: govulncheck
# Revoke default permissions and grant what's needed in each job.
permissions: {}
on:
workflow_dispatch:
pull_request:
paths:
- '**'
- '!**.md'
push:
paths:
- '**'
- '!**.md'
branches:
- 'main'
- 'master'
- 'release*'
- 'feature/stream-mode'
tags:
- 'v*'
jobs:
Check:
runs-on: ubuntu-latest
permissions:
# Grant permission to read content.
contents: read
steps:
- name: Checkout source
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
fetch-depth: 1
- name: Set up Go
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
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
- name: Run govulncheck
run: govulncheck -show=traces ./...

View File

@ -1,21 +0,0 @@
# Go Linters - GitHub Actions
name: linters
on: [push]
jobs:
# Check linters on latest-ubuntu with default version of Go.
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Install golangci-lint
run: |
go version
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.23.1
- name: Run required linters in .golangci.yml plus hard-coded ones here
run: $(go env GOPATH)/bin/golangci-lint run --timeout=5m -E deadcode -E errcheck -E gofmt -E golint -E gosec -E govet -E ineffassign -E maligned -E staticcheck -E structcheck -E unconvert -E varcheck
- name: Run optional linters (not required to pass)
run: $(go env GOPATH)/bin/golangci-lint run --timeout=5m --issues-exit-code=0 -E dupl -E gocritic -E gosimple -E lll -E prealloc -E deadcode -E errcheck -E gofmt -E golint -E gosec -E govet -E ineffassign -E maligned -E staticcheck -E structcheck -E unconvert -E varcheck

View File

@ -0,0 +1,71 @@
# Copyright © 2021-2023 Montgomery Edwards⁴⁴⁸ (github.com/x448).
# This file is licensed under MIT License.
#
# Safer GitHub Actions Workflow for golangci-lint.
# https://github.com/x448/safer-golangci-lint
#
name: linters
# Remove default permissions and grant only what is required in each job.
permissions: {}
on:
workflow_dispatch:
pull_request:
push:
branches: [main, master]
env:
GO_VERSION: '1.22'
GOLINTERS_VERSION: 1.56.2
GOLINTERS_ARCH: linux-amd64
GOLINTERS_TGZ_DGST: e1c313fb5fc85a33890fdee5dbb1777d1f5829c84d655a47a55688f3aad5e501
GOLINTERS_TIMEOUT: 15m
OPENSSL_DGST_CMD: openssl dgst -sha256 -r
CURL_CMD: curl --proto =https --tlsv1.2 --location --silent --show-error --fail
jobs:
main:
name: Lint
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout source
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
fetch-depth: 1
- name: Setup Go
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
with:
go-version: ${{ env.GO_VERSION }}
check-latest: true
- name: Install golangci-lint
run: |
GOLINTERS_URL_PREFIX="https://github.com/golangci/golangci-lint/releases/download/v${GOLINTERS_VERSION}/"
GOLINTERS_TGZ="golangci-lint-${GOLINTERS_VERSION}-${GOLINTERS_ARCH}.tar.gz"
GOLINTERS_EXPECTED_DGST="${GOLINTERS_TGZ_DGST} *${GOLINTERS_TGZ}"
DGST_CMD="${OPENSSL_DGST_CMD} ${GOLINTERS_TGZ}"
cd $(mktemp -d /tmp/golinters.XXXXX)
${CURL_CMD} "${GOLINTERS_URL_PREFIX}${GOLINTERS_TGZ}" --output ${GOLINTERS_TGZ}
GOLINTERS_GOT_DGST=$(${DGST_CMD})
if [ "${GOLINTERS_GOT_DGST}" != "${GOLINTERS_EXPECTED_DGST}" ]
then
echo "Digest of tarball is not equal to expected digest."
echo "Expected digest: " "${GOLINTERS_EXPECTED_DGST}"
echo "Got digest: " "${GOLINTERS_GOT_DGST}"
exit 1
fi
tar --no-same-owner -xzf "${GOLINTERS_TGZ}" --strip-components 1
install golangci-lint $(go env GOPATH)/bin
shell: bash
# Run required linters enabled in .golangci.yml (or default linters if yml doesn't exist)
- name: Run golangci-lint
run: $(go env GOPATH)/bin/golangci-lint run --timeout="${GOLINTERS_TIMEOUT}"
shell: bash

View File

@ -1,12 +1,26 @@
# 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:
@ -17,14 +31,14 @@ linters-settings:
- performance
- style
disabled-checks:
- commentedOutCode
- dupImport # https://github.com/go-critic/go-critic/issues/845
- ifElseChain
- octalLiteral
- paramTypeCombine
- whyNoLint
- wrapperFunc
gofmt:
simplify: false
simplify: false
goimports:
local-prefixes: github.com/fxamacker/cbor
golint:
@ -37,50 +51,54 @@ linters-settings:
suggest-new: true
misspell:
locale: US
staticcheck:
checks: ["all"]
linters:
disable-all: true
enable:
- deadcode
- asciicheck
- bidichk
- depguard
- errcheck
- exportloopref
- goconst
- gocritic
- gocyclo
- gofmt
- goimports
- golint
- goprintffuncname
- gosec
- gosimple
- govet
- ineffassign
- maligned
- misspell
- nilerr
- revive
- staticcheck
- structcheck
- stylecheck
- typecheck
- unconvert
- unused
- varcheck
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
# Excluding configuration per-path, per-linter, per-text and per-source
exclude-rules:
- path: _test\.go
linters:
- goconst
- dupl
- gomnd
- lll
- path: doc\.go
linters:
- goimports
- gomnd
- lll
# golangci.com configuration
# https://github.com/golangci/golangci/wiki/Configuration
service:
golangci-lint-version: 1.23.x # use the fixed version to not introduce new linters unexpectedly
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"

View File

@ -1,264 +0,0 @@
# CBOR Benchmarks for fxamacker/cbor
See [bench_test.go](bench_test.go).
Benchmarks on Jan. 12, 2020 with cbor v1.5.0:
* [Go builtin types](#go-builtin-types)
* [Go structs](#go-structs)
* [Go structs with "keyasint" struct tag](#go-structs-with-keyasint-struct-tag)
* [Go structs with "toarray" struct tag](#go-structs-with-toarray-struct-tag)
* [COSE data](#cose-data)
* [CWT claims data](#cwt-claims-data)
* [SenML data](#SenML-data)
## Go builtin types
Benchmarks use data representing the following values:
* Boolean: `true`
* Positive integer: `18446744073709551615`
* Negative integer: `-1000`
* Float: `-4.1`
* Byte string: `h'0102030405060708090a0b0c0d0e0f101112131415161718191a'`
* Text string: `"The quick brown fox jumps over the lazy dog"`
* Array: `[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26]`
* Map: `{"a": "A", "b": "B", "c": "C", "d": "D", "e": "E", "f": "F", "g": "G", "h": "H", "i": "I", "j": "J", "l": "L", "m": "M", "n": "N"}}`
Decoding Benchmark | Time | Memory | Allocs
--- | ---: | ---: | ---:
BenchmarkUnmarshal/CBOR_bool_to_Go_interface_{}-2 | 114 ns/op | 16 B/op | 1 allocs/op
BenchmarkUnmarshal/CBOR_bool_to_Go_bool-2 | 101 ns/op | 1 B/op | 1 allocs/op
BenchmarkUnmarshal/CBOR_positive_int_to_Go_interface_{}-2 | 141 ns/op | 24 B/op | 2 allocs/op
BenchmarkUnmarshal/CBOR_positive_int_to_Go_uint64-2 | 120 ns/op | 8 B/op | 1 allocs/op
BenchmarkUnmarshal/CBOR_negative_int_to_Go_interface_{}-2 | 141 ns/op | 24 B/op | 2 allocs/op
BenchmarkUnmarshal/CBOR_negative_int_to_Go_int64-2 | 120 ns/op | 8 B/op | 1 allocs/op
BenchmarkUnmarshal/CBOR_float_to_Go_interface_{}-2 | 143 ns/op | 24 B/op | 2 allocs/op
BenchmarkUnmarshal/CBOR_float_to_Go_float64-2 | 121 ns/op | 8 B/op | 1 allocs/op
BenchmarkUnmarshal/CBOR_bytes_to_Go_interface_{}-2 | 182 ns/op | 80 B/op | 3 allocs/op
BenchmarkUnmarshal/CBOR_bytes_to_Go_[]uint8-2 | 201 ns/op | 64 B/op | 2 allocs/op
BenchmarkUnmarshal/CBOR_text_to_Go_interface_{}-2 | 213 ns/op | 80 B/op | 3 allocs/op
BenchmarkUnmarshal/CBOR_text_to_Go_string-2 | 196 ns/op | 64 B/op | 2 allocs/op
BenchmarkUnmarshal/CBOR_array_to_Go_interface_{}-2 |1094 ns/op | 672 B/op | 29 allocs/op
BenchmarkUnmarshal/CBOR_array_to_Go_[]int-2 | 1102 ns/op | 272 B/op | 3 allocs/op
BenchmarkUnmarshal/CBOR_map_to_Go_interface_{}-2 | 2966 ns/op | 1420 B/op | 30 allocs/op
BenchmarkUnmarshal/CBOR_map_to_Go_map[string]interface_{}-2 | 3754 ns/op | 964 B/op | 19 allocs/op
BenchmarkUnmarshal/CBOR_map_to_Go_map[string]string-2 | 2621 ns/op | 740 B/op | 5 allocs/op
Encoding Benchmark | Time | Memory | Allocs
--- | ---: | ---: | ---:
BenchmarkMarshal/Go_bool_to_CBOR_bool-2 | 88.1 ns/op | 1 B/op | 1 allocs/op
BenchmarkMarshal/Go_uint64_to_CBOR_positive_int-2 | 97.3 ns/op | 16 B/op | 1 allocs/op
BenchmarkMarshal/Go_int64_to_CBOR_negative_int-2 | 91.2 ns/op | 3 B/op | 1 allocs/op
BenchmarkMarshal/Go_float64_to_CBOR_float-2 | 100 ns/op | 16 B/op | 1 allocs/op
BenchmarkMarshal/Go_[]uint8_to_CBOR_bytes-2 | 123 ns/op | 32 B/op | 1 allocs/op
BenchmarkMarshal/Go_string_to_CBOR_text-2 | 118 ns/op | 48 B/op | 1 allocs/op
BenchmarkMarshal/Go_[]int_to_CBOR_array-2 | 528 ns/op | 32 B/op | 1 allocs/op
BenchmarkMarshal/Go_map[string]string_to_CBOR_map-2 | 2096 ns/op | 576 B/op | 28 allocs/op
## Go structs
Benchmarks use struct and map[string]interface{} representing the following value:
```
{
"T": true,
"Ui": uint(18446744073709551615),
"I": -1000,
"F": -4.1,
"B": []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26},
"S": "The quick brown fox jumps over the lazy dog",
"Slci": []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26},
"Mss": map[string]string{"a": "A", "b": "B", "c": "C", "d": "D", "e": "E", "f": "F", "g": "G", "h": "H", "i": "I", "j": "J", "l": "L", "m": "M", "n": "N"},
}
```
Decoding Benchmark | Time | Memory | Allocs
--- | ---: | ---: | ---:
BenchmarkUnmarshal/CBOR_map_to_Go_map[string]interface{}-2 | 6260 ns/op | 2621 B/op | 73 allocs/op
BenchmarkUnmarshal/CBOR_map_to_Go_struct-2 | 4481 ns/op | 1172 B/op | 10 allocs/op
Encoding Benchmark | Time | Memory | Allocs
--- | ---: | ---: | ---:
BenchmarkMarshal/Go_map[string]interface{}_to_CBOR_map-2 | 4462 ns/op | 1072 B/op | 45 allocs/op
BenchmarkMarshal/Go_struct_to_CBOR_map-2 | 2891 ns/op | 720 B/op | 28 allocs/op
## Go structs with "keyasint" struct tag
Benchmarks use struct (with keyasint struct tag) and map[int]interface{} representing the following value:
```
{
1: true,
2: uint(18446744073709551615),
3: -1000,
4: -4.1,
5: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26},
6: "The quick brown fox jumps over the lazy dog",
7: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26},
8: map[string]string{"a": "A", "b": "B", "c": "C", "d": "D", "e": "E", "f": "F", "g": "G", "h": "H", "i": "I", "j": "J", "l": "L", "m": "M", "n": "N"},
}
```
Struct type with keyasint struct tag is used to handle CBOR map with integer keys.
```
type T struct {
T bool `cbor:"1,keyasint"`
Ui uint `cbor:"2,keyasint"`
I int `cbor:"3,keyasint"`
F float64 `cbor:"4,keyasint"`
B []byte `cbor:"5,keyasint"`
S string `cbor:"6,keyasint"`
Slci []int `cbor:"7,keyasint"`
Mss map[string]string `cbor:"8,keyasint"`
}
```
Decoding Benchmark | Time | Memory | Allocs
--- | ---: | ---: | ---:
BenchmarkUnmarshal/CBOR_map_to_Go_map[int]interface{}-2| 6070 ns/op | 2517 B/op | 70 allocs/op
BenchmarkUnmarshal/CBOR_map_to_Go_struct_keyasint-2 | 4355 ns/op | 1173 B/op | 10 allocs/op
Encoding Benchmark | Time | Memory | Allocs
--- | ---: | ---: | ---:
BenchmarkMarshal/Go_map[int]interface{}_to_CBOR_map-2 | 4318 ns/op | 992 B/op | 45 allocs/op
BenchmarkMarshal/Go_struct_keyasint_to_CBOR_map-2 | 2879 ns/op | 704 B/op | 28 allocs/op
## Go structs with "toarray" struct tag
Benchmarks use struct (with toarray struct tag) and []interface{} representing the following value:
```
[
true,
uint(18446744073709551615),
-1000,
-4.1,
[]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26},
"The quick brown fox jumps over the lazy dog",
[]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26},
map[string]string{"a": "A", "b": "B", "c": "C", "d": "D", "e": "E", "f": "F", "g": "G", "h": "H", "i": "I", "j": "J", "l": "L", "m": "M", "n": "N"}
]
```
Struct type with toarray struct tag is used to handle CBOR array.
```
type T struct {
_ struct{} `cbor:",toarray"`
T bool
Ui uint
I int
F float64
B []byte
S string
Slci []int
Mss map[string]string
}
```
Decoding Benchmark | Time | Memory | Allocs
--- | ---: | ---: | ---:
BenchmarkUnmarshal/CBOR_array_to_Go_[]interface{}-2 | 4968 ns/op | 2404 B/op | 67 allocs/op
BenchmarkUnmarshal/CBOR_array_to_Go_struct_toarray-2 | 4205 ns/op | 1164 B/op | 9 allocs/op
Encoding Benchmark | Time | Memory | Allocs
--- | ---: | ---: | ---:
BenchmarkMarshal/Go_[]interface{}_to_CBOR_map-2 | 3272 ns/op | 704 B/op | 28 allocs/op
BenchmarkMarshal/Go_struct_toarray_to_CBOR_array-2 | 2840 ns/op | 704 B/op | 28 allocs/op
## COSE data
Benchmarks use COSE data from https://tools.ietf.org/html/rfc8392#appendix-A section A.2
```
// 128-Bit Symmetric COSE_Key
{
/ k / -1: h'231f4c4d4d3051fdc2ec0a3851d5b383'
/ kty / 1: 4 / Symmetric /,
/ kid / 2: h'53796d6d6574726963313238' / 'Symmetric128' /,
/ alg / 3: 10 / AES-CCM-16-64-128 /
}
// 256-Bit Symmetric COSE_Key
{
/ k / -1: h'403697de87af64611c1d32a05dab0fe1fcb715a86ab435f1
ec99192d79569388'
/ kty / 1: 4 / Symmetric /,
/ kid / 4: h'53796d6d6574726963323536' / 'Symmetric256' /,
/ alg / 3: 4 / HMAC 256/64 /
}
// ECDSA 256-Bit COSE Key
{
/ d / -4: h'6c1382765aec5358f117733d281c1c7bdc39884d04a45a1e
6c67c858bc206c19',
/ y / -3: h'60f7f1a780d8a783bfb7a2dd6b2796e8128dbbcef9d3d168
db9529971a36e7b9',
/ x / -2: h'143329cce7868e416927599cf65a34f3ce2ffda55a7eca69
ed8919a394d42f0f',
/ crv / -1: 1 / P-256 /,
/ kty / 1: 2 / EC2 /,
/ kid / 2: h'4173796d6d657472696345434453413
23536' / 'AsymmetricECDSA256' /,
/ alg / 3: -7 / ECDSA 256 /
}
```
Decoding Benchmark | Time | Memory | Allocs
--- | ---: | ---: | ---:
BenchmarkUnmarshalCOSE/128-Bit_Symmetric_Key-2 | 578 ns/op | 240 B/op | 4 allocs/op
BenchmarkUnmarshalCOSE/256-Bit_Symmetric_Key-2 | 586 ns/op | 256 B/op | 4 allocs/op
BenchmarkUnmarshalCOSE/ECDSA_P256_256-Bit_Key-2 | 989 ns/op | 360 B/op | 7 allocs/op
Encoding Benchmark | Time | Memory | Allocs
--- | ---: | ---: | ---:
BenchmarkMarshalCOSE/128-Bit_Symmetric_Key-2 | 535 ns/op | 224 B/op | 2 allocs/op
BenchmarkMarshalCOSE/256-Bit_Symmetric_Key-2 | 543 ns/op | 240 B/op | 2 allocs/op
BenchmarkMarshalCOSE/ECDSA_P256_256-Bit_Key-2 | 681 ns/op | 320 B/op | 2 allocs/op
## CWT claims data
Benchmarks use CTW claims data from https://tools.ietf.org/html/rfc8392#appendix-A section A.1
```
{
/ iss / 1: "coap://as.example.com",
/ sub / 2: "erikw",
/ aud / 3: "coap://light.example.com",
/ exp / 4: 1444064944,
/ nbf / 5: 1443944944,
/ iat / 6: 1443944944,
/ cti / 7: h'0b71'
}
```
Decoding Benchmark | Time | Memory | Allocs
--- | ---: | ---: | ---:
BenchmarkUnmarshalCWTClaims-2 | 796 ns/op | 176 B/op | 6 allocs/op
Encoding Benchmark | Time | Memory | Allocs
--- | ---: | ---: | ---:
BenchmarkMarshalCWTClaims-2 | 466 ns/op | 176 B/op | 2 allocs/op
## SenML data
Benchmarks use SenML data from https://tools.ietf.org/html/rfc8428#section-6
```
[
{-2: "urn:dev:ow:10e2073a0108006:", -3: 1276020076.001, -4: "A", -1: 5, 0: "voltage", 1: "V", 2: 120.1},
{0: "current", 6: -5, 2: 1.2},
{0: "current", 6: -4, 2: 1.3},
{0: "current", 6: -3, 2: 1.4},
{0: "current", 6: -2, 2: 1.5},
{0: "current", 6: -1, 2: 1.6},
{0: "current", 6: 0, 2: 1.7}
]
```
Decoding Benchmark | Time | Memory | Allocs
--- | ---: | ---: | ---:
BenchmarkUnmarshalSenML-2 | 3146 ns/op | 1544 B/op | 18 allocs/op
Encoding Benchmark | Time | Memory | Allocs
--- | ---: | ---: | ---:
BenchmarkMarshalSenML-2 | 2968 ns/op | 272 B/op | 2 allocs/op

View File

@ -1,32 +0,0 @@
👉 [Comparisons](https://github.com/fxamacker/cbor#comparisons) • [Status](https://github.com/fxamacker/cbor#current-status) • [Design Goals](https://github.com/fxamacker/cbor#design-goals) • [Features](https://github.com/fxamacker/cbor#features) • [Standards](https://github.com/fxamacker/cbor#standards) • [Fuzzing](https://github.com/fxamacker/cbor#fuzzing-and-code-coverage) • [Usage](https://github.com/fxamacker/cbor#usage) • [Security Policy](https://github.com/fxamacker/cbor#security-policy) • [License](https://github.com/fxamacker/cbor#license)
# CBOR
[CBOR](https://en.wikipedia.org/wiki/CBOR) is a data format designed to allow small code size and small message size. CBOR is defined in [RFC 7049 Concise Binary Object Representation](https://tools.ietf.org/html/rfc7049), an [IETF](http://ietf.org/) Internet Standards Document.
CBOR is also designed to be stable for decades, be extensible without need for version negotiation, and not require a schema.
While JSON uses text, CBOR uses binary. CDDL can be used to express CBOR (and JSON) in an easy and unambiguous way. CDDL is defined in (RFC 8610 Concise Data Definition Language).
## CBOR in Golang (Go)
[Golang](https://golang.org/) is a nickname for the Go programming language. Go is specified in [The Go Programming Language Specification](https://golang.org/ref/spec).
__[fxamacker/cbor](https://github.com/fxamacker/cbor)__ is a library (written in Go) that encodes and decodes CBOR. The API design of fxamacker/cbor is based on Go's [`encoding/json`](https://golang.org/pkg/encoding/json/). The design and reliability of fxamacker/cbor makes it ideal for encoding and decoding COSE.
## COSE
COSE is a protocol using CBOR for basic security services. COSE is defined in ([RFC 8152 CBOR Object Signing and Encryption](https://tools.ietf.org/html/rfc8152)).
COSE describes how to create and process signatures, message authentication codes, and encryption using CBOR for serialization. COSE specification also describes how to represent cryptographic keys using CBOR. COSE is used by WebAuthn.
## CWT
CBOR Web Token (CWT) is defined in [RFC 8392](http://tools.ietf.org/html/rfc8392). CWT is based on COSE and was derived in part from JSON Web Token (JWT). CWT is a compact way to securely represent claims to be transferred between two parties.
## WebAuthn
[WebAuthn](https://en.wikipedia.org/wiki/WebAuthn) (Web Authentication) is a web standard for authenticating users to web-based apps and services. It's a core component of FIDO2, the successor of FIDO U2F legacy protocol.
__[fxamacker/webauthn](https://github.com/fxamacker/webauthn)__ is a library (written in Go) that performs server-side authentication for clients using FIDO2 keys, legacy FIDO U2F keys, tpm, and etc.
Copyright (c) Faye Amacker and contributors.
<hr>
👉 [Comparisons](https://github.com/fxamacker/cbor#comparisons) • [Status](https://github.com/fxamacker/cbor#current-status) • [Design Goals](https://github.com/fxamacker/cbor#design-goals) • [Features](https://github.com/fxamacker/cbor#features) • [Standards](https://github.com/fxamacker/cbor#standards) • [Fuzzing](https://github.com/fxamacker/cbor#fuzzing-and-code-coverage) • [Usage](https://github.com/fxamacker/cbor#usage) • [Security Policy](https://github.com/fxamacker/cbor#security-policy) • [License](https://github.com/fxamacker/cbor#license)

View File

@ -1,76 +1,133 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
Examples of behavior that contributes to a positive environment for our
community include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall
community
Examples of unacceptable behavior by participants include:
Examples of unacceptable behavior include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* The use of sexualized language or imagery, and sexual attention or advances of
any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Publishing others' private information, such as a physical or email address,
without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
professional setting
## Our Responsibilities
## Enforcement Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at faye.github@gmail.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
reported to the community leaders responsible for enforcement at
faye.github@gmail.com.
All complaints will be reviewed and investigated promptly and fairly.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of
actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent
ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the
community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
[https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations

View File

@ -1,47 +1,41 @@
# How to contribute
This project started because I needed an easy, small, and crash-proof CBOR library for my [WebAuthn (FIDO2) server library](https://github.com/fxamacker/webauthn). I believe this was the first and still only standalone CBOR library (in Go) that is fuzz tested as of November 10, 2019.
You can contribute by using the library, opening issues, or opening pull requests.
To my surprise, Stefan Tatschner (rumpelsepp) submitted the first 2 issues when I didn't expect this project to be noticed. So I decided to make it more full-featured for others by announcing releases and asking for feedback. Even this document exists because Montgomery Edwards⁴⁴⁸ (x448) opened [issue #22](https://github.com/fxamacker/cbor/issues/22). In other words, you can contribute by opening an issue that helps the project improve. Especially in the early stages.
## Bug reports and security vulnerabilities
When I announced v1.2 on Go Forum, Jakob Borg (calmh) responded with a thumbs up and encouragement. Another project of equal priority needed my time and Jakob's kind words tipped the scale for me to work on this one (speedups for [milestone v1.3](https://github.com/fxamacker/cbor/issues?q=is%3Aopen+is%3Aissue+milestone%3Av1.3.0).) So words of appreciation or encouragement is nice way to contribute to open source projects.
Another way is by using this library in your project. It can lead to features that benefit both projects, which is what happened when oasislabs/oasis-core switched to this CBOR libary -- thanks Yawning Angel (yawning) for requesting BinaryMarshaler/BinaryUnmarshaler and Jernej Kos (kostco) for requesting RawMessage!
If you'd like to contribute code or send CBOR data, please read on (it can save you time!)
## Private reports
Usually, all issues are tracked publicly on [GitHub](https://github.com/fxamacker/cbor/issues).
Most issues are tracked publicly on [GitHub](https://github.com/fxamacker/cbor/issues).
To report security vulnerabilities, please email faye.github@gmail.com and allow time for the problem to be resolved before disclosing it to the public. For more info, see [Security Policy](https://github.com/fxamacker/cbor#security-policy).
Please do not send data that might contain personally identifiable information, even if you think you have permission. That type of support requires payment and a contract where I'm indemnified, held harmless, and defended for any data you send to me.
Please do not send data that might contain personally identifiable information, even if you think you have permission. That type of support requires payment and a signed contract where I'm indemnified, held harmless, and defended by you for any data you send to me.
## Prerequisites to pull requests
Please [create an issue](https://github.com/fxamacker/cbor/issues/new/choose), if one doesn't already exist, and describe your concern. You'll need a [GitHub account](https://github.com/signup/free) to do this.
## Pull requests
If you submit a pull request without creating an issue and getting a response, you risk having your work unused because the bugfix or feature was already done by others and being reviewed before reaching Github.
Please [create an issue](https://github.com/fxamacker/cbor/issues/new/choose) before you begin work on a PR. The improvement may have already been considered, etc.
Pull requests have signing requirements and must not be anonymous. Exceptions are usually made for docs and CI scripts.
See the [Pull Request Template](https://github.com/fxamacker/cbor/blob/master/.github/pull_request_template.md) for details.
Pull requests have a greater chance of being approved if:
- it does not reduce speed, increase memory use, reduce security, etc. for people not using the new option or feature.
- it has > 97% code coverage.
## Describe your issue
Clearly describe the issue:
* If it's a bug, please provide: **version of this library** and **Go** (`go version`), **unmodified error message**, and describe **how to reproduce it**. Also state **what you expected to happen** instead of the error.
* If you propose a change or addition, try to give an example how the improved code could look like or how to use it.
* If you found a compilation error, please confirm you're using a supported version of Go. If you are, then provide the output of `go version` first, followed by the complete error message.
## Please don't
Please don't send data containing personally identifiable information, even if you think you have permission. That type of support requires payment and a contract where I'm indemnified, held harmless, and defended for any data you send to me.
Please don't send CBOR data larger than 512 bytes. If you want to send crash-producing CBOR data > 512 bytes, please get my permission before sending it to me.
## Wanted
* Opening issues that are helpful to the project
* Using this library in your project and letting me know
* Sending well-formed CBOR data (<= 512 bytes) that causes crashes (none found yet).
* Sending malformed CBOR data (<= 512 bytes) that causes crashes (none found yet, but bad actors are better than me at breaking things).
* Sending tests or data for unit tests that increase code coverage (currently at 97.8% for v1.2.)
* Pull requests with small changes that are well-documented and easily understandable.
* Sponsors, donations, bounties, subscriptions: I'd like to run uninterrupted fuzzing between releases on a server with dedicated CPUs (after v1.3 or v1.4.)
Please don't send CBOR data larger than 1024 bytes by email. If you want to send crash-producing CBOR data > 1024 bytes by email, please get my permission before sending it to me.
## Credits
This guide used nlohmann/json contribution guidelines for inspiration as suggested in issue #22.
- This guide used nlohmann/json contribution guidelines for inspiration as suggested in issue #22.
- Special thanks to @lukseven for pointing out the contribution guidelines didn't mention signing requirements.

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2019 - present Faye Amacker
Copyright (c) 2019-present Faye Amacker
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
SOFTWARE.

1241
README.md

File diff suppressed because it is too large Load Diff

7
SECURITY.md Normal file
View File

@ -0,0 +1,7 @@
# Security Policy
Security fixes are provided for the latest released version of fxamacker/cbor.
If the security vulnerability is already known to the public, then you can open an issue as a bug report.
To report security vulnerabilities not yet known to the public, please email faye.github@gmail.com and allow time for the problem to be resolved before reporting it to the public.

View File

@ -5,7 +5,8 @@ package cbor
import (
"bytes"
"io/ioutil"
"fmt"
"io"
"reflect"
"testing"
)
@ -106,9 +107,68 @@ type T3 struct {
Mss map[string]string
}
type ManyFieldsOneOmitEmpty struct {
F01, F02, F03, F04, F05, F06, F07, F08, F09, F10, F11, F12, F13, F14, F15, F16 int
F17, F18, F19, F20, F21, F22, F23, F24, F25, F26, F27, F28, F29, F30, F31 int
F32 int `cbor:",omitempty"`
}
type SomeFieldsOneOmitEmpty struct {
F01, F02, F03, F04, F05, F06, F07 int
F08 int `cbor:",omitempty"`
}
type ManyFieldsAllOmitEmpty struct {
F01 int `cbor:",omitempty"`
F02 int `cbor:",omitempty"`
F03 int `cbor:",omitempty"`
F04 int `cbor:",omitempty"`
F05 int `cbor:",omitempty"`
F06 int `cbor:",omitempty"`
F07 int `cbor:",omitempty"`
F08 int `cbor:",omitempty"`
F09 int `cbor:",omitempty"`
F10 int `cbor:",omitempty"`
F11 int `cbor:",omitempty"`
F12 int `cbor:",omitempty"`
F13 int `cbor:",omitempty"`
F14 int `cbor:",omitempty"`
F15 int `cbor:",omitempty"`
F16 int `cbor:",omitempty"`
F17 int `cbor:",omitempty"`
F18 int `cbor:",omitempty"`
F19 int `cbor:",omitempty"`
F20 int `cbor:",omitempty"`
F21 int `cbor:",omitempty"`
F22 int `cbor:",omitempty"`
F23 int `cbor:",omitempty"`
F24 int `cbor:",omitempty"`
F25 int `cbor:",omitempty"`
F26 int `cbor:",omitempty"`
F27 int `cbor:",omitempty"`
F28 int `cbor:",omitempty"`
F29 int `cbor:",omitempty"`
F30 int `cbor:",omitempty"`
F31 int `cbor:",omitempty"`
F32 int `cbor:",omitempty"`
}
type SomeFieldsAllOmitEmpty struct {
F01 int `cbor:",omitempty"`
F02 int `cbor:",omitempty"`
F03 int `cbor:",omitempty"`
F04 int `cbor:",omitempty"`
F05 int `cbor:",omitempty"`
F06 int `cbor:",omitempty"`
F07 int `cbor:",omitempty"`
F08 int `cbor:",omitempty"`
}
var decodeBenchmarks = []struct {
name string
cborData []byte
data []byte
decodeToTypes []reflect.Type
}{
{"bool", hexDecode("f5"), []reflect.Type{typeIntf, typeBool}}, // true
@ -126,9 +186,9 @@ var decodeBenchmarks = []struct {
}
var encodeBenchmarks = []struct {
name string
cborData []byte
values []interface{}
name string
data []byte
values []interface{}
}{
{"bool", hexDecode("f5"), []interface{}{true}},
{"positive int", hexDecode("1bffffffffffffffff"), []interface{}{uint64(18446744073709551615)}},
@ -150,7 +210,7 @@ func BenchmarkUnmarshal(b *testing.B) {
b.Run(name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
vPtr := reflect.New(t).Interface()
if err := Unmarshal(bm.cborData, vPtr); err != nil {
if err := Unmarshal(bm.data, vPtr); err != nil {
b.Fatal("Unmarshal:", err)
}
}
@ -159,7 +219,7 @@ func BenchmarkUnmarshal(b *testing.B) {
}
var moreBenchmarks = []struct {
name string
cborData []byte
data []byte
decodeToType reflect.Type
}{
// Unmarshal CBOR map with string key to map[string]interface{}.
@ -203,7 +263,7 @@ func BenchmarkUnmarshal(b *testing.B) {
b.Run(bm.name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
vPtr := reflect.New(bm.decodeToType).Interface()
if err := Unmarshal(bm.cborData, vPtr); err != nil {
if err := Unmarshal(bm.data, vPtr); err != nil {
b.Fatal("Unmarshal:", err)
}
}
@ -211,6 +271,54 @@ func BenchmarkUnmarshal(b *testing.B) {
}
}
func BenchmarkUnmarshalFirst(b *testing.B) {
// Random trailing data
trailingData := hexDecode("4a6b0f4718c73f391091ea1c")
for _, bm := range decodeBenchmarks {
for _, t := range bm.decodeToTypes {
name := "CBOR " + bm.name + " to Go " + t.String()
if t.Kind() == reflect.Struct {
name = "CBOR " + bm.name + " to Go " + t.Kind().String()
}
data := make([]byte, 0, len(bm.data)+len(trailingData))
data = append(data, bm.data...)
data = append(data, trailingData...)
b.Run(name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
vPtr := reflect.New(t).Interface()
if _, err := UnmarshalFirst(data, vPtr); err != nil {
b.Fatal("UnmarshalFirst:", err)
}
}
})
}
}
}
func BenchmarkUnmarshalFirstViaDecoder(b *testing.B) {
// Random trailing data
trailingData := hexDecode("4a6b0f4718c73f391091ea1c")
for _, bm := range decodeBenchmarks {
for _, t := range bm.decodeToTypes {
name := "CBOR " + bm.name + " to Go " + t.String()
if t.Kind() == reflect.Struct {
name = "CBOR " + bm.name + " to Go " + t.Kind().String()
}
data := make([]byte, 0, len(bm.data)+len(trailingData))
data = append(data, bm.data...)
data = append(data, trailingData...)
b.Run(name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
vPtr := reflect.New(t).Interface()
if err := NewDecoder(bytes.NewReader(data)).Decode(vPtr); err != nil {
b.Fatal("UnmarshalDecoder:", err)
}
}
})
}
}
}
func BenchmarkDecode(b *testing.B) {
for _, bm := range decodeBenchmarks {
for _, t := range bm.decodeToTypes {
@ -218,7 +326,7 @@ func BenchmarkDecode(b *testing.B) {
if t.Kind() == reflect.Struct {
name = "CBOR " + bm.name + " to Go " + t.Kind().String()
}
buf := bytes.NewReader(bm.cborData)
buf := bytes.NewReader(bm.data)
decoder := NewDecoder(buf)
b.Run(name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
@ -234,16 +342,16 @@ func BenchmarkDecode(b *testing.B) {
}
func BenchmarkDecodeStream(b *testing.B) {
var cborData []byte
var data []byte
for _, bm := range decodeBenchmarks {
for i := 0; i < len(bm.decodeToTypes); i++ {
cborData = append(cborData, bm.cborData...)
data = append(data, bm.data...)
}
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
buf := bytes.NewReader(cborData)
buf := bytes.NewReader(data)
decoder := NewDecoder(buf)
for j := 0; j < rounds; j++ {
for _, bm := range decodeBenchmarks {
@ -347,6 +455,17 @@ func BenchmarkMarshal(b *testing.B) {
}{
{"Go map[string]interface{} to CBOR map", m1},
{"Go struct to CBOR map", v1},
{"Go struct many fields all omitempty all empty to CBOR map", ManyFieldsAllOmitEmpty{}},
{"Go struct some fields all omitempty all empty to CBOR map", SomeFieldsAllOmitEmpty{}},
{"Go struct many fields all omitempty all nonempty to CBOR map", ManyFieldsAllOmitEmpty{
F01: 1, F02: 1, F03: 1, F04: 1, F05: 1, F06: 1, F07: 1, F08: 1, F09: 1, F10: 1, F11: 1, F12: 1, F13: 1, F14: 1, F15: 1, F16: 1,
F17: 1, F18: 1, F19: 1, F20: 1, F21: 1, F22: 1, F23: 1, F24: 1, F25: 1, F26: 1, F27: 1, F28: 1, F29: 1, F30: 1, F31: 1, F32: 1,
}},
{"Go struct some fields all omitempty all nonempty to CBOR map", SomeFieldsAllOmitEmpty{
F01: 1, F02: 1, F03: 1, F04: 1, F05: 1, F06: 1, F07: 1, F08: 1,
}},
{"Go struct many fields one omitempty to CBOR map", ManyFieldsOneOmitEmpty{}},
{"Go struct some fields one omitempty to CBOR map", SomeFieldsOneOmitEmpty{}},
{"Go map[int]interface{} to CBOR map", m2},
{"Go struct keyasint to CBOR map", v2},
{"Go []interface{} to CBOR map", slc},
@ -380,11 +499,11 @@ func BenchmarkMarshalCanonical(b *testing.B) {
N string `cbor:"n"`
}
for _, bm := range []struct {
name string
cborData []byte
values []interface{}
name string
data []byte
values []interface{}
}{
{"map", hexDecode("ad616161416162614261636143616461446165614561666146616761476168614861696149616a614a616c614c616d614d616e614e"), []interface{}{map[string]string{"a": "A", "b": "B", "c": "C", "d": "D", "e": "E", "f": "F", "g": "G", "h": "H", "i": "I", "j": "J", "l": "L", "m": "M", "n": "N"}, strc{A: "A", B: "B", C: "C", D: "D", E: "E", F: "F", G: "G", H: "H", I: "I", J: "J", L: "L", M: "M", N: "N"}}},
{"map", hexDecode("ad616161416162614261636143616461446165614561666146616761476168614861696149616a614a616c614c616d614d616e614e"), []interface{}{map[string]string{"a": "A", "b": "B", "c": "C", "d": "D", "e": "E", "f": "F", "g": "G", "h": "H", "i": "I", "j": "J", "l": "L", "m": "M", "n": "N"}, strc{A: "A", B: "B", C: "C", D: "D", E: "E", F: "F", G: "G", H: "H", I: "I", J: "J", L: "L", M: "M", N: "N"}, map[int]int{0: 0} /* single-entry map */}},
} {
for _, v := range bm.values {
name := "Go " + reflect.TypeOf(v).String() + " to CBOR " + bm.name
@ -415,6 +534,29 @@ func BenchmarkMarshalCanonical(b *testing.B) {
}
}
// BenchmarkNewEncoderEncode benchmarks NewEncoder() and Encode().
func BenchmarkNewEncoderEncode(b *testing.B) {
for _, bm := range encodeBenchmarks {
for _, v := range bm.values {
name := "Go " + reflect.TypeOf(v).String() + " to CBOR " + bm.name
if reflect.TypeOf(v).Kind() == reflect.Struct {
name = "Go " + reflect.TypeOf(v).Kind().String() + " to CBOR " + bm.name
}
b.Run(name, func(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
encoder := NewEncoder(io.Discard)
if err := encoder.Encode(v); err != nil {
b.Fatal("Encode:", err)
}
}
})
}
}
}
// BenchmarkEncode benchmarks Encode(). It reuses same Encoder to exclude NewEncoder()
// from the benchmark.
func BenchmarkEncode(b *testing.B) {
for _, bm := range encodeBenchmarks {
for _, v := range bm.values {
@ -423,7 +565,7 @@ func BenchmarkEncode(b *testing.B) {
name = "Go " + reflect.TypeOf(v).Kind().String() + " to CBOR " + bm.name
}
b.Run(name, func(b *testing.B) {
encoder := NewEncoder(ioutil.Discard)
encoder := NewEncoder(io.Discard)
b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := encoder.Encode(v); err != nil {
@ -437,7 +579,7 @@ func BenchmarkEncode(b *testing.B) {
func BenchmarkEncodeStream(b *testing.B) {
for i := 0; i < b.N; i++ {
encoder := NewEncoder(ioutil.Discard)
encoder := NewEncoder(io.Discard)
for i := 0; i < rounds; i++ {
for _, bm := range encodeBenchmarks {
for _, v := range bm.values {
@ -453,8 +595,8 @@ func BenchmarkEncodeStream(b *testing.B) {
func BenchmarkUnmarshalCOSE(b *testing.B) {
// Data from https://tools.ietf.org/html/rfc8392#appendix-A section A.2
testCases := []struct {
name string
cborData []byte
name string
data []byte
}{
{"128-Bit Symmetric Key", hexDecode("a42050231f4c4d4d3051fdc2ec0a3851d5b3830104024c53796d6d6574726963313238030a")},
{"256-Bit Symmetric Key", hexDecode("a4205820403697de87af64611c1d32a05dab0fe1fcb715a86ab435f1ec99192d795693880104024c53796d6d6574726963323536030a")},
@ -464,7 +606,7 @@ func BenchmarkUnmarshalCOSE(b *testing.B) {
b.Run(tc.name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
var v coseKey
if err := Unmarshal(tc.cborData, &v); err != nil {
if err := Unmarshal(tc.data, &v); err != nil {
b.Fatal("Unmarshal:", err)
}
}
@ -475,8 +617,8 @@ func BenchmarkUnmarshalCOSE(b *testing.B) {
func BenchmarkMarshalCOSE(b *testing.B) {
// Data from https://tools.ietf.org/html/rfc8392#appendix-A section A.2
testCases := []struct {
name string
cborData []byte
name string
data []byte
}{
{"128-Bit Symmetric Key", hexDecode("a42050231f4c4d4d3051fdc2ec0a3851d5b3830104024c53796d6d6574726963313238030a")},
{"256-Bit Symmetric Key", hexDecode("a4205820403697de87af64611c1d32a05dab0fe1fcb715a86ab435f1ec99192d795693880104024c53796d6d6574726963323536030a")},
@ -484,7 +626,7 @@ func BenchmarkMarshalCOSE(b *testing.B) {
}
for _, tc := range testCases {
var v coseKey
if err := Unmarshal(tc.cborData, &v); err != nil {
if err := Unmarshal(tc.data, &v); err != nil {
b.Fatal("Unmarshal:", err)
}
b.Run(tc.name, func(b *testing.B) {
@ -499,10 +641,10 @@ func BenchmarkMarshalCOSE(b *testing.B) {
func BenchmarkUnmarshalCWTClaims(b *testing.B) {
// Data from https://tools.ietf.org/html/rfc8392#appendix-A section A.1
cborData := hexDecode("a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b71")
data := hexDecode("a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b71")
for i := 0; i < b.N; i++ {
var v claims
if err := Unmarshal(cborData, &v); err != nil {
if err := Unmarshal(data, &v); err != nil {
b.Fatal("Unmarshal:", err)
}
}
@ -510,11 +652,11 @@ func BenchmarkUnmarshalCWTClaims(b *testing.B) {
func BenchmarkUnmarshalCWTClaimsWithDupMapKeyOpt(b *testing.B) {
// Data from https://tools.ietf.org/html/rfc8392#appendix-A section A.1
cborData := hexDecode("a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b71")
data := hexDecode("a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b71")
dm, _ := DecOptions{DupMapKey: DupMapKeyEnforcedAPF}.DecMode()
for i := 0; i < b.N; i++ {
var v claims
if err := dm.Unmarshal(cborData, &v); err != nil {
if err := dm.Unmarshal(data, &v); err != nil {
b.Fatal("Unmarshal:", err)
}
}
@ -522,9 +664,9 @@ func BenchmarkUnmarshalCWTClaimsWithDupMapKeyOpt(b *testing.B) {
func BenchmarkMarshalCWTClaims(b *testing.B) {
// Data from https://tools.ietf.org/html/rfc8392#appendix-A section A.1
cborData := hexDecode("a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b71")
data := hexDecode("a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b71")
var v claims
if err := Unmarshal(cborData, &v); err != nil {
if err := Unmarshal(data, &v); err != nil {
b.Fatal("Unmarshal:", err)
}
for i := 0; i < b.N; i++ {
@ -536,10 +678,10 @@ func BenchmarkMarshalCWTClaims(b *testing.B) {
func BenchmarkUnmarshalSenML(b *testing.B) {
// Data from https://tools.ietf.org/html/rfc8428#section-6
cborData := hexDecode("87a721781b75726e3a6465763a6f773a3130653230373361303130383030363a22fb41d303a15b00106223614120050067766f6c7461676501615602fb405e066666666666a3006763757272656e74062402fb3ff3333333333333a3006763757272656e74062302fb3ff4cccccccccccda3006763757272656e74062202fb3ff6666666666666a3006763757272656e74062102f93e00a3006763757272656e74062002fb3ff999999999999aa3006763757272656e74060002fb3ffb333333333333")
data := hexDecode("87a721781b75726e3a6465763a6f773a3130653230373361303130383030363a22fb41d303a15b00106223614120050067766f6c7461676501615602fb405e066666666666a3006763757272656e74062402fb3ff3333333333333a3006763757272656e74062302fb3ff4cccccccccccda3006763757272656e74062202fb3ff6666666666666a3006763757272656e74062102f93e00a3006763757272656e74062002fb3ff999999999999aa3006763757272656e74060002fb3ffb333333333333")
for i := 0; i < b.N; i++ {
var v []SenMLRecord
if err := Unmarshal(cborData, &v); err != nil {
if err := Unmarshal(data, &v); err != nil {
b.Fatal("Unmarshal:", err)
}
}
@ -547,9 +689,9 @@ func BenchmarkUnmarshalSenML(b *testing.B) {
func BenchmarkMarshalSenML(b *testing.B) {
// Data from https://tools.ietf.org/html/rfc8428#section-6
cborData := hexDecode("87a721781b75726e3a6465763a6f773a3130653230373361303130383030363a22fb41d303a15b00106223614120050067766f6c7461676501615602fb405e066666666666a3006763757272656e74062402fb3ff3333333333333a3006763757272656e74062302fb3ff4cccccccccccda3006763757272656e74062202fb3ff6666666666666a3006763757272656e74062102f93e00a3006763757272656e74062002fb3ff999999999999aa3006763757272656e74060002fb3ffb333333333333")
data := hexDecode("87a721781b75726e3a6465763a6f773a3130653230373361303130383030363a22fb41d303a15b00106223614120050067766f6c7461676501615602fb405e066666666666a3006763757272656e74062402fb3ff3333333333333a3006763757272656e74062302fb3ff4cccccccccccda3006763757272656e74062202fb3ff6666666666666a3006763757272656e74062102f93e00a3006763757272656e74062002fb3ff999999999999aa3006763757272656e74060002fb3ffb333333333333")
var v []SenMLRecord
if err := Unmarshal(cborData, &v); err != nil {
if err := Unmarshal(data, &v); err != nil {
b.Fatal("Unmarshal:", err)
}
for i := 0; i < b.N; i++ {
@ -561,9 +703,9 @@ func BenchmarkMarshalSenML(b *testing.B) {
func BenchmarkMarshalSenMLShortestFloat16(b *testing.B) {
// Data from https://tools.ietf.org/html/rfc8428#section-6
cborData := hexDecode("87a721781b75726e3a6465763a6f773a3130653230373361303130383030363a22fb41d303a15b00106223614120050067766f6c7461676501615602fb405e066666666666a3006763757272656e74062402fb3ff3333333333333a3006763757272656e74062302fb3ff4cccccccccccda3006763757272656e74062202fb3ff6666666666666a3006763757272656e74062102f93e00a3006763757272656e74062002fb3ff999999999999aa3006763757272656e74060002fb3ffb333333333333")
data := hexDecode("87a721781b75726e3a6465763a6f773a3130653230373361303130383030363a22fb41d303a15b00106223614120050067766f6c7461676501615602fb405e066666666666a3006763757272656e74062402fb3ff3333333333333a3006763757272656e74062302fb3ff4cccccccccccda3006763757272656e74062202fb3ff6666666666666a3006763757272656e74062102f93e00a3006763757272656e74062002fb3ff999999999999aa3006763757272656e74060002fb3ffb333333333333")
var v []SenMLRecord
if err := Unmarshal(cborData, &v); err != nil {
if err := Unmarshal(data, &v); err != nil {
b.Fatal("Unmarshal:", err)
}
em, _ := EncOptions{ShortestFloat: ShortestFloat16}.EncMode()
@ -576,10 +718,10 @@ func BenchmarkMarshalSenMLShortestFloat16(b *testing.B) {
func BenchmarkUnmarshalWebAuthn(b *testing.B) {
// Data generated from Yubico security key
cborData := hexDecode("a363666d74686669646f2d7532666761747453746d74a26373696758483046022100e7ab373cfbd99fcd55fd59b0f6f17fef5b77a20ddec3db7f7e4d55174e366236022100828336b4822125fb56541fb14a8a273876acd339395ec2dad95cf41c1dd2a9ae637835638159024e3082024a30820132a0030201020204124a72fe300d06092a864886f70d01010b0500302e312c302a0603550403132359756269636f2055324620526f6f742043412053657269616c203435373230303633313020170d3134303830313030303030305a180f32303530303930343030303030305a302c312a302806035504030c2159756269636f205532462045452053657269616c203234393431343937323135383059301306072a8648ce3d020106082a8648ce3d030107034200043d8b1bbd2fcbf6086e107471601468484153c1c6d3b4b68a5e855e6e40757ee22bcd8988bf3befd7cdf21cb0bf5d7a150d844afe98103c6c6607d9faae287c02a33b3039302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e313013060b2b0601040182e51c020101040403020520300d06092a864886f70d01010b05000382010100a14f1eea0076f6b8476a10a2be72e60d0271bb465b2dfbfc7c1bd12d351989917032631d795d097fa30a26a325634e85721bc2d01a86303f6bc075e5997319e122148b0496eec8d1f4f94cf4110de626c289443d1f0f5bbb239ca13e81d1d5aa9df5af8e36126475bfc23af06283157252762ff68879bcf0ef578d55d67f951b4f32b63c8aea5b0f99c67d7d814a7ff5a6f52df83e894a3a5d9c8b82e7f8bc8daf4c80175ff8972fda79333ec465d806eacc948f1bab22045a95558a48c20226dac003d41fbc9e05ea28a6bb5e10a49de060a0a4f6a2676a34d68c4abe8c61874355b9027e828ca9e064b002d62e8d8cf0744921753d35e3c87c5d5779453e7768617574684461746158c449960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d976341000000000000000000000000000000000000000000408903fd7dfd2c9770e98cae0123b13a2c27828a106349bc6277140e7290b7e9eb7976aa3c04ed347027caf7da3a2fa76304751c02208acfc4e7fc6c7ebbc375c8a5010203262001215820ad7f7992c335b90d882b2802061b97a4fabca7e2ee3e7a51e728b8055e4eb9c7225820e0966ba7005987fece6f0e0e13447aa98cec248e4000a594b01b74c1cb1d40b3")
data := hexDecode("a363666d74686669646f2d7532666761747453746d74a26373696758483046022100e7ab373cfbd99fcd55fd59b0f6f17fef5b77a20ddec3db7f7e4d55174e366236022100828336b4822125fb56541fb14a8a273876acd339395ec2dad95cf41c1dd2a9ae637835638159024e3082024a30820132a0030201020204124a72fe300d06092a864886f70d01010b0500302e312c302a0603550403132359756269636f2055324620526f6f742043412053657269616c203435373230303633313020170d3134303830313030303030305a180f32303530303930343030303030305a302c312a302806035504030c2159756269636f205532462045452053657269616c203234393431343937323135383059301306072a8648ce3d020106082a8648ce3d030107034200043d8b1bbd2fcbf6086e107471601468484153c1c6d3b4b68a5e855e6e40757ee22bcd8988bf3befd7cdf21cb0bf5d7a150d844afe98103c6c6607d9faae287c02a33b3039302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e313013060b2b0601040182e51c020101040403020520300d06092a864886f70d01010b05000382010100a14f1eea0076f6b8476a10a2be72e60d0271bb465b2dfbfc7c1bd12d351989917032631d795d097fa30a26a325634e85721bc2d01a86303f6bc075e5997319e122148b0496eec8d1f4f94cf4110de626c289443d1f0f5bbb239ca13e81d1d5aa9df5af8e36126475bfc23af06283157252762ff68879bcf0ef578d55d67f951b4f32b63c8aea5b0f99c67d7d814a7ff5a6f52df83e894a3a5d9c8b82e7f8bc8daf4c80175ff8972fda79333ec465d806eacc948f1bab22045a95558a48c20226dac003d41fbc9e05ea28a6bb5e10a49de060a0a4f6a2676a34d68c4abe8c61874355b9027e828ca9e064b002d62e8d8cf0744921753d35e3c87c5d5779453e7768617574684461746158c449960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d976341000000000000000000000000000000000000000000408903fd7dfd2c9770e98cae0123b13a2c27828a106349bc6277140e7290b7e9eb7976aa3c04ed347027caf7da3a2fa76304751c02208acfc4e7fc6c7ebbc375c8a5010203262001215820ad7f7992c335b90d882b2802061b97a4fabca7e2ee3e7a51e728b8055e4eb9c7225820e0966ba7005987fece6f0e0e13447aa98cec248e4000a594b01b74c1cb1d40b3")
for i := 0; i < b.N; i++ {
var v attestationObject
if err := Unmarshal(cborData, &v); err != nil {
if err := Unmarshal(data, &v); err != nil {
b.Fatal("Unmarshal:", err)
}
}
@ -587,9 +729,9 @@ func BenchmarkUnmarshalWebAuthn(b *testing.B) {
func BenchmarkMarshalWebAuthn(b *testing.B) {
// Data generated from Yubico security key
cborData := hexDecode("a363666d74686669646f2d7532666761747453746d74a26373696758483046022100e7ab373cfbd99fcd55fd59b0f6f17fef5b77a20ddec3db7f7e4d55174e366236022100828336b4822125fb56541fb14a8a273876acd339395ec2dad95cf41c1dd2a9ae637835638159024e3082024a30820132a0030201020204124a72fe300d06092a864886f70d01010b0500302e312c302a0603550403132359756269636f2055324620526f6f742043412053657269616c203435373230303633313020170d3134303830313030303030305a180f32303530303930343030303030305a302c312a302806035504030c2159756269636f205532462045452053657269616c203234393431343937323135383059301306072a8648ce3d020106082a8648ce3d030107034200043d8b1bbd2fcbf6086e107471601468484153c1c6d3b4b68a5e855e6e40757ee22bcd8988bf3befd7cdf21cb0bf5d7a150d844afe98103c6c6607d9faae287c02a33b3039302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e313013060b2b0601040182e51c020101040403020520300d06092a864886f70d01010b05000382010100a14f1eea0076f6b8476a10a2be72e60d0271bb465b2dfbfc7c1bd12d351989917032631d795d097fa30a26a325634e85721bc2d01a86303f6bc075e5997319e122148b0496eec8d1f4f94cf4110de626c289443d1f0f5bbb239ca13e81d1d5aa9df5af8e36126475bfc23af06283157252762ff68879bcf0ef578d55d67f951b4f32b63c8aea5b0f99c67d7d814a7ff5a6f52df83e894a3a5d9c8b82e7f8bc8daf4c80175ff8972fda79333ec465d806eacc948f1bab22045a95558a48c20226dac003d41fbc9e05ea28a6bb5e10a49de060a0a4f6a2676a34d68c4abe8c61874355b9027e828ca9e064b002d62e8d8cf0744921753d35e3c87c5d5779453e7768617574684461746158c449960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d976341000000000000000000000000000000000000000000408903fd7dfd2c9770e98cae0123b13a2c27828a106349bc6277140e7290b7e9eb7976aa3c04ed347027caf7da3a2fa76304751c02208acfc4e7fc6c7ebbc375c8a5010203262001215820ad7f7992c335b90d882b2802061b97a4fabca7e2ee3e7a51e728b8055e4eb9c7225820e0966ba7005987fece6f0e0e13447aa98cec248e4000a594b01b74c1cb1d40b3")
data := hexDecode("a363666d74686669646f2d7532666761747453746d74a26373696758483046022100e7ab373cfbd99fcd55fd59b0f6f17fef5b77a20ddec3db7f7e4d55174e366236022100828336b4822125fb56541fb14a8a273876acd339395ec2dad95cf41c1dd2a9ae637835638159024e3082024a30820132a0030201020204124a72fe300d06092a864886f70d01010b0500302e312c302a0603550403132359756269636f2055324620526f6f742043412053657269616c203435373230303633313020170d3134303830313030303030305a180f32303530303930343030303030305a302c312a302806035504030c2159756269636f205532462045452053657269616c203234393431343937323135383059301306072a8648ce3d020106082a8648ce3d030107034200043d8b1bbd2fcbf6086e107471601468484153c1c6d3b4b68a5e855e6e40757ee22bcd8988bf3befd7cdf21cb0bf5d7a150d844afe98103c6c6607d9faae287c02a33b3039302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e313013060b2b0601040182e51c020101040403020520300d06092a864886f70d01010b05000382010100a14f1eea0076f6b8476a10a2be72e60d0271bb465b2dfbfc7c1bd12d351989917032631d795d097fa30a26a325634e85721bc2d01a86303f6bc075e5997319e122148b0496eec8d1f4f94cf4110de626c289443d1f0f5bbb239ca13e81d1d5aa9df5af8e36126475bfc23af06283157252762ff68879bcf0ef578d55d67f951b4f32b63c8aea5b0f99c67d7d814a7ff5a6f52df83e894a3a5d9c8b82e7f8bc8daf4c80175ff8972fda79333ec465d806eacc948f1bab22045a95558a48c20226dac003d41fbc9e05ea28a6bb5e10a49de060a0a4f6a2676a34d68c4abe8c61874355b9027e828ca9e064b002d62e8d8cf0744921753d35e3c87c5d5779453e7768617574684461746158c449960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d976341000000000000000000000000000000000000000000408903fd7dfd2c9770e98cae0123b13a2c27828a106349bc6277140e7290b7e9eb7976aa3c04ed347027caf7da3a2fa76304751c02208acfc4e7fc6c7ebbc375c8a5010203262001215820ad7f7992c335b90d882b2802061b97a4fabca7e2ee3e7a51e728b8055e4eb9c7225820e0966ba7005987fece6f0e0e13447aa98cec248e4000a594b01b74c1cb1d40b3")
var v attestationObject
if err := Unmarshal(cborData, &v); err != nil {
if err := Unmarshal(data, &v); err != nil {
b.Fatal("Unmarshal:", err)
}
for i := 0; i < b.N; i++ {
@ -601,11 +743,11 @@ func BenchmarkMarshalWebAuthn(b *testing.B) {
func BenchmarkUnmarshalCOSEMAC(b *testing.B) {
// Data from https://tools.ietf.org/html/rfc8392#appendix-A section A.4
cborData := hexDecode("d83dd18443a10104a1044c53796d6d65747269633235365850a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b7148093101ef6d789200")
data := hexDecode("d83dd18443a10104a1044c53796d6d65747269633235365850a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b7148093101ef6d789200")
for i := 0; i < b.N; i++ {
var v macedCOSE
if err := Unmarshal(cborData, &v); err != nil {
if err := Unmarshal(data, &v); err != nil {
b.Fatal("Unmarshal:", err)
}
}
@ -613,7 +755,7 @@ func BenchmarkUnmarshalCOSEMAC(b *testing.B) {
func BenchmarkUnmarshalCOSEMACWithTag(b *testing.B) {
// Data from https://tools.ietf.org/html/rfc8392#appendix-A section A.4
cborData := hexDecode("d83dd18443a10104a1044c53796d6d65747269633235365850a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b7148093101ef6d789200")
data := hexDecode("d83dd18443a10104a1044c53796d6d65747269633235365850a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b7148093101ef6d789200")
// Register tag CBOR Web Token (CWT) 61 and COSE_Mac0 17 with macedCOSE type
tags := NewTagSet()
@ -626,17 +768,17 @@ func BenchmarkUnmarshalCOSEMACWithTag(b *testing.B) {
for i := 0; i < b.N; i++ {
var v macedCOSE
if err := dm.Unmarshal(cborData, &v); err != nil {
if err := dm.Unmarshal(data, &v); err != nil {
b.Fatal("Unmarshal:", err)
}
}
}
func BenchmarkMarshalCOSEMAC(b *testing.B) {
// Data from https://tools.ietf.org/html/rfc8392#appendix-A section A.4
cborData := hexDecode("d83dd18443a10104a1044c53796d6d65747269633235365850a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b7148093101ef6d789200")
data := hexDecode("d83dd18443a10104a1044c53796d6d65747269633235365850a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b7148093101ef6d789200")
var v macedCOSE
if err := Unmarshal(cborData, &v); err != nil {
if err := Unmarshal(data, &v); err != nil {
b.Fatal("Unmarshal():", err)
}
@ -649,7 +791,7 @@ func BenchmarkMarshalCOSEMAC(b *testing.B) {
func BenchmarkMarshalCOSEMACWithTag(b *testing.B) {
// Data from https://tools.ietf.org/html/rfc8392#appendix-A section A.4
cborData := hexDecode("d83dd18443a10104a1044c53796d6d65747269633235365850a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b7148093101ef6d789200")
data := hexDecode("d83dd18443a10104a1044c53796d6d65747269633235365850a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b7148093101ef6d789200")
// Register tag CBOR Web Token (CWT) 61 and COSE_Mac0 17 with macedCOSE type
tags := NewTagSet()
@ -662,7 +804,7 @@ func BenchmarkMarshalCOSEMACWithTag(b *testing.B) {
em, _ := EncOptions{}.EncModeWithTags(tags)
var v macedCOSE
if err := dm.Unmarshal(cborData, &v); err != nil {
if err := dm.Unmarshal(data, &v); err != nil {
b.Fatal("Unmarshal():", err)
}
@ -672,3 +814,209 @@ func BenchmarkMarshalCOSEMACWithTag(b *testing.B) {
}
}
}
func BenchmarkUnmarshalMapToStruct(b *testing.B) {
type S struct {
A, B, C, D, E, F, G, H, I, J, K, L, M bool
}
var (
allKnownFields = hexDecode("ad6141f56142f56143f56144f56145f56146f56147f56148f56149f5614af5614bf5614cf5614df5") // {"A": true, ... "M": true }
allKnownDuplicateFields = hexDecode("ad6141f56141f56141f56141f56141f56141f56141f56141f56141f56141f56141f56141f56141f5") // {"A": true, "A": true, "A": true, ...}
allUnknownFields = hexDecode("ad614ef5614ff56150f56151f56152f56153f56154f56155f56156f56157f56158f56159f5615af5") // {"N": true, ... "Z": true }
allUnknownDuplicateFields = hexDecode("ad614ef5614ef5614ef5614ef5614ef5614ef5614ef5614ef5614ef5614ef5614ef5614ef5614ef5") // {"N": true, "N": true, "N": true, ...}
)
type ManyFields struct {
AA, AB, AC, AD, AE, AF, AG, AH, AI, AJ, AK, AL, AM, AN, AO, AP, AQ, AR, AS, AT, AU, AV, AW, AX, AY, AZ bool
BA, BB, BC, BD, BE, BF, BG, BH, BI, BJ, BK, BL, BM, BN, BO, BP, BQ, BR, BS, BT, BU, BV, BW, BX, BY, BZ bool
CA, CB, CC, CD, CE, CF, CG, CH, CI, CJ, CK, CL, CM, CN, CO, CP, CQ, CR, CS, CT, CU, CV, CW, CX, CY, CZ bool
DA, DB, DC, DD, DE, DF, DG, DH, DI, DJ, DK, DL, DM, DN, DO, DP, DQ, DR, DS, DT, DU, DV, DW, DX, DY, DZ bool
}
var manyFieldsOneKeyPerField []byte
{
// An EncOption that accepts a function to sort or shuffle keys might be useful for
// cases like this. Here we are manually encoding the fields in reverse order to
// target worst-case key-to-field matching.
rt := reflect.TypeOf(ManyFields{})
var buf bytes.Buffer
if rt.NumField() > 255 {
b.Fatalf("invalid test assumption: ManyFields expected to have no more than 255 fields, has %d", rt.NumField())
}
buf.WriteByte(0xb8)
buf.WriteByte(byte(rt.NumField()))
for i := rt.NumField() - 1; i >= 0; i-- { // backwards
f := rt.Field(i)
if len(f.Name) > 23 {
b.Fatalf("invalid test assumption: field name %q longer than 23 bytes", f.Name)
}
buf.WriteByte(byte(0x60 + len(f.Name)))
buf.WriteString(f.Name)
buf.WriteByte(0xf5) // true
}
manyFieldsOneKeyPerField = buf.Bytes()
}
type input struct {
name string
data []byte
into interface{}
reject bool
}
for _, tc := range []*struct {
name string
opts DecOptions
inputs []input
}{
{
name: "default options",
opts: DecOptions{},
inputs: []input{
{
name: "all known fields",
data: allKnownFields,
into: S{},
reject: false,
},
{
name: "all known duplicate fields",
data: allKnownDuplicateFields,
into: S{},
reject: false,
},
{
name: "all unknown fields",
data: allUnknownFields,
into: S{},
reject: false,
},
{
name: "all unknown duplicate fields",
data: allUnknownDuplicateFields,
into: S{},
reject: false,
},
{
name: "many fields one key per field",
data: manyFieldsOneKeyPerField,
into: ManyFields{},
reject: false,
},
},
},
{
name: "reject unknown",
opts: DecOptions{ExtraReturnErrors: ExtraDecErrorUnknownField},
inputs: []input{
{
name: "all known fields",
data: allKnownFields,
into: S{},
reject: false,
},
{
name: "all known duplicate fields",
data: allKnownDuplicateFields,
into: S{},
reject: false,
},
{
name: "all unknown fields",
data: allUnknownFields,
into: S{},
reject: true,
},
{
name: "all unknown duplicate fields",
data: allUnknownDuplicateFields,
into: S{},
reject: true,
},
},
},
{
name: "reject duplicate",
opts: DecOptions{DupMapKey: DupMapKeyEnforcedAPF},
inputs: []input{
{
name: "all known fields",
data: allKnownFields,
into: S{},
reject: false,
},
{
name: "all known duplicate fields",
data: allKnownDuplicateFields,
into: S{},
reject: true,
},
{
name: "all unknown fields",
data: allUnknownFields,
into: S{},
reject: false,
},
{
name: "all unknown duplicate fields",
data: allUnknownDuplicateFields,
into: S{},
reject: true,
},
},
},
{
name: "reject unknown and duplicate",
opts: DecOptions{
DupMapKey: DupMapKeyEnforcedAPF,
ExtraReturnErrors: ExtraDecErrorUnknownField,
},
inputs: []input{
{
name: "all known fields",
data: allKnownFields,
into: S{},
reject: false,
},
{
name: "all known duplicate fields",
data: allKnownDuplicateFields,
into: S{},
reject: true,
},
{
name: "all unknown fields",
data: allUnknownFields,
into: S{},
reject: true,
},
{
name: "all unknown duplicate fields",
data: allUnknownDuplicateFields,
into: S{},
reject: true,
},
},
},
} {
for _, in := range tc.inputs {
b.Run(fmt.Sprintf("%s/%s", tc.name, in.name), func(b *testing.B) {
dm, err := tc.opts.DecMode()
if err != nil {
b.Fatal(err)
}
dst := reflect.New(reflect.TypeOf(in.into)).Interface()
b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := dm.Unmarshal(in.data, dst); !in.reject && err != nil {
b.Fatalf("unexpected error: %v", err)
} else if in.reject && err == nil {
b.Fatal("expected non-nil error")
}
}
})
}
}
}

63
bytestring.go Normal file
View File

@ -0,0 +1,63 @@
// Copyright (c) Faye Amacker. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.
package cbor
import (
"errors"
)
// ByteString represents CBOR byte string (major type 2). ByteString can be used
// when using a Go []byte is not possible or convenient. For example, Go doesn't
// allow []byte as map key, so ByteString can be used to support data formats
// having CBOR map with byte string keys. ByteString can also be used to
// encode invalid UTF-8 string as CBOR byte string.
// See DecOption.MapKeyByteStringMode for more details.
type ByteString string
// Bytes returns bytes representing ByteString.
func (bs ByteString) Bytes() []byte {
return []byte(bs)
}
// MarshalCBOR encodes ByteString as CBOR byte string (major type 2).
func (bs ByteString) MarshalCBOR() ([]byte, error) {
e := getEncodeBuffer()
defer putEncodeBuffer(e)
// Encode length
encodeHead(e, byte(cborTypeByteString), uint64(len(bs)))
// Encode data
buf := make([]byte, e.Len()+len(bs))
n := copy(buf, e.Bytes())
copy(buf[n:], bs)
return buf, nil
}
// UnmarshalCBOR decodes CBOR byte string (major type 2) to ByteString.
// Decoding CBOR null and CBOR undefined sets ByteString to be empty.
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) {
*bs = ""
return nil
}
d := decoder{data: data, dm: defaultDecMode}
// Check if CBOR data type is byte string
if typ := d.nextCBORType(); typ != cborTypeByteString {
return &UnmarshalTypeError{CBORType: typ.String(), GoType: typeByteString.String()}
}
b, _ := d.parseByteString()
*bs = ByteString(b)
return nil
}

101
bytestring_test.go Normal file
View File

@ -0,0 +1,101 @@
// Copyright (c) Faye Amacker. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.
package cbor
import "testing"
func TestByteString(t *testing.T) {
type s1 struct {
A ByteString `cbor:"a"`
}
type s2 struct {
A *ByteString `cbor:"a"`
}
type s3 struct {
A ByteString `cbor:"a,omitempty"`
}
type s4 struct {
A *ByteString `cbor:"a,omitempty"`
}
emptybs := ByteString("")
bs := ByteString("\x01\x02\x03\x04")
testCases := []roundTripTest{
{
name: "empty",
obj: emptybs,
wantCborData: hexDecode("40"),
},
{
name: "not empty",
obj: bs,
wantCborData: hexDecode("4401020304"),
},
{
name: "array",
obj: []ByteString{bs},
wantCborData: hexDecode("814401020304"),
},
{
name: "map with ByteString key",
obj: map[ByteString]bool{bs: true},
wantCborData: hexDecode("a14401020304f5"),
},
{
name: "empty ByteString field",
obj: s1{},
wantCborData: hexDecode("a1616140"),
},
{
name: "not empty ByteString field",
obj: s1{A: bs},
wantCborData: hexDecode("a161614401020304"),
},
{
name: "nil *ByteString field",
obj: s2{},
wantCborData: hexDecode("a16161f6"),
},
{
name: "empty *ByteString field",
obj: s2{A: &emptybs},
wantCborData: hexDecode("a1616140"),
},
{
name: "not empty *ByteString field",
obj: s2{A: &bs},
wantCborData: hexDecode("a161614401020304"),
},
{
name: "empty ByteString field with omitempty option",
obj: s3{},
wantCborData: hexDecode("a0"),
},
{
name: "not empty ByteString field with omitempty option",
obj: s3{A: bs},
wantCborData: hexDecode("a161614401020304"),
},
{
name: "nil *ByteString field with omitempty option",
obj: s4{},
wantCborData: hexDecode("a0"),
},
{
name: "empty *ByteString field with omitempty option",
obj: s4{A: &emptybs},
wantCborData: hexDecode("a1616140"),
},
{
name: "not empty *ByteString field with omitempty option",
obj: s4{A: &bs},
wantCborData: hexDecode("a161614401020304"),
},
}
em, _ := EncOptions{}.EncMode()
dm, _ := DecOptions{}.DecMode()
testRoundTrip(t, testCases, em, dm)
}

171
cache.go
View File

@ -6,6 +6,7 @@ package cbor
import (
"bytes"
"errors"
"fmt"
"reflect"
"sort"
"strconv"
@ -13,10 +14,15 @@ import (
"sync"
)
type encodeFuncs struct {
ef encodeFunc
ief isEmptyFunc
}
var (
decodingStructTypeCache sync.Map // map[reflect.Type]*decodingStructType
encodingStructTypeCache sync.Map // map[reflect.Type]*encodingStructType
encodeFuncCache sync.Map // map[reflect.Type]encodeFunc
encodeFuncCache sync.Map // map[reflect.Type]encodeFuncs
typeInfoCache sync.Map // map[reflect.Type]*typeInfo
)
@ -26,6 +32,7 @@ const (
specialTypeNone specialType = iota
specialTypeUnmarshalerIface
specialTypeEmptyIface
specialTypeIface
specialTypeTag
specialTypeTime
)
@ -52,8 +59,12 @@ func newTypeInfo(t reflect.Type) *typeInfo {
tInfo.nonPtrType = t
tInfo.nonPtrKind = k
if k == reflect.Interface && t.NumMethod() == 0 {
tInfo.spclType = specialTypeEmptyIface
if k == reflect.Interface {
if t.NumMethod() == 0 {
tInfo.spclType = specialTypeEmptyIface
} else {
tInfo.spclType = specialTypeIface
}
} else if t == typeTag {
tInfo.spclType = specialTypeTag
} else if t == typeTime {
@ -74,9 +85,25 @@ func newTypeInfo(t reflect.Type) *typeInfo {
}
type decodingStructType struct {
fields fields
err error
toArray bool
fields fields
fieldIndicesByName map[string]int
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 {
@ -88,12 +115,12 @@ func getDecodingStructType(t reflect.Type) *decodingStructType {
toArray := hasToArrayOption(structOptions)
var err error
var errs []error
for i := 0; i < len(flds); i++ {
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() + ")")
errs = append(errs, errors.New("cbor: failed to parse field name \""+flds[i].name+"\" to int ("+numErr.Error()+")"))
break
}
flds[i].nameAsInt = int64(nameAsInt)
@ -102,29 +129,58 @@ func getDecodingStructType(t reflect.Type) *decodingStructType {
flds[i].typInfo = getTypeInfo(flds[i].typ)
}
structType := &decodingStructType{fields: flds, err: err, toArray: toArray}
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 err error
{
var multi multierror
for _, each := range errs {
if each != nil {
multi = append(multi, each)
}
}
if len(multi) == 1 {
err = multi[0]
} else if len(multi) > 1 {
err = multi
}
}
structType := &decodingStructType{
fields: flds,
fieldIndicesByName: fieldIndicesByName,
err: err,
toArray: toArray,
}
decodingStructTypeCache.Store(t, structType)
return structType
}
type encodingStructType struct {
fields fields
bytewiseFields fields
lengthFirstFields fields
err error
toArray bool
omitEmpty bool
hasAnonymousField bool
fields fields
bytewiseFields fields
lengthFirstFields fields
omitEmptyFieldsIdx []int
err error
toArray bool
}
func (st *encodingStructType) getFields(em *encMode) fields {
if em.sort == SortNone {
switch em.sort {
case SortNone, SortFastShuffle:
return st.fields
}
if em.sort == SortLengthFirst {
case SortLengthFirst:
return st.lengthFirstFields
default:
return st.bytewiseFields
}
return st.bytewiseFields
}
type bytewiseFieldSorter struct {
@ -162,9 +218,10 @@ func (x *lengthFirstFieldSorter) Less(i, j int) bool {
return bytes.Compare(x.fields[i].cborName, x.fields[j].cborName) <= 0
}
func getEncodingStructType(t reflect.Type) *encodingStructType {
func getEncodingStructType(t reflect.Type) (*encodingStructType, error) {
if v, _ := encodingStructTypeCache.Load(t); v != nil {
return v.(*encodingStructType)
structType := v.(*encodingStructType)
return structType, structType.err
}
flds, structOptions := getFields(t)
@ -174,14 +231,13 @@ func getEncodingStructType(t reflect.Type) *encodingStructType {
}
var err error
var omitEmpty bool
var hasAnonymousField bool
var hasKeyAsInt bool
var hasKeyAsStr bool
e := getEncodeState()
var omitEmptyIdx []int
e := getEncodeBuffer()
for i := 0; i < len(flds); i++ {
// Get field's encodeFunc
flds[i].ef = getEncodeFunc(flds[i].typ)
flds[i].ef, flds[i].ief = getEncodeFunc(flds[i].typ)
if flds[i].ef == nil {
err = &UnsupportedTypeError{t}
break
@ -213,25 +269,30 @@ func getEncodingStructType(t reflect.Type) *encodingStructType {
copy(flds[i].cborName[n:], flds[i].name)
e.Reset()
hasKeyAsStr = true
}
// 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)
// Reset encoded CBOR type to byte string, preserving the "additional
// information" bits:
flds[i].cborNameByteString[0] = byte(cborTypeByteString) |
getAdditionalInformation(flds[i].cborNameByteString[0])
// Check if field is from embedded struct
if len(flds[i].idx) > 1 {
hasAnonymousField = true
hasKeyAsStr = true
}
// Check if field can be omitted when empty
if flds[i].omitEmpty {
omitEmpty = true
omitEmptyIdx = append(omitEmptyIdx, i)
}
}
putEncodeState(e)
putEncodeBuffer(e)
if err != nil {
structType := &encodingStructType{err: err}
encodingStructTypeCache.Store(t, structType)
return structType
return structType, structType.err
}
// Sort fields by canonical order
@ -247,49 +308,43 @@ func getEncodingStructType(t reflect.Type) *encodingStructType {
}
structType := &encodingStructType{
fields: flds,
bytewiseFields: bytewiseFields,
lengthFirstFields: lengthFirstFields,
omitEmpty: omitEmpty,
hasAnonymousField: hasAnonymousField,
fields: flds,
bytewiseFields: bytewiseFields,
lengthFirstFields: lengthFirstFields,
omitEmptyFieldsIdx: omitEmptyIdx,
}
encodingStructTypeCache.Store(t, structType)
return structType
return structType, structType.err
}
func getEncodingStructToArrayType(t reflect.Type, flds fields) *encodingStructType {
var hasAnonymousField bool
func getEncodingStructToArrayType(t reflect.Type, flds fields) (*encodingStructType, error) {
for i := 0; i < len(flds); i++ {
// Get field's encodeFunc
flds[i].ef = getEncodeFunc(flds[i].typ)
flds[i].ef, flds[i].ief = getEncodeFunc(flds[i].typ)
if flds[i].ef == nil {
structType := &encodingStructType{err: &UnsupportedTypeError{t}}
encodingStructTypeCache.Store(t, structType)
return structType
}
// Check if field is from embedded struct
if len(flds[i].idx) > 1 {
hasAnonymousField = true
return structType, structType.err
}
}
structType := &encodingStructType{
fields: flds,
toArray: true,
hasAnonymousField: hasAnonymousField,
fields: flds,
toArray: true,
}
encodingStructTypeCache.Store(t, structType)
return structType
return structType, structType.err
}
func getEncodeFunc(t reflect.Type) encodeFunc {
func getEncodeFunc(t reflect.Type) (encodeFunc, isEmptyFunc) {
if v, _ := encodeFuncCache.Load(t); v != nil {
return v.(encodeFunc)
fs := v.(encodeFuncs)
return fs.ef, fs.ief
}
f := getEncodeFuncInternal(t)
encodeFuncCache.Store(t, f)
return f
ef, ief := getEncodeFuncInternal(t)
encodeFuncCache.Store(t, encodeFuncs{ef, ief})
return ef, ief
}
func getTypeInfo(t reflect.Type) *typeInfo {

182
common.go Normal file
View File

@ -0,0 +1,182 @@
// Copyright (c) Faye Amacker. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.
package cbor
import (
"fmt"
"strconv"
)
type cborType uint8
const (
cborTypePositiveInt cborType = 0x00
cborTypeNegativeInt cborType = 0x20
cborTypeByteString cborType = 0x40
cborTypeTextString cborType = 0x60
cborTypeArray cborType = 0x80
cborTypeMap cborType = 0xa0
cborTypeTag cborType = 0xc0
cborTypePrimitives cborType = 0xe0
)
func (t cborType) String() string {
switch t {
case cborTypePositiveInt:
return "positive integer"
case cborTypeNegativeInt:
return "negative integer"
case cborTypeByteString:
return "byte string"
case cborTypeTextString:
return "UTF-8 text string"
case cborTypeArray:
return "array"
case cborTypeMap:
return "map"
case cborTypeTag:
return "tag"
case cborTypePrimitives:
return "primitives"
default:
return "Invalid type " + strconv.Itoa(int(t))
}
}
type additionalInformation uint8
const (
maxAdditionalInformationWithoutArgument = 23
additionalInformationWith1ByteArgument = 24
additionalInformationWith2ByteArgument = 25
additionalInformationWith4ByteArgument = 26
additionalInformationWith8ByteArgument = 27
// For major type 7.
additionalInformationAsFalse = 20
additionalInformationAsTrue = 21
additionalInformationAsNull = 22
additionalInformationAsUndefined = 23
additionalInformationAsFloat16 = 25
additionalInformationAsFloat32 = 26
additionalInformationAsFloat64 = 27
// For major type 2, 3, 4, 5.
additionalInformationAsIndefiniteLengthFlag = 31
)
const (
maxSimpleValueInAdditionalInformation = 23
minSimpleValueIn1ByteArgument = 32
)
func (ai additionalInformation) isIndefiniteLength() bool {
return ai == additionalInformationAsIndefiniteLengthFlag
}
const (
// From RFC 8949 Section 3:
// "The initial byte of each encoded data item contains both information about the major type
// (the high-order 3 bits, described in Section 3.1) and additional information
// (the low-order 5 bits)."
// typeMask is used to extract major type in initial byte of encoded data item.
typeMask = 0xe0
// additionalInformationMask is used to extract additional information in initial byte of encoded data item.
additionalInformationMask = 0x1f
)
func getType(raw byte) cborType {
return cborType(raw & typeMask)
}
func getAdditionalInformation(raw byte) byte {
return raw & additionalInformationMask
}
func isBreakFlag(raw byte) bool {
return raw == cborBreakFlag
}
func parseInitialByte(b byte) (t cborType, ai byte) {
return getType(b), getAdditionalInformation(b)
}
const (
tagNumRFC3339Time = 0
tagNumEpochTime = 1
tagNumUnsignedBignum = 2
tagNumNegativeBignum = 3
tagNumExpectedLaterEncodingBase64URL = 21
tagNumExpectedLaterEncodingBase64 = 22
tagNumExpectedLaterEncodingBase16 = 23
tagNumSelfDescribedCBOR = 55799
)
const (
cborBreakFlag = byte(0xff)
cborByteStringWithIndefiniteLengthHead = byte(0x5f)
cborTextStringWithIndefiniteLengthHead = byte(0x7f)
cborArrayWithIndefiniteLengthHead = byte(0x9f)
cborMapWithIndefiniteLengthHead = byte(0xbf)
)
var (
cborFalse = []byte{0xf4}
cborTrue = []byte{0xf5}
cborNil = []byte{0xf6}
cborNaN = []byte{0xf9, 0x7e, 0x00}
cborPositiveInfinity = []byte{0xf9, 0x7c, 0x00}
cborNegativeInfinity = []byte{0xf9, 0xfc, 0x00}
)
// validBuiltinTag checks that supported built-in tag numbers are followed by expected content types.
func validBuiltinTag(tagNum uint64, contentHead byte) error {
t := getType(contentHead)
switch tagNum {
case tagNumRFC3339Time:
// Tag content (date/time text string in RFC 3339 format) must be string type.
if t != cborTypeTextString {
return newInadmissibleTagContentTypeError(
tagNumRFC3339Time,
"text string",
t.String())
}
return nil
case tagNumEpochTime:
// Tag content (epoch date/time) must be uint, int, or float type.
if t != cborTypePositiveInt && t != cborTypeNegativeInt && (contentHead < 0xf9 || contentHead > 0xfb) {
return newInadmissibleTagContentTypeError(
tagNumEpochTime,
"integer or floating-point number",
t.String())
}
return nil
case tagNumUnsignedBignum, tagNumNegativeBignum:
// Tag content (bignum) must be byte type.
if t != cborTypeByteString {
return newInadmissibleTagContentTypeErrorf(
fmt.Sprintf(
"tag number %d or %d must be followed by byte string, got %s",
tagNumUnsignedBignum,
tagNumNegativeBignum,
t.String(),
))
}
return nil
case tagNumExpectedLaterEncodingBase64URL, tagNumExpectedLaterEncodingBase64, tagNumExpectedLaterEncodingBase16:
// From RFC 8949 3.4.5.2:
// The data item tagged can be a byte string or any other data item. In the latter
// case, the tag applies to all of the byte string data items contained in the data
// item, except for those contained in a nested data item tagged with an expected
// conversion.
return nil
}
return nil
}

2764
decode.go

File diff suppressed because it is too large Load Diff

19
decode_not_tinygo.go Normal file
View File

@ -0,0 +1,19 @@
// 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)
}

109
decode_not_tinygo_test.go Normal file
View File

@ -0,0 +1,109 @@
// 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)
}
})
}
}

File diff suppressed because it is too large Load Diff

24
decode_tinygo.go Normal file
View File

@ -0,0 +1,24 @@
// 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
}

111
decode_tinygo_test.go Normal file
View File

@ -0,0 +1,111 @@
// 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)
}
})
}
}

724
diagnose.go Normal file
View File

@ -0,0 +1,724 @@
// Copyright (c) Faye Amacker. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.
package cbor
import (
"bytes"
"encoding/base32"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"io"
"math"
"math/big"
"strconv"
"unicode/utf16"
"unicode/utf8"
"github.com/x448/float16"
)
// DiagMode is the main interface for CBOR diagnostic notation.
type DiagMode interface {
// Diagnose returns extended diagnostic notation (EDN) of CBOR data items using this DiagMode.
Diagnose([]byte) (string, error)
// DiagnoseFirst returns extended diagnostic notation (EDN) of the first CBOR data item using the DiagMode. Any remaining bytes are returned in rest.
DiagnoseFirst([]byte) (string, []byte, error)
// DiagOptions returns user specified options used to create this DiagMode.
DiagOptions() DiagOptions
}
// ByteStringEncoding specifies the base encoding that byte strings are notated.
type ByteStringEncoding uint8
const (
// ByteStringBase16Encoding encodes byte strings in base16, without padding.
ByteStringBase16Encoding ByteStringEncoding = iota
// ByteStringBase32Encoding encodes byte strings in base32, without padding.
ByteStringBase32Encoding
// ByteStringBase32HexEncoding encodes byte strings in base32hex, without padding.
ByteStringBase32HexEncoding
// ByteStringBase64Encoding encodes byte strings in base64url, without padding.
ByteStringBase64Encoding
maxByteStringEncoding
)
func (bse ByteStringEncoding) valid() error {
if bse >= maxByteStringEncoding {
return errors.New("cbor: invalid ByteStringEncoding " + strconv.Itoa(int(bse)))
}
return nil
}
// DiagOptions specifies Diag options.
type DiagOptions struct {
// ByteStringEncoding specifies the base encoding that byte strings are notated.
// Default is ByteStringBase16Encoding.
ByteStringEncoding ByteStringEncoding
// ByteStringHexWhitespace specifies notating with whitespace in byte string
// when ByteStringEncoding is ByteStringBase16Encoding.
ByteStringHexWhitespace bool
// ByteStringText specifies notating with text in byte string
// if it is a valid UTF-8 text.
ByteStringText bool
// ByteStringEmbeddedCBOR specifies notating embedded CBOR in byte string
// if it is a valid CBOR bytes.
ByteStringEmbeddedCBOR bool
// CBORSequence specifies notating CBOR sequences.
// otherwise, it returns an error if there are more bytes after the first CBOR.
CBORSequence bool
// FloatPrecisionIndicator specifies appending a suffix to indicate float precision.
// Refer to https://www.rfc-editor.org/rfc/rfc8949.html#name-encoding-indicators.
FloatPrecisionIndicator bool
// MaxNestedLevels specifies the max nested levels allowed for any combination of CBOR array, maps, and tags.
// Default is 32 levels and it can be set to [4, 65535]. Note that higher maximum levels of nesting can
// require larger amounts of stack to deserialize. Don't increase this higher than you require.
MaxNestedLevels int
// MaxArrayElements specifies the max number of elements for CBOR arrays.
// Default is 128*1024=131072 and it can be set to [16, 2147483647]
MaxArrayElements int
// MaxMapPairs specifies the max number of key-value pairs for CBOR maps.
// Default is 128*1024=131072 and it can be set to [16, 2147483647]
MaxMapPairs int
}
// DiagMode returns a DiagMode with immutable options.
func (opts DiagOptions) DiagMode() (DiagMode, error) {
return opts.diagMode()
}
func (opts DiagOptions) diagMode() (*diagMode, error) {
if err := opts.ByteStringEncoding.valid(); err != nil {
return nil, err
}
decMode, err := DecOptions{
MaxNestedLevels: opts.MaxNestedLevels,
MaxArrayElements: opts.MaxArrayElements,
MaxMapPairs: opts.MaxMapPairs,
}.decMode()
if err != nil {
return nil, err
}
return &diagMode{
byteStringEncoding: opts.ByteStringEncoding,
byteStringHexWhitespace: opts.ByteStringHexWhitespace,
byteStringText: opts.ByteStringText,
byteStringEmbeddedCBOR: opts.ByteStringEmbeddedCBOR,
cborSequence: opts.CBORSequence,
floatPrecisionIndicator: opts.FloatPrecisionIndicator,
decMode: decMode,
}, nil
}
type diagMode struct {
byteStringEncoding ByteStringEncoding
byteStringHexWhitespace bool
byteStringText bool
byteStringEmbeddedCBOR bool
cborSequence bool
floatPrecisionIndicator bool
decMode *decMode
}
// DiagOptions returns user specified options used to create this DiagMode.
func (dm *diagMode) DiagOptions() DiagOptions {
return DiagOptions{
ByteStringEncoding: dm.byteStringEncoding,
ByteStringHexWhitespace: dm.byteStringHexWhitespace,
ByteStringText: dm.byteStringText,
ByteStringEmbeddedCBOR: dm.byteStringEmbeddedCBOR,
CBORSequence: dm.cborSequence,
FloatPrecisionIndicator: dm.floatPrecisionIndicator,
MaxNestedLevels: dm.decMode.maxNestedLevels,
MaxArrayElements: dm.decMode.maxArrayElements,
MaxMapPairs: dm.decMode.maxMapPairs,
}
}
// Diagnose returns extended diagnostic notation (EDN) of CBOR data items using the DiagMode.
func (dm *diagMode) Diagnose(data []byte) (string, error) {
return newDiagnose(data, dm.decMode, dm).diag(dm.cborSequence)
}
// DiagnoseFirst returns extended diagnostic notation (EDN) of the first CBOR data item using the DiagMode. Any remaining bytes are returned in rest.
func (dm *diagMode) DiagnoseFirst(data []byte) (diagNotation string, rest []byte, err error) {
return newDiagnose(data, dm.decMode, dm).diagFirst()
}
var defaultDiagMode, _ = DiagOptions{}.diagMode()
// Diagnose returns extended diagnostic notation (EDN) of CBOR data items
// using the default diagnostic mode.
//
// Refer to https://www.rfc-editor.org/rfc/rfc8949.html#name-diagnostic-notation.
func Diagnose(data []byte) (string, error) {
return defaultDiagMode.Diagnose(data)
}
// Diagnose returns extended diagnostic notation (EDN) of the first CBOR data item using the DiagMode. Any remaining bytes are returned in rest.
func DiagnoseFirst(data []byte) (diagNotation string, rest []byte, err error) {
return defaultDiagMode.DiagnoseFirst(data)
}
type diagnose struct {
dm *diagMode
d *decoder
w *bytes.Buffer
}
func newDiagnose(data []byte, decm *decMode, diagm *diagMode) *diagnose {
return &diagnose{
dm: diagm,
d: &decoder{data: data, dm: decm},
w: &bytes.Buffer{},
}
}
func (di *diagnose) diag(cborSequence bool) (string, error) {
// CBOR Sequence
firstItem := true
for {
switch err := di.wellformed(cborSequence); err {
case nil:
if !firstItem {
di.w.WriteString(", ")
}
firstItem = false
if itemErr := di.item(); itemErr != nil {
return di.w.String(), itemErr
}
case io.EOF:
if firstItem {
return di.w.String(), err
}
return di.w.String(), nil
default:
return di.w.String(), err
}
}
}
func (di *diagnose) diagFirst() (diagNotation string, rest []byte, err error) {
err = di.wellformed(true)
if err == nil {
err = di.item()
}
if err == nil {
// Return EDN and the rest of the data slice (which might be len 0)
return di.w.String(), di.d.data[di.d.off:], nil
}
return di.w.String(), nil, err
}
func (di *diagnose) wellformed(allowExtraData bool) error {
off := di.d.off
err := di.d.wellformed(allowExtraData, false)
di.d.off = off
return err
}
func (di *diagnose) item() error { //nolint:gocyclo
initialByte := di.d.data[di.d.off]
switch initialByte {
case cborByteStringWithIndefiniteLengthHead,
cborTextStringWithIndefiniteLengthHead: // indefinite-length byte/text string
di.d.off++
if isBreakFlag(di.d.data[di.d.off]) {
di.d.off++
switch initialByte {
case cborByteStringWithIndefiniteLengthHead:
// indefinite-length bytes with no chunks.
di.w.WriteString(`''_`)
return nil
case cborTextStringWithIndefiniteLengthHead:
// indefinite-length text with no chunks.
di.w.WriteString(`""_`)
return nil
}
}
di.w.WriteString("(_ ")
i := 0
for !di.d.foundBreak() {
if i > 0 {
di.w.WriteString(", ")
}
i++
// wellformedIndefiniteString() already checked that the next item is a byte/text string.
if err := di.item(); err != nil {
return err
}
}
di.w.WriteByte(')')
return nil
case cborArrayWithIndefiniteLengthHead: // indefinite-length array
di.d.off++
di.w.WriteString("[_ ")
i := 0
for !di.d.foundBreak() {
if i > 0 {
di.w.WriteString(", ")
}
i++
if err := di.item(); err != nil {
return err
}
}
di.w.WriteByte(']')
return nil
case cborMapWithIndefiniteLengthHead: // indefinite-length map
di.d.off++
di.w.WriteString("{_ ")
i := 0
for !di.d.foundBreak() {
if i > 0 {
di.w.WriteString(", ")
}
i++
// key
if err := di.item(); err != nil {
return err
}
di.w.WriteString(": ")
// value
if err := di.item(); err != nil {
return err
}
}
di.w.WriteByte('}')
return nil
}
t := di.d.nextCBORType()
switch t {
case cborTypePositiveInt:
_, _, val := di.d.getHead()
di.w.WriteString(strconv.FormatUint(val, 10))
return nil
case cborTypeNegativeInt:
_, _, val := di.d.getHead()
if val > math.MaxInt64 {
// CBOR negative integer overflows int64, use big.Int to store value.
bi := new(big.Int)
bi.SetUint64(val)
bi.Add(bi, big.NewInt(1))
bi.Neg(bi)
di.w.WriteString(bi.String())
return nil
}
nValue := int64(-1) ^ int64(val)
di.w.WriteString(strconv.FormatInt(nValue, 10))
return nil
case cborTypeByteString:
b, _ := di.d.parseByteString()
return di.encodeByteString(b)
case cborTypeTextString:
b, err := di.d.parseTextString()
if err != nil {
return err
}
return di.encodeTextString(string(b), '"')
case cborTypeArray:
_, _, val := di.d.getHead()
count := int(val)
di.w.WriteByte('[')
for i := 0; i < count; i++ {
if i > 0 {
di.w.WriteString(", ")
}
if err := di.item(); err != nil {
return err
}
}
di.w.WriteByte(']')
return nil
case cborTypeMap:
_, _, val := di.d.getHead()
count := int(val)
di.w.WriteByte('{')
for i := 0; i < count; i++ {
if i > 0 {
di.w.WriteString(", ")
}
// key
if err := di.item(); err != nil {
return err
}
di.w.WriteString(": ")
// value
if err := di.item(); err != nil {
return err
}
}
di.w.WriteByte('}')
return nil
case cborTypeTag:
_, _, tagNum := di.d.getHead()
switch tagNum {
case tagNumUnsignedBignum:
if nt := di.d.nextCBORType(); nt != cborTypeByteString {
return newInadmissibleTagContentTypeError(
tagNumUnsignedBignum,
"byte string",
nt.String())
}
b, _ := di.d.parseByteString()
bi := new(big.Int).SetBytes(b)
di.w.WriteString(bi.String())
return nil
case tagNumNegativeBignum:
if nt := di.d.nextCBORType(); nt != cborTypeByteString {
return newInadmissibleTagContentTypeError(
tagNumNegativeBignum,
"byte string",
nt.String(),
)
}
b, _ := di.d.parseByteString()
bi := new(big.Int).SetBytes(b)
bi.Add(bi, big.NewInt(1))
bi.Neg(bi)
di.w.WriteString(bi.String())
return nil
default:
di.w.WriteString(strconv.FormatUint(tagNum, 10))
di.w.WriteByte('(')
if err := di.item(); err != nil {
return err
}
di.w.WriteByte(')')
return nil
}
case cborTypePrimitives:
_, ai, val := di.d.getHead()
switch ai {
case additionalInformationAsFalse:
di.w.WriteString("false")
return nil
case additionalInformationAsTrue:
di.w.WriteString("true")
return nil
case additionalInformationAsNull:
di.w.WriteString("null")
return nil
case additionalInformationAsUndefined:
di.w.WriteString("undefined")
return nil
case additionalInformationAsFloat16,
additionalInformationAsFloat32,
additionalInformationAsFloat64:
return di.encodeFloat(ai, val)
default:
di.w.WriteString("simple(")
di.w.WriteString(strconv.FormatUint(val, 10))
di.w.WriteByte(')')
return nil
}
}
return nil
}
// writeU16 format a rune as "\uxxxx"
func (di *diagnose) writeU16(val rune) {
di.w.WriteString("\\u")
var in [2]byte
in[0] = byte(val >> 8)
in[1] = byte(val)
sz := hex.EncodedLen(len(in))
di.w.Grow(sz)
dst := di.w.Bytes()[di.w.Len() : di.w.Len()+sz]
hex.Encode(dst, in[:])
di.w.Write(dst)
}
var rawBase32Encoding = base32.StdEncoding.WithPadding(base32.NoPadding)
var rawBase32HexEncoding = base32.HexEncoding.WithPadding(base32.NoPadding)
func (di *diagnose) encodeByteString(val []byte) error {
if len(val) > 0 {
if di.dm.byteStringText && utf8.Valid(val) {
return di.encodeTextString(string(val), '\'')
}
if di.dm.byteStringEmbeddedCBOR {
di2 := newDiagnose(val, di.dm.decMode, di.dm)
// should always notating embedded CBOR sequence.
if str, err := di2.diag(true); err == nil {
di.w.WriteString("<<")
di.w.WriteString(str)
di.w.WriteString(">>")
return nil
}
}
}
switch di.dm.byteStringEncoding {
case ByteStringBase16Encoding:
di.w.WriteString("h'")
if di.dm.byteStringHexWhitespace {
sz := hex.EncodedLen(len(val))
if len(val) > 0 {
sz += len(val) - 1
}
di.w.Grow(sz)
dst := di.w.Bytes()[di.w.Len():]
for i := range val {
if i > 0 {
dst = append(dst, ' ')
}
hex.Encode(dst[len(dst):len(dst)+2], val[i:i+1])
dst = dst[:len(dst)+2]
}
di.w.Write(dst)
} else {
sz := hex.EncodedLen(len(val))
di.w.Grow(sz)
dst := di.w.Bytes()[di.w.Len() : di.w.Len()+sz]
hex.Encode(dst, val)
di.w.Write(dst)
}
di.w.WriteByte('\'')
return nil
case ByteStringBase32Encoding:
di.w.WriteString("b32'")
sz := rawBase32Encoding.EncodedLen(len(val))
di.w.Grow(sz)
dst := di.w.Bytes()[di.w.Len() : di.w.Len()+sz]
rawBase32Encoding.Encode(dst, val)
di.w.Write(dst)
di.w.WriteByte('\'')
return nil
case ByteStringBase32HexEncoding:
di.w.WriteString("h32'")
sz := rawBase32HexEncoding.EncodedLen(len(val))
di.w.Grow(sz)
dst := di.w.Bytes()[di.w.Len() : di.w.Len()+sz]
rawBase32HexEncoding.Encode(dst, val)
di.w.Write(dst)
di.w.WriteByte('\'')
return nil
case ByteStringBase64Encoding:
di.w.WriteString("b64'")
sz := base64.RawURLEncoding.EncodedLen(len(val))
di.w.Grow(sz)
dst := di.w.Bytes()[di.w.Len() : di.w.Len()+sz]
base64.RawURLEncoding.Encode(dst, val)
di.w.Write(dst)
di.w.WriteByte('\'')
return nil
default:
// It should not be possible for users to construct a *diagMode with an invalid byte
// string encoding.
panic(fmt.Sprintf("diagmode has invalid ByteStringEncoding %v", di.dm.byteStringEncoding))
}
}
const utf16SurrSelf = rune(0x10000)
// quote should be either `'` or `"`
func (di *diagnose) encodeTextString(val string, quote byte) error {
di.w.WriteByte(quote)
for i := 0; i < len(val); {
if b := val[i]; b < utf8.RuneSelf {
switch {
case b == '\t', b == '\n', b == '\r', b == '\\', b == quote:
di.w.WriteByte('\\')
switch b {
case '\t':
b = 't'
case '\n':
b = 'n'
case '\r':
b = 'r'
}
di.w.WriteByte(b)
case b >= ' ' && b <= '~':
di.w.WriteByte(b)
default:
di.writeU16(rune(b))
}
i++
continue
}
c, size := utf8.DecodeRuneInString(val[i:])
switch {
case c == utf8.RuneError:
return &SemanticError{"cbor: invalid UTF-8 string"}
case c < utf16SurrSelf:
di.writeU16(c)
default:
c1, c2 := utf16.EncodeRune(c)
di.writeU16(c1)
di.writeU16(c2)
}
i += size
}
di.w.WriteByte(quote)
return nil
}
func (di *diagnose) encodeFloat(ai byte, val uint64) error {
f64 := float64(0)
switch ai {
case additionalInformationAsFloat16:
f16 := float16.Frombits(uint16(val))
switch {
case f16.IsNaN():
di.w.WriteString("NaN")
return nil
case f16.IsInf(1):
di.w.WriteString("Infinity")
return nil
case f16.IsInf(-1):
di.w.WriteString("-Infinity")
return nil
default:
f64 = float64(f16.Float32())
}
case additionalInformationAsFloat32:
f32 := math.Float32frombits(uint32(val))
switch {
case f32 != f32:
di.w.WriteString("NaN")
return nil
case f32 > math.MaxFloat32:
di.w.WriteString("Infinity")
return nil
case f32 < -math.MaxFloat32:
di.w.WriteString("-Infinity")
return nil
default:
f64 = float64(f32)
}
case additionalInformationAsFloat64:
f64 = math.Float64frombits(val)
switch {
case f64 != f64:
di.w.WriteString("NaN")
return nil
case f64 > math.MaxFloat64:
di.w.WriteString("Infinity")
return nil
case f64 < -math.MaxFloat64:
di.w.WriteString("-Infinity")
return nil
}
}
// Use ES6 number to string conversion which should match most JSON generators.
// Inspired by https://github.com/golang/go/blob/4df10fba1687a6d4f51d7238a403f8f2298f6a16/src/encoding/json/encode.go#L585
const bitSize = 64
b := make([]byte, 0, 32)
if abs := math.Abs(f64); abs != 0 && (abs < 1e-6 || abs >= 1e21) {
b = strconv.AppendFloat(b, f64, 'e', -1, bitSize)
// clean up e-09 to e-9
n := len(b)
if n >= 4 && string(b[n-4:n-1]) == "e-0" {
b = append(b[:n-2], b[n-1])
}
} else {
b = strconv.AppendFloat(b, f64, 'f', -1, bitSize)
}
// add decimal point and trailing zero if needed
if bytes.IndexByte(b, '.') < 0 {
if i := bytes.IndexByte(b, 'e'); i < 0 {
b = append(b, '.', '0')
} else {
b = append(b[:i+2], b[i:]...)
b[i] = '.'
b[i+1] = '0'
}
}
di.w.WriteString(string(b))
if di.dm.floatPrecisionIndicator {
switch ai {
case additionalInformationAsFloat16:
di.w.WriteString("_1")
return nil
case additionalInformationAsFloat32:
di.w.WriteString("_2")
return nil
case additionalInformationAsFloat64:
di.w.WriteString("_3")
return nil
}
}
return nil
}

1150
diagnose_test.go Normal file

File diff suppressed because it is too large Load Diff

130
doc.go
View File

@ -2,93 +2,111 @@
// Licensed under the MIT License. See LICENSE in the project root for license information.
/*
Package cbor is a fast & safe CBOR encoder & decoder (RFC 7049) with a
standard API + toarray & keyasint struct tags, CBOR tags, float64->32->16,
CTAP2 & Canonical CBOR, duplicate map key options, and is customizable via
simple API.
Package cbor is a modern CBOR codec (RFC 8949 & RFC 7049) with CBOR tags,
Go struct tags (toarray/keyasint/omitempty), Core Deterministic Encoding,
CTAP2, Canonical CBOR, float64->32->16, and duplicate map key detection.
CBOR encoding options allow "preferred serialization" by encoding integers and floats
to their smallest forms (like float16) when values fit.
Encoding options allow "preferred serialization" by encoding integers and floats
to their smallest forms (e.g. float16) when values fit.
Struct tags like "keyasint", "toarray" and "omitempty" makes CBOR data smaller.
Struct tags like "keyasint", "toarray" and "omitempty" make CBOR data smaller
and easier to use with structs.
For example, "toarray" makes struct fields encode to array elements. And "keyasint"
makes struct fields encode to elements of CBOR map with int keys.
For example, "toarray" tag makes struct fields encode to CBOR array elements. And
"keyasint" makes a field encode to an element of CBOR map with specified int key.
Basics
Latest docs can be viewed at https://github.com/fxamacker/cbor#cbor-library-in-go
# Basics
The Quick Start guide is at https://github.com/fxamacker/cbor#quick-start
Function signatures identical to encoding/json include:
Marshal, Unmarshal, NewEncoder, NewDecoder, encoder.Encode, decoder.Decode.
Marshal, Unmarshal, NewEncoder, NewDecoder, (*Encoder).Encode, (*Decoder).Decode.
Codec functions are available at package-level (using defaults) or by creating modes
from options at runtime.
Standard interfaces include:
"Mode" in this API means definite way of encoding or decoding. Specifically, EncMode or DecMode.
BinaryMarshaler, BinaryUnmarshaler, Marshaler, and Unmarshaler.
EncMode and DecMode interfaces are created from EncOptions or DecOptions structs. For example,
Custom encoding and decoding is possible by implementing standard interfaces for
user-defined Go types.
em := cbor.EncOptions{...}.EncMode()
em := cbor.CanonicalEncOptions().EncMode()
em := cbor.CTAP2EncOptions().EncMode()
Codec functions are available at package-level (using defaults options) or by
creating modes from options at runtime.
Modes use immutable options to avoid side-effects and simplify concurrency. Behavior of modes
won't accidentally change at runtime after they're created.
"Mode" in this API means definite way of encoding (EncMode) or decoding (DecMode).
EncMode and DecMode interfaces are created from EncOptions or DecOptions structs.
em, err := cbor.EncOptions{...}.EncMode()
em, err := cbor.CanonicalEncOptions().EncMode()
em, err := cbor.CTAP2EncOptions().EncMode()
Modes use immutable options to avoid side-effects and simplify concurrency. Behavior of
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 interface uses immutable options and is safe for concurrent use.
type EncMode interface {
Marshal(v interface{}) ([]byte, error)
NewEncoder(w io.Writer) *Encoder
EncOptions() EncOptions // returns copy of options
}
// EncMode interface uses immutable options and is safe for concurrent use.
type EncMode interface {
Marshal(v interface{}) ([]byte, error)
NewEncoder(w io.Writer) *Encoder
EncOptions() EncOptions // returns copy of options
}
// DecMode interface uses immutable options and is safe for concurrent use.
type DecMode interface {
Unmarshal(data []byte, v interface{}) error
NewDecoder(r io.Reader) *Decoder
DecOptions() DecOptions // returns copy of options
}
// DecMode interface uses immutable options and is safe for concurrent use.
type DecMode interface {
Unmarshal(data []byte, v interface{}) error
NewDecoder(r io.Reader) *Decoder
DecOptions() DecOptions // returns copy of options
}
Using Default Encoding Mode
b, err := cbor.Marshal(v)
b, err := cbor.Marshal(v)
encoder := cbor.NewEncoder(w)
err = encoder.Encode(v)
encoder := cbor.NewEncoder(w)
err = encoder.Encode(v)
Using Default Decoding Mode
err := cbor.Unmarshal(b, &v)
err := cbor.Unmarshal(b, &v)
decoder := cbor.NewDecoder(r)
err = decoder.Decode(&v)
decoder := cbor.NewDecoder(r)
err = decoder.Decode(&v)
Creating and Using Encoding Modes
// Create EncOptions using either struct literal or a function.
opts := cbor.CanonicalEncOptions()
// Create EncOptions using either struct literal or a function.
opts := cbor.CanonicalEncOptions()
// If needed, modify encoding options
opts.Time = cbor.TimeUnix
// If needed, modify encoding options
opts.Time = cbor.TimeUnix
// Create reusable EncMode interface with immutable options, safe for concurrent use.
em, err := opts.EncMode()
// Create reusable EncMode interface with immutable options, safe for concurrent use.
em, err := opts.EncMode()
// Use EncMode like encoding/json, with same function signatures.
b, err := em.Marshal(v)
// or
encoder := em.NewEncoder(w)
err := encoder.Encode(v)
// Use EncMode like encoding/json, with same function signatures.
b, err := em.Marshal(v)
// or
encoder := em.NewEncoder(w)
err := encoder.Encode(v)
Default Options
// NOTE: Both em.Marshal(v) and encoder.Encode(v) use encoding options
// specified during creation of em (encoding mode).
Default encoding options are listed at https://github.com/fxamacker/cbor#api
# CBOR Options
Struct Tags
Predefined Encoding Options: https://github.com/fxamacker/cbor#predefined-encoding-options
Encoding Options: https://github.com/fxamacker/cbor#encoding-options
Decoding Options: https://github.com/fxamacker/cbor#decoding-options
# Struct Tags
Struct tags like `cbor:"name,omitempty"` and `json:"name,omitempty"` work as expected.
If both struct tags are specified then `cbor` is used.
@ -101,9 +119,11 @@ makes struct fields encode to elements of CBOR map with int keys.
https://raw.githubusercontent.com/fxamacker/images/master/cbor/v2.0.0/cbor_easy_api.png
Tests and Fuzzing
Struct tags are listed at https://github.com/fxamacker/cbor#struct-tags-1
Over 375 tests are included in this package. Cover-guided fuzzing is handled by a separate package:
fxamacker/cbor-fuzz.
# Tests and Fuzzing
Over 375 tests are included in this package. Cover-guided fuzzing is handled by
a private fuzzer that replaced fxamacker/cbor-fuzz years ago.
*/
package cbor

1535
encode.go

File diff suppressed because it is too large Load Diff

94
encode_map.go Normal file
View File

@ -0,0 +1,94 @@
// 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
package cbor
import (
"bytes"
"reflect"
"sync"
)
type mapKeyValueEncodeFunc struct {
kf, ef encodeFunc
kpool, vpool sync.Pool
}
func (me *mapKeyValueEncodeFunc) encodeKeyValues(e *bytes.Buffer, em *encMode, v reflect.Value, kvs []keyValue) error {
iterk := me.kpool.Get().(*reflect.Value)
defer func() {
iterk.SetZero()
me.kpool.Put(iterk)
}()
iterv := me.vpool.Get().(*reflect.Value)
defer func() {
iterv.SetZero()
me.vpool.Put(iterv)
}()
if kvs == nil {
for i, iter := 0, v.MapRange(); iter.Next(); i++ {
iterk.SetIterKey(iter)
iterv.SetIterValue(iter)
if err := me.kf(e, em, *iterk); err != nil {
return err
}
if err := me.ef(e, em, *iterv); err != nil {
return err
}
}
return nil
}
initial := e.Len()
for i, iter := 0, v.MapRange(); iter.Next(); i++ {
iterk.SetIterKey(iter)
iterv.SetIterValue(iter)
offset := e.Len()
if err := me.kf(e, em, *iterk); err != nil {
return err
}
valueOffset := e.Len()
if err := me.ef(e, em, *iterv); err != nil {
return err
}
kvs[i] = keyValue{
offset: offset - initial,
valueOffset: valueOffset - initial,
nextOffset: e.Len() - initial,
}
}
return nil
}
func getEncodeMapFunc(t reflect.Type) encodeFunc {
kf, _ := getEncodeFunc(t.Key())
ef, _ := getEncodeFunc(t.Elem())
if kf == nil || ef == nil {
return nil
}
mkv := &mapKeyValueEncodeFunc{
kf: kf,
ef: ef,
kpool: sync.Pool{
New: func() interface{} {
rk := reflect.New(t.Key()).Elem()
return &rk
},
},
vpool: sync.Pool{
New: func() interface{} {
rv := reflect.New(t.Elem()).Elem()
return &rv
},
},
}
return mapEncodeFunc{
e: mkv.encodeKeyValues,
}.encode
}

60
encode_map_go117.go Normal file
View File

@ -0,0 +1,60 @@
// 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
package cbor
import (
"bytes"
"reflect"
)
type mapKeyValueEncodeFunc struct {
kf, ef encodeFunc
}
func (me *mapKeyValueEncodeFunc) encodeKeyValues(e *bytes.Buffer, em *encMode, v reflect.Value, kvs []keyValue) error {
if kvs == nil {
for i, iter := 0, v.MapRange(); iter.Next(); i++ {
if err := me.kf(e, em, iter.Key()); err != nil {
return err
}
if err := me.ef(e, em, iter.Value()); err != nil {
return err
}
}
return nil
}
initial := e.Len()
for i, iter := 0, v.MapRange(); iter.Next(); i++ {
offset := e.Len()
if err := me.kf(e, em, iter.Key()); err != nil {
return err
}
valueOffset := e.Len()
if err := me.ef(e, em, iter.Value()); err != nil {
return err
}
kvs[i] = keyValue{
offset: offset - initial,
valueOffset: valueOffset - initial,
nextOffset: e.Len() - initial,
}
}
return nil
}
func getEncodeMapFunc(t reflect.Type) encodeFunc {
kf, _ := getEncodeFunc(t.Key())
ef, _ := getEncodeFunc(t.Elem())
if kf == nil || ef == nil {
return nil
}
mkv := &mapKeyValueEncodeFunc{kf: kf, ef: ef}
return mapEncodeFunc{
e: mkv.encodeKeyValues,
}.encode
}

File diff suppressed because it is too large Load Diff

View File

@ -11,7 +11,7 @@ import (
"reflect"
"time"
"github.com/fxamacker/cbor/v2"
"github.com/fxamacker/cbor/v2" // remove "/v2" suffix if you're not using Go modules (see README.md)
)
func ExampleMarshal() {
@ -125,9 +125,9 @@ func ExampleUnmarshal() {
Owners []string
Male bool
}
cborData, _ := hex.DecodeString("a46341676504644e616d656543616e6479664f776e65727382644d617279634a6f65644d616c65f4")
data, _ := hex.DecodeString("a46341676504644e616d656543616e6479664f776e65727382644d617279634a6f65644d616c65f4")
var animal Animal
err := cbor.Unmarshal(cborData, &animal)
err := cbor.Unmarshal(data, &animal)
if err != nil {
fmt.Println("error:", err)
}
@ -334,8 +334,8 @@ func ExampleDecoder() {
Owners []string
Male bool
}
cborData, _ := hex.DecodeString("a46341676504644d616c65f4644e616d656543616e6479664f776e65727382644d617279634a6f65a46341676506644d616c65f5644e616d656452756479664f776e657273816543696e6479a46341676502644d616c65f5644e616d656444756b65664f776e65727381664e6f72746f6e")
dec := cbor.NewDecoder(bytes.NewReader(cborData))
data, _ := hex.DecodeString("a46341676504644d616c65f4644e616d656543616e6479664f776e65727382644d617279634a6f65a46341676506644d616c65f5644e616d656452756479664f776e657273816543696e6479a46341676502644d616c65f5644e616d656444756b65664f776e65727381664e6f72746f6e")
dec := cbor.NewDecoder(bytes.NewReader(data))
for {
var animal Animal
if err := dec.Decode(&animal); err != nil {
@ -364,9 +364,9 @@ func Example_cWT() {
Cti []byte `cbor:"7,keyasint"`
}
// Data from https://tools.ietf.org/html/rfc8392#appendix-A section A.1
cborData, _ := hex.DecodeString("a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b71")
data, _ := hex.DecodeString("a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b71")
var v claims
if err := cbor.Unmarshal(cborData, &v); err != nil {
if err := cbor.Unmarshal(data, &v); err != nil {
fmt.Println("error:", err)
}
if _, err := cbor.Marshal(v); err != nil {
@ -389,12 +389,12 @@ func Example_cWTWithDupMapKeyOption() {
}
// Data from https://tools.ietf.org/html/rfc8392#appendix-A section A.1
cborData, _ := hex.DecodeString("a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b71")
data, _ := hex.DecodeString("a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b71")
dm, _ := cbor.DecOptions{DupMapKey: cbor.DupMapKeyEnforcedAPF}.DecMode()
var v claims
if err := dm.Unmarshal(cborData, &v); err != nil {
if err := dm.Unmarshal(data, &v); err != nil {
fmt.Println("error:", err)
}
fmt.Printf("%+v", v)
@ -419,9 +419,9 @@ func Example_signedCWT() {
Signature []byte
}
// Data from https://tools.ietf.org/html/rfc8392#appendix-A section A.3
cborData, _ := hex.DecodeString("d28443a10126a104524173796d6d657472696345434453413235365850a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b7158405427c1ff28d23fbad1f29c4c7c6a555e601d6fa29f9179bc3d7438bacaca5acd08c8d4d4f96131680c429a01f85951ecee743a52b9b63632c57209120e1c9e30")
data, _ := hex.DecodeString("d28443a10126a104524173796d6d657472696345434453413235365850a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b7158405427c1ff28d23fbad1f29c4c7c6a555e601d6fa29f9179bc3d7438bacaca5acd08c8d4d4f96131680c429a01f85951ecee743a52b9b63632c57209120e1c9e30")
var v signedCWT
if err := cbor.Unmarshal(cborData, &v); err != nil {
if err := cbor.Unmarshal(data, &v); err != nil {
fmt.Println("error:", err)
}
if _, err := cbor.Marshal(v); err != nil {
@ -450,7 +450,7 @@ func Example_signedCWTWithTag() {
}
// Data from https://tools.ietf.org/html/rfc8392#appendix-A section A.3
cborData, _ := hex.DecodeString("d28443a10126a104524173796d6d657472696345434453413235365850a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b7158405427c1ff28d23fbad1f29c4c7c6a555e601d6fa29f9179bc3d7438bacaca5acd08c8d4d4f96131680c429a01f85951ecee743a52b9b63632c57209120e1c9e30")
data, _ := hex.DecodeString("d28443a10126a104524173796d6d657472696345434453413235365850a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b7158405427c1ff28d23fbad1f29c4c7c6a555e601d6fa29f9179bc3d7438bacaca5acd08c8d4d4f96131680c429a01f85951ecee743a52b9b63632c57209120e1c9e30")
// Register tag COSE_Sign1 18 with signedCWT type.
tags := cbor.NewTagSet()
@ -465,7 +465,7 @@ func Example_signedCWTWithTag() {
em, _ := cbor.EncOptions{}.EncModeWithTags(tags)
var v signedCWT
if err := dm.Unmarshal(cborData, &v); err != nil {
if err := dm.Unmarshal(data, &v); err != nil {
fmt.Println("error:", err)
}
@ -493,9 +493,9 @@ func Example_cOSE() {
}
// Data from https://tools.ietf.org/html/rfc8392#appendix-A section A.2
// 128-Bit Symmetric Key
cborData, _ := hex.DecodeString("a42050231f4c4d4d3051fdc2ec0a3851d5b3830104024c53796d6d6574726963313238030a")
data, _ := hex.DecodeString("a42050231f4c4d4d3051fdc2ec0a3851d5b3830104024c53796d6d6574726963313238030a")
var v coseKey
if err := cbor.Unmarshal(cborData, &v); err != nil {
if err := cbor.Unmarshal(data, &v); err != nil {
fmt.Println("error:", err)
}
if _, err := cbor.Marshal(v); err != nil {
@ -526,9 +526,9 @@ func Example_senML() {
Sum float64 `cbor:"5,keyasint,omitempty"`
}
// Data from https://tools.ietf.org/html/rfc8428#section-6
cborData, _ := hex.DecodeString("87a721781b75726e3a6465763a6f773a3130653230373361303130383030363a22fb41d303a15b00106223614120050067766f6c7461676501615602fb405e066666666666a3006763757272656e74062402fb3ff3333333333333a3006763757272656e74062302fb3ff4cccccccccccda3006763757272656e74062202fb3ff6666666666666a3006763757272656e74062102f93e00a3006763757272656e74062002fb3ff999999999999aa3006763757272656e74060002fb3ffb333333333333")
data, _ := hex.DecodeString("87a721781b75726e3a6465763a6f773a3130653230373361303130383030363a22fb41d303a15b00106223614120050067766f6c7461676501615602fb405e066666666666a3006763757272656e74062402fb3ff3333333333333a3006763757272656e74062302fb3ff4cccccccccccda3006763757272656e74062202fb3ff6666666666666a3006763757272656e74062102f93e00a3006763757272656e74062002fb3ff999999999999aa3006763757272656e74060002fb3ffb333333333333")
var v []*SenMLRecord
if err := cbor.Unmarshal(cborData, &v); err != nil {
if err := cbor.Unmarshal(data, &v); err != nil {
fmt.Println("error:", err)
}
// Encoder uses ShortestFloat16 option to use float16 as the shortest form that preserves floating-point value.
@ -559,9 +559,9 @@ func Example_webAuthn() {
Fmt string `cbor:"fmt"`
AttStmt cbor.RawMessage `cbor:"attStmt"`
}
cborData, _ := hex.DecodeString("a363666d74686669646f2d7532666761747453746d74a26373696758483046022100e7ab373cfbd99fcd55fd59b0f6f17fef5b77a20ddec3db7f7e4d55174e366236022100828336b4822125fb56541fb14a8a273876acd339395ec2dad95cf41c1dd2a9ae637835638159024e3082024a30820132a0030201020204124a72fe300d06092a864886f70d01010b0500302e312c302a0603550403132359756269636f2055324620526f6f742043412053657269616c203435373230303633313020170d3134303830313030303030305a180f32303530303930343030303030305a302c312a302806035504030c2159756269636f205532462045452053657269616c203234393431343937323135383059301306072a8648ce3d020106082a8648ce3d030107034200043d8b1bbd2fcbf6086e107471601468484153c1c6d3b4b68a5e855e6e40757ee22bcd8988bf3befd7cdf21cb0bf5d7a150d844afe98103c6c6607d9faae287c02a33b3039302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e313013060b2b0601040182e51c020101040403020520300d06092a864886f70d01010b05000382010100a14f1eea0076f6b8476a10a2be72e60d0271bb465b2dfbfc7c1bd12d351989917032631d795d097fa30a26a325634e85721bc2d01a86303f6bc075e5997319e122148b0496eec8d1f4f94cf4110de626c289443d1f0f5bbb239ca13e81d1d5aa9df5af8e36126475bfc23af06283157252762ff68879bcf0ef578d55d67f951b4f32b63c8aea5b0f99c67d7d814a7ff5a6f52df83e894a3a5d9c8b82e7f8bc8daf4c80175ff8972fda79333ec465d806eacc948f1bab22045a95558a48c20226dac003d41fbc9e05ea28a6bb5e10a49de060a0a4f6a2676a34d68c4abe8c61874355b9027e828ca9e064b002d62e8d8cf0744921753d35e3c87c5d5779453e7768617574684461746158c449960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d976341000000000000000000000000000000000000000000408903fd7dfd2c9770e98cae0123b13a2c27828a106349bc6277140e7290b7e9eb7976aa3c04ed347027caf7da3a2fa76304751c02208acfc4e7fc6c7ebbc375c8a5010203262001215820ad7f7992c335b90d882b2802061b97a4fabca7e2ee3e7a51e728b8055e4eb9c7225820e0966ba7005987fece6f0e0e13447aa98cec248e4000a594b01b74c1cb1d40b3")
data, _ := hex.DecodeString("a363666d74686669646f2d7532666761747453746d74a26373696758483046022100e7ab373cfbd99fcd55fd59b0f6f17fef5b77a20ddec3db7f7e4d55174e366236022100828336b4822125fb56541fb14a8a273876acd339395ec2dad95cf41c1dd2a9ae637835638159024e3082024a30820132a0030201020204124a72fe300d06092a864886f70d01010b0500302e312c302a0603550403132359756269636f2055324620526f6f742043412053657269616c203435373230303633313020170d3134303830313030303030305a180f32303530303930343030303030305a302c312a302806035504030c2159756269636f205532462045452053657269616c203234393431343937323135383059301306072a8648ce3d020106082a8648ce3d030107034200043d8b1bbd2fcbf6086e107471601468484153c1c6d3b4b68a5e855e6e40757ee22bcd8988bf3befd7cdf21cb0bf5d7a150d844afe98103c6c6607d9faae287c02a33b3039302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e313013060b2b0601040182e51c020101040403020520300d06092a864886f70d01010b05000382010100a14f1eea0076f6b8476a10a2be72e60d0271bb465b2dfbfc7c1bd12d351989917032631d795d097fa30a26a325634e85721bc2d01a86303f6bc075e5997319e122148b0496eec8d1f4f94cf4110de626c289443d1f0f5bbb239ca13e81d1d5aa9df5af8e36126475bfc23af06283157252762ff68879bcf0ef578d55d67f951b4f32b63c8aea5b0f99c67d7d814a7ff5a6f52df83e894a3a5d9c8b82e7f8bc8daf4c80175ff8972fda79333ec465d806eacc948f1bab22045a95558a48c20226dac003d41fbc9e05ea28a6bb5e10a49de060a0a4f6a2676a34d68c4abe8c61874355b9027e828ca9e064b002d62e8d8cf0744921753d35e3c87c5d5779453e7768617574684461746158c449960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d976341000000000000000000000000000000000000000000408903fd7dfd2c9770e98cae0123b13a2c27828a106349bc6277140e7290b7e9eb7976aa3c04ed347027caf7da3a2fa76304751c02208acfc4e7fc6c7ebbc375c8a5010203262001215820ad7f7992c335b90d882b2802061b97a4fabca7e2ee3e7a51e728b8055e4eb9c7225820e0966ba7005987fece6f0e0e13447aa98cec248e4000a594b01b74c1cb1d40b3")
var v attestationObject
if err := cbor.Unmarshal(cborData, &v); err != nil {
if err := cbor.Unmarshal(data, &v); err != nil {
fmt.Println("error:", err)
}
if _, err := cbor.Marshal(v); err != nil {

2
go.mod
View File

@ -1,5 +1,5 @@
module github.com/fxamacker/cbor/v2
go 1.12
go 1.17 // Compiling with go 1.20+ uses new features and optimizations
require github.com/x448/float16 v0.8.4

132
json_test.go Normal file
View File

@ -0,0 +1,132 @@
package cbor_test
import (
"encoding/json"
"reflect"
"testing"
"github.com/fxamacker/cbor/v2"
)
// TestStdlibJSONCompatibility tests compatibility as a drop-in replacement for the standard library
// encoding/json package on a round trip encoding from Go object to interface{}.
func TestStdlibJSONCompatibility(t *testing.T) {
// TODO: With better coverage and compatibility, it could be useful to expose these option
// configurations to users.
enc, err := cbor.EncOptions{
ByteSliceLaterFormat: cbor.ByteSliceLaterFormatBase64,
String: cbor.StringToByteString,
ByteArray: cbor.ByteArrayToArray,
}.EncMode()
if err != nil {
t.Fatal(err)
}
dec, err := cbor.DecOptions{
DefaultByteStringType: reflect.TypeOf(""),
ByteStringToString: cbor.ByteStringToStringAllowedWithExpectedLaterEncoding,
ByteStringExpectedFormat: cbor.ByteStringExpectedBase64,
}.DecMode()
if err != nil {
t.Fatal(err)
}
for _, tc := range []struct {
name string
original interface{}
ifaceEqual bool // require equal intermediate interface{} values from both protocols
}{
{
name: "byte slice to base64-encoded string",
original: []byte("hello world"),
ifaceEqual: true,
},
{
name: "byte array to array of integers",
original: [11]byte{'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd'},
ifaceEqual: false, // encoding/json decodes the array elements to float64
},
} {
t.Run(tc.name, func(t *testing.T) {
t.Logf("original: %#v", tc.original)
j1, err := json.Marshal(tc.original)
if err != nil {
t.Fatal(err)
}
t.Logf("original to json: %s", string(j1))
c1, err := enc.Marshal(tc.original)
if err != nil {
t.Fatal(err)
}
diag1, err := cbor.Diagnose(c1)
if err != nil {
t.Fatal(err)
}
t.Logf("original to cbor: %s", diag1)
var jintf interface{}
err = json.Unmarshal(j1, &jintf)
if err != nil {
t.Fatal(err)
}
t.Logf("json to interface{} (%T): %#v", jintf, jintf)
var cintf interface{}
err = dec.Unmarshal(c1, &cintf)
if err != nil {
t.Fatal(err)
}
t.Logf("cbor to interface{} (%T): %#v", cintf, cintf)
j2, err := json.Marshal(jintf)
if err != nil {
t.Fatal(err)
}
t.Logf("interface{} to json: %s", string(j2))
c2, err := enc.Marshal(cintf)
if err != nil {
t.Fatal(err)
}
diag2, err := cbor.Diagnose(c2)
if err != nil {
t.Fatal(err)
}
t.Logf("interface{} to cbor: %s", diag2)
if !reflect.DeepEqual(jintf, cintf) {
if tc.ifaceEqual {
t.Errorf("native-to-interface{} via cbor differed from native-to-interface{} via json")
} else {
t.Logf("native-to-interface{} via cbor differed from native-to-interface{} via json")
}
}
jfinalValue := reflect.New(reflect.TypeOf(tc.original))
err = json.Unmarshal(j2, jfinalValue.Interface())
if err != nil {
t.Fatal(err)
}
jfinal := jfinalValue.Elem().Interface()
t.Logf("json to native: %#v", jfinal)
if !reflect.DeepEqual(tc.original, jfinal) {
t.Error("diff in json roundtrip")
}
cfinalValue := reflect.New(reflect.TypeOf(tc.original))
err = dec.Unmarshal(c2, cfinalValue.Interface())
if err != nil {
t.Fatal(err)
}
cfinal := cfinalValue.Elem().Interface()
t.Logf("cbor to native: %#v", cfinal)
if !reflect.DeepEqual(tc.original, cfinal) {
t.Error("diff in cbor roundtrip")
}
})
}
}

View File

@ -1,83 +0,0 @@
// Copyright (c) Faye Amacker. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.
package cbor
import (
"testing"
)
func TestDepth(t *testing.T) {
testCases := []struct {
name string
cborData []byte
wantDepth int
}{
{"uint", hexDecode("00"), 1}, // 0
{"int", hexDecode("20"), 1}, // -1
{"bool", hexDecode("f4"), 1}, // false
{"nil", hexDecode("f6"), 1}, // nil
{"float", hexDecode("fa47c35000"), 1}, // 100000.0
{"byte string", hexDecode("40"), 1}, // []byte{}
{"indefinite length byte string", hexDecode("5f42010243030405ff"), 1}, // []byte{1, 2, 3, 4, 5}
{"text string", hexDecode("60"), 1}, // ""
{"indefinite length text string", hexDecode("7f657374726561646d696e67ff"), 1}, // "streaming"
{"empty array", hexDecode("80"), 1}, // []
{"indefinite length empty array", hexDecode("9fff"), 1}, // []
{"array", hexDecode("98190102030405060708090a0b0c0d0e0f101112131415161718181819"), 2}, // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]
{"indefinite length array", hexDecode("9f0102030405060708090a0b0c0d0e0f101112131415161718181819ff"), 2}, // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]
{"nested array", hexDecode("8301820203820405"), 3}, // [1,[2,3],[4,5]]
{"indefinite length nested array", hexDecode("83018202039f0405ff"), 3}, // [1,[2,3],[4,5]]
{"array and map", hexDecode("826161a161626163"), 3}, // [a", {"b": "c"}]
{"indefinite length array and map", hexDecode("826161bf61626163ff"), 3}, // [a", {"b": "c"}]
{"empty map", hexDecode("a0"), 1}, // {}
{"indefinite length empty map", hexDecode("bfff"), 1}, // {}
{"map", hexDecode("a201020304"), 2}, // {1:2, 3:4}
{"nested map", hexDecode("a26161016162820203"), 3}, // {"a": 1, "b": [2, 3]}
{"indefinite length nested map", hexDecode("bf61610161629f0203ffff"), 3}, // {"a": 1, "b": [2, 3]}
{"tag", hexDecode("c074323031332d30332d32315432303a30343a30305a"), 1}, // 0("2013-03-21T20:04:00Z")
{"tagged map", hexDecode("d864a26161016162820203"), 3}, // 100({"a": 1, "b": [2, 3]})
{"tagged map and array", hexDecode("d864a26161016162d865d866820203"), 4}, // 100({"a": 1, "b": 101(102([2, 3]))})
{"nested tag", hexDecode("d864d865d86674323031332d30332d32315432303a30343a30305a"), 3}, // 100(101(102("2013-03-21T20:04:00Z")))
{"32-level array", hexDecode("820181818181818181818181818181818181818181818181818181818181818101"), 32},
{"32-level indefinite length array", hexDecode("9f0181818181818181818181818181818181818181818181818181818181818101ff"), 32},
{"32-level map", hexDecode("a10181818181818181818181818181818181818181818181818181818181818101"), 32},
{"32-level indefinite length map", hexDecode("bf0181818181818181818181818181818181818181818181818181818181818101ff"), 32},
{"32-level tag", hexDecode("d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d86474323031332d30332d32315432303a30343a30305a"), 32}, // 100(100(...("2013-03-21T20:04:00Z")))
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
_, depth, err := validInternal(tc.cborData, 0, 1)
if err != nil {
t.Errorf("valid(0x%x) returned error %v", tc.cborData, err)
}
if depth != tc.wantDepth {
t.Errorf("valid(0x%x) returned depth %d, want %d", tc.cborData, depth, tc.wantDepth)
}
})
}
}
func TestDepthError(t *testing.T) {
testCases := []struct {
name string
cborData []byte
wantErrorMsg string
}{
{"33-level array", hexDecode("82018181818181818181818181818181818181818181818181818181818181818101"), "cbor: reached max depth 32"},
{"33-level indefinite length array", hexDecode("9f018181818181818181818181818181818181818181818181818181818181818101ff"), "cbor: reached max depth 32"},
{"33-level map", hexDecode("a1018181818181818181818181818181818181818181818181818181818181818101"), "cbor: reached max depth 32"},
{"33-level indefinite length map", hexDecode("bf018181818181818181818181818181818181818181818181818181818181818101ff"), "cbor: reached max depth 32"},
{"33-level tag", hexDecode("d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d86474323031332d30332d32315432303a30343a30305a"), "cbor: reached max depth 32"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
_, _, err := validInternal(tc.cborData, 0, 1)
if err == nil {
t.Errorf("valid(0x%x) didn't return an error, want %q", tc.cborData, tc.wantErrorMsg)
} else if err.Error() != tc.wantErrorMsg {
t.Errorf("valid(0x%x) returned error %q, want %q", tc.cborData, err.Error(), tc.wantErrorMsg)
}
})
}
}

69
simplevalue.go Normal file
View File

@ -0,0 +1,69 @@
package cbor
import (
"errors"
"fmt"
"reflect"
)
// SimpleValue represents CBOR simple value.
// CBOR simple value is:
// - an extension point like CBOR tag.
// - a subset of CBOR major type 7 that isn't floating-point.
// - "identified by a number between 0 and 255, but distinct from that number itself".
// For example, "a simple value 2 is not equivalent to an integer 2" as a CBOR map key.
//
// CBOR simple values identified by 20..23 are: "false", "true" , "null", and "undefined".
// Other CBOR simple values are currently unassigned/reserved by IANA.
type SimpleValue uint8
var (
typeSimpleValue = reflect.TypeOf(SimpleValue(0))
)
// MarshalCBOR encodes SimpleValue as CBOR simple value (major type 7).
func (sv SimpleValue) MarshalCBOR() ([]byte, error) {
// RFC 8949 3.3. Floating-Point Numbers and Values with No Content says:
// "An encoder MUST NOT issue two-byte sequences that start with 0xf8
// (major type 7, additional information 24) and continue with a byte
// less than 0x20 (32 decimal). Such sequences are not well-formed.
// (This implies that an encoder cannot encode false, true, null, or
// undefined in two-byte sequences and that only the one-byte variants
// of these are well-formed; more generally speaking, each simple value
// only has a single representation variant)."
switch {
case sv <= maxSimpleValueInAdditionalInformation:
return []byte{byte(cborTypePrimitives) | byte(sv)}, nil
case sv >= minSimpleValueIn1ByteArgument:
return []byte{byte(cborTypePrimitives) | additionalInformationWith1ByteArgument, byte(sv)}, nil
default:
return nil, &UnsupportedValueError{msg: fmt.Sprintf("SimpleValue(%d)", sv)}
}
}
// UnmarshalCBOR decodes CBOR simple value (major type 7) to SimpleValue.
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 {
return &UnmarshalTypeError{CBORType: typ.String(), GoType: "SimpleValue"}
}
if ai > additionalInformationWith1ByteArgument {
return &UnmarshalTypeError{CBORType: typ.String(), GoType: "SimpleValue", errorMsg: "not simple values"}
}
// 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)
return nil
}

177
simplevalue_test.go Normal file
View File

@ -0,0 +1,177 @@
// Copyright (c) Faye Amacker. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.
package cbor
import (
"bytes"
"reflect"
"testing"
)
func TestUnmarshalSimpleValue(t *testing.T) {
t.Run("0..23", func(t *testing.T) {
for i := 0; i <= 23; i++ {
data := []byte{byte(cborTypePrimitives) | byte(i)}
want := SimpleValue(i)
switch i {
case 20: // false
testUnmarshalSimpleValueToEmptyInterface(t, data, false)
case 21: // true
testUnmarshalSimpleValueToEmptyInterface(t, data, true)
case 22: // null
testUnmarshalSimpleValueToEmptyInterface(t, data, nil)
case 23: // undefined
testUnmarshalSimpleValueToEmptyInterface(t, data, nil)
default:
testUnmarshalSimpleValueToEmptyInterface(t, data, want)
}
testUnmarshalSimpleValue(t, data, want)
}
})
t.Run("24..31", func(t *testing.T) {
for i := 24; i <= 31; i++ {
data := []byte{byte(cborTypePrimitives) | byte(24), byte(i)}
testUnmarshalInvalidSimpleValueToEmptyInterface(t, data)
testUnmarshalInvalidSimpleValue(t, data)
}
})
t.Run("32..255", func(t *testing.T) {
for i := 32; i <= 255; i++ {
data := []byte{byte(cborTypePrimitives) | byte(24), byte(i)}
want := SimpleValue(i)
testUnmarshalSimpleValueToEmptyInterface(t, data, want)
testUnmarshalSimpleValue(t, data, want)
}
})
}
func testUnmarshalInvalidSimpleValueToEmptyInterface(t *testing.T, data []byte) {
var v interface{}
if err := Unmarshal(data, v); err == nil {
t.Errorf("Unmarshal(0x%x) didn't return an error", data)
} else if _, ok := err.(*SyntaxError); !ok {
t.Errorf("Unmarshal(0x%x) returned wrong error type %T, want (*SyntaxError)", data, err)
}
}
func testUnmarshalInvalidSimpleValue(t *testing.T, data []byte) {
var v SimpleValue
if err := Unmarshal(data, v); err == nil {
t.Errorf("Unmarshal(0x%x) didn't return an error", data)
} else if _, ok := err.(*SyntaxError); !ok {
t.Errorf("Unmarshal(0x%x) returned wrong error type %T, want (*SyntaxError)", data, err)
}
}
func testUnmarshalSimpleValueToEmptyInterface(t *testing.T, data []byte, want interface{}) {
var v interface{}
if err := Unmarshal(data, &v); err != nil {
t.Errorf("Unmarshal(0x%x) returned error %v", data, err)
return
}
if !reflect.DeepEqual(v, want) {
t.Errorf("Unmarshal(0x%x) = %v (%T), want %v (%T)", data, v, v, want, want)
}
}
func testUnmarshalSimpleValue(t *testing.T, data []byte, want SimpleValue) {
cborNil := isCBORNil(data)
// Decode to SimpleValue
var v SimpleValue
err := Unmarshal(data, &v)
if err != nil {
t.Errorf("Unmarshal(0x%x) returned error %v", data, err)
return
}
if !reflect.DeepEqual(v, want) {
t.Errorf("Unmarshal(0x%x) = %v (%T), want %v (%T)", data, v, v, want, want)
}
// Decode to uninitialized *SimpleValue
var pv *SimpleValue
err = Unmarshal(data, &pv)
if err != nil {
t.Errorf("Unmarshal(0x%x) returned error %v", data, err)
return
}
if cborNil {
if pv != nil {
t.Errorf("Unmarshal(0x%x) returned %v, want nil *SimpleValue", data, *pv)
}
} else {
if !reflect.DeepEqual(*pv, want) {
t.Errorf("Unmarshal(0x%x) = %v (%T), want %v (%T)", data, *pv, *pv, want, want)
}
}
// Decode to initialized *SimpleValue
v = SimpleValue(0)
pv = &v
err = Unmarshal(data, &pv)
if err != nil {
t.Errorf("Unmarshal(0x%x) returned error %v", data, err)
return
}
if cborNil {
if pv != nil {
t.Errorf("Unmarshal(0x%x) returned %v, want nil *SimpleValue", data, *pv)
}
} else {
if !reflect.DeepEqual(v, want) {
t.Errorf("Unmarshal(0x%x) = %v (%T), want %v (%T)", data, v, v, want, want)
}
}
}
func TestMarshalSimpleValue(t *testing.T) {
t.Run("0..23", func(t *testing.T) {
for i := 0; i <= 23; i++ {
wantData := []byte{byte(cborTypePrimitives) | byte(i)}
v := SimpleValue(i)
data, err := Marshal(v)
if err != nil {
t.Errorf("Marshal(%v) returned error %v", v, err)
continue
}
if !bytes.Equal(data, wantData) {
t.Errorf("Marshal(%v) = 0x%x, want 0x%x", v, data, wantData)
}
}
})
t.Run("24..31", func(t *testing.T) {
for i := 24; i <= 31; i++ {
v := SimpleValue(i)
if data, err := Marshal(v); err == nil {
t.Errorf("Marshal(%v) didn't return an error", data)
} else if _, ok := err.(*UnsupportedValueError); !ok {
t.Errorf("Marshal(%v) returned wrong error type %T, want (*UnsupportedValueError)", data, err)
}
}
})
t.Run("32..255", func(t *testing.T) {
for i := 32; i <= 255; i++ {
wantData := []byte{byte(cborTypePrimitives) | byte(24), byte(i)}
v := SimpleValue(i)
data, err := Marshal(v)
if err != nil {
t.Errorf("Marshal(%v) returned error %v", v, err)
continue
}
if !bytes.Equal(data, wantData) {
t.Errorf("Marshal(%v) = 0x%x, want 0x%x", v, data, wantData)
}
}
})
}

181
stream.go
View File

@ -4,48 +4,60 @@
package cbor
import (
"bytes"
"errors"
"io"
"reflect"
)
// Decoder reads and decodes CBOR values from an input stream.
// Decoder reads and decodes CBOR values from io.Reader.
type Decoder struct {
r io.Reader
d decoder
buf []byte
d decodeState
off int // start of unread data in buf
off int // next read offset in buf
bytesRead int
}
// NewDecoder returns a new decoder that reads from r using the default decoding options.
// NewDecoder returns a new decoder that reads and decodes from r using
// the default decoding options.
func NewDecoder(r io.Reader) *Decoder {
return defaultDecMode.NewDecoder(r)
}
// Decode reads the next CBOR-encoded value from its input and stores it in
// the value pointed to by v.
// Decode reads CBOR value and decodes it into the value pointed to by v.
func (dec *Decoder) Decode(v interface{}) error {
if len(dec.buf) == dec.off {
if n, err := dec.read(); n == 0 {
return err
}
_, err := dec.readNext()
if err != nil {
// Return validation error or read error.
return err
}
dec.d.reset(dec.buf[dec.off:])
err := dec.d.value(v)
err = dec.d.value(v)
// Increment dec.off even if decoding err is not nil because
// dec.d.off points to the next CBOR data item if current
// CBOR data item is valid but failed to be decoded into v.
// This allows next CBOR data item to be decoded in next
// call to this function.
dec.off += dec.d.off
dec.bytesRead += dec.d.off
return err
}
// Skip skips to the next CBOR data item (if there is any),
// otherwise it returns error such as io.EOF, io.UnexpectedEOF, etc.
func (dec *Decoder) Skip() error {
n, err := dec.readNext()
if err != nil {
if err != io.ErrUnexpectedEOF {
return err
}
// Need to read more data.
if n, e := dec.read(); n == 0 {
return e
}
return dec.Decode(v)
// Return validation error or read error.
return err
}
dec.off += n
dec.bytesRead += n
return nil
}
@ -54,20 +66,83 @@ func (dec *Decoder) NumBytesRead() int {
return dec.bytesRead
}
func (dec *Decoder) read() (int, error) {
// Copy unread data over read data and reset off to 0.
if dec.off > 0 {
n := copy(dec.buf, dec.buf[dec.off:])
dec.buf = dec.buf[:n]
dec.off = 0
}
// Buffered returns a reader for data remaining in Decoder's buffer.
// Returned reader is valid until the next call to Decode or Skip.
func (dec *Decoder) Buffered() io.Reader {
return bytes.NewReader(dec.buf[dec.off:])
}
// readNext() reads next CBOR data item from Reader to buffer.
// It returns the size of next CBOR data item.
// It also returns validation error or read error if any.
func (dec *Decoder) readNext() (int, error) {
var readErr error
var validErr error
for {
// Process any unread data in dec.buf.
if dec.off < len(dec.buf) {
dec.d.reset(dec.buf[dec.off:])
off := dec.off // Save offset before data validation
validErr = dec.d.wellformed(true, false)
dec.off = off // Restore offset
if validErr == nil {
return dec.d.off, nil
}
if validErr != io.ErrUnexpectedEOF {
return 0, validErr
}
// Process last read error on io.ErrUnexpectedEOF.
if readErr != nil {
if readErr == io.EOF {
// current CBOR data item is incomplete.
return 0, io.ErrUnexpectedEOF
}
return 0, readErr
}
}
// More data is needed and there was no read error.
var n int
for n == 0 {
n, readErr = dec.read()
if n == 0 && readErr != nil {
// No more data can be read and read error is encountered.
// At this point, validErr is either nil or io.ErrUnexpectedEOF.
if readErr == io.EOF {
if validErr == io.ErrUnexpectedEOF {
// current CBOR data item is incomplete.
return 0, io.ErrUnexpectedEOF
}
}
return 0, readErr
}
}
// At this point, dec.buf contains new data from last read (n > 0).
}
}
// read() reads data from Reader to buffer.
// It returns number of bytes read and any read error encountered.
// Postconditions:
// - dec.buf contains previously unread data and new data.
// - dec.off is 0.
func (dec *Decoder) read() (int, error) {
// Grow buf if needed.
const minRead = 512
if cap(dec.buf)-len(dec.buf) < minRead {
newBuf := make([]byte, len(dec.buf), 2*cap(dec.buf)+minRead)
copy(newBuf, dec.buf)
dec.buf = newBuf
if cap(dec.buf)-len(dec.buf)+dec.off < minRead {
oldUnreadBuf := dec.buf[dec.off:]
dec.buf = make([]byte, len(dec.buf)-dec.off, 2*cap(dec.buf)+minRead)
dec.overwriteBuf(oldUnreadBuf)
}
// Copy unread data over read data and reset off to 0.
if dec.off > 0 {
dec.overwriteBuf(dec.buf[dec.off:])
}
// Read from reader and reslice buf.
@ -76,11 +151,16 @@ func (dec *Decoder) read() (int, error) {
return n, err
}
// Encoder writes CBOR values to an output stream.
func (dec *Decoder) overwriteBuf(newBuf []byte) {
n := copy(dec.buf, newBuf)
dec.buf = dec.buf[:n]
dec.off = 0
}
// Encoder writes CBOR values to io.Writer.
type Encoder struct {
w io.Writer
em *encMode
e *encodeState
indefTypes []cborType
}
@ -89,7 +169,7 @@ func NewEncoder(w io.Writer) *Encoder {
return defaultEncMode.NewEncoder(w)
}
// Encode writes the CBOR encoding of v to the stream.
// Encode writes the CBOR encoding of v.
func (enc *Encoder) Encode(v interface{}) error {
if len(enc.indefTypes) > 0 && v != nil {
indefType := enc.indefTypes[len(enc.indefTypes)-1]
@ -107,24 +187,27 @@ func (enc *Encoder) Encode(v interface{}) error {
}
}
err := encode(enc.e, enc.em, reflect.ValueOf(v))
buf := getEncodeBuffer()
err := encode(buf, enc.em, reflect.ValueOf(v))
if err == nil {
_, err = enc.e.WriteTo(enc.w)
_, err = enc.w.Write(buf.Bytes())
}
enc.e.Reset()
putEncodeBuffer(buf)
return err
}
// StartIndefiniteByteString starts byte string encoding of indefinite length.
// Subsequent calls of (*Encoder).Encode() encodes definite length byte strings
// ("chunks") as one continguous string until EndIndefinite is called.
// ("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.
// Subsequent calls of (*Encoder).Encode() encodes definite length text strings
// ("chunks") as one continguous string until EndIndefinite is called.
// ("chunks") as one contiguous string until EndIndefinite is called.
func (enc *Encoder) StartIndefiniteTextString() error {
return enc.startIndefinite(cborTypeTextString)
}
@ -148,7 +231,7 @@ func (enc *Encoder) EndIndefinite() error {
if len(enc.indefTypes) == 0 {
return errors.New("cbor: cannot encode \"break\" code outside indefinite length values")
}
_, err := enc.w.Write([]byte{0xff})
_, err := enc.w.Write([]byte{cborBreakFlag})
if err == nil {
enc.indefTypes = enc.indefTypes[:len(enc.indefTypes)-1]
}
@ -156,15 +239,15 @@ func (enc *Encoder) EndIndefinite() error {
}
var cborIndefHeader = map[cborType][]byte{
cborTypeByteString: {0x5f},
cborTypeTextString: {0x7f},
cborTypeArray: {0x9f},
cborTypeMap: {0xbf},
cborTypeByteString: {cborByteStringWithIndefiniteLengthHead},
cborTypeTextString: {cborTextStringWithIndefiniteLengthHead},
cborTypeArray: {cborArrayWithIndefiniteLengthHead},
cborTypeMap: {cborMapWithIndefiniteLengthHead},
}
func (enc *Encoder) startIndefinite(typ cborType) error {
if enc.em.disableIndefiniteLength {
return errors.New("cbor: indefinite-length items are not allowed")
if enc.em.indefLength == IndefLengthForbidden {
return &IndefiniteLengthError{typ}
}
_, err := enc.w.Write(cborIndefHeader[typ])
if err == nil {
@ -173,12 +256,10 @@ func (enc *Encoder) startIndefinite(typ cborType) error {
return err
}
// RawMessage is a raw encoded CBOR value. It implements Marshaler and
// Unmarshaler interfaces and can be used to delay CBOR decoding or
// precompute a CBOR encoding.
// RawMessage is a raw encoded CBOR value.
type RawMessage []byte
// MarshalCBOR returns m as the CBOR encoding of m.
// MarshalCBOR returns m or CBOR nil if m is nil.
func (m RawMessage) MarshalCBOR() ([]byte, error) {
if len(m) == 0 {
return cborNil, nil
@ -186,7 +267,7 @@ func (m RawMessage) MarshalCBOR() ([]byte, error) {
return m, nil
}
// UnmarshalCBOR sets *m to a copy of data.
// UnmarshalCBOR creates a copy of data and saves to *m.
func (m *RawMessage) UnmarshalCBOR(data []byte) error {
if m == nil {
return errors.New("cbor.RawMessage: UnmarshalCBOR on nil pointer")

View File

@ -5,8 +5,11 @@ package cbor
import (
"bytes"
"errors"
"fmt"
"io"
"reflect"
"strings"
"testing"
"time"
)
@ -15,38 +18,54 @@ func TestDecoder(t *testing.T) {
var buf bytes.Buffer
for i := 0; i < 5; i++ {
for _, tc := range unmarshalTests {
buf.Write(tc.cborData)
buf.Write(tc.data)
}
}
decoder := NewDecoder(&buf)
bytesRead := 0
for i := 0; i < 5; i++ {
for _, tc := range unmarshalTests {
var v interface{}
if err := decoder.Decode(&v); err != nil {
t.Fatalf("Decode() returned error %v", err)
}
if tm, ok := tc.emptyInterfaceValue.(time.Time); ok {
if vt, ok := v.(time.Time); !ok || !tm.Equal(vt) {
t.Errorf("Decode() = %v (%T), want %v (%T)", v, v, tc.emptyInterfaceValue, tc.emptyInterfaceValue)
testCases := []struct {
name string
reader io.Reader
}{
{"bytes.Buffer", &buf},
{"1 byte reader", newNBytesReader(buf.Bytes(), 1)},
{"toggled reader", newToggledReader(buf.Bytes(), 1)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
decoder := NewDecoder(tc.reader)
bytesRead := 0
for i := 0; i < 5; i++ {
for _, tc := range unmarshalTests {
var v interface{}
if err := decoder.Decode(&v); err != nil {
t.Fatalf("Decode() returned error %v", err)
}
if tm, ok := tc.wantInterfaceValue.(time.Time); ok {
if vt, ok := v.(time.Time); !ok || !tm.Equal(vt) {
t.Errorf("Decode() = %v (%T), want %v (%T)", v, v, tc.wantInterfaceValue, tc.wantInterfaceValue)
}
} else if !reflect.DeepEqual(v, tc.wantInterfaceValue) {
t.Errorf("Decode() = %v (%T), want %v (%T)", v, v, tc.wantInterfaceValue, tc.wantInterfaceValue)
}
bytesRead += len(tc.data)
if decoder.NumBytesRead() != bytesRead {
t.Errorf("NumBytesRead() = %v, want %v", decoder.NumBytesRead(), bytesRead)
}
}
} else if !reflect.DeepEqual(v, tc.emptyInterfaceValue) {
t.Errorf("Decode() = %v (%T), want %v (%T)", v, v, tc.emptyInterfaceValue, tc.emptyInterfaceValue)
}
bytesRead += len(tc.cborData)
if decoder.NumBytesRead() != bytesRead {
t.Errorf("NumBytesRead() = %v, want %v", decoder.NumBytesRead(), bytesRead)
for i := 0; i < 2; i++ {
// no more data
var v interface{}
err := decoder.Decode(&v)
if v != nil {
t.Errorf("Decode() = %v (%T), want nil (no more data)", v, v)
}
if err != io.EOF {
t.Errorf("Decode() returned error %v, want io.EOF (no more data)", err)
}
}
}
}
// no more data
var v interface{}
err := decoder.Decode(&v)
if v != nil {
t.Errorf("Decode() = %v (%T), want nil (no more data)", v, v)
}
if err != io.EOF {
t.Errorf("Decode() returned error %v, want io.EOF (no more data)", err)
})
}
}
@ -55,47 +74,237 @@ func TestDecoderUnmarshalTypeError(t *testing.T) {
for i := 0; i < 5; i++ {
for _, tc := range unmarshalTests {
for j := 0; j < len(tc.wrongTypes)*2; j++ {
buf.Write(tc.cborData)
buf.Write(tc.data)
}
}
}
decoder := NewDecoder(&buf)
bytesRead := 0
for i := 0; i < 5; i++ {
for _, tc := range unmarshalTests {
for _, typ := range tc.wrongTypes {
v := reflect.New(typ)
if err := decoder.Decode(v.Interface()); err == nil {
t.Errorf("Decode(0x%x) didn't return an error, want UnmarshalTypeError", tc.cborData)
} else if _, ok := err.(*UnmarshalTypeError); !ok {
t.Errorf("Decode(0x%x) returned wrong error type %T, want UnmarshalTypeError", tc.cborData, err)
}
bytesRead += len(tc.cborData)
if decoder.NumBytesRead() != bytesRead {
t.Errorf("NumBytesRead() = %v, want %v", decoder.NumBytesRead(), bytesRead)
}
var vi interface{}
if err := decoder.Decode(&vi); err != nil {
t.Errorf("Decode() returned error %v", err)
}
if tm, ok := tc.emptyInterfaceValue.(time.Time); ok {
if vt, ok := vi.(time.Time); !ok || !tm.Equal(vt) {
t.Errorf("Decode() = %v (%T), want %v (%T)", vi, vi, tc.emptyInterfaceValue, tc.emptyInterfaceValue)
testCases := []struct {
name string
reader io.Reader
}{
{"bytes.Buffer", &buf},
{"1 byte reader", newNBytesReader(buf.Bytes(), 1)},
{"toggled reader", newToggledReader(buf.Bytes(), 1)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
decoder := NewDecoder(tc.reader)
bytesRead := 0
for i := 0; i < 5; i++ {
for _, tc := range unmarshalTests {
for _, typ := range tc.wrongTypes {
v := reflect.New(typ)
if err := decoder.Decode(v.Interface()); err == nil {
t.Errorf("Decode(0x%x) didn't return an error, want UnmarshalTypeError", tc.data)
} else if _, ok := err.(*UnmarshalTypeError); !ok {
t.Errorf("Decode(0x%x) returned wrong error type %T, want UnmarshalTypeError", tc.data, err)
}
bytesRead += len(tc.data)
if decoder.NumBytesRead() != bytesRead {
t.Errorf("NumBytesRead() = %v, want %v", decoder.NumBytesRead(), bytesRead)
}
var vi interface{}
if err := decoder.Decode(&vi); err != nil {
t.Errorf("Decode() returned error %v", err)
}
if tm, ok := tc.wantInterfaceValue.(time.Time); ok {
if vt, ok := vi.(time.Time); !ok || !tm.Equal(vt) {
t.Errorf("Decode() = %v (%T), want %v (%T)", vi, vi, tc.wantInterfaceValue, tc.wantInterfaceValue)
}
} else if !reflect.DeepEqual(vi, tc.wantInterfaceValue) {
t.Errorf("Decode() = %v (%T), want %v (%T)", vi, vi, tc.wantInterfaceValue, tc.wantInterfaceValue)
}
bytesRead += len(tc.data)
if decoder.NumBytesRead() != bytesRead {
t.Errorf("NumBytesRead() = %v, want %v", decoder.NumBytesRead(), bytesRead)
}
}
} else if !reflect.DeepEqual(vi, tc.emptyInterfaceValue) {
t.Errorf("Decode() = %v (%T), want %v (%T)", vi, vi, tc.emptyInterfaceValue, tc.emptyInterfaceValue)
}
bytesRead += len(tc.cborData)
}
for i := 0; i < 2; i++ {
// no more data
var v interface{}
err := decoder.Decode(&v)
if v != nil {
t.Errorf("Decode() = %v (%T), want nil (no more data)", v, v)
}
if err != io.EOF {
t.Errorf("Decode() returned error %v, want io.EOF (no more data)", err)
}
}
})
}
}
func TestDecoderUnexpectedEOFError(t *testing.T) {
var buf bytes.Buffer
for _, tc := range unmarshalTests {
buf.Write(tc.data)
}
buf.Truncate(buf.Len() - 1)
testCases := []struct {
name string
reader io.Reader
}{
{"bytes.Buffer", &buf},
{"1 byte reader", newNBytesReader(buf.Bytes(), 1)},
{"toggled reader", newToggledReader(buf.Bytes(), 1)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
decoder := NewDecoder(tc.reader)
bytesRead := 0
for i := 0; i < len(unmarshalTests)-1; i++ {
tc := unmarshalTests[i]
var v interface{}
if err := decoder.Decode(&v); err != nil {
t.Fatalf("Decode() returned error %v", err)
}
if tm, ok := tc.wantInterfaceValue.(time.Time); ok {
if vt, ok := v.(time.Time); !ok || !tm.Equal(vt) {
t.Errorf("Decode() = %v (%T), want %v (%T)", v, v, tc.wantInterfaceValue, tc.wantInterfaceValue)
}
} else if !reflect.DeepEqual(v, tc.wantInterfaceValue) {
t.Errorf("Decode() = %v (%T), want %v (%T)", v, v, tc.wantInterfaceValue, tc.wantInterfaceValue)
}
bytesRead += len(tc.data)
if decoder.NumBytesRead() != bytesRead {
t.Errorf("NumBytesRead() = %v, want %v", decoder.NumBytesRead(), bytesRead)
}
}
}
for i := 0; i < 2; i++ {
// truncated data
var v interface{}
err := decoder.Decode(&v)
if v != nil {
t.Errorf("Decode() = %v (%T), want nil (no more data)", v, v)
}
if err != io.ErrUnexpectedEOF {
t.Errorf("Decode() returned error %v, want io.UnexpectedEOF (truncated data)", err)
}
}
})
}
// no more data
}
func TestDecoderReadError(t *testing.T) {
var buf bytes.Buffer
for _, tc := range unmarshalTests {
buf.Write(tc.data)
}
buf.Truncate(buf.Len() - 1)
readerErr := errors.New("reader error")
testCases := []struct {
name string
reader io.Reader
}{
{"byte reader", newNBytesReaderWithError(buf.Bytes(), 512, readerErr)},
{"1 byte reader", newNBytesReaderWithError(buf.Bytes(), 1, readerErr)},
{"toggled reader", newToggledReaderWithError(buf.Bytes(), 1, readerErr)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
decoder := NewDecoder(tc.reader)
bytesRead := 0
for i := 0; i < len(unmarshalTests)-1; i++ {
tc := unmarshalTests[i]
var v interface{}
if err := decoder.Decode(&v); err != nil {
t.Fatalf("Decode() returned error %v", err)
}
if tm, ok := tc.wantInterfaceValue.(time.Time); ok {
if vt, ok := v.(time.Time); !ok || !tm.Equal(vt) {
t.Errorf("Decode() = %v (%T), want %v (%T)", v, v, tc.wantInterfaceValue, tc.wantInterfaceValue)
}
} else if !reflect.DeepEqual(v, tc.wantInterfaceValue) {
t.Errorf("Decode() = %v (%T), want %v (%T)", v, v, tc.wantInterfaceValue, tc.wantInterfaceValue)
}
bytesRead += len(tc.data)
if decoder.NumBytesRead() != bytesRead {
t.Errorf("NumBytesRead() = %v, want %v", decoder.NumBytesRead(), bytesRead)
}
}
for i := 0; i < 2; i++ {
// truncated data because Reader returned error
var v interface{}
err := decoder.Decode(&v)
if v != nil {
t.Errorf("Decode() = %v (%T), want nil (no more data)", v, v)
}
if err != readerErr {
t.Errorf("Decode() returned error %v, want reader error", err)
}
}
})
}
}
func TestDecoderNoData(t *testing.T) {
readerErr := errors.New("reader error")
testCases := []struct {
name string
reader io.Reader
wantErr error
}{
{"byte.Buffer", new(bytes.Buffer), io.EOF},
{"1 byte reader", newNBytesReaderWithError(nil, 0, readerErr), readerErr},
{"toggled reader", newToggledReaderWithError(nil, 0, readerErr), readerErr},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
decoder := NewDecoder(tc.reader)
for i := 0; i < 2; i++ {
var v interface{}
err := decoder.Decode(&v)
if v != nil {
t.Errorf("Decode() = %v (%T), want nil", v, v)
}
if err != tc.wantErr {
t.Errorf("Decode() returned error %v, want error %v", err, tc.wantErr)
}
}
})
}
}
func TestDecoderRecoverableReadError(t *testing.T) {
data := hexDecode("83010203") // [1,2,3]
wantValue := []interface{}{uint64(1), uint64(2), uint64(3)}
recoverableReaderErr := errors.New("recoverable reader error")
decoder := NewDecoder(newRecoverableReader(data, 1, recoverableReaderErr))
var v interface{}
err := decoder.Decode(&v)
if err != recoverableReaderErr {
t.Fatalf("Decode() returned error %v, want error %v", err, recoverableReaderErr)
}
err = decoder.Decode(&v)
if err != nil {
t.Fatalf("Decode() returned error %v", err)
}
if !reflect.DeepEqual(v, wantValue) {
t.Errorf("Decode() = %v (%T), want %v (%T)", v, v, wantValue, wantValue)
}
if decoder.NumBytesRead() != len(data) {
t.Errorf("NumBytesRead() = %v, want %v", decoder.NumBytesRead(), len(data))
}
// no more data
v = interface{}(nil)
err = decoder.Decode(&v)
if v != nil {
t.Errorf("Decode() = %v (%T), want nil (no more data)", v, v)
}
@ -104,6 +313,247 @@ func TestDecoderUnmarshalTypeError(t *testing.T) {
}
}
func TestDecoderInvalidData(t *testing.T) {
data := []byte{0x01, 0x3e}
decoder := NewDecoder(bytes.NewReader(data))
var v1 interface{}
err := decoder.Decode(&v1)
if err != nil {
t.Errorf("Decode() returned error %v when decoding valid data item", err)
}
var v2 interface{}
err = decoder.Decode(&v2)
if err == nil {
t.Errorf("Decode() didn't return error when decoding invalid data item")
} else if !strings.Contains(err.Error(), "cbor: invalid additional information") {
t.Errorf("Decode() error %q, want \"cbor: invalid additional information\"", err)
}
}
func TestDecoderSkip(t *testing.T) {
var buf bytes.Buffer
for i := 0; i < 5; i++ {
for _, tc := range unmarshalTests {
buf.Write(tc.data)
}
}
testCases := []struct {
name string
reader io.Reader
}{
{"bytes.Buffer", &buf},
{"1 byte reader", newNBytesReader(buf.Bytes(), 1)},
{"toggled reader", newToggledReader(buf.Bytes(), 1)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
decoder := NewDecoder(tc.reader)
bytesRead := 0
for i := 0; i < 5; i++ {
for _, tc := range unmarshalTests {
if err := decoder.Skip(); err != nil {
t.Fatalf("Skip() returned error %v", err)
}
bytesRead += len(tc.data)
if decoder.NumBytesRead() != bytesRead {
t.Errorf("NumBytesRead() = %v, want %v", decoder.NumBytesRead(), bytesRead)
}
}
}
for i := 0; i < 2; i++ {
// no more data
err := decoder.Skip()
if err != io.EOF {
t.Errorf("Skip() returned error %v, want io.EOF (no more data)", err)
}
}
})
}
}
func TestDecoderSkipInvalidDataError(t *testing.T) {
var buf bytes.Buffer
for _, tc := range unmarshalTests {
buf.Write(tc.data)
}
buf.WriteByte(0x3e)
testCases := []struct {
name string
reader io.Reader
}{
{"bytes.Buffer", &buf},
{"1 byte reader", newNBytesReader(buf.Bytes(), 1)},
{"toggled reader", newToggledReader(buf.Bytes(), 1)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
decoder := NewDecoder(tc.reader)
bytesRead := 0
for i := 0; i < len(unmarshalTests); i++ {
tc := unmarshalTests[i]
if err := decoder.Skip(); err != nil {
t.Fatalf("Skip() returned error %v", err)
}
bytesRead += len(tc.data)
if decoder.NumBytesRead() != bytesRead {
t.Errorf("NumBytesRead() = %v, want %v", decoder.NumBytesRead(), bytesRead)
}
}
for i := 0; i < 2; i++ {
// last data item is invalid
err := decoder.Skip()
if err == nil {
t.Fatalf("Skip() didn't return error")
} else if !strings.Contains(err.Error(), "cbor: invalid additional information") {
t.Errorf("Skip() error %q, want \"cbor: invalid additional information\"", err)
}
}
})
}
}
func TestDecoderSkipUnexpectedEOFError(t *testing.T) {
var buf bytes.Buffer
for _, tc := range unmarshalTests {
buf.Write(tc.data)
}
buf.Truncate(buf.Len() - 1)
testCases := []struct {
name string
reader io.Reader
}{
{"bytes.Buffer", &buf},
{"1 byte reader", newNBytesReader(buf.Bytes(), 1)},
{"toggled reader", newToggledReader(buf.Bytes(), 1)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
decoder := NewDecoder(tc.reader)
bytesRead := 0
for i := 0; i < len(unmarshalTests)-1; i++ {
tc := unmarshalTests[i]
if err := decoder.Skip(); err != nil {
t.Fatalf("Skip() returned error %v", err)
}
bytesRead += len(tc.data)
if decoder.NumBytesRead() != bytesRead {
t.Errorf("NumBytesRead() = %v, want %v", decoder.NumBytesRead(), bytesRead)
}
}
for i := 0; i < 2; i++ {
// last data item is invalid
err := decoder.Skip()
if err != io.ErrUnexpectedEOF {
t.Errorf("Skip() returned error %v, want io.ErrUnexpectedEOF (truncated data)", err)
}
}
})
}
}
func TestDecoderSkipReadError(t *testing.T) {
var buf bytes.Buffer
for _, tc := range unmarshalTests {
buf.Write(tc.data)
}
buf.Truncate(buf.Len() - 1)
readerErr := errors.New("reader error")
testCases := []struct {
name string
reader io.Reader
}{
{"byte reader", newNBytesReaderWithError(buf.Bytes(), 512, readerErr)},
{"1 byte reader", newNBytesReaderWithError(buf.Bytes(), 1, readerErr)},
{"toggled reader", newToggledReaderWithError(buf.Bytes(), 1, readerErr)},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
decoder := NewDecoder(tc.reader)
bytesRead := 0
for i := 0; i < len(unmarshalTests)-1; i++ {
tc := unmarshalTests[i]
if err := decoder.Skip(); err != nil {
t.Fatalf("Skip() returned error %v", err)
}
bytesRead += len(tc.data)
if decoder.NumBytesRead() != bytesRead {
t.Errorf("NumBytesRead() = %v, want %v", decoder.NumBytesRead(), bytesRead)
}
}
for i := 0; i < 2; i++ {
// truncated data because Reader returned error
err := decoder.Skip()
if err != readerErr {
t.Errorf("Skip() returned error %v, want reader error", err)
}
}
})
}
}
func TestDecoderSkipNoData(t *testing.T) {
readerErr := errors.New("reader error")
testCases := []struct {
name string
reader io.Reader
wantErr error
}{
{"byte.Buffer", new(bytes.Buffer), io.EOF},
{"1 byte reader", newNBytesReaderWithError(nil, 0, readerErr), readerErr},
{"toggled reader", newToggledReaderWithError(nil, 0, readerErr), readerErr},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
decoder := NewDecoder(tc.reader)
for i := 0; i < 2; i++ {
err := decoder.Skip()
if err != tc.wantErr {
t.Errorf("Decode() returned error %v, want error %v", err, tc.wantErr)
}
}
})
}
}
func TestDecoderSkipRecoverableReadError(t *testing.T) {
data := hexDecode("83010203") // [1,2,3]
recoverableReaderErr := errors.New("recoverable reader error")
decoder := NewDecoder(newRecoverableReader(data, 1, recoverableReaderErr))
err := decoder.Skip()
if err != recoverableReaderErr {
t.Fatalf("Skip() returned error %v, want error %v", err, recoverableReaderErr)
}
err = decoder.Skip()
if err != nil {
t.Fatalf("Skip() returned error %v", err)
}
if decoder.NumBytesRead() != len(data) {
t.Errorf("NumBytesRead() = %v, want %v", decoder.NumBytesRead(), len(data))
}
// no more data
err = decoder.Skip()
if err != io.EOF {
t.Errorf("Skip() returned error %v, want io.EOF (no more data)", err)
}
}
func TestDecoderStructTag(t *testing.T) {
type strc struct {
A string `json:"x" cbor:"a"`
@ -115,10 +565,10 @@ func TestDecoderStructTag(t *testing.T) {
B: "B",
C: "C",
}
cborData := hexDecode("a36161614161626142617a6143") // {"a":"A", "b":"B", "z":"C"}
data := hexDecode("a36161614161626142617a6143") // {"a":"A", "b":"B", "z":"C"}
var v strc
dec := NewDecoder(bytes.NewReader(cborData))
dec := NewDecoder(bytes.NewReader(data))
if err := dec.Decode(&v); err != nil {
t.Errorf("Decode() returned error %v", err)
}
@ -127,6 +577,92 @@ func TestDecoderStructTag(t *testing.T) {
}
}
func TestDecoderBuffered(t *testing.T) {
testCases := []struct {
name string
data []byte
buffered []byte
decodeErr error
}{
{
name: "empty",
data: []byte{},
buffered: []byte{},
decodeErr: io.EOF,
},
{
name: "malformed CBOR data item",
data: []byte{0xc0},
buffered: []byte{0xc0},
decodeErr: io.ErrUnexpectedEOF,
},
{
name: "1 CBOR data item",
data: []byte{0xc2, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
buffered: []byte{},
},
{
name: "2 CBOR data items",
data: []byte{
// First CBOR data item
0xc2, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Second CBOR data item
0xc3, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
},
buffered: []byte{
// Second CBOR data item
0xc3, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
},
},
{
name: "1 CBOR data item followed by non-CBOR data",
data: []byte{
// CBOR data item
0xc2, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Extraneous non-CBOR data ("abc")
0x61, 0x62, 0x63,
},
buffered: []byte{
// non-CBOR data ("abc")
0x61, 0x62, 0x63,
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
r := bytes.NewReader(tc.data)
decoder := NewDecoder(r)
// Decoder's buffer has no data yet.
br := decoder.Buffered()
buffered, err := io.ReadAll(br)
if err != nil {
t.Errorf("failed to read from reader returned by Buffered(): %v", err)
}
if len(buffered) > 0 {
t.Errorf("Buffered() = 0x%x (%d bytes), want 0 bytes", buffered, len(buffered))
}
var v interface{}
err = decoder.Decode(&v)
if err != tc.decodeErr {
t.Errorf("Decode() returned error %v, want %v", err, tc.decodeErr)
}
br = decoder.Buffered()
buffered, err = io.ReadAll(br)
if err != nil {
t.Errorf("failed to read from reader returned by Buffered(): %v", err)
}
if !bytes.Equal(tc.buffered, buffered) {
t.Errorf("Buffered() = 0x%x (%d bytes), want 0x%x (%d bytes)", buffered, len(buffered), tc.buffered, len(tc.buffered))
}
})
}
}
func TestEncoder(t *testing.T) {
var want bytes.Buffer
var w bytes.Buffer
@ -137,7 +673,7 @@ func TestEncoder(t *testing.T) {
encoder := em.NewEncoder(&w)
for _, tc := range marshalTests {
for _, value := range tc.values {
want.Write(tc.cborData)
want.Write(tc.wantData)
if err := encoder.Encode(value); err != nil {
t.Fatalf("Encode() returned error %v", err)
@ -156,19 +692,20 @@ 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(int) int"},
{"function cannot be marshaled", func(i int) int { return i * i }, "cbor: unsupported type: func"},
{"complex cannot be marshaled", complex(100, 8), "cbor: unsupported type: complex128"},
}
var w bytes.Buffer
encoder := NewEncoder(&w)
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
err := encoder.Encode(&tc.value)
v := tc.value
err := encoder.Encode(&v)
if err == nil {
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 err.Error() != tc.wantErrorMsg {
} else if !strings.HasPrefix(err.Error(), tc.wantErrorMsg) {
t.Errorf("Encode(%v) error %q, want %q", tc.value, err.Error(), tc.wantErrorMsg)
}
})
@ -367,25 +904,39 @@ func TestRawMessage(t *testing.T) {
B *RawMessage `cbor:"b"`
C *RawMessage `cbor:"c"`
}
cborData := hexDecode("a361610161628202036163f6") // {"a": 1, "b": [2, 3], "c": nil},
data := hexDecode("a361610161628202036163f6") // {"a": 1, "b": [2, 3], "c": nil},
r := RawMessage(hexDecode("820203"))
want := strc{
A: RawMessage([]byte{0x01}),
B: &r,
}
var v strc
if err := Unmarshal(cborData, &v); err != nil {
t.Fatalf("Unmarshal(0x%x) returned error %v", cborData, err)
if err := Unmarshal(data, &v); err != nil {
t.Fatalf("Unmarshal(0x%x) returned error %v", data, err)
}
if !reflect.DeepEqual(v, want) {
t.Errorf("Unmarshal(0x%x) returned v %v, want %v", cborData, v, want)
t.Errorf("Unmarshal(0x%x) returned v %v, want %v", data, v, want)
}
b, err := Marshal(v)
if err != nil {
t.Fatalf("Marshal(%+v) returned error %v", v, err)
}
if !bytes.Equal(b, cborData) {
t.Errorf("Marshal(%+v) = 0x%x, want 0x%x", v, b, cborData)
if !bytes.Equal(b, data) {
t.Errorf("Marshal(%+v) = 0x%x, want 0x%x", v, b, data)
}
address := fmt.Sprintf("%p", *v.B)
if err := Unmarshal(v.A, v.B); err != nil {
t.Fatalf("Unmarshal(0x%x) returned error %v", v.A, err)
}
if address != fmt.Sprintf("%p", *v.B) {
t.Fatalf("Unmarshal RawMessage should reuse underlying array if it has sufficient capacity")
}
if err := Unmarshal(data, v.B); err != nil {
t.Fatalf("Unmarshal(0x%x) returned error %v", data, err)
}
if address == fmt.Sprintf("%p", *v.B) {
t.Fatalf("Unmarshal RawMessage should allocate a new underlying array if it does not have sufficient capacity")
}
}
@ -416,10 +967,115 @@ func TestEmptyRawMessage(t *testing.T) {
func TestNilRawMessageUnmarshalCBORError(t *testing.T) {
wantErrorMsg := "cbor.RawMessage: UnmarshalCBOR on nil pointer"
var r *RawMessage
cborData := hexDecode("01")
if err := r.UnmarshalCBOR(cborData); err == nil {
data := hexDecode("01")
if err := r.UnmarshalCBOR(data); err == nil {
t.Errorf("UnmarshalCBOR() didn't return error")
} else if err.Error() != wantErrorMsg {
t.Errorf("UnmarshalCBOR() returned error %q, want %q", err.Error(), wantErrorMsg)
}
}
// nBytesReader reads at most maxBytesPerRead into b. It also returns error at the last read.
type nBytesReader struct {
data []byte
maxBytesPerRead int
off int
err error
}
func newNBytesReader(data []byte, maxBytesPerRead int) *nBytesReader {
return &nBytesReader{
data: append([]byte{}, data...),
maxBytesPerRead: maxBytesPerRead,
err: io.EOF,
}
}
func newNBytesReaderWithError(data []byte, maxBytesPerRead int, err error) *nBytesReader {
return &nBytesReader{
data: append([]byte{}, data...),
maxBytesPerRead: maxBytesPerRead,
err: err,
}
}
func (r *nBytesReader) Read(b []byte) (int, error) {
var n int
if r.off < len(r.data) {
numOfBytesToRead := len(r.data) - r.off
if numOfBytesToRead > r.maxBytesPerRead {
numOfBytesToRead = r.maxBytesPerRead
}
n = copy(b, r.data[r.off:r.off+numOfBytesToRead])
r.off += n
}
if r.off == len(r.data) {
return n, r.err
}
return n, nil
}
// toggledReader returns (0, nil) for every other read to mimic non-blocking read for stream reader.
type toggledReader struct {
nBytesReader
toggle bool
}
func newToggledReader(data []byte, maxBytesPerRead int) *toggledReader {
return &toggledReader{
nBytesReader: nBytesReader{
data: append([]byte{}, data...),
maxBytesPerRead: maxBytesPerRead,
err: io.EOF,
},
toggle: true, // first read returns (0, nil)
}
}
func newToggledReaderWithError(data []byte, maxBytesPerRead int, err error) *toggledReader {
return &toggledReader{
nBytesReader: nBytesReader{
data: append([]byte{}, data...),
maxBytesPerRead: maxBytesPerRead,
err: err,
},
toggle: true, // first read returns (0, nil)
}
}
func (r *toggledReader) Read(b []byte) (int, error) {
defer func() {
r.toggle = !r.toggle
}()
if r.toggle {
return 0, nil
}
return r.nBytesReader.Read(b)
}
// recoverableReader returns a recoverable error at first read operation.
type recoverableReader struct {
nBytesReader
recoverableErr error
first bool
}
func newRecoverableReader(data []byte, maxBytesPerRead int, err error) *recoverableReader {
return &recoverableReader{
nBytesReader: nBytesReader{
data: append([]byte{}, data...),
maxBytesPerRead: maxBytesPerRead,
err: io.EOF,
},
recoverableErr: err,
first: true,
}
}
func (r *recoverableReader) Read(b []byte) (int, error) {
if r.first {
r.first = false
return 0, r.recoverableErr
}
return r.nBytesReader.Read(b)
}

View File

@ -10,16 +10,18 @@ import (
)
type field struct {
name string
nameAsInt int64 // used to decoder to match field name with CBOR int
cborName []byte
idx []int
typ reflect.Type
ef encodeFunc
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
keyAsInt bool // used to encode/decode field name as int
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
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
keyAsInt bool // used to encode/decode field name as int
}
type fields []*field
@ -38,20 +40,13 @@ func (x *indexFieldSorter) Swap(i, j int) {
}
func (x *indexFieldSorter) Less(i, j int) bool {
iIdx := x.fields[i].idx
jIdx := x.fields[j].idx
for k, d := range iIdx {
if k >= len(jIdx) {
// fields[j].idx is a subset of fields[i].idx.
return false
}
if d != jIdx[k] {
// fields[i].idx and fields[j].idx are different.
return d < jIdx[k]
iIdx, jIdx := x.fields[i].idx, x.fields[j].idx
for k := 0; k < len(iIdx) && k < len(jIdx); k++ {
if iIdx[k] != jIdx[k] {
return iIdx[k] < jIdx[k]
}
}
// fields[i].idx is either the same as, or a subset of fields[j].idx.
return true
return len(iIdx) <= len(jIdx)
}
// nameLevelAndTagFieldSorter sorts fields by field name, idx depth, and presence of tag.
@ -68,99 +63,54 @@ func (x *nameLevelAndTagFieldSorter) Swap(i, j int) {
}
func (x *nameLevelAndTagFieldSorter) Less(i, j int) bool {
if x.fields[i].name != x.fields[j].name {
return x.fields[i].name < x.fields[j].name
fi, fj := x.fields[i], x.fields[j]
if fi.name != fj.name {
return fi.name < fj.name
}
if len(x.fields[i].idx) != len(x.fields[j].idx) {
return len(x.fields[i].idx) < len(x.fields[j].idx)
if len(fi.idx) != len(fj.idx) {
return len(fi.idx) < len(fj.idx)
}
if x.fields[i].tagged != x.fields[j].tagged {
return x.fields[i].tagged
if fi.tagged != fj.tagged {
return fi.tagged
}
return i < j // Field i and j have the same name, depth, and tagged status. Nothing else matters.
}
// getFields returns a list of visible fields of struct type typ following Go
// visibility rules for struct fields.
func getFields(typ reflect.Type) (flds fields, structOptions string) {
// Inspired by typeFields() in stdlib's encoding/json/encode.go.
// getFields returns visible fields of struct type t following visibility rules for JSON encoding.
func getFields(t reflect.Type) (flds fields, structOptions string) {
// Get special field "_" tag options
if f, ok := t.FieldByName("_"); ok {
tag := f.Tag.Get("cbor")
if tag != "-" {
structOptions = tag
}
}
var current map[reflect.Type][][]int // key: struct type, value: field index of this struct type at the same level
next := map[reflect.Type][][]int{typ: nil}
visited := map[reflect.Type]bool{} // Inspected struct type at less nested levels.
// nTypes contains next level anonymous fields' types and indexes
// (there can be multiple fields of the same type at the same level)
flds, nTypes := appendFields(t, nil, nil, nil)
for len(next) > 0 {
current, next = next, map[reflect.Type][][]int{}
if len(nTypes) > 0 {
for structType, structIdx := range current {
if len(structIdx) > 1 {
continue // Fields of the same embedded struct type at the same level are ignored.
}
var cTypes map[reflect.Type][][]int // current level anonymous fields' types and indexes
vTypes := map[reflect.Type]bool{t: true} // visited field types at less nested levels
if visited[structType] {
continue
}
visited[structType] = true
for len(nTypes) > 0 {
cTypes, nTypes = nTypes, nil
var fieldIdx []int
if len(structIdx) > 0 {
fieldIdx = structIdx[0]
}
for i := 0; i < structType.NumField(); i++ {
f := structType.Field(i)
ft := f.Type
if ft.Kind() == reflect.Ptr {
ft = ft.Elem()
}
exportable := f.PkgPath == ""
if f.Anonymous {
if !exportable && ft.Kind() != reflect.Struct {
// Nonexportable anonymous fields of non-struct type are ignored.
continue
}
// Nonexportable anonymous field of struct type can contain exportable fields for serialization.
} else if !exportable {
// Get special field "_" struct options
if f.Name == "_" {
tag := f.Tag.Get("cbor")
if tag != "-" {
structOptions = tag
}
}
// Nonexportable fields are ignored.
for t, idx := range cTypes {
// If there are multiple anonymous fields of the same struct type at the same level, all are ignored.
if len(idx) > 1 {
continue
}
tag := f.Tag.Get("cbor")
if tag == "" {
tag = f.Tag.Get("json")
}
if tag == "-" {
// Anonymous field of the same type at deeper nested level is ignored.
if vTypes[t] {
continue
}
vTypes[t] = true
idx := make([]int, len(fieldIdx)+1)
copy(idx, fieldIdx)
idx[len(fieldIdx)] = i
tagged := len(tag) > 0
tagFieldName, omitempty, keyasint := getFieldNameAndOptionsFromTag(tag)
fieldName := tagFieldName
if tagFieldName == "" {
fieldName = f.Name
}
if !f.Anonymous || ft.Kind() != reflect.Struct || len(tagFieldName) > 0 {
flds = append(flds, &field{name: fieldName, idx: idx, typ: f.Type, tagged: tagged, omitEmpty: omitempty, keyAsInt: keyasint})
continue
}
// f is anonymous struct of type ft.
next[ft] = append(next[ft], idx)
flds, nTypes = appendFields(t, idx[0], flds, nTypes)
}
}
}
@ -168,43 +118,143 @@ func getFields(typ reflect.Type) (flds fields, structOptions string) {
sort.Sort(&nameLevelAndTagFieldSorter{flds})
// Keep visible fields.
visibleFields := flds[:0]
for i, j := 0, 0; i < len(flds); i = j {
j := 0 // index of next unique field
for i := 0; i < len(flds); {
name := flds[i].name
for j = i + 1; j < len(flds) && flds[j].name == name; j++ {
if i == len(flds)-1 || // last field
name != flds[i+1].name || // field i has unique field name
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++
}
if j-i == 1 || len(flds[i].idx) < len(flds[i+1].idx) || (flds[i].tagged && !flds[i+1].tagged) {
// Keep the field if the field name is unique, or if the first field
// is at a less nested level, or if the first field is tagged and
// the second field is not.
visibleFields = append(visibleFields, flds[i])
// Skip fields with the same field name.
for i++; i < len(flds) && name == flds[i].name; i++ { //nolint:revive
}
}
if j != len(flds) {
flds = flds[:j]
}
// Sort fields by field index
sort.Sort(&indexFieldSorter{flds})
return flds, structOptions
}
// appendFields appends type t's exportable fields to flds and anonymous struct fields to nTypes .
func appendFields(
t reflect.Type,
idx []int,
flds fields,
nTypes map[reflect.Type][][]int,
) (
_flds fields,
_nTypes map[reflect.Type][][]int,
) {
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
ft := f.Type
for ft.Kind() == reflect.Ptr {
ft = ft.Elem()
}
if !isFieldExportable(f, ft.Kind()) {
continue
}
tag := f.Tag.Get("cbor")
if tag == "" {
tag = f.Tag.Get("json")
}
if tag == "-" {
continue
}
tagged := tag != ""
// Parse field tag options
var tagFieldName string
var omitempty, keyasint bool
for j := 0; tag != ""; j++ {
var token string
idx := strings.IndexByte(tag, ',')
if idx == -1 {
token, tag = tag, ""
} else {
token, tag = tag[:idx], tag[idx+1:]
}
if j == 0 {
tagFieldName = token
} else {
switch token {
case "omitempty":
omitempty = true
case "keyasint":
keyasint = true
}
}
}
fieldName := tagFieldName
if tagFieldName == "" {
fieldName = f.Name
}
fIdx := make([]int, len(idx)+1)
copy(fIdx, idx)
fIdx[len(fIdx)-1] = i
if !f.Anonymous || ft.Kind() != reflect.Struct || tagFieldName != "" {
flds = append(flds, &field{
name: fieldName,
idx: fIdx,
typ: f.Type,
omitEmpty: omitempty,
keyAsInt: keyasint,
tagged: tagged})
} else {
if nTypes == nil {
nTypes = make(map[reflect.Type][][]int)
}
nTypes[ft] = append(nTypes[ft], fIdx)
}
}
sort.Sort(&indexFieldSorter{visibleFields})
return visibleFields, structOptions
return flds, nTypes
}
func getFieldNameAndOptionsFromTag(tag string) (name string, omitEmpty bool, keyAsInt bool) {
if tag == "" {
return
}
idx := strings.Index(tag, ",")
if idx == -1 {
return tag, false, false
}
if idx > 0 {
name = tag[:idx]
tag = tag[idx:]
}
s := ",omitempty"
if idx = strings.Index(tag, s); idx >= 0 && (len(tag) == idx+len(s) || tag[idx+len(s)] == ',') {
omitEmpty = true
}
s = ",keyasint"
if idx = strings.Index(tag, s); idx >= 0 && (len(tag) == idx+len(s) || tag[idx+len(s)] == ',') {
keyAsInt = true
}
return
// isFieldExportable returns true if f is an exportable (regular or anonymous) field or
// a nonexportable anonymous field of struct type.
// Nonexportable anonymous field of struct type can contain exportable fields.
func isFieldExportable(f reflect.StructField, fk reflect.Kind) bool { //nolint:gocritic // ignore hugeParam
exportable := f.PkgPath == ""
return exportable || (f.Anonymous && fk == reflect.Struct)
}
type embeddedFieldNullPtrFunc func(reflect.Value) (reflect.Value, error)
// getFieldValue returns field value of struct v by index. When encountering null pointer
// to anonymous (embedded) struct field, f is called with the last traversed field value.
func getFieldValue(v reflect.Value, idx []int, f embeddedFieldNullPtrFunc) (fv reflect.Value, err error) {
fv = v
for i, n := range idx {
fv = fv.Field(n)
if i < len(idx)-1 {
if fv.Kind() == reflect.Ptr && fv.Type().Elem().Kind() == reflect.Struct {
if fv.IsNil() {
// Null pointer to embedded struct field
fv, err = f(fv)
if err != nil || !fv.IsValid() {
return fv, err
}
}
fv = fv.Elem()
}
}
}
return fv, nil
}

118
tag.go
View File

@ -7,24 +7,14 @@ import (
"sync"
)
// Tag represents CBOR tag data, including tag number and unmarshaled tag content.
// Tag represents CBOR tag data, including tag number and unmarshaled tag content. Marshaling and
// unmarshaling of tag content is subject to any encode and decode options that would apply to
// enclosed data item if it were to appear outside of a tag.
type Tag struct {
Number uint64
Content interface{}
}
func (t Tag) contentKind() reflect.Kind {
c := t.Content
for {
t, ok := c.(Tag)
if !ok {
break
}
c = t.Content
}
return reflect.ValueOf(c).Kind()
}
// RawTag represents CBOR tag data, including tag number and raw tag content.
// RawTag implements Unmarshaler and Marshaler interfaces.
type RawTag struct {
@ -38,12 +28,17 @@ func (t *RawTag) UnmarshalCBOR(data []byte) error {
return errors.New("cbor.RawTag: UnmarshalCBOR on nil pointer")
}
d := decodeState{data: data, dm: defaultDecMode}
// Decoding CBOR null and undefined to cbor.RawTag is no-op.
if len(data) == 1 && (data[0] == 0xf6 || data[0] == 0xf7) {
return nil
}
d := decoder{data: data, dm: defaultDecMode}
// Unmarshal tag number.
typ, _, num := d.getHead()
if typ != cborTypeTag {
return &UnmarshalTypeError{Value: typ.String(), Type: typeRawTag}
return &UnmarshalTypeError{CBORType: typ.String(), GoType: typeRawTag.String()}
}
t.Number = num
@ -56,14 +51,27 @@ func (t *RawTag) UnmarshalCBOR(data []byte) error {
// MarshalCBOR returns CBOR encoding of t.
func (t RawTag) MarshalCBOR() ([]byte, error) {
e := getEncodeState()
if t.Number == 0 && len(t.Content) == 0 {
// Marshal uninitialized cbor.RawTag
b := make([]byte, len(cborNil))
copy(b, cborNil)
return b, nil
}
e := getEncodeBuffer()
encodeHead(e, byte(cborTypeTag), t.Number)
buf := make([]byte, len(e.Bytes())+len(t.Content))
n := copy(buf, e.Bytes())
copy(buf[n:], t.Content)
content := t.Content
if len(content) == 0 {
content = cborNil
}
putEncodeState(e)
buf := make([]byte, len(e.Bytes())+len(content))
n := copy(buf, e.Bytes())
copy(buf[n:], content)
putEncodeBuffer(e)
return buf, nil
}
@ -84,7 +92,7 @@ const (
)
func (dtm DecTagMode) valid() bool {
return dtm < maxDecTagMode
return dtm >= 0 && dtm < maxDecTagMode
}
// EncTagMode specifies how encoder handles tag number.
@ -101,7 +109,7 @@ const (
)
func (etm EncTagMode) valid() bool {
return etm < maxEncTagMode
return etm >= 0 && etm < maxEncTagMode
}
// TagOptions specifies how encoder and decoder handle tag number.
@ -123,7 +131,8 @@ type TagSet interface {
}
type tagProvider interface {
get(t reflect.Type) *tagItem
getTagItemFromType(t reflect.Type) *tagItem
getTypeFromTagNum(num []uint64) reflect.Type
}
type tagItem struct {
@ -133,6 +142,25 @@ type tagItem struct {
opts TagOptions
}
func (t *tagItem) equalTagNum(num []uint64) bool {
// Fast path to compare 1 tag number
if len(t.num) == 1 && len(num) == 1 && t.num[0] == num[0] {
return true
}
if len(t.num) != len(num) {
return false
}
for i := 0; i < len(t.num); i++ {
if t.num[i] != num[i] {
return false
}
}
return true
}
type (
tagSet map[reflect.Type]*tagItem
@ -142,10 +170,19 @@ type (
}
)
func (t tagSet) get(typ reflect.Type) *tagItem {
func (t tagSet) getTagItemFromType(typ reflect.Type) *tagItem {
return t[typ]
}
func (t tagSet) getTypeFromTagNum(num []uint64) reflect.Type {
for typ, tag := range t {
if tag.equalTagNum(num) {
return typ
}
}
return nil
}
// NewTagSet returns TagSet (safe for concurrency).
func NewTagSet() TagSet {
return &syncTagSet{t: make(map[reflect.Type]*tagItem)}
@ -165,8 +202,13 @@ func (t *syncTagSet) Add(opts TagOptions, contentType reflect.Type, num uint64,
}
t.Lock()
defer t.Unlock()
if _, ok := t.t[contentType]; ok {
return errors.New("cbor: content type " + contentType.String() + " already exists in TagSet")
for typ, ti := range t.t {
if typ == contentType {
return errors.New("cbor: content type " + contentType.String() + " already exists in TagSet")
}
if ti.equalTagNum(tag.num) {
return fmt.Errorf("cbor: tag number %v already exists in TagSet", tag.num)
}
}
t.t[contentType] = tag
return nil
@ -182,13 +224,20 @@ func (t *syncTagSet) Remove(contentType reflect.Type) {
t.Unlock()
}
func (t *syncTagSet) get(typ reflect.Type) *tagItem {
func (t *syncTagSet) getTagItemFromType(typ reflect.Type) *tagItem {
t.RLock()
ti := t.t[typ]
t.RUnlock()
return ti
}
func (t *syncTagSet) getTypeFromTagNum(num []uint64) reflect.Type {
t.RLock()
rt := t.t.getTypeFromTagNum(num)
t.RUnlock()
return rt
}
func newTagItem(opts TagOptions, contentType reflect.Type, num uint64, nestedNum ...uint64) (*tagItem, error) {
if opts.DecTag == DecTagIgnored && opts.EncTag == EncTagNone {
return nil, errors.New("cbor: cannot add tag with DecTagIgnored and EncTagNone options to TagSet")
@ -199,6 +248,9 @@ func newTagItem(opts TagOptions, contentType reflect.Type, num uint64, nestedNum
if contentType == typeTime {
return nil, errors.New("cbor: cannot add time.Time to TagSet, use EncOptions.TimeTag and DecOptions.TimeTag instead")
}
if contentType == typeBigInt {
return nil, errors.New("cbor: cannot add big.Int to TagSet, it's built-in and supported automatically")
}
if contentType == typeTag {
return nil, errors.New("cbor: cannot add cbor.Tag to TagSet")
}
@ -208,24 +260,24 @@ func newTagItem(opts TagOptions, contentType reflect.Type, num uint64, nestedNum
if num == 0 || num == 1 {
return nil, errors.New("cbor: cannot add tag number 0 or 1 to TagSet, use EncOptions.TimeTag and DecOptions.TimeTag instead")
}
if reflect.PtrTo(contentType).Implements(typeMarshaler) && opts.EncTag != EncTagNone {
return nil, errors.New("cbor: cannot add cbor.Marshaler to TagSet with EncTag != EncTagNone")
if num == 2 || num == 3 {
return nil, errors.New("cbor: cannot add tag number 2 or 3 to TagSet, it's built-in and supported automatically")
}
if reflect.PtrTo(contentType).Implements(typeUnmarshaler) && opts.DecTag != DecTagIgnored {
return nil, errors.New("cbor: cannot add cbor.Unmarshaler to TagSet with DecTag != DecTagIgnored")
if num == tagNumSelfDescribedCBOR {
return nil, errors.New("cbor: cannot add tag number 55799 to TagSet, it's built-in and ignored automatically")
}
te := tagItem{num: []uint64{num}, opts: opts, contentType: contentType}
te.num = append(te.num, nestedNum...)
// Cache encoded tag numbers
e := getEncodeState()
e := getEncodeBuffer()
for _, n := range te.num {
encodeHead(e, byte(cborTypeTag), n)
}
te.cborTagNum = make([]byte, e.Len())
copy(te.cborTagNum, e.Bytes())
putEncodeState(e)
putEncodeBuffer(e)
return &te, nil
}

View File

@ -2,7 +2,9 @@ package cbor
import (
"bytes"
"fmt"
"io"
"math/big"
"reflect"
"strings"
"testing"
@ -189,6 +191,64 @@ func TestTagBinaryMarshalerUnmarshaler(t *testing.T) {
}
func TestTagStruct(t *testing.T) {
type T struct {
S string `cbor:"s,omitempty"`
}
t1 := reflect.TypeOf(T{})
tags := NewTagSet()
if err := tags.Add(TagOptions{EncTag: EncTagRequired, DecTag: DecTagRequired}, t1, 100); err != nil {
t.Fatalf("TagSet.Add(%s, %d) returned error %v", t1, 100, err)
}
em, _ := EncOptions{}.EncModeWithTags(tags)
dm, _ := DecOptions{}.DecModeWithTags(tags)
data := hexDecode("d864a0") // {}
var v T
if err := dm.Unmarshal(data, &v); err != nil {
t.Errorf("Unmarshal() returned error %v", err)
}
b, err := em.Marshal(v)
if err != nil {
t.Errorf("Marshal(%+v) returned error %v", v, err)
}
if !bytes.Equal(b, data) {
t.Errorf("Marshal(%+v) = 0x%x, want 0x%x", v, b, data)
}
}
func TestTagFixedLengthStruct(t *testing.T) {
type T struct {
S string `cbor:"s"`
}
t1 := reflect.TypeOf(T{})
tags := NewTagSet()
if err := tags.Add(TagOptions{EncTag: EncTagRequired, DecTag: DecTagRequired}, t1, 100); err != nil {
t.Fatalf("TagSet.Add(%s, %d) returned error %v", t1, 100, err)
}
em, _ := EncOptions{}.EncModeWithTags(tags)
dm, _ := DecOptions{}.DecModeWithTags(tags)
data := hexDecode("d864a1617360") // {"s":""}
var v T
if err := dm.Unmarshal(data, &v); err != nil {
t.Errorf("Unmarshal() returned error %v", err)
}
b, err := em.Marshal(v)
if err != nil {
t.Errorf("Marshal(%+v) returned error %v", v, err)
}
if !bytes.Equal(b, data) {
t.Errorf("Marshal(%+v) = 0x%x, want 0x%x", v, b, data)
}
}
func TestTagToArrayStruct(t *testing.T) {
type coseHeader struct {
Alg int `cbor:"1,keyasint,omitempty"`
Kid []byte `cbor:"4,keyasint,omitempty"`
@ -213,17 +273,17 @@ func TestTagStruct(t *testing.T) {
dm, _ := DecOptions{}.DecModeWithTags(tags)
// Data from https://tools.ietf.org/html/rfc8392#appendix-A section A.3
cborData := hexDecode("d28443a10126a104524173796d6d657472696345434453413235365850a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b7158405427c1ff28d23fbad1f29c4c7c6a555e601d6fa29f9179bc3d7438bacaca5acd08c8d4d4f96131680c429a01f85951ecee743a52b9b63632c57209120e1c9e30")
data := hexDecode("d28443a10126a104524173796d6d657472696345434453413235365850a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b7158405427c1ff28d23fbad1f29c4c7c6a555e601d6fa29f9179bc3d7438bacaca5acd08c8d4d4f96131680c429a01f85951ecee743a52b9b63632c57209120e1c9e30")
var v signedCWT
if err := dm.Unmarshal(cborData, &v); err != nil {
if err := dm.Unmarshal(data, &v); err != nil {
t.Errorf("Unmarshal() returned error %v", err)
}
b, err := em.Marshal(v)
if err != nil {
t.Errorf("Marshal(%+v) returned error %v", v, err)
}
if !bytes.Equal(b, cborData) {
t.Errorf("Marshal(%+v) = 0x%x, want 0x%x", v, b, cborData)
if !bytes.Equal(b, data) {
t.Errorf("Marshal(%+v) = 0x%x, want 0x%x", v, b, data)
}
}
@ -253,17 +313,17 @@ func TestNestedTagStruct(t *testing.T) {
dm, _ := DecOptions{}.DecModeWithTags(tags)
// Data from https://tools.ietf.org/html/rfc8392#appendix-A section A.4
cborData := hexDecode("d83dd18443a10104a1044c53796d6d65747269633235365850a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b7148093101ef6d789200")
data := hexDecode("d83dd18443a10104a1044c53796d6d65747269633235365850a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b7148093101ef6d789200")
var v macedCOSE
if err := dm.Unmarshal(cborData, &v); err != nil {
if err := dm.Unmarshal(data, &v); err != nil {
t.Errorf("Unmarshal() returned error %v", err)
}
b, err := em.Marshal(v)
if err != nil {
t.Errorf("Marshal(%+v) returned error %v", v, err)
}
if !bytes.Equal(b, cborData) {
t.Errorf("Marshal(%+v) = 0x%x, want 0x%x", v, b, cborData)
if !bytes.Equal(b, data) {
t.Errorf("Marshal(%+v) = 0x%x, want 0x%x", v, b, data)
}
}
@ -333,19 +393,28 @@ func TestAddTagError(t *testing.T) {
wantErrorMsg: "cbor: cannot add cbor.RawTag to TagSet",
},
{
name: "cbor.Unmarshaler",
typ: reflect.TypeOf(number2(0)),
name: "big.Int",
typ: reflect.TypeOf(big.Int{}),
num: 107,
opts: TagOptions{DecTag: DecTagRequired, EncTag: EncTagNone},
wantErrorMsg: "cbor: cannot add cbor.Unmarshaler to TagSet with DecTag != DecTagIgnored",
},
{
name: "cbor.Marshaler",
typ: reflect.TypeOf(number2(0)),
num: 108,
opts: TagOptions{DecTag: DecTagRequired, EncTag: EncTagRequired},
wantErrorMsg: "cbor: cannot add cbor.Marshaler to TagSet with EncTag != EncTagNone",
wantErrorMsg: "cbor: cannot add big.Int to TagSet, it's built-in and supported automatically",
},
/*
{
name: "cbor.Unmarshaler",
typ: reflect.TypeOf(number2(0)),
num: 107,
opts: TagOptions{DecTag: DecTagRequired, EncTag: EncTagNone},
wantErrorMsg: "cbor: cannot add cbor.Unmarshaler to TagSet with DecTag != DecTagIgnored",
},
{
name: "cbor.Marshaler",
typ: reflect.TypeOf(number2(0)),
num: 108,
opts: TagOptions{DecTag: DecTagRequired, EncTag: EncTagRequired},
wantErrorMsg: "cbor: cannot add cbor.Marshaler to TagSet with EncTag != EncTagNone",
},
*/
{
name: "tag number 0",
typ: reflect.TypeOf(myInt(0)),
@ -360,6 +429,27 @@ func TestAddTagError(t *testing.T) {
opts: TagOptions{DecTag: DecTagRequired, EncTag: EncTagRequired},
wantErrorMsg: "cbor: cannot add tag number 0 or 1 to TagSet, use EncOptions.TimeTag and DecOptions.TimeTag instead",
},
{
name: "tag number 2",
typ: reflect.TypeOf(myInt(0)),
num: 2,
opts: TagOptions{DecTag: DecTagRequired, EncTag: EncTagRequired},
wantErrorMsg: "cbor: cannot add tag number 2 or 3 to TagSet, it's built-in and supported automatically",
},
{
name: "tag number 3",
typ: reflect.TypeOf(myInt(0)),
num: 3,
opts: TagOptions{DecTag: DecTagRequired, EncTag: EncTagRequired},
wantErrorMsg: "cbor: cannot add tag number 2 or 3 to TagSet, it's built-in and supported automatically",
},
{
name: "tag number 55799",
typ: reflect.TypeOf(myInt(0)),
num: 55799,
opts: TagOptions{DecTag: DecTagRequired, EncTag: EncTagRequired},
wantErrorMsg: "cbor: cannot add tag number 55799 to TagSet, it's built-in and ignored automatically",
},
}
tags := NewTagSet()
for _, tc := range testCases {
@ -379,7 +469,7 @@ func TestAddTagError(t *testing.T) {
}
}
func TestAddDuplicateTagError(t *testing.T) {
func TestAddDuplicateTagContentTypeError(t *testing.T) {
type myInt int
myIntType := reflect.TypeOf(myInt(0))
wantErrorMsg := "cbor: content type cbor.myInt already exists in TagSet"
@ -393,7 +483,49 @@ func TestAddDuplicateTagError(t *testing.T) {
if err := tags.Add(TagOptions{DecTag: DecTagRequired, EncTag: EncTagRequired}, myIntType, 101); err == nil {
t.Errorf("TagSet.Add(%s, %d) didn't return an error", myIntType.String(), 101)
} else if err.Error() != wantErrorMsg {
t.Errorf("TagSet.Add(%s, %d) returned error msg %q, want %q", myIntType, 100, err, wantErrorMsg)
t.Errorf("TagSet.Add(%s, %d) returned error msg %q, want %q", myIntType, 101, err, wantErrorMsg)
}
}
func TestAddDuplicateTagNumError(t *testing.T) {
type myBool bool
type myInt int
myBoolType := reflect.TypeOf(myBool(false))
myIntType := reflect.TypeOf(myInt(0))
wantErrorMsg := "cbor: tag number [100] already exists in TagSet"
tags := NewTagSet()
// Add myIntType and 100 to tags
if err := tags.Add(TagOptions{DecTag: DecTagRequired, EncTag: EncTagRequired}, myIntType, 100); err != nil {
t.Errorf("TagSet.Add(%s, %d) returned error %v", myIntType.String(), 100, err)
}
// Add myBoolType and 100 to tags
if err := tags.Add(TagOptions{DecTag: DecTagRequired, EncTag: EncTagRequired}, myBoolType, 100); err == nil {
t.Errorf("TagSet.Add(%s, %d) didn't return an error", myBoolType.String(), 100)
} else if err.Error() != wantErrorMsg {
t.Errorf("TagSet.Add(%s, %d) returned error msg %q, want %q", myBoolType, 100, err, wantErrorMsg)
}
}
func TestAddDuplicateTagNumsError(t *testing.T) {
type myBool bool
type myInt int
myBoolType := reflect.TypeOf(myBool(false))
myIntType := reflect.TypeOf(myInt(0))
wantErrorMsg := "cbor: tag number [100 101] already exists in TagSet"
tags := NewTagSet()
// Add myIntType and [100, 101] to tags
if err := tags.Add(TagOptions{DecTag: DecTagRequired, EncTag: EncTagRequired}, myIntType, 100, 101); err != nil {
t.Errorf("TagSet.Add(%s, %d, %d) returned error %v", myIntType.String(), 100, 101, err)
}
// Add myBoolType and [100, 101] to tags
if err := tags.Add(TagOptions{DecTag: DecTagRequired, EncTag: EncTagRequired}, myBoolType, 100, 101); err == nil {
t.Errorf("TagSet.Add(%s, %d, %d) didn't return an error", myBoolType.String(), 100, 101)
} else if err.Error() != wantErrorMsg {
t.Errorf("TagSet.Add(%s, %d, %d) returned error msg %q, want %q", myBoolType, 100, 101, err, wantErrorMsg)
}
}
@ -774,31 +906,31 @@ func TestDecodeWrongTag(t *testing.T) {
testCases := []struct {
name string
obj interface{}
cborData []byte
data []byte
wantErrorMsg string
}{
{
name: "BinaryMarshaler non-struct",
obj: number(1234567890),
cborData: hexDecode("d87d4800000000499602d2"),
data: hexDecode("d87d4800000000499602d2"),
wantErrorMsg: "cbor: wrong tag number for cbor.number, got [125], expected [123]",
},
{
name: "BinaryMarshaler struct",
obj: stru{a: "a", b: "b", c: "c"},
cborData: hexDecode("d87d45612C622C63"),
data: hexDecode("d87d45612C622C63"),
wantErrorMsg: "cbor: wrong tag number for cbor.stru, got [125], expected [124]",
},
{
name: "non-struct",
obj: myInt(1),
cborData: hexDecode("d87d01"),
data: hexDecode("d87d01"),
wantErrorMsg: "cbor: wrong tag number for cbor.myInt, got [125], expected [100]",
},
{
name: "struct",
obj: s{A: "A", B: "B", C: "C"},
cborData: hexDecode("d87ea3616161416162614261636143"), // {"a":"A", "b":"B", "c":"C"}
data: hexDecode("d87ea3616161416162614261636143"), // {"a":"A", "b":"B", "c":"C"}
wantErrorMsg: "cbor: wrong tag number for cbor.s, got [126], expected [101 102]",
},
}
@ -809,13 +941,13 @@ func TestDecodeWrongTag(t *testing.T) {
t.Run(name, func(t *testing.T) {
dm, _ := DecOptions{}.DecModeWithTags(tag.tagSet)
v := reflect.New(reflect.TypeOf(tc.obj))
if err := dm.Unmarshal(tc.cborData, v.Interface()); err == nil {
t.Errorf("Unmarshal(0x%x) didn't return an error", tc.cborData)
if err := dm.Unmarshal(tc.data, v.Interface()); err == nil {
t.Errorf("Unmarshal(0x%x) didn't return an error", tc.data)
} else {
if _, ok := err.(*WrongTagError); !ok {
t.Errorf("Unmarshal(0x%x) returned wrong type of error %T, want (*WrongTagError)", tc.cborData, err)
t.Errorf("Unmarshal(0x%x) returned wrong type of error %T, want (*WrongTagError)", tc.data, err)
} else if err.Error() != tc.wantErrorMsg {
t.Errorf("Unmarshal(0x%x) returned error %q, want error %q", tc.cborData, err.Error(), tc.wantErrorMsg)
t.Errorf("Unmarshal(0x%x) returned error %q, want error %q", tc.data, err.Error(), tc.wantErrorMsg)
}
}
})
@ -828,7 +960,7 @@ func TestDecodeWrongTag(t *testing.T) {
t.Run(name, func(t *testing.T) {
dm, _ := DecOptions{}.DecModeWithTags(tagsDecIgnored)
v := reflect.New(reflect.TypeOf(tc.obj))
if err := dm.Unmarshal(tc.cborData, v.Interface()); err != nil {
if err := dm.Unmarshal(tc.data, v.Interface()); err != nil {
t.Errorf("Unmarshal() returned error %v", err)
}
if !reflect.DeepEqual(tc.obj, v.Elem().Interface()) {
@ -918,12 +1050,12 @@ func TestDecodeSharedTag(t *testing.T) {
// Decode myInt with tag number 123
var v myInt
wantV := myInt(1)
cborData := hexDecode("d87b01")
if err = dm.Unmarshal(cborData, &v); err != nil {
t.Errorf("Unmarshal(0x%x) returned error %v", cborData, err)
data := hexDecode("d87b01")
if err = dm.Unmarshal(data, &v); err != nil {
t.Errorf("Unmarshal(0x%x) returned error %v", data, err)
}
if !reflect.DeepEqual(v, wantV) {
t.Errorf("Unmarshal(0x%x) = %v (%T), want %v (%T)", cborData, v, v, wantV, wantV)
t.Errorf("Unmarshal(0x%x) = %v (%T), want %v (%T)", data, v, v, wantV, wantV)
}
// Unregister myInt type
@ -931,12 +1063,12 @@ func TestDecodeSharedTag(t *testing.T) {
// Decode myInt without tag number
wantV = myInt(2)
cborData = hexDecode("02")
if err := dm.Unmarshal(cborData, &v); err != nil {
t.Errorf("Unmarshal(0x%x) returned error %v", cborData, err)
data = hexDecode("02")
if err := dm.Unmarshal(data, &v); err != nil {
t.Errorf("Unmarshal(0x%x) returned error %v", data, err)
}
if !reflect.DeepEqual(v, wantV) {
t.Errorf("Unmarshal(0x%x) = %v (%T), want %v (%T)", cborData, v, v, wantV, wantV)
t.Errorf("Unmarshal(0x%x) = %v (%T), want %v (%T)", data, v, v, wantV, wantV)
}
// Register myInt type with tag number 234
@ -946,12 +1078,12 @@ func TestDecodeSharedTag(t *testing.T) {
// Decode myInt with tag number 234
wantV = myInt(3)
cborData = hexDecode("d8ea03")
if err := dm.Unmarshal(cborData, &v); err != nil {
t.Errorf("Unmarshal(0x%x) returned error %v", cborData, err)
data = hexDecode("d8ea03")
if err := dm.Unmarshal(data, &v); err != nil {
t.Errorf("Unmarshal(0x%x) returned error %v", data, err)
}
if !reflect.DeepEqual(v, wantV) {
t.Errorf("Unmarshal(0x%x) = %v (%T), want %v (%T)", cborData, v, v, wantV, wantV)
t.Errorf("Unmarshal(0x%x) = %v (%T), want %v (%T)", data, v, v, wantV, wantV)
}
}
@ -1030,8 +1162,8 @@ func TestEncModeWithTagsError(t *testing.T) {
func TestNilRawTagUnmarshalCBORError(t *testing.T) {
wantErrorMsg := "cbor.RawTag: UnmarshalCBOR on nil pointer"
var tag *RawTag
cborData := hexDecode("c249010000000000000000")
if err := tag.UnmarshalCBOR(cborData); err == nil {
data := hexDecode("c249010000000000000000")
if err := tag.UnmarshalCBOR(data); err == nil {
t.Errorf("UnmarshalCBOR() didn't return error")
} else if err.Error() != wantErrorMsg {
t.Errorf("UnmarshalCBOR() returned error %q, want %q", err.Error(), wantErrorMsg)
@ -1039,12 +1171,12 @@ func TestNilRawTagUnmarshalCBORError(t *testing.T) {
}
func TestTagUnmarshalError(t *testing.T) {
cborData := hexDecode("d87b61fe") // invalid UTF-8 string
data := hexDecode("d87b61fe") // invalid UTF-8 string
var tag Tag
if err := Unmarshal(cborData, &tag); err == nil {
t.Errorf("Unmarshal(0x%x) didn't return error", cborData)
if err := Unmarshal(data, &tag); err == nil {
t.Errorf("Unmarshal(0x%x) didn't return error", data)
} else if err.Error() != invalidUTF8ErrorMsg {
t.Errorf("Unmarshal(0x%x) returned error %q, want %q", cborData, err.Error(), invalidUTF8ErrorMsg)
t.Errorf("Unmarshal(0x%x) returned error %q, want %q", data, err.Error(), invalidUTF8ErrorMsg)
}
}
@ -1061,7 +1193,53 @@ func TestTagMarshalError(t *testing.T) {
}
}
func TestTagMarshal(t *testing.T) {
func TestMarshalUninitializedTag(t *testing.T) {
var v Tag
b, err := Marshal(v)
if err != nil {
t.Errorf("Marshal(%v) returned error %v", v, err)
}
if !bytes.Equal(b, cborNil) {
t.Errorf("Marshal(%v) = 0x%x, want 0x%x", v, b, cborNil)
}
}
func TestMarshalUninitializedRawTag(t *testing.T) {
var v RawTag
b, err := Marshal(v)
if err != nil {
t.Errorf("Marshal(%v) returned error %v", v, err)
}
if !bytes.Equal(b, cborNil) {
t.Errorf("Marshal(%v) = 0x%x, want 0x%x", v, b, cborNil)
}
}
func TestMarshalTagWithEmptyContent(t *testing.T) {
v := Tag{Number: 100} // Tag.Content is empty
want := hexDecode("d864f6") // 100(null)
b, err := Marshal(v)
if err != nil {
t.Errorf("Marshal(%v) returned error %v", v, err)
}
if !bytes.Equal(b, want) {
t.Errorf("Marshal(%v) = 0x%x, want 0x%x", v, b, want)
}
}
func TestMarshalRawTagWithEmptyContent(t *testing.T) {
v := RawTag{Number: 100} // RawTag.Content is empty
want := hexDecode("d864f6") // 100(null)
b, err := Marshal(v)
if err != nil {
t.Errorf("Marshal(%v) returned error %v", v, err)
}
if !bytes.Equal(b, want) {
t.Errorf("Marshal(%v) = 0x%x, want 0x%x", v, b, want)
}
}
func TestEncodeTag(t *testing.T) {
m := make(map[interface{}]bool)
m[10] = true
m[100] = true
@ -1095,3 +1273,266 @@ func TestTagMarshal(t *testing.T) {
t.Errorf("Marshal(%v) = 0x%x, want 0x%x", v, b, bytewiseSortedCborData)
}
}
func TestDecodeTagToEmptyIface(t *testing.T) {
type myBool bool
type myUint uint
typeMyBool := reflect.TypeOf(myBool(false))
typeMyUint := reflect.TypeOf(myUint(0))
tags := NewTagSet()
if err := tags.Add(TagOptions{EncTag: EncTagRequired, DecTag: DecTagRequired}, typeMyBool, 100); err != nil {
t.Fatalf("TagSet.Add(%s, %d) returned error %v", typeMyBool, 100, err)
}
if err := tags.Add(TagOptions{EncTag: EncTagRequired, DecTag: DecTagRequired}, typeMyUint, 101, 102); err != nil {
t.Fatalf("TagSet.Add(%s, %d, %d) returned error %v", typeMyUint, 101, 102, err)
}
dm, _ := DecOptions{}.DecModeWithTags(tags)
dmSharedTags, _ := DecOptions{}.DecModeWithSharedTags(tags)
testCases := []struct {
name string
data []byte
wantObj interface{}
}{
{
name: "registered myBool",
data: hexDecode("d864f5"), // 100(true)
wantObj: myBool(true),
},
{
name: "registered myUint",
data: hexDecode("d865d86600"), // 101(102(0))
wantObj: myUint(0),
},
{
name: "not registered bool",
data: hexDecode("d865f5"), // 101(true)
wantObj: Tag{101, true},
},
{
name: "not registered uint",
data: hexDecode("d865d86700"), // 101(103(0))
wantObj: Tag{101, Tag{103, uint64(0)}},
},
{
name: "not registered uint",
data: hexDecode("d865d866d86700"), // 101(102(103(0)))
wantObj: Tag{101, Tag{102, Tag{103, uint64(0)}}},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
var v1 interface{}
if err := dm.Unmarshal(tc.data, &v1); err != nil {
t.Errorf("Unmarshal() returned error %v", err)
}
if !reflect.DeepEqual(tc.wantObj, v1) {
t.Errorf("Unmarshal to interface{} returned different values: %v, %v", tc.wantObj, v1)
}
var v2 interface{}
if err := dmSharedTags.Unmarshal(tc.data, &v2); err != nil {
t.Errorf("Unmarshal() returned error %v", err)
}
if !reflect.DeepEqual(tc.wantObj, v2) {
t.Errorf("Unmarshal to interface{} returned different values: %v, %v", tc.wantObj, v2)
}
})
}
}
func TestDecodeRegisteredTagToEmptyIfaceError(t *testing.T) {
type myInt int
typeMyInt := reflect.TypeOf(myInt(0))
tags := NewTagSet()
if err := tags.Add(TagOptions{EncTag: EncTagRequired, DecTag: DecTagRequired}, typeMyInt, 101, 102); err != nil {
t.Fatalf("TagSet.Add(%s, %d, %d) returned error %v", typeMyInt, 101, 102, err)
}
dm, _ := DecOptions{}.DecModeWithTags(tags)
data := hexDecode("d865d8663bffffffffffffffff") // 101(102(-18446744073709551616))
var v interface{}
if err := dm.Unmarshal(data, &v); err == nil {
t.Errorf("Unmarshal(0x%x) didn't return an error", data)
} else if _, ok := err.(*UnmarshalTypeError); !ok {
t.Errorf("Unmarshal(0x%x) returned wrong error type %T, want (*UnmarshalTypeError)", data, err)
} else if !strings.Contains(err.Error(), "cannot unmarshal") {
t.Errorf("Unmarshal(0x%x) returned error %q, want error containing %q", data, err.Error(), "cannot unmarshal")
}
}
type number3 uint64
// MarshalCBOR marshals number3 to CBOR tagged map (tag number 100)
func (n number3) MarshalCBOR() (data []byte, err error) {
m := map[string]uint64{"num": uint64(n)}
return Marshal(Tag{100, m})
}
// UnmarshalCBOR unmarshals CBOR tagged map to number3
func (n *number3) UnmarshalCBOR(data []byte) (err error) {
var rawTag RawTag
if err := Unmarshal(data, &rawTag); err != nil {
return err
}
if rawTag.Number != 100 {
return fmt.Errorf("wrong tag number %d, want %d", rawTag.Number, 100)
}
if getType(rawTag.Content[0]) != cborTypeMap {
return fmt.Errorf("wrong tag content type, want map")
}
var v map[string]uint64
if err := Unmarshal(rawTag.Content, &v); err != nil {
return err
}
*n = number3(v["num"])
return nil
}
func TestDecodeRegisterTagForUnmarshaler(t *testing.T) {
typ := reflect.TypeOf(number3(0))
tags := NewTagSet()
if err := tags.Add(TagOptions{EncTag: EncTagRequired, DecTag: DecTagRequired}, typ, 100); err != nil {
t.Fatalf("TagSet.Add(%s, %d) returned error %v", typ, 100, err)
}
data := hexDecode("d864a1636e756d01") // 100({"num": 1})
wantObj := number3(1)
dm, _ := DecOptions{}.DecModeWithTags(tags)
em, _ := EncOptions{}.EncModeWithTags(tags)
// Decode to empty interface. Unmarshal() should return object of registered type.
var v1 interface{}
if err := dm.Unmarshal(data, &v1); err != nil {
t.Errorf("Unmarshal() returned error %v", err)
}
if !reflect.DeepEqual(wantObj, v1) {
t.Errorf("Unmarshal() returned different values: %v (%T), %v (%T)", wantObj, wantObj, v1, v1)
}
b, err := em.Marshal(v1)
if err != nil {
t.Errorf("Marshal(%v) returned error %v", v1, err)
} else if !bytes.Equal(b, data) {
t.Errorf("Marshal(%v) returned %v, want %v", v1, b, data)
}
// Decode to registered type.
var v2 number3
if err = dm.Unmarshal(data, &v2); err != nil {
t.Errorf("Unmarshal() returned error %v", err)
}
if !reflect.DeepEqual(wantObj, v2) {
t.Errorf("Unmarshal() returned different values: %v, %v", wantObj, v2)
}
b, err = em.Marshal(v2)
if err != nil {
t.Errorf("Marshal(%v) returned error %v", v2, err)
} else if !bytes.Equal(b, data) {
t.Errorf("Marshal(%v) returned %v, want %v", v2, b, data)
}
}
func TestMarshalRawTagContainingMalformedCBORData(t *testing.T) {
testCases := []struct {
name string
value interface{}
wantErrorMsg string
}{
// Nil RawMessage and empty RawMessage are encoded as CBOR nil.
{
name: "truncated data",
value: RawTag{Number: 100, Content: RawMessage{0xa6}},
wantErrorMsg: "cbor: error calling MarshalCBOR for type cbor.RawTag: unexpected EOF",
},
{
name: "malformed data",
value: RawTag{Number: 100, Content: RawMessage{0x1f}},
wantErrorMsg: "cbor: error calling MarshalCBOR for type cbor.RawTag: cbor: invalid additional information 31 for type positive integer",
},
{
name: "extraneous data",
value: RawTag{Number: 100, Content: RawMessage{0x01, 0x01}},
wantErrorMsg: "cbor: error calling MarshalCBOR for type cbor.RawTag: cbor: 1 bytes of extraneous data starting at index 3",
},
{
name: "invalid builtin tag",
value: RawTag{Number: 0, Content: RawMessage{0x01}},
wantErrorMsg: "cbor: error calling MarshalCBOR for type cbor.RawTag: cbor: tag number 0 must be followed by text string, got positive integer",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
b, err := Marshal(tc.value)
if err == nil {
t.Errorf("Marshal(%v) didn't return an error, want error %q", tc.value, tc.wantErrorMsg)
} else if _, ok := err.(*MarshalerError); !ok {
t.Errorf("Marshal(%v) error type %T, want *MarshalerError", tc.value, err)
} else if err.Error() != tc.wantErrorMsg {
t.Errorf("Marshal(%v) error %q, want %q", tc.value, err.Error(), tc.wantErrorMsg)
}
if b != nil {
t.Errorf("Marshal(%v) = 0x%x, want nil", tc.value, b)
}
})
}
}
// TestEncodeBuiltinTag tests that marshaling a value of type Tag "does the right thing" when
// marshaling the enclosed data item of a built-in tag number.
func TestEncodeBuiltinTag(t *testing.T) {
for _, tc := range []struct {
name string
tag Tag
opts EncOptions
want []byte
}{
{
name: "unsigned bignum content not enclosed in expected encoding tag",
tag: Tag{Number: tagNumUnsignedBignum, Content: []byte{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
opts: EncOptions{ByteSliceLaterFormat: ByteSliceLaterFormatBase16},
want: []byte{0xc2, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
},
{
name: "negative bignum content not enclosed in expected encoding tag",
tag: Tag{Number: tagNumNegativeBignum, Content: []byte{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
opts: EncOptions{ByteSliceLaterFormat: ByteSliceLaterFormatBase16},
want: []byte{0xc3, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
},
{
name: "rfc 3339 content is not encoded as byte string",
tag: Tag{Number: tagNumRFC3339Time, Content: "2013-03-21T20:04:00Z"},
opts: EncOptions{String: StringToByteString},
want: hexDecode("c074323031332d30332d32315432303a30343a30305a"),
},
} {
t.Run(tc.name, func(t *testing.T) {
em, err := tc.opts.EncMode()
if err != nil {
t.Fatal(err)
}
got, err := em.Marshal(tc.tag)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(got, tc.want) {
t.Errorf("unexpected difference\ngot: 0x%x\nwant: 0x%x", got, tc.want)
}
})
}
}

387
valid.go
View File

@ -7,7 +7,10 @@ import (
"encoding/binary"
"errors"
"io"
"math"
"strconv"
"github.com/x448/float16"
)
// SyntaxError is a description of a CBOR syntax error.
@ -24,59 +27,131 @@ type SemanticError struct {
func (e *SemanticError) Error() string { return e.msg }
// valid checks whether CBOR data is complete and well-formed.
func valid(data []byte) (rest []byte, err error) {
if len(data) == 0 {
return nil, io.EOF
}
offset, _, err := validInternal(data, 0, 1)
if err != nil {
return nil, err
}
return data[offset:], nil
// MaxNestedLevelError indicates exceeded max nested level of any combination of CBOR arrays/maps/tags.
type MaxNestedLevelError struct {
maxNestedLevels int
}
const (
maxNestingLevel = 32
)
func (e *MaxNestedLevelError) Error() string {
return "cbor: exceeded max nested level " + strconv.Itoa(e.maxNestedLevels)
}
// validInternal checks data's well-formedness and returns data's next offset, max depth, and error.
func validInternal(data []byte, off int, depth int) (int, int, error) {
if depth > maxNestingLevel {
return 0, 0, errors.New("cbor: reached max depth " + strconv.Itoa(maxNestingLevel))
// MaxArrayElementsError indicates exceeded max number of elements for CBOR arrays.
type MaxArrayElementsError struct {
maxArrayElements int
}
func (e *MaxArrayElementsError) Error() string {
return "cbor: exceeded max number of elements " + strconv.Itoa(e.maxArrayElements) + " for CBOR array"
}
// MaxMapPairsError indicates exceeded max number of key-value pairs for CBOR maps.
type MaxMapPairsError struct {
maxMapPairs int
}
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.
type IndefiniteLengthError struct {
t cborType
}
func (e *IndefiniteLengthError) Error() string {
return "cbor: indefinite-length " + e.t.String() + " isn't allowed"
}
// TagsMdError indicates found disallowed CBOR tags.
type TagsMdError struct {
}
func (e *TagsMdError) Error() string {
return "cbor: CBOR tag isn't allowed"
}
// ExtraneousDataError indicates found extraneous data following well-formed CBOR data item.
type ExtraneousDataError struct {
numOfBytes int // number of bytes of extraneous data
index int // location of extraneous data
}
func (e *ExtraneousDataError) Error() string {
return "cbor: " + strconv.Itoa(e.numOfBytes) + " bytes of extraneous data starting at index " + strconv.Itoa(e.index)
}
// wellformed checks whether the CBOR data item is well-formed.
// allowExtraData indicates if extraneous data is allowed after the CBOR data item.
// - use allowExtraData = true when using Decoder.Decode()
// - use allowExtraData = false when using Unmarshal()
func (d *decoder) wellformed(allowExtraData bool, checkBuiltinTags bool) error {
if len(d.data) == d.off {
return io.EOF
}
off, t, ai, val, err := validHead(data, off)
if err != nil {
return 0, 0, err
}
if ai == 31 {
if t == cborTypeByteString || t == cborTypeTextString {
return validIndefiniteString(data, off, t, depth)
_, err := d.wellformedInternal(0, checkBuiltinTags)
if err == nil {
if !allowExtraData && d.off != len(d.data) {
err = &ExtraneousDataError{len(d.data) - d.off, d.off}
}
return validIndefiniteArrOrMap(data, off, t, depth)
}
return err
}
dataLen := len(data)
// wellformedInternal checks data's well-formedness and returns max depth and error.
func (d *decoder) wellformedInternal(depth int, checkBuiltinTags bool) (int, error) { //nolint:gocyclo
t, _, val, indefiniteLength, err := d.wellformedHeadWithIndefiniteLengthFlag()
if err != nil {
return 0, err
}
switch t {
case cborTypeByteString, cborTypeTextString:
if indefiniteLength {
if d.dm.indefLength == IndefLengthForbidden {
return 0, &IndefiniteLengthError{t}
}
return d.wellformedIndefiniteString(t, depth, checkBuiltinTags)
}
valInt := int(val)
if valInt < 0 {
// Detect integer overflow
return 0, 0, errors.New("cbor: " + t.String() + " length " + strconv.FormatUint(val, 10) + " is too large, causing integer overflow")
return 0, errors.New("cbor: " + t.String() + " length " + strconv.FormatUint(val, 10) + " is too large, causing integer overflow")
}
if dataLen-off < valInt { // valInt+off may overflow integer
return 0, 0, io.ErrUnexpectedEOF
if len(d.data)-d.off < valInt { // valInt+off may overflow integer
return 0, io.ErrUnexpectedEOF
}
off += valInt
d.off += valInt
case cborTypeArray, cborTypeMap:
depth++
if depth > d.dm.maxNestedLevels {
return 0, &MaxNestedLevelError{d.dm.maxNestedLevels}
}
if indefiniteLength {
if d.dm.indefLength == IndefLengthForbidden {
return 0, &IndefiniteLengthError{t}
}
return d.wellformedIndefiniteArrayOrMap(t, depth, checkBuiltinTags)
}
valInt := int(val)
if valInt < 0 {
// Detect integer overflow
return 0, 0, errors.New("cbor: " + t.String() + " length " + strconv.FormatUint(val, 10) + " is too large, causing integer overflow")
return 0, errors.New("cbor: " + t.String() + " length " + strconv.FormatUint(val, 10) + " is too large, it would cause integer overflow")
}
if t == cborTypeArray {
if valInt > d.dm.maxArrayElements {
return 0, &MaxArrayElementsError{d.dm.maxArrayElements}
}
} else {
if valInt > d.dm.maxMapPairs {
return 0, &MaxMapPairsError{d.dm.maxMapPairs}
}
}
count := 1
if t == cborTypeMap {
count = 2
@ -84,150 +159,236 @@ func validInternal(data []byte, off int, depth int) (int, int, error) {
maxDepth := depth
for j := 0; j < count; j++ {
for i := 0; i < valInt; i++ {
var d int
if off, d, err = validInternal(data, off, depth+1); err != nil {
return 0, 0, err
var dpt int
if dpt, err = d.wellformedInternal(depth, checkBuiltinTags); err != nil {
return 0, err
}
if d > maxDepth {
maxDepth = d // Save max depth
if dpt > maxDepth {
maxDepth = dpt // Save max depth
}
}
}
depth = maxDepth
case cborTypeTag:
if d.dm.tagsMd == TagsForbidden {
return 0, &TagsMdError{}
}
tagNum := val
// Scan nested tag numbers to avoid recursion.
for {
if dataLen == off { // Tag number must be followed by tag content.
return 0, 0, io.ErrUnexpectedEOF
if len(d.data) == d.off { // Tag number must be followed by tag content.
return 0, io.ErrUnexpectedEOF
}
if cborType(data[off]&0xe0) != cborTypeTag {
if checkBuiltinTags {
err = validBuiltinTag(tagNum, d.data[d.off])
if err != nil {
return 0, err
}
}
if d.dm.bignumTag == BignumTagForbidden && (tagNum == 2 || tagNum == 3) {
return 0, &UnacceptableDataItemError{
CBORType: cborTypeTag.String(),
Message: "bignum",
}
}
if getType(d.data[d.off]) != cborTypeTag {
break
}
if off, _, _, _, err = validHead(data, off); err != nil {
return 0, 0, err
if _, _, tagNum, err = d.wellformedHead(); err != nil {
return 0, err
}
depth++
if depth > d.dm.maxNestedLevels {
return 0, &MaxNestedLevelError{d.dm.maxNestedLevels}
}
}
// Check tag content.
return validInternal(data, off, depth)
return d.wellformedInternal(depth, checkBuiltinTags)
}
return off, depth, nil
return depth, nil
}
// validIndefiniteString checks indefinite length byte/text string's well-formedness and returns data's next offset, max depth, and error.
func validIndefiniteString(data []byte, off int, t cborType, depth int) (int, int, 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
dataLen := len(data)
for {
if dataLen == off {
return 0, 0, io.ErrUnexpectedEOF
if len(d.data) == d.off {
return 0, io.ErrUnexpectedEOF
}
if data[off] == 0xff {
off++
if isBreakFlag(d.data[d.off]) {
d.off++
break
}
// Peek ahead to get next type and indefinite length status.
nt := cborType(data[off] & 0xe0)
nt, ai := parseInitialByte(d.data[d.off])
if t != nt {
return 0, 0, &SyntaxError{"cbor: wrong element type " + nt.String() + " for indefinite-length " + t.String()}
return 0, &SyntaxError{"cbor: wrong element type " + nt.String() + " for indefinite-length " + t.String()}
}
if (data[off] & 0x1f) == 31 {
return 0, 0, &SyntaxError{"cbor: indefinite-length " + t.String() + " chunk is not definite-length"}
if additionalInformation(ai).isIndefiniteLength() {
return 0, &SyntaxError{"cbor: indefinite-length " + t.String() + " chunk is not definite-length"}
}
if off, depth, err = validInternal(data, off, depth); err != nil {
return 0, 0, err
if depth, err = d.wellformedInternal(depth, checkBuiltinTags); err != nil {
return 0, err
}
}
return off, depth, nil
return depth, nil
}
// validIndefiniteArrOrMap checks indefinite length array/map's well-formedness and returns data's next offset, max depth, and error.
func validIndefiniteArrOrMap(data []byte, off int, t cborType, depth int) (int, int, 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
dataLen := len(data)
i := 0
for {
if dataLen == off {
return 0, 0, io.ErrUnexpectedEOF
if len(d.data) == d.off {
return 0, io.ErrUnexpectedEOF
}
if data[off] == 0xff {
off++
if isBreakFlag(d.data[d.off]) {
d.off++
break
}
var d int
if off, d, err = validInternal(data, off, depth+1); err != nil {
return 0, 0, err
var dpt int
if dpt, err = d.wellformedInternal(depth, checkBuiltinTags); err != nil {
return 0, err
}
if d > maxDepth {
maxDepth = d
if dpt > maxDepth {
maxDepth = dpt
}
i++
if t == cborTypeArray {
if i > d.dm.maxArrayElements {
return 0, &MaxArrayElementsError{d.dm.maxArrayElements}
}
} else {
if i%2 == 0 && i/2 > d.dm.maxMapPairs {
return 0, &MaxMapPairsError{d.dm.maxMapPairs}
}
}
}
if t == cborTypeMap && i%2 == 1 {
return 0, 0, &SyntaxError{"cbor: unexpected \"break\" code"}
return 0, &SyntaxError{"cbor: unexpected \"break\" code"}
}
return off, maxDepth, nil
return maxDepth, nil
}
func validHead(data []byte, off int) (_ int, t cborType, ai byte, val uint64, err error) {
dataLen := len(data) - off
func (d *decoder) wellformedHeadWithIndefiniteLengthFlag() (
t cborType,
ai byte,
val uint64,
indefiniteLength bool,
err error,
) {
t, ai, val, err = d.wellformedHead()
if err != nil {
return
}
indefiniteLength = additionalInformation(ai).isIndefiniteLength()
return
}
func (d *decoder) wellformedHead() (t cborType, ai byte, val uint64, err error) {
dataLen := len(d.data) - d.off
if dataLen == 0 {
return 0, 0, 0, 0, io.ErrUnexpectedEOF
return 0, 0, 0, io.ErrUnexpectedEOF
}
t = cborType(data[off] & 0xe0)
ai = data[off] & 0x1f
t, ai = parseInitialByte(d.data[d.off])
val = uint64(ai)
off++
d.off++
dataLen--
if ai < 24 {
return off, t, ai, val, nil
if ai <= maxAdditionalInformationWithoutArgument {
return t, ai, val, nil
}
if ai == 24 {
if dataLen < 2 {
return 0, 0, 0, 0, io.ErrUnexpectedEOF
if ai == additionalInformationWith1ByteArgument {
const argumentSize = 1
if dataLen < argumentSize {
return 0, 0, 0, io.ErrUnexpectedEOF
}
val = uint64(data[off])
off++
val = uint64(d.data[d.off])
d.off++
if t == cborTypePrimitives && val < 32 {
return 0, 0, 0, 0, &SyntaxError{"cbor: invalid simple value " + strconv.Itoa(int(val)) + " for type " + t.String()}
return 0, 0, 0, &SyntaxError{"cbor: invalid simple value " + strconv.Itoa(int(val)) + " for type " + t.String()}
}
return off, t, ai, val, nil
return t, ai, val, nil
}
if ai == 25 {
if dataLen < 3 {
return 0, 0, 0, 0, io.ErrUnexpectedEOF
if ai == additionalInformationWith2ByteArgument {
const argumentSize = 2
if dataLen < argumentSize {
return 0, 0, 0, io.ErrUnexpectedEOF
}
val = uint64(binary.BigEndian.Uint16(data[off : off+2]))
off += 2
return off, t, ai, val, nil
}
if ai == 26 {
if dataLen < 5 {
return 0, 0, 0, 0, io.ErrUnexpectedEOF
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 {
return 0, 0, 0, err
}
}
val = uint64(binary.BigEndian.Uint32(data[off : off+4]))
off += 4
return off, t, ai, val, nil
return t, ai, val, nil
}
if ai == 27 {
if dataLen < 9 {
return 0, 0, 0, 0, io.ErrUnexpectedEOF
if ai == additionalInformationWith4ByteArgument {
const argumentSize = 4
if dataLen < argumentSize {
return 0, 0, 0, io.ErrUnexpectedEOF
}
val = binary.BigEndian.Uint64(data[off : off+8])
off += 8
return off, t, ai, val, nil
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 {
return 0, 0, 0, err
}
}
return t, ai, val, nil
}
if ai == 31 {
if ai == additionalInformationWith8ByteArgument {
const argumentSize = 8
if dataLen < argumentSize {
return 0, 0, 0, io.ErrUnexpectedEOF
}
val = binary.BigEndian.Uint64(d.data[d.off : d.off+argumentSize])
d.off += argumentSize
if t == cborTypePrimitives {
if err := d.acceptableFloat(math.Float64frombits(val)); err != nil {
return 0, 0, 0, err
}
}
return t, ai, val, nil
}
if additionalInformation(ai).isIndefiniteLength() {
switch t {
case cborTypePositiveInt, cborTypeNegativeInt, cborTypeTag:
return 0, 0, 0, 0, &SyntaxError{"cbor: invalid additional information " + strconv.Itoa(int(ai)) + " for type " + t.String()}
case cborTypePrimitives: // 0xff (break code) should not be outside validIndefinite().
return 0, 0, 0, 0, &SyntaxError{"cbor: unexpected \"break\" code"}
return 0, 0, 0, &SyntaxError{"cbor: invalid additional information " + strconv.Itoa(int(ai)) + " for type " + t.String()}
case cborTypePrimitives: // 0xff (break code) should not be outside wellformedIndefinite().
return 0, 0, 0, &SyntaxError{"cbor: unexpected \"break\" code"}
}
return off, t, ai, val, nil
return t, ai, val, nil
}
// ai == 28, 29, 30
return 0, 0, 0, 0, &SyntaxError{"cbor: invalid additional information " + strconv.Itoa(int(ai)) + " for type " + t.String()}
return 0, 0, 0, &SyntaxError{"cbor: invalid additional information " + strconv.Itoa(int(ai)) + " for type " + t.String()}
}
func (d *decoder) acceptableFloat(f float64) error {
switch {
case d.dm.nanDec == NaNDecodeForbidden && math.IsNaN(f):
return &UnacceptableDataItemError{
CBORType: cborTypePrimitives.String(),
Message: "floating-point NaN",
}
case d.dm.infDec == InfDecodeForbidden && math.IsInf(f, 0):
return &UnacceptableDataItemError{
CBORType: cborTypePrimitives.String(),
Message: "floating-point infinity",
}
}
return nil
}

34
valid_not_tinygo_test.go Normal file
View File

@ -0,0 +1,34 @@
// 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)
}
})
}
}

299
valid_test.go Normal file
View File

@ -0,0 +1,299 @@
// Copyright (c) Faye Amacker. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.
package cbor
import (
"bytes"
"strconv"
"testing"
)
func TestValid1(t *testing.T) {
for _, mt := range marshalTests {
if err := Wellformed(mt.wantData); err != nil {
t.Errorf("Wellformed() returned error %v", err)
}
}
}
func TestValid2(t *testing.T) {
for _, mt := range marshalTests {
dm, _ := DecOptions{DupMapKey: DupMapKeyEnforcedAPF}.DecMode()
if err := dm.Wellformed(mt.wantData); err != nil {
t.Errorf("Wellformed() returned error %v", err)
}
}
}
func TestValidExtraneousData(t *testing.T) {
testCases := []struct {
name string
data []byte
extraneousDataNumOfBytes int
extraneousDataIndex int
}{
{"two numbers", []byte{0x00, 0x01}, 1, 1}, // 0, 1
{"bytestring and int", []byte{0x44, 0x01, 0x02, 0x03, 0x04, 0x00}, 1, 5}, // h'01020304', 0
{"int and partial array", []byte{0x00, 0x83, 0x01, 0x02}, 3, 1},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err := Wellformed(tc.data)
if err == nil {
t.Errorf("Wellformed(0x%x) didn't return an error", tc.data)
} else {
ederr, ok := err.(*ExtraneousDataError)
if !ok {
t.Errorf("Wellformed(0x%x) error type %T, want *ExtraneousDataError", tc.data, err)
} else if ederr.numOfBytes != tc.extraneousDataNumOfBytes {
t.Errorf("Wellformed(0x%x) returned %d bytes of extraneous data, want %d", tc.data, ederr.numOfBytes, tc.extraneousDataNumOfBytes)
} else if ederr.index != tc.extraneousDataIndex {
t.Errorf("Wellformed(0x%x) returned extraneous data index %d, want %d", tc.data, ederr.index, tc.extraneousDataIndex)
}
}
})
}
}
func TestValidOnStreamingData(t *testing.T) {
var buf bytes.Buffer
for _, t := range marshalTests {
buf.Write(t.wantData)
}
d := decoder{data: buf.Bytes(), dm: defaultDecMode}
for i := 0; i < len(marshalTests); i++ {
if err := d.wellformed(true, false); err != nil {
t.Errorf("wellformed() returned error %v", err)
}
}
}
func TestDepth(t *testing.T) {
testCases := []struct {
name string
data []byte
wantDepth int
}{
{"uint", hexDecode("00"), 0}, // 0
{"int", hexDecode("20"), 0}, // -1
{"bool", hexDecode("f4"), 0}, // false
{"nil", hexDecode("f6"), 0}, // nil
{"float", hexDecode("fa47c35000"), 0}, // 100000.0
{"byte string", hexDecode("40"), 0}, // []byte{}
{"indefinite length byte string", hexDecode("5f42010243030405ff"), 0}, // []byte{1, 2, 3, 4, 5}
{"text string", hexDecode("60"), 0}, // ""
{"indefinite length text string", hexDecode("7f657374726561646d696e67ff"), 0}, // "streaming"
{"empty array", hexDecode("80"), 1}, // []
{"indefinite length empty array", hexDecode("9fff"), 1}, // []
{"array", hexDecode("98190102030405060708090a0b0c0d0e0f101112131415161718181819"), 1}, // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]
{"indefinite length array", hexDecode("9f0102030405060708090a0b0c0d0e0f101112131415161718181819ff"), 1}, // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]
{"nested array", hexDecode("8301820203820405"), 2}, // [1,[2,3],[4,5]]
{"indefinite length nested array", hexDecode("83018202039f0405ff"), 2}, // [1,[2,3],[4,5]]
{"array and map", hexDecode("826161a161626163"), 2}, // [a", {"b": "c"}]
{"indefinite length array and map", hexDecode("826161bf61626163ff"), 2}, // [a", {"b": "c"}]
{"empty map", hexDecode("a0"), 1}, // {}
{"indefinite length empty map", hexDecode("bfff"), 1}, // {}
{"map", hexDecode("a201020304"), 1}, // {1:2, 3:4}
{"nested map", hexDecode("a26161016162820203"), 2}, // {"a": 1, "b": [2, 3]}
{"indefinite length nested map", hexDecode("bf61610161629f0203ffff"), 2}, // {"a": 1, "b": [2, 3]}
{"tag", hexDecode("c074323031332d30332d32315432303a30343a30305a"), 0}, // 0("2013-03-21T20:04:00Z")
{"tagged map", hexDecode("d864a26161016162820203"), 2}, // 100({"a": 1, "b": [2, 3]})
{"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")))
}
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)
}
})
}
}
func TestDepthError(t *testing.T) {
testCases := []struct {
name string
data []byte
opts DecOptions
wantErrorMsg string
}{
{
name: "33-level array",
data: hexDecode("82018181818181818181818181818181818181818181818181818181818181818101"),
opts: DecOptions{MaxNestedLevels: 4},
wantErrorMsg: "cbor: exceeded max nested level 4",
},
{
name: "33-level array",
data: hexDecode("82018181818181818181818181818181818181818181818181818181818181818101"),
opts: DecOptions{MaxNestedLevels: 10},
wantErrorMsg: "cbor: exceeded max nested level 10",
},
{
name: "33-level array",
data: hexDecode("8201818181818181818181818181818181818181818181818181818181818181818101"),
opts: DecOptions{},
wantErrorMsg: "cbor: exceeded max nested level " + strconv.Itoa(defaultMaxNestedLevels),
},
{
name: "33-level indefinite length array",
data: hexDecode("9f01818181818181818181818181818181818181818181818181818181818181818101ff"),
opts: DecOptions{},
wantErrorMsg: "cbor: exceeded max nested level " + strconv.Itoa(defaultMaxNestedLevels),
},
{
name: "33-level map",
data: hexDecode("a101818181818181818181818181818181818181818181818181818181818181818101"),
opts: DecOptions{},
wantErrorMsg: "cbor: exceeded max nested level " + strconv.Itoa(defaultMaxNestedLevels),
},
{
name: "33-level indefinite length map",
data: hexDecode("bf01818181818181818181818181818181818181818181818181818181818181818101ff"),
opts: DecOptions{},
wantErrorMsg: "cbor: exceeded max nested level " + strconv.Itoa(defaultMaxNestedLevels),
},
{
name: "33-level tag",
data: hexDecode("d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d86474323031332d30332d32315432303a30343a30305a"),
opts: DecOptions{},
wantErrorMsg: "cbor: exceeded max nested level " + strconv.Itoa(defaultMaxNestedLevels),
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
dm, _ := tc.opts.decMode()
d := decoder{data: tc.data, dm: dm}
if _, err := d.wellformedInternal(0, false); err == nil {
t.Errorf("wellformed(0x%x) didn't return an error", tc.data)
} else if _, ok := err.(*MaxNestedLevelError); !ok {
t.Errorf("wellformed(0x%x) returned wrong error type %T, want (*MaxNestedLevelError)", tc.data, err)
} else if err.Error() != tc.wantErrorMsg {
t.Errorf("wellformed(0x%x) returned error %q, want error %q", tc.data, err.Error(), tc.wantErrorMsg)
}
})
}
}
func TestValidBuiltinTagTest(t *testing.T) {
testCases := []struct {
name string
data []byte
}{
{
name: "tag 0",
data: hexDecode("c074323031332d30332d32315432303a30343a30305a"),
},
{
name: "tag 1",
data: hexDecode("c11a514b67b0"),
},
{
name: "tag 2",
data: hexDecode("c249010000000000000000"),
},
{
name: "tag 3",
data: hexDecode("c349010000000000000000"),
},
{
name: "nested tag 0",
data: hexDecode("d9d9f7c074323031332d30332d32315432303a30343a30305a"),
},
{
name: "nested tag 1",
data: hexDecode("d9d9f7c11a514b67b0"),
},
{
name: "nested tag 2",
data: hexDecode("d9d9f7c249010000000000000000"),
},
{
name: "nested tag 3",
data: hexDecode("d9d9f7c349010000000000000000"),
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
d := decoder{data: tc.data, dm: defaultDecMode}
if err := d.wellformed(true, true); err != nil {
t.Errorf("wellformed(0x%x) returned error %v", tc.data, err)
}
})
}
}
func TestInvalidBuiltinTagTest(t *testing.T) {
testCases := []struct {
name string
data []byte
wantErrorMsg string
}{
{
name: "tag 0",
data: hexDecode("c01a514b67b0"),
wantErrorMsg: "cbor: tag number 0 must be followed by text string, got positive integer",
},
{
name: "tag 1",
data: hexDecode("c174323031332d30332d32315432303a30343a30305a"),
wantErrorMsg: "cbor: tag number 1 must be followed by integer or floating-point number, got UTF-8 text string",
},
{
name: "tag 2",
data: hexDecode("c269010000000000000000"),
wantErrorMsg: "cbor: tag number 2 or 3 must be followed by byte string, got UTF-8 text string",
},
{
name: "tag 3",
data: hexDecode("c300"),
wantErrorMsg: "cbor: tag number 2 or 3 must be followed by byte string, got positive integer",
},
{
name: "nested tag 0",
data: hexDecode("d9d9f7c01a514b67b0"),
wantErrorMsg: "cbor: tag number 0 must be followed by text string, got positive integer",
},
{
name: "nested tag 1",
data: hexDecode("d9d9f7c174323031332d30332d32315432303a30343a30305a"),
wantErrorMsg: "cbor: tag number 1 must be followed by integer or floating-point number, got UTF-8 text string",
},
{
name: "nested tag 2",
data: hexDecode("d9d9f7c269010000000000000000"),
wantErrorMsg: "cbor: tag number 2 or 3 must be followed by byte string, got UTF-8 text string",
},
{
name: "nested tag 3",
data: hexDecode("d9d9f7c300"),
wantErrorMsg: "cbor: tag number 2 or 3 must be followed by byte string, got positive integer",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
d := decoder{data: tc.data, dm: defaultDecMode}
err := d.wellformed(true, true)
if err == nil {
t.Errorf("wellformed(0x%x) didn't return an error", tc.data)
} else if err.Error() != tc.wantErrorMsg {
t.Errorf("wellformed(0x%x) error %q, want %q", tc.data, err.Error(), tc.wantErrorMsg)
}
})
}
}