Compare commits

..

2 Commits

Author SHA1 Message Date
Faye Amacker
49b43cbd39
Update ci-go-cover.yml
Some checks failed
ci / Test on ${{matrix.os}} (macos-latest) (push) Has been cancelled
ci / Test on ${{matrix.os}} (ubuntu-latest) (push) Has been cancelled
cover ≥98% / Coverage (push) Has been cancelled
linters / Lint (push) Has been cancelled
Update CI to regenerate code coverage badge for old releases.
2023-06-25 13:38:34 -05:00
Faye Amacker
0dd8886f91
Update ci-go-cover.yml
Add workflow_dispatch to old branch.
2023-06-25 13:32:25 -05:00
52 changed files with 4646 additions and 20204 deletions

View File

@ -1,36 +0,0 @@
---
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

@ -1,20 +0,0 @@
---
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

@ -1,16 +0,0 @@
---
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

@ -1,16 +0,0 @@
---
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
-->

View File

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

View File

@ -1,69 +0,0 @@
<!--
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-2023 Montgomery Edwards⁴⁴⁸ (github.com/x448).
# Copyright 2020-present 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 2023.5.14.
# CI Go Cover 2020.1.28.
# 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,7 +14,7 @@
# 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 ≥96%
name: cover ≥98%
# Remove default permissions.
permissions: {}
@ -23,7 +23,6 @@ on:
workflow_dispatch:
pull_request:
push:
branches: [main, master]
jobs:
@ -36,11 +35,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
uses: actions/checkout@v3
- name: Install Go
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
uses: actions/setup-go@v4
with:
go-version: 1.21
go-version: 1.19
check-latest: true
- name: Install x448/float16
run: go get github.com/x448/float16@v0.8.4

View File

@ -1,54 +1,26 @@
# GitHub Actions - CI for Go to build & test.
# GitHub Actions - CI for Go to build & test. See ci-go-cover.yml and linters.yml for code coverage and linters.
# https://github.com/fxamacker/cbor/workflows/ci.yml
# See ci-go-cover.yml for coverage and safer-golangci-lint.yml for linting.
name: ci
# Revoke default permissions.
permissions: {}
on:
workflow_dispatch:
pull_request:
push:
branches:
- 'master'
- 'release*'
- 'feature/stream-mode'
tags:
- 'v*'
on: [push]
jobs:
# Test on various OS with default Go version.
tests:
name: test ${{matrix.os}} go-${{ matrix.go-version }}
name: Test on ${{matrix.os}}
runs-on: ${{ matrix.os }}
permissions:
contents: read
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
go-version: [1.17, 1.18, 1.19, '1.20', 1.21, 1.22, 1.23]
os: [macos-latest, ubuntu-latest]
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@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
uses: actions/checkout@v1
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 -race -v ./...
go test -short -race -v ./...

View File

@ -1,45 +0,0 @@
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

View File

@ -1,47 +0,0 @@
# 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 ./...

21
.github/workflows/linters.yml vendored Normal file
View File

@ -0,0 +1,21 @@
# 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

@ -1,71 +0,0 @@
# 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,26 +1,12 @@
# 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:
@ -31,14 +17,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:
@ -51,54 +37,50 @@ linters-settings:
suggest-new: true
misspell:
locale: US
staticcheck:
checks: ["all"]
linters:
disable-all: true
enable:
- asciicheck
- bidichk
- depguard
- deadcode
- errcheck
- exportloopref
- goconst
- gocritic
- gocyclo
- gofmt
- goimports
- goprintffuncname
- golint
- gosec
- gosimple
- govet
- ineffassign
- maligned
- misspell
- nilerr
- revive
- staticcheck
- stylecheck
- structcheck
- 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: 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"
- 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

264
CBOR_BENCHMARKS.md Normal file
View File

@ -0,0 +1,264 @@
# 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

32
CBOR_GOLANG.md Normal file
View File

@ -0,0 +1,32 @@
👉 [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,133 +1,76 @@
# Contributor Covenant Code of Conduct
## Our Pledge
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.
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.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
Examples of behavior that contributes to creating a positive environment
include:
* 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
* 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
Examples of unacceptable behavior include:
Examples of unacceptable behavior by participants include:
* 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
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email address,
without their explicit permission
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
professional setting
## Enforcement Responsibilities
## Our Responsibilities
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 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 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.
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.
## Scope
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.
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.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
faye.github@gmail.com.
All complaints will be reviewed and investigated promptly and fairly.
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.
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.
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.
## Attribution
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].
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
[homepage]: https://www.contributor-covenant.org
[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
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq

View File

@ -1,41 +1,47 @@
# How to contribute
You can contribute by using the library, opening issues, or opening pull requests.
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.
## Bug reports and security vulnerabilities
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.
Most issues are tracked publicly on [GitHub](https://github.com/fxamacker/cbor/issues).
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).
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 signed contract where I'm indemnified, held harmless, and defended by you 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 contract where I'm indemnified, held harmless, and defended for any data you send to me.
## Pull requests
## 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.
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.
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.
## 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 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.
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.)
## 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

View File

@ -1,7 +0,0 @@
# 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,8 +5,7 @@ package cbor
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"reflect"
"testing"
)
@ -107,68 +106,9 @@ 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
data []byte
cborData []byte
decodeToTypes []reflect.Type
}{
{"bool", hexDecode("f5"), []reflect.Type{typeIntf, typeBool}}, // true
@ -186,9 +126,9 @@ var decodeBenchmarks = []struct {
}
var encodeBenchmarks = []struct {
name string
data []byte
values []interface{}
name string
cborData []byte
values []interface{}
}{
{"bool", hexDecode("f5"), []interface{}{true}},
{"positive int", hexDecode("1bffffffffffffffff"), []interface{}{uint64(18446744073709551615)}},
@ -210,7 +150,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.data, vPtr); err != nil {
if err := Unmarshal(bm.cborData, vPtr); err != nil {
b.Fatal("Unmarshal:", err)
}
}
@ -219,7 +159,7 @@ func BenchmarkUnmarshal(b *testing.B) {
}
var moreBenchmarks = []struct {
name string
data []byte
cborData []byte
decodeToType reflect.Type
}{
// Unmarshal CBOR map with string key to map[string]interface{}.
@ -263,7 +203,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.data, vPtr); err != nil {
if err := Unmarshal(bm.cborData, vPtr); err != nil {
b.Fatal("Unmarshal:", err)
}
}
@ -271,54 +211,6 @@ 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 {
@ -326,7 +218,7 @@ func BenchmarkDecode(b *testing.B) {
if t.Kind() == reflect.Struct {
name = "CBOR " + bm.name + " to Go " + t.Kind().String()
}
buf := bytes.NewReader(bm.data)
buf := bytes.NewReader(bm.cborData)
decoder := NewDecoder(buf)
b.Run(name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
@ -342,16 +234,16 @@ func BenchmarkDecode(b *testing.B) {
}
func BenchmarkDecodeStream(b *testing.B) {
var data []byte
var cborData []byte
for _, bm := range decodeBenchmarks {
for i := 0; i < len(bm.decodeToTypes); i++ {
data = append(data, bm.data...)
cborData = append(cborData, bm.cborData...)
}
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
buf := bytes.NewReader(data)
buf := bytes.NewReader(cborData)
decoder := NewDecoder(buf)
for j := 0; j < rounds; j++ {
for _, bm := range decodeBenchmarks {
@ -455,17 +347,6 @@ 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},
@ -499,11 +380,11 @@ func BenchmarkMarshalCanonical(b *testing.B) {
N string `cbor:"n"`
}
for _, bm := range []struct {
name string
data []byte
values []interface{}
name string
cborData []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[int]int{0: 0} /* single-entry map */}},
{"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"}}},
} {
for _, v := range bm.values {
name := "Go " + reflect.TypeOf(v).String() + " to CBOR " + bm.name
@ -534,29 +415,6 @@ 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 {
@ -565,7 +423,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(io.Discard)
encoder := NewEncoder(ioutil.Discard)
b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := encoder.Encode(v); err != nil {
@ -579,7 +437,7 @@ func BenchmarkEncode(b *testing.B) {
func BenchmarkEncodeStream(b *testing.B) {
for i := 0; i < b.N; i++ {
encoder := NewEncoder(io.Discard)
encoder := NewEncoder(ioutil.Discard)
for i := 0; i < rounds; i++ {
for _, bm := range encodeBenchmarks {
for _, v := range bm.values {
@ -595,8 +453,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
data []byte
name string
cborData []byte
}{
{"128-Bit Symmetric Key", hexDecode("a42050231f4c4d4d3051fdc2ec0a3851d5b3830104024c53796d6d6574726963313238030a")},
{"256-Bit Symmetric Key", hexDecode("a4205820403697de87af64611c1d32a05dab0fe1fcb715a86ab435f1ec99192d795693880104024c53796d6d6574726963323536030a")},
@ -606,7 +464,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.data, &v); err != nil {
if err := Unmarshal(tc.cborData, &v); err != nil {
b.Fatal("Unmarshal:", err)
}
}
@ -617,8 +475,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
data []byte
name string
cborData []byte
}{
{"128-Bit Symmetric Key", hexDecode("a42050231f4c4d4d3051fdc2ec0a3851d5b3830104024c53796d6d6574726963313238030a")},
{"256-Bit Symmetric Key", hexDecode("a4205820403697de87af64611c1d32a05dab0fe1fcb715a86ab435f1ec99192d795693880104024c53796d6d6574726963323536030a")},
@ -626,7 +484,7 @@ func BenchmarkMarshalCOSE(b *testing.B) {
}
for _, tc := range testCases {
var v coseKey
if err := Unmarshal(tc.data, &v); err != nil {
if err := Unmarshal(tc.cborData, &v); err != nil {
b.Fatal("Unmarshal:", err)
}
b.Run(tc.name, func(b *testing.B) {
@ -641,10 +499,10 @@ func BenchmarkMarshalCOSE(b *testing.B) {
func BenchmarkUnmarshalCWTClaims(b *testing.B) {
// Data from https://tools.ietf.org/html/rfc8392#appendix-A section A.1
data := hexDecode("a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b71")
cborData := hexDecode("a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b71")
for i := 0; i < b.N; i++ {
var v claims
if err := Unmarshal(data, &v); err != nil {
if err := Unmarshal(cborData, &v); err != nil {
b.Fatal("Unmarshal:", err)
}
}
@ -652,11 +510,11 @@ func BenchmarkUnmarshalCWTClaims(b *testing.B) {
func BenchmarkUnmarshalCWTClaimsWithDupMapKeyOpt(b *testing.B) {
// Data from https://tools.ietf.org/html/rfc8392#appendix-A section A.1
data := hexDecode("a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b71")
cborData := hexDecode("a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b71")
dm, _ := DecOptions{DupMapKey: DupMapKeyEnforcedAPF}.DecMode()
for i := 0; i < b.N; i++ {
var v claims
if err := dm.Unmarshal(data, &v); err != nil {
if err := dm.Unmarshal(cborData, &v); err != nil {
b.Fatal("Unmarshal:", err)
}
}
@ -664,9 +522,9 @@ func BenchmarkUnmarshalCWTClaimsWithDupMapKeyOpt(b *testing.B) {
func BenchmarkMarshalCWTClaims(b *testing.B) {
// Data from https://tools.ietf.org/html/rfc8392#appendix-A section A.1
data := hexDecode("a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b71")
cborData := hexDecode("a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b71")
var v claims
if err := Unmarshal(data, &v); err != nil {
if err := Unmarshal(cborData, &v); err != nil {
b.Fatal("Unmarshal:", err)
}
for i := 0; i < b.N; i++ {
@ -678,10 +536,10 @@ func BenchmarkMarshalCWTClaims(b *testing.B) {
func BenchmarkUnmarshalSenML(b *testing.B) {
// Data from https://tools.ietf.org/html/rfc8428#section-6
data := hexDecode("87a721781b75726e3a6465763a6f773a3130653230373361303130383030363a22fb41d303a15b00106223614120050067766f6c7461676501615602fb405e066666666666a3006763757272656e74062402fb3ff3333333333333a3006763757272656e74062302fb3ff4cccccccccccda3006763757272656e74062202fb3ff6666666666666a3006763757272656e74062102f93e00a3006763757272656e74062002fb3ff999999999999aa3006763757272656e74060002fb3ffb333333333333")
cborData := hexDecode("87a721781b75726e3a6465763a6f773a3130653230373361303130383030363a22fb41d303a15b00106223614120050067766f6c7461676501615602fb405e066666666666a3006763757272656e74062402fb3ff3333333333333a3006763757272656e74062302fb3ff4cccccccccccda3006763757272656e74062202fb3ff6666666666666a3006763757272656e74062102f93e00a3006763757272656e74062002fb3ff999999999999aa3006763757272656e74060002fb3ffb333333333333")
for i := 0; i < b.N; i++ {
var v []SenMLRecord
if err := Unmarshal(data, &v); err != nil {
if err := Unmarshal(cborData, &v); err != nil {
b.Fatal("Unmarshal:", err)
}
}
@ -689,9 +547,9 @@ func BenchmarkUnmarshalSenML(b *testing.B) {
func BenchmarkMarshalSenML(b *testing.B) {
// Data from https://tools.ietf.org/html/rfc8428#section-6
data := hexDecode("87a721781b75726e3a6465763a6f773a3130653230373361303130383030363a22fb41d303a15b00106223614120050067766f6c7461676501615602fb405e066666666666a3006763757272656e74062402fb3ff3333333333333a3006763757272656e74062302fb3ff4cccccccccccda3006763757272656e74062202fb3ff6666666666666a3006763757272656e74062102f93e00a3006763757272656e74062002fb3ff999999999999aa3006763757272656e74060002fb3ffb333333333333")
cborData := hexDecode("87a721781b75726e3a6465763a6f773a3130653230373361303130383030363a22fb41d303a15b00106223614120050067766f6c7461676501615602fb405e066666666666a3006763757272656e74062402fb3ff3333333333333a3006763757272656e74062302fb3ff4cccccccccccda3006763757272656e74062202fb3ff6666666666666a3006763757272656e74062102f93e00a3006763757272656e74062002fb3ff999999999999aa3006763757272656e74060002fb3ffb333333333333")
var v []SenMLRecord
if err := Unmarshal(data, &v); err != nil {
if err := Unmarshal(cborData, &v); err != nil {
b.Fatal("Unmarshal:", err)
}
for i := 0; i < b.N; i++ {
@ -703,9 +561,9 @@ func BenchmarkMarshalSenML(b *testing.B) {
func BenchmarkMarshalSenMLShortestFloat16(b *testing.B) {
// Data from https://tools.ietf.org/html/rfc8428#section-6
data := hexDecode("87a721781b75726e3a6465763a6f773a3130653230373361303130383030363a22fb41d303a15b00106223614120050067766f6c7461676501615602fb405e066666666666a3006763757272656e74062402fb3ff3333333333333a3006763757272656e74062302fb3ff4cccccccccccda3006763757272656e74062202fb3ff6666666666666a3006763757272656e74062102f93e00a3006763757272656e74062002fb3ff999999999999aa3006763757272656e74060002fb3ffb333333333333")
cborData := hexDecode("87a721781b75726e3a6465763a6f773a3130653230373361303130383030363a22fb41d303a15b00106223614120050067766f6c7461676501615602fb405e066666666666a3006763757272656e74062402fb3ff3333333333333a3006763757272656e74062302fb3ff4cccccccccccda3006763757272656e74062202fb3ff6666666666666a3006763757272656e74062102f93e00a3006763757272656e74062002fb3ff999999999999aa3006763757272656e74060002fb3ffb333333333333")
var v []SenMLRecord
if err := Unmarshal(data, &v); err != nil {
if err := Unmarshal(cborData, &v); err != nil {
b.Fatal("Unmarshal:", err)
}
em, _ := EncOptions{ShortestFloat: ShortestFloat16}.EncMode()
@ -718,10 +576,10 @@ func BenchmarkMarshalSenMLShortestFloat16(b *testing.B) {
func BenchmarkUnmarshalWebAuthn(b *testing.B) {
// Data generated from Yubico security key
data := hexDecode("a363666d74686669646f2d7532666761747453746d74a26373696758483046022100e7ab373cfbd99fcd55fd59b0f6f17fef5b77a20ddec3db7f7e4d55174e366236022100828336b4822125fb56541fb14a8a273876acd339395ec2dad95cf41c1dd2a9ae637835638159024e3082024a30820132a0030201020204124a72fe300d06092a864886f70d01010b0500302e312c302a0603550403132359756269636f2055324620526f6f742043412053657269616c203435373230303633313020170d3134303830313030303030305a180f32303530303930343030303030305a302c312a302806035504030c2159756269636f205532462045452053657269616c203234393431343937323135383059301306072a8648ce3d020106082a8648ce3d030107034200043d8b1bbd2fcbf6086e107471601468484153c1c6d3b4b68a5e855e6e40757ee22bcd8988bf3befd7cdf21cb0bf5d7a150d844afe98103c6c6607d9faae287c02a33b3039302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e313013060b2b0601040182e51c020101040403020520300d06092a864886f70d01010b05000382010100a14f1eea0076f6b8476a10a2be72e60d0271bb465b2dfbfc7c1bd12d351989917032631d795d097fa30a26a325634e85721bc2d01a86303f6bc075e5997319e122148b0496eec8d1f4f94cf4110de626c289443d1f0f5bbb239ca13e81d1d5aa9df5af8e36126475bfc23af06283157252762ff68879bcf0ef578d55d67f951b4f32b63c8aea5b0f99c67d7d814a7ff5a6f52df83e894a3a5d9c8b82e7f8bc8daf4c80175ff8972fda79333ec465d806eacc948f1bab22045a95558a48c20226dac003d41fbc9e05ea28a6bb5e10a49de060a0a4f6a2676a34d68c4abe8c61874355b9027e828ca9e064b002d62e8d8cf0744921753d35e3c87c5d5779453e7768617574684461746158c449960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d976341000000000000000000000000000000000000000000408903fd7dfd2c9770e98cae0123b13a2c27828a106349bc6277140e7290b7e9eb7976aa3c04ed347027caf7da3a2fa76304751c02208acfc4e7fc6c7ebbc375c8a5010203262001215820ad7f7992c335b90d882b2802061b97a4fabca7e2ee3e7a51e728b8055e4eb9c7225820e0966ba7005987fece6f0e0e13447aa98cec248e4000a594b01b74c1cb1d40b3")
cborData := hexDecode("a363666d74686669646f2d7532666761747453746d74a26373696758483046022100e7ab373cfbd99fcd55fd59b0f6f17fef5b77a20ddec3db7f7e4d55174e366236022100828336b4822125fb56541fb14a8a273876acd339395ec2dad95cf41c1dd2a9ae637835638159024e3082024a30820132a0030201020204124a72fe300d06092a864886f70d01010b0500302e312c302a0603550403132359756269636f2055324620526f6f742043412053657269616c203435373230303633313020170d3134303830313030303030305a180f32303530303930343030303030305a302c312a302806035504030c2159756269636f205532462045452053657269616c203234393431343937323135383059301306072a8648ce3d020106082a8648ce3d030107034200043d8b1bbd2fcbf6086e107471601468484153c1c6d3b4b68a5e855e6e40757ee22bcd8988bf3befd7cdf21cb0bf5d7a150d844afe98103c6c6607d9faae287c02a33b3039302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e313013060b2b0601040182e51c020101040403020520300d06092a864886f70d01010b05000382010100a14f1eea0076f6b8476a10a2be72e60d0271bb465b2dfbfc7c1bd12d351989917032631d795d097fa30a26a325634e85721bc2d01a86303f6bc075e5997319e122148b0496eec8d1f4f94cf4110de626c289443d1f0f5bbb239ca13e81d1d5aa9df5af8e36126475bfc23af06283157252762ff68879bcf0ef578d55d67f951b4f32b63c8aea5b0f99c67d7d814a7ff5a6f52df83e894a3a5d9c8b82e7f8bc8daf4c80175ff8972fda79333ec465d806eacc948f1bab22045a95558a48c20226dac003d41fbc9e05ea28a6bb5e10a49de060a0a4f6a2676a34d68c4abe8c61874355b9027e828ca9e064b002d62e8d8cf0744921753d35e3c87c5d5779453e7768617574684461746158c449960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d976341000000000000000000000000000000000000000000408903fd7dfd2c9770e98cae0123b13a2c27828a106349bc6277140e7290b7e9eb7976aa3c04ed347027caf7da3a2fa76304751c02208acfc4e7fc6c7ebbc375c8a5010203262001215820ad7f7992c335b90d882b2802061b97a4fabca7e2ee3e7a51e728b8055e4eb9c7225820e0966ba7005987fece6f0e0e13447aa98cec248e4000a594b01b74c1cb1d40b3")
for i := 0; i < b.N; i++ {
var v attestationObject
if err := Unmarshal(data, &v); err != nil {
if err := Unmarshal(cborData, &v); err != nil {
b.Fatal("Unmarshal:", err)
}
}
@ -729,9 +587,9 @@ func BenchmarkUnmarshalWebAuthn(b *testing.B) {
func BenchmarkMarshalWebAuthn(b *testing.B) {
// Data generated from Yubico security key
data := hexDecode("a363666d74686669646f2d7532666761747453746d74a26373696758483046022100e7ab373cfbd99fcd55fd59b0f6f17fef5b77a20ddec3db7f7e4d55174e366236022100828336b4822125fb56541fb14a8a273876acd339395ec2dad95cf41c1dd2a9ae637835638159024e3082024a30820132a0030201020204124a72fe300d06092a864886f70d01010b0500302e312c302a0603550403132359756269636f2055324620526f6f742043412053657269616c203435373230303633313020170d3134303830313030303030305a180f32303530303930343030303030305a302c312a302806035504030c2159756269636f205532462045452053657269616c203234393431343937323135383059301306072a8648ce3d020106082a8648ce3d030107034200043d8b1bbd2fcbf6086e107471601468484153c1c6d3b4b68a5e855e6e40757ee22bcd8988bf3befd7cdf21cb0bf5d7a150d844afe98103c6c6607d9faae287c02a33b3039302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e313013060b2b0601040182e51c020101040403020520300d06092a864886f70d01010b05000382010100a14f1eea0076f6b8476a10a2be72e60d0271bb465b2dfbfc7c1bd12d351989917032631d795d097fa30a26a325634e85721bc2d01a86303f6bc075e5997319e122148b0496eec8d1f4f94cf4110de626c289443d1f0f5bbb239ca13e81d1d5aa9df5af8e36126475bfc23af06283157252762ff68879bcf0ef578d55d67f951b4f32b63c8aea5b0f99c67d7d814a7ff5a6f52df83e894a3a5d9c8b82e7f8bc8daf4c80175ff8972fda79333ec465d806eacc948f1bab22045a95558a48c20226dac003d41fbc9e05ea28a6bb5e10a49de060a0a4f6a2676a34d68c4abe8c61874355b9027e828ca9e064b002d62e8d8cf0744921753d35e3c87c5d5779453e7768617574684461746158c449960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d976341000000000000000000000000000000000000000000408903fd7dfd2c9770e98cae0123b13a2c27828a106349bc6277140e7290b7e9eb7976aa3c04ed347027caf7da3a2fa76304751c02208acfc4e7fc6c7ebbc375c8a5010203262001215820ad7f7992c335b90d882b2802061b97a4fabca7e2ee3e7a51e728b8055e4eb9c7225820e0966ba7005987fece6f0e0e13447aa98cec248e4000a594b01b74c1cb1d40b3")
cborData := hexDecode("a363666d74686669646f2d7532666761747453746d74a26373696758483046022100e7ab373cfbd99fcd55fd59b0f6f17fef5b77a20ddec3db7f7e4d55174e366236022100828336b4822125fb56541fb14a8a273876acd339395ec2dad95cf41c1dd2a9ae637835638159024e3082024a30820132a0030201020204124a72fe300d06092a864886f70d01010b0500302e312c302a0603550403132359756269636f2055324620526f6f742043412053657269616c203435373230303633313020170d3134303830313030303030305a180f32303530303930343030303030305a302c312a302806035504030c2159756269636f205532462045452053657269616c203234393431343937323135383059301306072a8648ce3d020106082a8648ce3d030107034200043d8b1bbd2fcbf6086e107471601468484153c1c6d3b4b68a5e855e6e40757ee22bcd8988bf3befd7cdf21cb0bf5d7a150d844afe98103c6c6607d9faae287c02a33b3039302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e313013060b2b0601040182e51c020101040403020520300d06092a864886f70d01010b05000382010100a14f1eea0076f6b8476a10a2be72e60d0271bb465b2dfbfc7c1bd12d351989917032631d795d097fa30a26a325634e85721bc2d01a86303f6bc075e5997319e122148b0496eec8d1f4f94cf4110de626c289443d1f0f5bbb239ca13e81d1d5aa9df5af8e36126475bfc23af06283157252762ff68879bcf0ef578d55d67f951b4f32b63c8aea5b0f99c67d7d814a7ff5a6f52df83e894a3a5d9c8b82e7f8bc8daf4c80175ff8972fda79333ec465d806eacc948f1bab22045a95558a48c20226dac003d41fbc9e05ea28a6bb5e10a49de060a0a4f6a2676a34d68c4abe8c61874355b9027e828ca9e064b002d62e8d8cf0744921753d35e3c87c5d5779453e7768617574684461746158c449960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d976341000000000000000000000000000000000000000000408903fd7dfd2c9770e98cae0123b13a2c27828a106349bc6277140e7290b7e9eb7976aa3c04ed347027caf7da3a2fa76304751c02208acfc4e7fc6c7ebbc375c8a5010203262001215820ad7f7992c335b90d882b2802061b97a4fabca7e2ee3e7a51e728b8055e4eb9c7225820e0966ba7005987fece6f0e0e13447aa98cec248e4000a594b01b74c1cb1d40b3")
var v attestationObject
if err := Unmarshal(data, &v); err != nil {
if err := Unmarshal(cborData, &v); err != nil {
b.Fatal("Unmarshal:", err)
}
for i := 0; i < b.N; i++ {
@ -743,11 +601,11 @@ func BenchmarkMarshalWebAuthn(b *testing.B) {
func BenchmarkUnmarshalCOSEMAC(b *testing.B) {
// Data from https://tools.ietf.org/html/rfc8392#appendix-A section A.4
data := hexDecode("d83dd18443a10104a1044c53796d6d65747269633235365850a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b7148093101ef6d789200")
cborData := hexDecode("d83dd18443a10104a1044c53796d6d65747269633235365850a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b7148093101ef6d789200")
for i := 0; i < b.N; i++ {
var v macedCOSE
if err := Unmarshal(data, &v); err != nil {
if err := Unmarshal(cborData, &v); err != nil {
b.Fatal("Unmarshal:", err)
}
}
@ -755,7 +613,7 @@ func BenchmarkUnmarshalCOSEMAC(b *testing.B) {
func BenchmarkUnmarshalCOSEMACWithTag(b *testing.B) {
// Data from https://tools.ietf.org/html/rfc8392#appendix-A section A.4
data := hexDecode("d83dd18443a10104a1044c53796d6d65747269633235365850a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b7148093101ef6d789200")
cborData := hexDecode("d83dd18443a10104a1044c53796d6d65747269633235365850a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b7148093101ef6d789200")
// Register tag CBOR Web Token (CWT) 61 and COSE_Mac0 17 with macedCOSE type
tags := NewTagSet()
@ -768,17 +626,17 @@ func BenchmarkUnmarshalCOSEMACWithTag(b *testing.B) {
for i := 0; i < b.N; i++ {
var v macedCOSE
if err := dm.Unmarshal(data, &v); err != nil {
if err := dm.Unmarshal(cborData, &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
data := hexDecode("d83dd18443a10104a1044c53796d6d65747269633235365850a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b7148093101ef6d789200")
cborData := hexDecode("d83dd18443a10104a1044c53796d6d65747269633235365850a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b7148093101ef6d789200")
var v macedCOSE
if err := Unmarshal(data, &v); err != nil {
if err := Unmarshal(cborData, &v); err != nil {
b.Fatal("Unmarshal():", err)
}
@ -791,7 +649,7 @@ func BenchmarkMarshalCOSEMAC(b *testing.B) {
func BenchmarkMarshalCOSEMACWithTag(b *testing.B) {
// Data from https://tools.ietf.org/html/rfc8392#appendix-A section A.4
data := hexDecode("d83dd18443a10104a1044c53796d6d65747269633235365850a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b7148093101ef6d789200")
cborData := hexDecode("d83dd18443a10104a1044c53796d6d65747269633235365850a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b7148093101ef6d789200")
// Register tag CBOR Web Token (CWT) 61 and COSE_Mac0 17 with macedCOSE type
tags := NewTagSet()
@ -804,7 +662,7 @@ func BenchmarkMarshalCOSEMACWithTag(b *testing.B) {
em, _ := EncOptions{}.EncModeWithTags(tags)
var v macedCOSE
if err := dm.Unmarshal(data, &v); err != nil {
if err := dm.Unmarshal(cborData, &v); err != nil {
b.Fatal("Unmarshal():", err)
}
@ -814,209 +672,3 @@ 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")
}
}
})
}
}
}

View File

@ -1,63 +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 (
"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
}

View File

@ -1,101 +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 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)
}

173
cache.go
View File

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

182
common.go
View File

@ -1,182 +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 (
"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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@ -1,724 +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 (
"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
}

File diff suppressed because it is too large Load Diff

130
doc.go
View File

@ -2,111 +2,93 @@
// Licensed under the MIT License. See LICENSE in the project root for license information.
/*
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.
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.
Encoding options allow "preferred serialization" by encoding integers and floats
to their smallest forms (e.g. float16) when values fit.
CBOR encoding options allow "preferred serialization" by encoding integers and floats
to their smallest forms (like float16) when values fit.
Struct tags like "keyasint", "toarray" and "omitempty" make CBOR data smaller
and easier to use with structs.
Struct tags like "keyasint", "toarray" and "omitempty" makes CBOR data smaller.
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.
For example, "toarray" makes struct fields encode to array elements. And "keyasint"
makes struct fields encode to elements of CBOR map with int keys.
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
Basics
Function signatures identical to encoding/json include:
Marshal, Unmarshal, NewEncoder, NewDecoder, (*Encoder).Encode, (*Decoder).Decode.
Marshal, Unmarshal, NewEncoder, NewDecoder, encoder.Encode, decoder.Decode.
Standard interfaces include:
Codec functions are available at package-level (using defaults) or by creating modes
from options at runtime.
BinaryMarshaler, BinaryUnmarshaler, Marshaler, and Unmarshaler.
"Mode" in this API means definite way of encoding or decoding. Specifically, EncMode or DecMode.
Custom encoding and decoding is possible by implementing standard interfaces for
user-defined Go types.
EncMode and DecMode interfaces are created from EncOptions or DecOptions structs. For example,
Codec functions are available at package-level (using defaults options) or by
creating modes from options at runtime.
em := cbor.EncOptions{...}.EncMode()
em := cbor.CanonicalEncOptions().EncMode()
em := cbor.CTAP2EncOptions().EncMode()
"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 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)
// NOTE: Both em.Marshal(v) and encoder.Encode(v) use encoding options
// specified during creation of em (encoding mode).
Default Options
# CBOR Options
Default encoding options are listed at https://github.com/fxamacker/cbor#api
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
Struct tags like `cbor:"name,omitempty"` and `json:"name,omitempty"` work as expected.
If both struct tags are specified then `cbor` is used.
@ -119,11 +101,9 @@ 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
Struct tags are listed at https://github.com/fxamacker/cbor#struct-tags-1
Tests and Fuzzing
# 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.
Over 375 tests are included in this package. Cover-guided fuzzing is handled by a separate package:
fxamacker/cbor-fuzz.
*/
package cbor

1509
encode.go

File diff suppressed because it is too large Load Diff

View File

@ -1,94 +0,0 @@
// Copyright (c) Faye Amacker. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.
//go:build 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
}

View File

@ -1,60 +0,0 @@
// Copyright (c) Faye Amacker. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.
//go:build !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" // remove "/v2" suffix if you're not using Go modules (see README.md)
"github.com/fxamacker/cbor/v2"
)
func ExampleMarshal() {
@ -125,9 +125,9 @@ func ExampleUnmarshal() {
Owners []string
Male bool
}
data, _ := hex.DecodeString("a46341676504644e616d656543616e6479664f776e65727382644d617279634a6f65644d616c65f4")
cborData, _ := hex.DecodeString("a46341676504644e616d656543616e6479664f776e65727382644d617279634a6f65644d616c65f4")
var animal Animal
err := cbor.Unmarshal(data, &animal)
err := cbor.Unmarshal(cborData, &animal)
if err != nil {
fmt.Println("error:", err)
}
@ -334,8 +334,8 @@ func ExampleDecoder() {
Owners []string
Male bool
}
data, _ := hex.DecodeString("a46341676504644d616c65f4644e616d656543616e6479664f776e65727382644d617279634a6f65a46341676506644d616c65f5644e616d656452756479664f776e657273816543696e6479a46341676502644d616c65f5644e616d656444756b65664f776e65727381664e6f72746f6e")
dec := cbor.NewDecoder(bytes.NewReader(data))
cborData, _ := hex.DecodeString("a46341676504644d616c65f4644e616d656543616e6479664f776e65727382644d617279634a6f65a46341676506644d616c65f5644e616d656452756479664f776e657273816543696e6479a46341676502644d616c65f5644e616d656444756b65664f776e65727381664e6f72746f6e")
dec := cbor.NewDecoder(bytes.NewReader(cborData))
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
data, _ := hex.DecodeString("a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b71")
cborData, _ := hex.DecodeString("a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b71")
var v claims
if err := cbor.Unmarshal(data, &v); err != nil {
if err := cbor.Unmarshal(cborData, &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
data, _ := hex.DecodeString("a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b71")
cborData, _ := hex.DecodeString("a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b71")
dm, _ := cbor.DecOptions{DupMapKey: cbor.DupMapKeyEnforcedAPF}.DecMode()
var v claims
if err := dm.Unmarshal(data, &v); err != nil {
if err := dm.Unmarshal(cborData, &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
data, _ := hex.DecodeString("d28443a10126a104524173796d6d657472696345434453413235365850a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b7158405427c1ff28d23fbad1f29c4c7c6a555e601d6fa29f9179bc3d7438bacaca5acd08c8d4d4f96131680c429a01f85951ecee743a52b9b63632c57209120e1c9e30")
cborData, _ := hex.DecodeString("d28443a10126a104524173796d6d657472696345434453413235365850a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b7158405427c1ff28d23fbad1f29c4c7c6a555e601d6fa29f9179bc3d7438bacaca5acd08c8d4d4f96131680c429a01f85951ecee743a52b9b63632c57209120e1c9e30")
var v signedCWT
if err := cbor.Unmarshal(data, &v); err != nil {
if err := cbor.Unmarshal(cborData, &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
data, _ := hex.DecodeString("d28443a10126a104524173796d6d657472696345434453413235365850a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b7158405427c1ff28d23fbad1f29c4c7c6a555e601d6fa29f9179bc3d7438bacaca5acd08c8d4d4f96131680c429a01f85951ecee743a52b9b63632c57209120e1c9e30")
cborData, _ := 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(data, &v); err != nil {
if err := dm.Unmarshal(cborData, &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
data, _ := hex.DecodeString("a42050231f4c4d4d3051fdc2ec0a3851d5b3830104024c53796d6d6574726963313238030a")
cborData, _ := hex.DecodeString("a42050231f4c4d4d3051fdc2ec0a3851d5b3830104024c53796d6d6574726963313238030a")
var v coseKey
if err := cbor.Unmarshal(data, &v); err != nil {
if err := cbor.Unmarshal(cborData, &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
data, _ := hex.DecodeString("87a721781b75726e3a6465763a6f773a3130653230373361303130383030363a22fb41d303a15b00106223614120050067766f6c7461676501615602fb405e066666666666a3006763757272656e74062402fb3ff3333333333333a3006763757272656e74062302fb3ff4cccccccccccda3006763757272656e74062202fb3ff6666666666666a3006763757272656e74062102f93e00a3006763757272656e74062002fb3ff999999999999aa3006763757272656e74060002fb3ffb333333333333")
cborData, _ := hex.DecodeString("87a721781b75726e3a6465763a6f773a3130653230373361303130383030363a22fb41d303a15b00106223614120050067766f6c7461676501615602fb405e066666666666a3006763757272656e74062402fb3ff3333333333333a3006763757272656e74062302fb3ff4cccccccccccda3006763757272656e74062202fb3ff6666666666666a3006763757272656e74062102f93e00a3006763757272656e74062002fb3ff999999999999aa3006763757272656e74060002fb3ffb333333333333")
var v []*SenMLRecord
if err := cbor.Unmarshal(data, &v); err != nil {
if err := cbor.Unmarshal(cborData, &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"`
}
data, _ := hex.DecodeString("a363666d74686669646f2d7532666761747453746d74a26373696758483046022100e7ab373cfbd99fcd55fd59b0f6f17fef5b77a20ddec3db7f7e4d55174e366236022100828336b4822125fb56541fb14a8a273876acd339395ec2dad95cf41c1dd2a9ae637835638159024e3082024a30820132a0030201020204124a72fe300d06092a864886f70d01010b0500302e312c302a0603550403132359756269636f2055324620526f6f742043412053657269616c203435373230303633313020170d3134303830313030303030305a180f32303530303930343030303030305a302c312a302806035504030c2159756269636f205532462045452053657269616c203234393431343937323135383059301306072a8648ce3d020106082a8648ce3d030107034200043d8b1bbd2fcbf6086e107471601468484153c1c6d3b4b68a5e855e6e40757ee22bcd8988bf3befd7cdf21cb0bf5d7a150d844afe98103c6c6607d9faae287c02a33b3039302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e313013060b2b0601040182e51c020101040403020520300d06092a864886f70d01010b05000382010100a14f1eea0076f6b8476a10a2be72e60d0271bb465b2dfbfc7c1bd12d351989917032631d795d097fa30a26a325634e85721bc2d01a86303f6bc075e5997319e122148b0496eec8d1f4f94cf4110de626c289443d1f0f5bbb239ca13e81d1d5aa9df5af8e36126475bfc23af06283157252762ff68879bcf0ef578d55d67f951b4f32b63c8aea5b0f99c67d7d814a7ff5a6f52df83e894a3a5d9c8b82e7f8bc8daf4c80175ff8972fda79333ec465d806eacc948f1bab22045a95558a48c20226dac003d41fbc9e05ea28a6bb5e10a49de060a0a4f6a2676a34d68c4abe8c61874355b9027e828ca9e064b002d62e8d8cf0744921753d35e3c87c5d5779453e7768617574684461746158c449960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d976341000000000000000000000000000000000000000000408903fd7dfd2c9770e98cae0123b13a2c27828a106349bc6277140e7290b7e9eb7976aa3c04ed347027caf7da3a2fa76304751c02208acfc4e7fc6c7ebbc375c8a5010203262001215820ad7f7992c335b90d882b2802061b97a4fabca7e2ee3e7a51e728b8055e4eb9c7225820e0966ba7005987fece6f0e0e13447aa98cec248e4000a594b01b74c1cb1d40b3")
cborData, _ := hex.DecodeString("a363666d74686669646f2d7532666761747453746d74a26373696758483046022100e7ab373cfbd99fcd55fd59b0f6f17fef5b77a20ddec3db7f7e4d55174e366236022100828336b4822125fb56541fb14a8a273876acd339395ec2dad95cf41c1dd2a9ae637835638159024e3082024a30820132a0030201020204124a72fe300d06092a864886f70d01010b0500302e312c302a0603550403132359756269636f2055324620526f6f742043412053657269616c203435373230303633313020170d3134303830313030303030305a180f32303530303930343030303030305a302c312a302806035504030c2159756269636f205532462045452053657269616c203234393431343937323135383059301306072a8648ce3d020106082a8648ce3d030107034200043d8b1bbd2fcbf6086e107471601468484153c1c6d3b4b68a5e855e6e40757ee22bcd8988bf3befd7cdf21cb0bf5d7a150d844afe98103c6c6607d9faae287c02a33b3039302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e313013060b2b0601040182e51c020101040403020520300d06092a864886f70d01010b05000382010100a14f1eea0076f6b8476a10a2be72e60d0271bb465b2dfbfc7c1bd12d351989917032631d795d097fa30a26a325634e85721bc2d01a86303f6bc075e5997319e122148b0496eec8d1f4f94cf4110de626c289443d1f0f5bbb239ca13e81d1d5aa9df5af8e36126475bfc23af06283157252762ff68879bcf0ef578d55d67f951b4f32b63c8aea5b0f99c67d7d814a7ff5a6f52df83e894a3a5d9c8b82e7f8bc8daf4c80175ff8972fda79333ec465d806eacc948f1bab22045a95558a48c20226dac003d41fbc9e05ea28a6bb5e10a49de060a0a4f6a2676a34d68c4abe8c61874355b9027e828ca9e064b002d62e8d8cf0744921753d35e3c87c5d5779453e7768617574684461746158c449960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d976341000000000000000000000000000000000000000000408903fd7dfd2c9770e98cae0123b13a2c27828a106349bc6277140e7290b7e9eb7976aa3c04ed347027caf7da3a2fa76304751c02208acfc4e7fc6c7ebbc375c8a5010203262001215820ad7f7992c335b90d882b2802061b97a4fabca7e2ee3e7a51e728b8055e4eb9c7225820e0966ba7005987fece6f0e0e13447aa98cec248e4000a594b01b74c1cb1d40b3")
var v attestationObject
if err := cbor.Unmarshal(data, &v); err != nil {
if err := cbor.Unmarshal(cborData, &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.17 // Compiling with go 1.20+ uses new features and optimizations
go 1.12
require github.com/x448/float16 v0.8.4

View File

@ -1,132 +0,0 @@
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")
}
})
}
}

83
maxdepth_test.go Normal file
View File

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

View File

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

View File

@ -1,177 +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 (
"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,60 +4,48 @@
package cbor
import (
"bytes"
"errors"
"io"
"reflect"
)
// Decoder reads and decodes CBOR values from io.Reader.
// Decoder reads and decodes CBOR values from an input stream.
type Decoder struct {
r io.Reader
d decoder
buf []byte
off int // next read offset in buf
d decodeState
off int // start of unread data in buf
bytesRead int
}
// NewDecoder returns a new decoder that reads and decodes from r using
// the default decoding options.
// NewDecoder returns a new decoder that reads from r using the default decoding options.
func NewDecoder(r io.Reader) *Decoder {
return defaultDecMode.NewDecoder(r)
}
// Decode reads CBOR value and decodes it into the value pointed to by v.
// Decode reads the next CBOR-encoded value from its input and stores it in
// the value pointed to by v.
func (dec *Decoder) Decode(v interface{}) error {
_, err := dec.readNext()
if err != nil {
// Return validation error or read error.
return err
if len(dec.buf) == dec.off {
if n, err := dec.read(); n == 0 {
return err
}
}
dec.d.reset(dec.buf[dec.off:])
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.
err := dec.d.value(v)
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 {
// Return validation error or read error.
return err
if err != io.ErrUnexpectedEOF {
return err
}
// Need to read more data.
if n, e := dec.read(); n == 0 {
return e
}
return dec.Decode(v)
}
dec.off += n
dec.bytesRead += n
return nil
}
@ -66,83 +54,20 @@ func (dec *Decoder) NumBytesRead() int {
return dec.bytesRead
}
// 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)+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:])
n := copy(dec.buf, dec.buf[dec.off:])
dec.buf = dec.buf[:n]
dec.off = 0
}
// 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
}
// Read from reader and reslice buf.
@ -151,16 +76,11 @@ func (dec *Decoder) read() (int, error) {
return n, err
}
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.
// Encoder writes CBOR values to an output stream.
type Encoder struct {
w io.Writer
em *encMode
e *encodeState
indefTypes []cborType
}
@ -169,7 +89,7 @@ func NewEncoder(w io.Writer) *Encoder {
return defaultEncMode.NewEncoder(w)
}
// Encode writes the CBOR encoding of v.
// Encode writes the CBOR encoding of v to the stream.
func (enc *Encoder) Encode(v interface{}) error {
if len(enc.indefTypes) > 0 && v != nil {
indefType := enc.indefTypes[len(enc.indefTypes)-1]
@ -187,27 +107,24 @@ func (enc *Encoder) Encode(v interface{}) error {
}
}
buf := getEncodeBuffer()
err := encode(buf, enc.em, reflect.ValueOf(v))
err := encode(enc.e, enc.em, reflect.ValueOf(v))
if err == nil {
_, err = enc.w.Write(buf.Bytes())
_, err = enc.e.WriteTo(enc.w)
}
putEncodeBuffer(buf)
enc.e.Reset()
return err
}
// StartIndefiniteByteString starts byte string encoding of indefinite length.
// Subsequent calls of (*Encoder).Encode() encodes definite length byte strings
// ("chunks") as one contiguous string until EndIndefinite is called.
// ("chunks") as one continguous 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 contiguous string until EndIndefinite is called.
// ("chunks") as one continguous string until EndIndefinite is called.
func (enc *Encoder) StartIndefiniteTextString() error {
return enc.startIndefinite(cborTypeTextString)
}
@ -231,7 +148,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{cborBreakFlag})
_, err := enc.w.Write([]byte{0xff})
if err == nil {
enc.indefTypes = enc.indefTypes[:len(enc.indefTypes)-1]
}
@ -239,15 +156,15 @@ func (enc *Encoder) EndIndefinite() error {
}
var cborIndefHeader = map[cborType][]byte{
cborTypeByteString: {cborByteStringWithIndefiniteLengthHead},
cborTypeTextString: {cborTextStringWithIndefiniteLengthHead},
cborTypeArray: {cborArrayWithIndefiniteLengthHead},
cborTypeMap: {cborMapWithIndefiniteLengthHead},
cborTypeByteString: {0x5f},
cborTypeTextString: {0x7f},
cborTypeArray: {0x9f},
cborTypeMap: {0xbf},
}
func (enc *Encoder) startIndefinite(typ cborType) error {
if enc.em.indefLength == IndefLengthForbidden {
return &IndefiniteLengthError{typ}
if enc.em.disableIndefiniteLength {
return errors.New("cbor: indefinite-length items are not allowed")
}
_, err := enc.w.Write(cborIndefHeader[typ])
if err == nil {
@ -256,10 +173,12 @@ func (enc *Encoder) startIndefinite(typ cborType) error {
return err
}
// RawMessage is a raw encoded CBOR value.
// 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.
type RawMessage []byte
// MarshalCBOR returns m or CBOR nil if m is nil.
// MarshalCBOR returns m as the CBOR encoding of m.
func (m RawMessage) MarshalCBOR() ([]byte, error) {
if len(m) == 0 {
return cborNil, nil
@ -267,7 +186,7 @@ func (m RawMessage) MarshalCBOR() ([]byte, error) {
return m, nil
}
// UnmarshalCBOR creates a copy of data and saves to *m.
// UnmarshalCBOR sets *m to a copy of data.
func (m *RawMessage) UnmarshalCBOR(data []byte) error {
if m == nil {
return errors.New("cbor.RawMessage: UnmarshalCBOR on nil pointer")

View File

@ -5,11 +5,8 @@ package cbor
import (
"bytes"
"errors"
"fmt"
"io"
"reflect"
"strings"
"testing"
"time"
)
@ -18,293 +15,33 @@ func TestDecoder(t *testing.T) {
var buf bytes.Buffer
for i := 0; i < 5; i++ {
for _, tc := range unmarshalTests {
buf.Write(tc.data)
buf.Write(tc.cborData)
}
}
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)
}
}
}
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 TestDecoderUnmarshalTypeError(t *testing.T) {
var buf bytes.Buffer
decoder := NewDecoder(&buf)
bytesRead := 0
for i := 0; i < 5; i++ {
for _, tc := range unmarshalTests {
for j := 0; j < len(tc.wrongTypes)*2; j++ {
buf.Write(tc.data)
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)
}
} 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)
}
}
}
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)
}
}
}
}
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)
}
}
})
}
}
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))
// no more data
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)
}
@ -313,244 +50,57 @@ func TestDecoderRecoverableReadError(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) {
func TestDecoderUnmarshalTypeError(t *testing.T) {
var buf bytes.Buffer
for i := 0; i < 5; i++ {
for _, tc := range unmarshalTests {
buf.Write(tc.data)
for j := 0; j < len(tc.wrongTypes)*2; j++ {
buf.Write(tc.cborData)
}
}
}
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)
}
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)
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)
}
} else if !reflect.DeepEqual(vi, tc.emptyInterfaceValue) {
t.Errorf("Decode() = %v (%T), want %v (%T)", vi, vi, tc.emptyInterfaceValue, tc.emptyInterfaceValue)
}
}
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)
bytesRead += len(tc.cborData)
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()
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("Skip() returned error %v, want io.EOF (no more data)", err)
t.Errorf("Decode() returned error %v, want io.EOF (no more data)", err)
}
}
@ -565,10 +115,10 @@ func TestDecoderStructTag(t *testing.T) {
B: "B",
C: "C",
}
data := hexDecode("a36161614161626142617a6143") // {"a":"A", "b":"B", "z":"C"}
cborData := hexDecode("a36161614161626142617a6143") // {"a":"A", "b":"B", "z":"C"}
var v strc
dec := NewDecoder(bytes.NewReader(data))
dec := NewDecoder(bytes.NewReader(cborData))
if err := dec.Decode(&v); err != nil {
t.Errorf("Decode() returned error %v", err)
}
@ -577,92 +127,6 @@ 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
@ -673,7 +137,7 @@ func TestEncoder(t *testing.T) {
encoder := em.NewEncoder(&w)
for _, tc := range marshalTests {
for _, value := range tc.values {
want.Write(tc.wantData)
want.Write(tc.cborData)
if err := encoder.Encode(value); err != nil {
t.Fatalf("Encode() returned error %v", err)
@ -692,20 +156,19 @@ func TestEncoderError(t *testing.T) {
wantErrorMsg string
}{
{"channel cannot be marshaled", make(chan bool), "cbor: unsupported type: chan bool"},
{"function cannot be marshaled", func(i int) int { return i * i }, "cbor: unsupported type: func"},
{"function cannot be marshaled", func(i int) int { return i * i }, "cbor: unsupported type: func(int) int"},
{"complex cannot be marshaled", complex(100, 8), "cbor: unsupported type: complex128"},
}
var w bytes.Buffer
encoder := NewEncoder(&w)
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
v := tc.value
err := encoder.Encode(&v)
err := encoder.Encode(&tc.value)
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 !strings.HasPrefix(err.Error(), tc.wantErrorMsg) {
} else if err.Error() != tc.wantErrorMsg {
t.Errorf("Encode(%v) error %q, want %q", tc.value, err.Error(), tc.wantErrorMsg)
}
})
@ -904,39 +367,25 @@ func TestRawMessage(t *testing.T) {
B *RawMessage `cbor:"b"`
C *RawMessage `cbor:"c"`
}
data := hexDecode("a361610161628202036163f6") // {"a": 1, "b": [2, 3], "c": nil},
cborData := 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(data, &v); err != nil {
t.Fatalf("Unmarshal(0x%x) returned error %v", data, err)
if err := Unmarshal(cborData, &v); err != nil {
t.Fatalf("Unmarshal(0x%x) returned error %v", cborData, err)
}
if !reflect.DeepEqual(v, want) {
t.Errorf("Unmarshal(0x%x) returned v %v, want %v", data, v, want)
t.Errorf("Unmarshal(0x%x) returned v %v, want %v", cborData, v, want)
}
b, err := Marshal(v)
if err != nil {
t.Fatalf("Marshal(%+v) returned error %v", v, err)
}
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")
if !bytes.Equal(b, cborData) {
t.Errorf("Marshal(%+v) = 0x%x, want 0x%x", v, b, cborData)
}
}
@ -967,115 +416,10 @@ func TestEmptyRawMessage(t *testing.T) {
func TestNilRawMessageUnmarshalCBORError(t *testing.T) {
wantErrorMsg := "cbor.RawMessage: UnmarshalCBOR on nil pointer"
var r *RawMessage
data := hexDecode("01")
if err := r.UnmarshalCBOR(data); err == nil {
cborData := hexDecode("01")
if err := r.UnmarshalCBOR(cborData); 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,18 +10,16 @@ import (
)
type field struct {
name string
nameAsInt int64 // used to decoder to match field name with CBOR int
cborName []byte
cborNameByteString []byte // major type 2 name encoding iff cborName has major type 3
idx []int
typ reflect.Type
ef encodeFunc
ief isEmptyFunc
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
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
}
type fields []*field
@ -40,13 +38,20 @@ func (x *indexFieldSorter) Swap(i, j int) {
}
func (x *indexFieldSorter) Less(i, j int) bool {
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]
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]
}
}
return len(iIdx) <= len(jIdx)
// fields[i].idx is either the same as, or a subset of fields[j].idx.
return true
}
// nameLevelAndTagFieldSorter sorts fields by field name, idx depth, and presence of tag.
@ -63,54 +68,99 @@ func (x *nameLevelAndTagFieldSorter) Swap(i, j int) {
}
func (x *nameLevelAndTagFieldSorter) Less(i, j int) bool {
fi, fj := x.fields[i], x.fields[j]
if fi.name != fj.name {
return fi.name < fj.name
if x.fields[i].name != x.fields[j].name {
return x.fields[i].name < x.fields[j].name
}
if len(fi.idx) != len(fj.idx) {
return len(fi.idx) < len(fj.idx)
if len(x.fields[i].idx) != len(x.fields[j].idx) {
return len(x.fields[i].idx) < len(x.fields[j].idx)
}
if fi.tagged != fj.tagged {
return fi.tagged
if x.fields[i].tagged != x.fields[j].tagged {
return x.fields[i].tagged
}
return i < j // Field i and j have the same name, depth, and tagged status. Nothing else matters.
}
// 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
}
}
// 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.
// 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)
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.
if len(nTypes) > 0 {
for len(next) > 0 {
current, next = next, map[reflect.Type][][]int{}
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
for structType, structIdx := range current {
if len(structIdx) > 1 {
continue // Fields of the same embedded struct type at the same level are ignored.
}
for len(nTypes) > 0 {
cTypes, nTypes = nTypes, nil
if visited[structType] {
continue
}
visited[structType] = true
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 {
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.
continue
}
// Anonymous field of the same type at deeper nested level is ignored.
if vTypes[t] {
tag := f.Tag.Get("cbor")
if tag == "" {
tag = f.Tag.Get("json")
}
if tag == "-" {
continue
}
vTypes[t] = true
flds, nTypes = appendFields(t, idx[0], flds, nTypes)
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)
}
}
}
@ -118,143 +168,43 @@ func getFields(t reflect.Type) (flds fields, structOptions string) {
sort.Sort(&nameLevelAndTagFieldSorter{flds})
// Keep visible fields.
j := 0 // index of next unique field
for i := 0; i < len(flds); {
visibleFields := flds[:0]
for i, j := 0, 0; i < len(flds); i = j {
name := flds[i].name
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++
for j = i + 1; j < len(flds) && flds[j].name == name; j++ {
}
// 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)
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])
}
}
return flds, nTypes
sort.Sort(&indexFieldSorter{visibleFields})
return visibleFields, structOptions
}
// 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()
}
}
func getFieldNameAndOptionsFromTag(tag string) (name string, omitEmpty bool, keyAsInt bool) {
if tag == "" {
return
}
return fv, nil
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
}

116
tag.go
View File

@ -7,14 +7,24 @@ import (
"sync"
)
// 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.
// Tag represents CBOR tag data, including tag number and unmarshaled tag content.
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 {
@ -28,17 +38,12 @@ func (t *RawTag) UnmarshalCBOR(data []byte) error {
return errors.New("cbor.RawTag: UnmarshalCBOR on nil pointer")
}
// Decoding CBOR null and undefined to cbor.RawTag is no-op.
if len(data) == 1 && (data[0] == 0xf6 || data[0] == 0xf7) {
return nil
}
d := decoder{data: data, dm: defaultDecMode}
d := decodeState{data: data, dm: defaultDecMode}
// Unmarshal tag number.
typ, _, num := d.getHead()
if typ != cborTypeTag {
return &UnmarshalTypeError{CBORType: typ.String(), GoType: typeRawTag.String()}
return &UnmarshalTypeError{Value: typ.String(), Type: typeRawTag}
}
t.Number = num
@ -51,27 +56,14 @@ func (t *RawTag) UnmarshalCBOR(data []byte) error {
// MarshalCBOR returns CBOR encoding of t.
func (t RawTag) MarshalCBOR() ([]byte, error) {
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()
e := getEncodeState()
encodeHead(e, byte(cborTypeTag), t.Number)
content := t.Content
if len(content) == 0 {
content = cborNil
}
buf := make([]byte, len(e.Bytes())+len(content))
buf := make([]byte, len(e.Bytes())+len(t.Content))
n := copy(buf, e.Bytes())
copy(buf[n:], content)
copy(buf[n:], t.Content)
putEncodeBuffer(e)
putEncodeState(e)
return buf, nil
}
@ -92,7 +84,7 @@ const (
)
func (dtm DecTagMode) valid() bool {
return dtm >= 0 && dtm < maxDecTagMode
return dtm < maxDecTagMode
}
// EncTagMode specifies how encoder handles tag number.
@ -109,7 +101,7 @@ const (
)
func (etm EncTagMode) valid() bool {
return etm >= 0 && etm < maxEncTagMode
return etm < maxEncTagMode
}
// TagOptions specifies how encoder and decoder handle tag number.
@ -131,8 +123,7 @@ type TagSet interface {
}
type tagProvider interface {
getTagItemFromType(t reflect.Type) *tagItem
getTypeFromTagNum(num []uint64) reflect.Type
get(t reflect.Type) *tagItem
}
type tagItem struct {
@ -142,25 +133,6 @@ 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
@ -170,19 +142,10 @@ type (
}
)
func (t tagSet) getTagItemFromType(typ reflect.Type) *tagItem {
func (t tagSet) get(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)}
@ -202,13 +165,8 @@ func (t *syncTagSet) Add(opts TagOptions, contentType reflect.Type, num uint64,
}
t.Lock()
defer t.Unlock()
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)
}
if _, ok := t.t[contentType]; ok {
return errors.New("cbor: content type " + contentType.String() + " already exists in TagSet")
}
t.t[contentType] = tag
return nil
@ -224,20 +182,13 @@ func (t *syncTagSet) Remove(contentType reflect.Type) {
t.Unlock()
}
func (t *syncTagSet) getTagItemFromType(typ reflect.Type) *tagItem {
func (t *syncTagSet) get(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")
@ -248,9 +199,6 @@ 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")
}
@ -260,24 +208,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 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(typeMarshaler) && opts.EncTag != EncTagNone {
return nil, errors.New("cbor: cannot add cbor.Marshaler to TagSet with EncTag != EncTagNone")
}
if num == tagNumSelfDescribedCBOR {
return nil, errors.New("cbor: cannot add tag number 55799 to TagSet, it's built-in and ignored automatically")
if reflect.PtrTo(contentType).Implements(typeUnmarshaler) && opts.DecTag != DecTagIgnored {
return nil, errors.New("cbor: cannot add cbor.Unmarshaler to TagSet with DecTag != DecTagIgnored")
}
te := tagItem{num: []uint64{num}, opts: opts, contentType: contentType}
te.num = append(te.num, nestedNum...)
// Cache encoded tag numbers
e := getEncodeBuffer()
e := getEncodeState()
for _, n := range te.num {
encodeHead(e, byte(cborTypeTag), n)
}
te.cborTagNum = make([]byte, e.Len())
copy(te.cborTagNum, e.Bytes())
putEncodeBuffer(e)
putEncodeState(e)
return &te, nil
}

View File

@ -2,9 +2,7 @@ package cbor
import (
"bytes"
"fmt"
"io"
"math/big"
"reflect"
"strings"
"testing"
@ -191,64 +189,6 @@ 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"`
@ -273,17 +213,17 @@ func TestTagToArrayStruct(t *testing.T) {
dm, _ := DecOptions{}.DecModeWithTags(tags)
// Data from https://tools.ietf.org/html/rfc8392#appendix-A section A.3
data := hexDecode("d28443a10126a104524173796d6d657472696345434453413235365850a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b7158405427c1ff28d23fbad1f29c4c7c6a555e601d6fa29f9179bc3d7438bacaca5acd08c8d4d4f96131680c429a01f85951ecee743a52b9b63632c57209120e1c9e30")
cborData := hexDecode("d28443a10126a104524173796d6d657472696345434453413235365850a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b7158405427c1ff28d23fbad1f29c4c7c6a555e601d6fa29f9179bc3d7438bacaca5acd08c8d4d4f96131680c429a01f85951ecee743a52b9b63632c57209120e1c9e30")
var v signedCWT
if err := dm.Unmarshal(data, &v); err != nil {
if err := dm.Unmarshal(cborData, &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)
if !bytes.Equal(b, cborData) {
t.Errorf("Marshal(%+v) = 0x%x, want 0x%x", v, b, cborData)
}
}
@ -313,17 +253,17 @@ func TestNestedTagStruct(t *testing.T) {
dm, _ := DecOptions{}.DecModeWithTags(tags)
// Data from https://tools.ietf.org/html/rfc8392#appendix-A section A.4
data := hexDecode("d83dd18443a10104a1044c53796d6d65747269633235365850a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b7148093101ef6d789200")
cborData := hexDecode("d83dd18443a10104a1044c53796d6d65747269633235365850a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b7148093101ef6d789200")
var v macedCOSE
if err := dm.Unmarshal(data, &v); err != nil {
if err := dm.Unmarshal(cborData, &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)
if !bytes.Equal(b, cborData) {
t.Errorf("Marshal(%+v) = 0x%x, want 0x%x", v, b, cborData)
}
}
@ -393,28 +333,19 @@ func TestAddTagError(t *testing.T) {
wantErrorMsg: "cbor: cannot add cbor.RawTag to TagSet",
},
{
name: "big.Int",
typ: reflect.TypeOf(big.Int{}),
name: "cbor.Unmarshaler",
typ: reflect.TypeOf(number2(0)),
num: 107,
opts: TagOptions{DecTag: DecTagRequired, EncTag: EncTagRequired},
wantErrorMsg: "cbor: cannot add big.Int to TagSet, it's built-in and supported automatically",
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: "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)),
@ -429,27 +360,6 @@ 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 {
@ -469,7 +379,7 @@ func TestAddTagError(t *testing.T) {
}
}
func TestAddDuplicateTagContentTypeError(t *testing.T) {
func TestAddDuplicateTagError(t *testing.T) {
type myInt int
myIntType := reflect.TypeOf(myInt(0))
wantErrorMsg := "cbor: content type cbor.myInt already exists in TagSet"
@ -483,49 +393,7 @@ func TestAddDuplicateTagContentTypeError(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, 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)
t.Errorf("TagSet.Add(%s, %d) returned error msg %q, want %q", myIntType, 100, err, wantErrorMsg)
}
}
@ -906,31 +774,31 @@ func TestDecodeWrongTag(t *testing.T) {
testCases := []struct {
name string
obj interface{}
data []byte
cborData []byte
wantErrorMsg string
}{
{
name: "BinaryMarshaler non-struct",
obj: number(1234567890),
data: hexDecode("d87d4800000000499602d2"),
cborData: 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"},
data: hexDecode("d87d45612C622C63"),
cborData: hexDecode("d87d45612C622C63"),
wantErrorMsg: "cbor: wrong tag number for cbor.stru, got [125], expected [124]",
},
{
name: "non-struct",
obj: myInt(1),
data: hexDecode("d87d01"),
cborData: hexDecode("d87d01"),
wantErrorMsg: "cbor: wrong tag number for cbor.myInt, got [125], expected [100]",
},
{
name: "struct",
obj: s{A: "A", B: "B", C: "C"},
data: hexDecode("d87ea3616161416162614261636143"), // {"a":"A", "b":"B", "c":"C"}
cborData: hexDecode("d87ea3616161416162614261636143"), // {"a":"A", "b":"B", "c":"C"}
wantErrorMsg: "cbor: wrong tag number for cbor.s, got [126], expected [101 102]",
},
}
@ -941,13 +809,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.data, v.Interface()); err == nil {
t.Errorf("Unmarshal(0x%x) didn't return an error", tc.data)
if err := dm.Unmarshal(tc.cborData, v.Interface()); err == nil {
t.Errorf("Unmarshal(0x%x) didn't return an error", tc.cborData)
} else {
if _, ok := err.(*WrongTagError); !ok {
t.Errorf("Unmarshal(0x%x) returned wrong type of error %T, want (*WrongTagError)", tc.data, err)
t.Errorf("Unmarshal(0x%x) returned wrong type of error %T, want (*WrongTagError)", tc.cborData, err)
} else if err.Error() != tc.wantErrorMsg {
t.Errorf("Unmarshal(0x%x) returned error %q, want error %q", tc.data, err.Error(), tc.wantErrorMsg)
t.Errorf("Unmarshal(0x%x) returned error %q, want error %q", tc.cborData, err.Error(), tc.wantErrorMsg)
}
}
})
@ -960,7 +828,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.data, v.Interface()); err != nil {
if err := dm.Unmarshal(tc.cborData, v.Interface()); err != nil {
t.Errorf("Unmarshal() returned error %v", err)
}
if !reflect.DeepEqual(tc.obj, v.Elem().Interface()) {
@ -1050,12 +918,12 @@ func TestDecodeSharedTag(t *testing.T) {
// Decode myInt with tag number 123
var v myInt
wantV := myInt(1)
data := hexDecode("d87b01")
if err = dm.Unmarshal(data, &v); err != nil {
t.Errorf("Unmarshal(0x%x) returned error %v", data, err)
cborData := hexDecode("d87b01")
if err = dm.Unmarshal(cborData, &v); err != nil {
t.Errorf("Unmarshal(0x%x) returned error %v", cborData, err)
}
if !reflect.DeepEqual(v, wantV) {
t.Errorf("Unmarshal(0x%x) = %v (%T), want %v (%T)", data, v, v, wantV, wantV)
t.Errorf("Unmarshal(0x%x) = %v (%T), want %v (%T)", cborData, v, v, wantV, wantV)
}
// Unregister myInt type
@ -1063,12 +931,12 @@ func TestDecodeSharedTag(t *testing.T) {
// Decode myInt without tag number
wantV = myInt(2)
data = hexDecode("02")
if err := dm.Unmarshal(data, &v); err != nil {
t.Errorf("Unmarshal(0x%x) returned error %v", data, err)
cborData = hexDecode("02")
if err := dm.Unmarshal(cborData, &v); err != nil {
t.Errorf("Unmarshal(0x%x) returned error %v", cborData, err)
}
if !reflect.DeepEqual(v, wantV) {
t.Errorf("Unmarshal(0x%x) = %v (%T), want %v (%T)", data, v, v, wantV, wantV)
t.Errorf("Unmarshal(0x%x) = %v (%T), want %v (%T)", cborData, v, v, wantV, wantV)
}
// Register myInt type with tag number 234
@ -1078,12 +946,12 @@ func TestDecodeSharedTag(t *testing.T) {
// Decode myInt with tag number 234
wantV = myInt(3)
data = hexDecode("d8ea03")
if err := dm.Unmarshal(data, &v); err != nil {
t.Errorf("Unmarshal(0x%x) returned error %v", data, err)
cborData = hexDecode("d8ea03")
if err := dm.Unmarshal(cborData, &v); err != nil {
t.Errorf("Unmarshal(0x%x) returned error %v", cborData, err)
}
if !reflect.DeepEqual(v, wantV) {
t.Errorf("Unmarshal(0x%x) = %v (%T), want %v (%T)", data, v, v, wantV, wantV)
t.Errorf("Unmarshal(0x%x) = %v (%T), want %v (%T)", cborData, v, v, wantV, wantV)
}
}
@ -1162,8 +1030,8 @@ func TestEncModeWithTagsError(t *testing.T) {
func TestNilRawTagUnmarshalCBORError(t *testing.T) {
wantErrorMsg := "cbor.RawTag: UnmarshalCBOR on nil pointer"
var tag *RawTag
data := hexDecode("c249010000000000000000")
if err := tag.UnmarshalCBOR(data); err == nil {
cborData := hexDecode("c249010000000000000000")
if err := tag.UnmarshalCBOR(cborData); 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)
@ -1171,12 +1039,12 @@ func TestNilRawTagUnmarshalCBORError(t *testing.T) {
}
func TestTagUnmarshalError(t *testing.T) {
data := hexDecode("d87b61fe") // invalid UTF-8 string
cborData := hexDecode("d87b61fe") // invalid UTF-8 string
var tag Tag
if err := Unmarshal(data, &tag); err == nil {
t.Errorf("Unmarshal(0x%x) didn't return error", data)
if err := Unmarshal(cborData, &tag); err == nil {
t.Errorf("Unmarshal(0x%x) didn't return error", cborData)
} else if err.Error() != invalidUTF8ErrorMsg {
t.Errorf("Unmarshal(0x%x) returned error %q, want %q", data, err.Error(), invalidUTF8ErrorMsg)
t.Errorf("Unmarshal(0x%x) returned error %q, want %q", cborData, err.Error(), invalidUTF8ErrorMsg)
}
}
@ -1193,53 +1061,7 @@ func TestTagMarshalError(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) {
func TestTagMarshal(t *testing.T) {
m := make(map[interface{}]bool)
m[10] = true
m[100] = true
@ -1273,266 +1095,3 @@ func TestEncodeTag(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)
}
})
}
}

393
valid.go
View File

@ -7,10 +7,7 @@ import (
"encoding/binary"
"errors"
"io"
"math"
"strconv"
"github.com/x448/float16"
)
// SyntaxError is a description of a CBOR syntax error.
@ -27,131 +24,59 @@ type SemanticError struct {
func (e *SemanticError) Error() string { return e.msg }
// MaxNestedLevelError indicates exceeded max nested level of any combination of CBOR arrays/maps/tags.
type MaxNestedLevelError struct {
maxNestedLevels int
}
func (e *MaxNestedLevelError) Error() string {
return "cbor: exceeded max nested level " + strconv.Itoa(e.maxNestedLevels)
}
// 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
// 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
}
_, 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 err
}
// 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()
offset, _, err := validInternal(data, 0, 1)
if err != nil {
return 0, err
return nil, err
}
return data[offset:], nil
}
const (
maxNestingLevel = 32
)
// 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))
}
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)
}
return validIndefiniteArrOrMap(data, off, t, depth)
}
dataLen := len(data)
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, errors.New("cbor: " + t.String() + " length " + strconv.FormatUint(val, 10) + " is too large, causing integer overflow")
return 0, 0, errors.New("cbor: " + t.String() + " length " + strconv.FormatUint(val, 10) + " is too large, causing integer overflow")
}
if len(d.data)-d.off < valInt { // valInt+off may overflow integer
return 0, io.ErrUnexpectedEOF
if dataLen-off < valInt { // valInt+off may overflow integer
return 0, 0, io.ErrUnexpectedEOF
}
d.off += valInt
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, errors.New("cbor: " + t.String() + " length " + strconv.FormatUint(val, 10) + " is too large, it would cause integer overflow")
return 0, 0, errors.New("cbor: " + t.String() + " length " + strconv.FormatUint(val, 10) + " is too large, causing 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
@ -159,236 +84,150 @@ func (d *decoder) wellformedInternal(depth int, checkBuiltinTags bool) (int, err
maxDepth := depth
for j := 0; j < count; j++ {
for i := 0; i < valInt; i++ {
var dpt int
if dpt, err = d.wellformedInternal(depth, checkBuiltinTags); err != nil {
return 0, err
var d int
if off, d, err = validInternal(data, off, depth+1); err != nil {
return 0, 0, err
}
if dpt > maxDepth {
maxDepth = dpt // Save max depth
if d > maxDepth {
maxDepth = d // 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 len(d.data) == d.off { // Tag number must be followed by tag content.
return 0, io.ErrUnexpectedEOF
if dataLen == off { // Tag number must be followed by tag content.
return 0, 0, io.ErrUnexpectedEOF
}
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 {
if cborType(data[off]&0xe0) != cborTypeTag {
break
}
if _, _, tagNum, err = d.wellformedHead(); err != nil {
return 0, err
if off, _, _, _, err = validHead(data, off); err != nil {
return 0, 0, err
}
depth++
if depth > d.dm.maxNestedLevels {
return 0, &MaxNestedLevelError{d.dm.maxNestedLevels}
}
}
// Check tag content.
return d.wellformedInternal(depth, checkBuiltinTags)
return validInternal(data, off, depth)
}
return depth, nil
return off, depth, nil
}
// 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) {
// 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) {
var err error
dataLen := len(data)
for {
if len(d.data) == d.off {
return 0, io.ErrUnexpectedEOF
if dataLen == off {
return 0, 0, io.ErrUnexpectedEOF
}
if isBreakFlag(d.data[d.off]) {
d.off++
if data[off] == 0xff {
off++
break
}
// Peek ahead to get next type and indefinite length status.
nt, ai := parseInitialByte(d.data[d.off])
nt := cborType(data[off] & 0xe0)
if t != nt {
return 0, &SyntaxError{"cbor: wrong element type " + nt.String() + " for indefinite-length " + t.String()}
return 0, 0, &SyntaxError{"cbor: wrong element type " + nt.String() + " for indefinite-length " + t.String()}
}
if additionalInformation(ai).isIndefiniteLength() {
return 0, &SyntaxError{"cbor: indefinite-length " + t.String() + " chunk is not definite-length"}
if (data[off] & 0x1f) == 31 {
return 0, 0, &SyntaxError{"cbor: indefinite-length " + t.String() + " chunk is not definite-length"}
}
if depth, err = d.wellformedInternal(depth, checkBuiltinTags); err != nil {
return 0, err
if off, depth, err = validInternal(data, off, depth); err != nil {
return 0, 0, err
}
}
return depth, nil
return off, depth, nil
}
// 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) {
// 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) {
var err error
maxDepth := depth
dataLen := len(data)
i := 0
for {
if len(d.data) == d.off {
return 0, io.ErrUnexpectedEOF
if dataLen == off {
return 0, 0, io.ErrUnexpectedEOF
}
if isBreakFlag(d.data[d.off]) {
d.off++
if data[off] == 0xff {
off++
break
}
var dpt int
if dpt, err = d.wellformedInternal(depth, checkBuiltinTags); err != nil {
return 0, err
var d int
if off, d, err = validInternal(data, off, depth+1); err != nil {
return 0, 0, err
}
if dpt > maxDepth {
maxDepth = dpt
if d > maxDepth {
maxDepth = d
}
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, &SyntaxError{"cbor: unexpected \"break\" code"}
return 0, 0, &SyntaxError{"cbor: unexpected \"break\" code"}
}
return maxDepth, nil
return off, maxDepth, nil
}
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
func validHead(data []byte, off int) (_ int, t cborType, ai byte, val uint64, err error) {
dataLen := len(data) - off
if dataLen == 0 {
return 0, 0, 0, io.ErrUnexpectedEOF
return 0, 0, 0, 0, io.ErrUnexpectedEOF
}
t, ai = parseInitialByte(d.data[d.off])
t = cborType(data[off] & 0xe0)
ai = data[off] & 0x1f
val = uint64(ai)
d.off++
dataLen--
off++
if ai <= maxAdditionalInformationWithoutArgument {
return t, ai, val, nil
if ai < 24 {
return off, t, ai, val, nil
}
if ai == additionalInformationWith1ByteArgument {
const argumentSize = 1
if dataLen < argumentSize {
return 0, 0, 0, io.ErrUnexpectedEOF
if ai == 24 {
if dataLen < 2 {
return 0, 0, 0, 0, io.ErrUnexpectedEOF
}
val = uint64(d.data[d.off])
d.off++
val = uint64(data[off])
off++
if t == cborTypePrimitives && val < 32 {
return 0, 0, 0, &SyntaxError{"cbor: invalid simple value " + strconv.Itoa(int(val)) + " for type " + t.String()}
return 0, 0, 0, 0, &SyntaxError{"cbor: invalid simple value " + strconv.Itoa(int(val)) + " for type " + t.String()}
}
return t, ai, val, nil
return off, t, ai, val, nil
}
if ai == additionalInformationWith2ByteArgument {
const argumentSize = 2
if dataLen < argumentSize {
return 0, 0, 0, io.ErrUnexpectedEOF
if ai == 25 {
if dataLen < 3 {
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
}
}
return t, ai, val, nil
val = uint64(binary.BigEndian.Uint16(data[off : off+2]))
off += 2
return off, t, ai, val, nil
}
if ai == additionalInformationWith4ByteArgument {
const argumentSize = 4
if dataLen < argumentSize {
return 0, 0, 0, io.ErrUnexpectedEOF
if ai == 26 {
if dataLen < 5 {
return 0, 0, 0, 0, io.ErrUnexpectedEOF
}
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
val = uint64(binary.BigEndian.Uint32(data[off : off+4]))
off += 4
return off, t, ai, val, nil
}
if ai == additionalInformationWith8ByteArgument {
const argumentSize = 8
if dataLen < argumentSize {
return 0, 0, 0, io.ErrUnexpectedEOF
if ai == 27 {
if dataLen < 9 {
return 0, 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
val = binary.BigEndian.Uint64(data[off : off+8])
off += 8
return off, t, ai, val, nil
}
if additionalInformation(ai).isIndefiniteLength() {
if ai == 31 {
switch t {
case cborTypePositiveInt, cborTypeNegativeInt, cborTypeTag:
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 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 t, ai, val, nil
return off, t, ai, val, nil
}
// ai == 28, 29, 30
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
return 0, 0, 0, 0, &SyntaxError{"cbor: invalid additional information " + strconv.Itoa(int(ai)) + " for type " + t.String()}
}

View File

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

View File

@ -1,299 +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 (
"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)
}
})
}
}