Compare commits
141 Commits
feature/cb
...
fxamacker/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b94ce794b | ||
|
|
508be9165f | ||
|
|
9251397c64 | ||
|
|
b4af226c7d | ||
|
|
4e825ca4d2 | ||
|
|
ba129ebd42 | ||
|
|
d8e0aa63e6 | ||
|
|
a8dbe3e5c3 | ||
|
|
db9afc542a | ||
|
|
bf9e7d09e2 | ||
|
|
7a9dbd0b0a | ||
|
|
d81767dfcc | ||
|
|
ab84f38373 | ||
|
|
1a69476094 | ||
|
|
6d25d3f82f | ||
|
|
3e7e03652c | ||
|
|
a132c35bfe | ||
|
|
f637d45b5c | ||
|
|
2cdcb17d0c | ||
|
|
0a60138d12 | ||
|
|
cad5c27bc7 | ||
|
|
6d626276a0 | ||
|
|
4c238c9a21 | ||
|
|
8caf8f0327 | ||
|
|
23293ce720 | ||
|
|
dce29d6706 | ||
|
|
fe7bae620f | ||
|
|
a1b00ee9dc | ||
|
|
c15166b972 | ||
|
|
96cabe1120 | ||
|
|
2779884d30 | ||
|
|
92f7b97a36 | ||
|
|
df7db22e3a | ||
|
|
b961ec4927 | ||
|
|
b4748bf734 | ||
|
|
148e522e0a | ||
|
|
08b5ff9fc2 | ||
|
|
b95b6910f2 | ||
|
|
b46b74de47 | ||
|
|
80633d5197 | ||
|
|
474d4b13af | ||
|
|
b4a71df1b6 | ||
|
|
cbe7442e9e | ||
|
|
c9f772d6d1 | ||
|
|
64fbdbc70d | ||
|
|
fe81c11acb | ||
|
|
d81ecdda4b | ||
|
|
4f3c6a5886 | ||
|
|
621c6665ca | ||
|
|
d34c8ec592 | ||
|
|
69da12b0b4 | ||
|
|
101dea75b0 | ||
|
|
167dc75c19 | ||
|
|
a2a5c5ae80 | ||
|
|
d395852ddb | ||
|
|
166983ea24 | ||
|
|
fcafe06327 | ||
|
|
7c72f96160 | ||
|
|
f30c336bbe | ||
|
|
7f0fef7083 | ||
|
|
337ac607b5 | ||
|
|
b1949d2397 | ||
|
|
cf5fbfb812 | ||
|
|
5ab6c9281a | ||
|
|
9cd84f26d1 | ||
|
|
9357f00454 | ||
|
|
728e5ac128 | ||
|
|
fcb7848012 | ||
|
|
d793074e33 | ||
|
|
94619f8cec | ||
|
|
0263af4182 | ||
|
|
c26c5a219e | ||
|
|
34c6f2b442 | ||
|
|
2e3b625a2f | ||
|
|
50b362d72f | ||
|
|
9fa2077793 | ||
|
|
57985adf1d | ||
|
|
40e05c4d19 | ||
|
|
f406e90b38 | ||
|
|
eccd7b1880 | ||
|
|
0eabc5f7af | ||
|
|
2b74e0134d | ||
|
|
d7cb68ea8f | ||
|
|
9a1f6311b0 | ||
|
|
c15799871f | ||
|
|
b7d18f2be1 | ||
|
|
525ad91862 | ||
|
|
8b7db35d04 | ||
|
|
b3f38d2530 | ||
|
|
8e50eaf1c9 | ||
|
|
7f356f9a8d | ||
|
|
cf2cb89c58 | ||
|
|
ced1b37795 | ||
|
|
82e1a540aa | ||
|
|
dc281024cb | ||
|
|
a16f7faa51 | ||
|
|
4c7fe52c5d | ||
|
|
5c6cbc9801 | ||
|
|
2d0787bd8a | ||
|
|
d68944fb08 | ||
|
|
8baf0c2fd9 | ||
|
|
36bbe36074 | ||
|
|
7391fe8524 | ||
|
|
d4065cf3a1 | ||
|
|
7398b0dd82 | ||
|
|
2b757e6e04 | ||
|
|
64df230cbe | ||
|
|
62b2e360ba | ||
|
|
d38afd46ec | ||
|
|
696cd83ac2 | ||
|
|
3ce8dda993 | ||
|
|
a2f5a4cdc4 | ||
|
|
047288a2ae | ||
|
|
d0435e47f1 | ||
|
|
e268fee497 | ||
|
|
b2962623de | ||
|
|
ae3bcc3e77 | ||
|
|
10f5fdc44b | ||
|
|
b7a1d226a0 | ||
|
|
057222d3ed | ||
|
|
8b9125ba24 | ||
|
|
b43cf6f0ca | ||
|
|
f4132d4af3 | ||
|
|
c0c0026367 | ||
|
|
8c81f98a7f | ||
|
|
0816831c34 | ||
|
|
3dd6572da2 | ||
|
|
b8f4ad5643 | ||
|
|
a9d2bc92fe | ||
|
|
c28eb653a1 | ||
|
|
a5930df35b | ||
|
|
20d7fbce6c | ||
|
|
7c4b10c00a | ||
|
|
46c3919161 | ||
|
|
1af2a8c757 | ||
|
|
28ecbc4ad7 | ||
|
|
3c21ddd603 | ||
|
|
b28c1f17a4 | ||
|
|
d0ba62f16c | ||
|
|
9d631afeec | ||
|
|
c67eb1449b |
12
.github/workflows/ci-go-cover.yml
vendored
12
.github/workflows/ci-go-cover.yml
vendored
@ -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 ≥97%
|
||||
|
||||
# Remove default permissions.
|
||||
permissions: {}
|
||||
@ -25,6 +25,10 @@ on:
|
||||
push:
|
||||
branches: [main, master]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
|
||||
# Verify minimum coverage is reached using `go test -short -cover` on latest-ubuntu with default version of Go.
|
||||
@ -36,11 +40,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
with:
|
||||
go-version: 1.21
|
||||
go-version: 1.24
|
||||
check-latest: true
|
||||
- name: Install x448/float16
|
||||
run: go get github.com/x448/float16@v0.8.4
|
||||
|
||||
19
.github/workflows/ci.yml
vendored
19
.github/workflows/ci.yml
vendored
@ -11,11 +11,16 @@ on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
- 'master'
|
||||
- 'release*'
|
||||
- 'feature/stream-mode'
|
||||
tags:
|
||||
- 'v*'
|
||||
- 'release**' # Match both 'release-v2.3.4' and 'release/*'.
|
||||
- 'feature/**'
|
||||
- 'v**'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
# Test on various OS with default Go version.
|
||||
tests:
|
||||
@ -26,16 +31,16 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest, windows-latest]
|
||||
go-version: [1.17, 1.18, 1.19, '1.20', 1.21, 1.22, 1.23]
|
||||
go-version: ['1.20', 1.21, 1.22, 1.23, 1.24]
|
||||
steps:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
check-latest: true
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
|
||||
21
.github/workflows/codeql-analysis.yml
vendored
21
.github/workflows/codeql-analysis.yml
vendored
@ -5,12 +5,15 @@ permissions: {}
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
# Run on push for all branches.
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ master ]
|
||||
# Run on pull request for all branches.
|
||||
schedule:
|
||||
- cron: '30 5 * * 4'
|
||||
- cron: '30 12 * * *' # Run daily at 12:30 UTC / 7:30 AM Central.
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
@ -28,18 +31,20 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6
|
||||
uses: github/codeql-action/init@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
|
||||
# Don't need setup-go because default version of Go is updated regularly in ubuntu-latest.
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6
|
||||
uses: github/codeql-action/autobuild@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6
|
||||
uses: github/codeql-action/analyze@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0
|
||||
|
||||
38
.github/workflows/govulncheck.yml
vendored
38
.github/workflows/govulncheck.yml
vendored
@ -10,20 +10,15 @@ permissions: {}
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
paths:
|
||||
- '**'
|
||||
- '!**.md'
|
||||
# Run govulncheck on PR for all branches and paths.
|
||||
push:
|
||||
paths:
|
||||
- '**'
|
||||
- '!**.md'
|
||||
branches:
|
||||
- 'main'
|
||||
- 'master'
|
||||
- 'release*'
|
||||
- 'feature/stream-mode'
|
||||
tags:
|
||||
- 'v*'
|
||||
# Run govulncheck on push for all branches and paths.
|
||||
schedule:
|
||||
- cron: '45 12 * * *' # Daily at 12:45 UTC / 7:45 AM Central
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
Check:
|
||||
@ -33,15 +28,18 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout source
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
|
||||
|
||||
- name: Install Go and setup env
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
with:
|
||||
go-version: 1.21.x
|
||||
go-version: 1.23
|
||||
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
|
||||
|
||||
- name: Install latest govulncheck
|
||||
run: go install golang.org/x/vuln/cmd/govulncheck@d1f380186385b4f64e00313f31743df8e4b89a77 # v1.1.4
|
||||
|
||||
- name: Run govulncheck
|
||||
run: govulncheck -show=traces ./...
|
||||
|
||||
12
.github/workflows/safer-golangci-lint.yml
vendored
12
.github/workflows/safer-golangci-lint.yml
vendored
@ -17,13 +17,17 @@ on:
|
||||
|
||||
env:
|
||||
GO_VERSION: '1.22'
|
||||
GOLINTERS_VERSION: 1.56.2
|
||||
GOLINTERS_VERSION: 1.59.1
|
||||
GOLINTERS_ARCH: linux-amd64
|
||||
GOLINTERS_TGZ_DGST: e1c313fb5fc85a33890fdee5dbb1777d1f5829c84d655a47a55688f3aad5e501
|
||||
GOLINTERS_TGZ_DGST: c30696f1292cff8778a495400745f0f9c0406a3f38d8bb12cef48d599f6c7791
|
||||
GOLINTERS_TIMEOUT: 15m
|
||||
OPENSSL_DGST_CMD: openssl dgst -sha256 -r
|
||||
CURL_CMD: curl --proto =https --tlsv1.2 --location --silent --show-error --fail
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
main:
|
||||
name: Lint
|
||||
@ -32,12 +36,12 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout source
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
check-latest: true
|
||||
|
||||
585
README.md
585
README.md
@ -1,6 +1,4 @@
|
||||
# CBOR Codec in Go
|
||||
|
||||
<!-- [](#cbor-library-in-go) -->
|
||||
<h1>CBOR Codec <a href="https://pkg.go.dev/github.com/fxamacker/cbor/v2"><img src="https://raw.githubusercontent.com/fxamacker/images/refs/heads/master/cbor/go-logo-blue.svg" alt="Go logo" style="height: 1em;" align="right"></a></h1>
|
||||
|
||||
[fxamacker/cbor](https://github.com/fxamacker/cbor) is a library for encoding and decoding [CBOR](https://www.rfc-editor.org/info/std94) and [CBOR Sequences](https://www.rfc-editor.org/rfc/rfc8742.html).
|
||||
|
||||
@ -8,23 +6,26 @@ CBOR is a [trusted alternative](https://www.rfc-editor.org/rfc/rfc8949.html#name
|
||||
|
||||
`fxamacker/cbor` is used in projects by Arm Ltd., Cisco, EdgeX Foundry, Flow Foundation, Fraunhofer‑AISEC, Kubernetes, Let's Encrypt (ISRG), Linux Foundation, Microsoft, Mozilla, Oasis Protocol, Tailscale, Teleport, [etc](https://github.com/fxamacker/cbor#who-uses-fxamackercbor).
|
||||
|
||||
See [Quick Start](#quick-start) and [Releases](https://github.com/fxamacker/cbor/releases/). 🆕 `UnmarshalFirst` and `DiagnoseFirst` can decode CBOR Sequences. `cbor.MarshalToBuffer()` and `UserBufferEncMode` accepts user-specified buffer.
|
||||
See [Quick Start](#quick-start) and [Releases](https://github.com/fxamacker/cbor/releases/). 🆕 `UnmarshalFirst` and `DiagnoseFirst` can decode CBOR Sequences. `MarshalToBuffer` and `UserBufferEncMode` accepts user-specified buffer.
|
||||
|
||||
## fxamacker/cbor
|
||||
|
||||
[](https://github.com/fxamacker/cbor/actions?query=workflow%3Aci)
|
||||
[](https://github.com/fxamacker/cbor/actions?query=workflow%3A%22cover+%E2%89%A596%25%22)
|
||||
[](https://github.com/fxamacker/cbor/actions?query=workflow%3A%22cover+%E2%89%A597%25%22)
|
||||
[](https://github.com/fxamacker/cbor/actions/workflows/codeql-analysis.yml)
|
||||
[](#fuzzing-and-code-coverage)
|
||||
[](https://goreportcard.com/report/github.com/fxamacker/cbor)
|
||||
[](https://github.com/fxamacker/cbor#fuzzing-and-code-coverage)
|
||||
|
||||
`fxamacker/cbor` is a CBOR codec in full conformance with [IETF STD 94 (RFC 8949)](https://www.rfc-editor.org/info/std94). It also supports CBOR Sequences ([RFC 8742](https://www.rfc-editor.org/rfc/rfc8742.html)) and Extended Diagnostic Notation ([Appendix G of RFC 8610](https://www.rfc-editor.org/rfc/rfc8610.html#appendix-G)).
|
||||
|
||||
Features include full support for CBOR tags, [Core Deterministic Encoding](https://www.rfc-editor.org/rfc/rfc8949.html#name-core-deterministic-encoding), duplicate map key detection, etc.
|
||||
|
||||
API is mostly same as `encoding/json`, plus interfaces that simplify concurrency and CBOR options.
|
||||
|
||||
Design balances trade-offs between security, speed, concurrency, encoded data size, usability, etc.
|
||||
|
||||
<details><summary>Highlights</summary><p/>
|
||||
<details><summary> 🔎 Highlights</summary><p/>
|
||||
|
||||
__🚀 Speed__
|
||||
|
||||
@ -38,7 +39,7 @@ Codec passed multiple confidential security assessments in 2022. No vulnerabili
|
||||
|
||||
__🗜️ Data Size__
|
||||
|
||||
Struct tags (`toarray`, `keyasint`, `omitempty`) automatically reduce size of encoded structs. Encoding optionally shrinks float64→32→16 when values fit.
|
||||
Struct tag options (`toarray`, `keyasint`, `omitempty`, `omitzero`) and field tag "-" automatically reduce size of encoded structs. Encoding optionally shrinks float64→32→16 when values fit.
|
||||
|
||||
__:jigsaw: Usability__
|
||||
|
||||
@ -58,164 +59,203 @@ Features include CBOR [extension points](https://www.rfc-editor.org/rfc/rfc8949.
|
||||
|
||||
`fxamacker/cbor` has configurable limits, etc. that defend against malicious CBOR data.
|
||||
|
||||
By contrast, `encoding/gob` is [not designed to be hardened against adversarial inputs](https://pkg.go.dev/encoding/gob#hdr-Security).
|
||||
Notably, `fxamacker/cbor` is fast at rejecting malformed CBOR data.
|
||||
|
||||
<details><summary>Example decoding with encoding/gob 💥 fatal error (out of memory)</summary><p/>
|
||||
> [!NOTE]
|
||||
> Benchmarks rejecting 10 bytes of malicious CBOR data decoding to `[]byte`:
|
||||
>
|
||||
> | Codec | Speed (ns/op) | Memory | Allocs |
|
||||
> | :---- | ------------: | -----: | -----: |
|
||||
> | fxamacker/cbor 2.7.0 | 47 ± 7% | 32 B/op | 2 allocs/op |
|
||||
> | ugorji/go 1.2.12 | 5878187 ± 3% | 67111556 B/op | 13 allocs/op |
|
||||
>
|
||||
> Faster hardware (overclocked DDR4 or DDR5) can reduce speed difference.
|
||||
>
|
||||
> <details><summary> 🔎 Benchmark details </summary><p/>
|
||||
>
|
||||
> Latest comparison for decoding CBOR data to Go `[]byte`:
|
||||
> - Input: `[]byte{0x9B, 0x00, 0x00, 0x42, 0xFA, 0x42, 0xFA, 0x42, 0xFA, 0x42}`
|
||||
> - go1.22.7, linux/amd64, i5-13600K (DDR4-2933, disabled e-cores)
|
||||
> - go test -bench=. -benchmem -count=20
|
||||
>
|
||||
> #### Prior comparisons
|
||||
>
|
||||
> | Codec | Speed (ns/op) | Memory | Allocs |
|
||||
> | :---- | ------------: | -----: | -----: |
|
||||
> | fxamacker/cbor 2.5.0-beta2 | 44.33 ± 2% | 32 B/op | 2 allocs/op |
|
||||
> | fxamacker/cbor 0.1.0 - 2.4.0 | ~44.68 ± 6% | 32 B/op | 2 allocs/op |
|
||||
> | ugorji/go 1.2.10 | 5524792.50 ± 3% | 67110491 B/op | 12 allocs/op |
|
||||
> | ugorji/go 1.1.0 - 1.2.6 | 💥 runtime: | out of memory: | cannot allocate |
|
||||
>
|
||||
> - Input: `[]byte{0x9B, 0x00, 0x00, 0x42, 0xFA, 0x42, 0xFA, 0x42, 0xFA, 0x42}`
|
||||
> - go1.19.6, linux/amd64, i5-13600K (DDR4)
|
||||
> - go test -bench=. -benchmem -count=20
|
||||
>
|
||||
> </details>
|
||||
|
||||
```Go
|
||||
// Example of encoding/gob having "fatal error: runtime: out of memory"
|
||||
// while decoding 181 bytes.
|
||||
package main
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
)
|
||||
In contrast, some codecs can crash or use excessive resources while decoding bad data.
|
||||
|
||||
// Example data is from https://github.com/golang/go/issues/24446
|
||||
// (shortened to 181 bytes).
|
||||
const data = "4dffb503010102303001ff30000109010130010800010130010800010130" +
|
||||
"01ffb80001014a01ffb60001014b01ff860001013001ff860001013001ff" +
|
||||
"860001013001ff860001013001ffb80000001eff850401010e3030303030" +
|
||||
"30303030303030303001ff3000010c0104000016ffb70201010830303030" +
|
||||
"3030303001ff3000010c000030ffb6040405fcff00303030303030303030" +
|
||||
"303030303030303030303030303030303030303030303030303030303030" +
|
||||
"30"
|
||||
> [!WARNING]
|
||||
> Go's `encoding/gob` is [not designed to be hardened against adversarial inputs](https://pkg.go.dev/encoding/gob#hdr-Security).
|
||||
>
|
||||
> <details><summary> 🔎 gob fatal error (out of memory) 💥 decoding 181 bytes</summary><p/>
|
||||
>
|
||||
> ```Go
|
||||
> // Example of encoding/gob having "fatal error: runtime: out of memory"
|
||||
> // while decoding 181 bytes (all Go versions as of Dec. 8, 2024).
|
||||
> package main
|
||||
> import (
|
||||
> "bytes"
|
||||
> "encoding/gob"
|
||||
> "encoding/hex"
|
||||
> "fmt"
|
||||
> )
|
||||
>
|
||||
> // Example data is from https://github.com/golang/go/issues/24446
|
||||
> // (shortened to 181 bytes).
|
||||
> const data = "4dffb503010102303001ff30000109010130010800010130010800010130" +
|
||||
> "01ffb80001014a01ffb60001014b01ff860001013001ff860001013001ff" +
|
||||
> "860001013001ff860001013001ffb80000001eff850401010e3030303030" +
|
||||
> "30303030303030303001ff3000010c0104000016ffb70201010830303030" +
|
||||
> "3030303001ff3000010c000030ffb6040405fcff00303030303030303030" +
|
||||
> "303030303030303030303030303030303030303030303030303030303030" +
|
||||
> "30"
|
||||
>
|
||||
> type X struct {
|
||||
> J *X
|
||||
> K map[string]int
|
||||
> }
|
||||
>
|
||||
> func main() {
|
||||
> raw, _ := hex.DecodeString(data)
|
||||
> decoder := gob.NewDecoder(bytes.NewReader(raw))
|
||||
>
|
||||
> var x X
|
||||
> decoder.Decode(&x) // fatal error: runtime: out of memory
|
||||
> fmt.Println("Decoding finished.")
|
||||
> }
|
||||
> ```
|
||||
>
|
||||
>
|
||||
> </details>
|
||||
|
||||
type X struct {
|
||||
J *X
|
||||
K map[string]int
|
||||
}
|
||||
### Smaller Encodings with Struct Tag Options
|
||||
|
||||
func main() {
|
||||
raw, _ := hex.DecodeString(data)
|
||||
decoder := gob.NewDecoder(bytes.NewReader(raw))
|
||||
Struct tags automatically reduce encoded size of structs and improve speed.
|
||||
|
||||
var x X
|
||||
decoder.Decode(&x) // fatal error: runtime: out of memory
|
||||
fmt.Println("Decoding finished.")
|
||||
}
|
||||
```
|
||||
We can write less code by using struct tag options:
|
||||
- `toarray`: encode without field names (decode back to original struct)
|
||||
- `keyasint`: encode field names as integers (decode back to original struct)
|
||||
- `omitempty`: omit empty field when encoding
|
||||
- `omitzero`: omit zero-value field when encoding
|
||||
|
||||
<hr/>
|
||||
|
||||
</details>
|
||||
|
||||
`fxamacker/cbor` is fast at rejecting malformed CBOR data. E.g. attempts to
|
||||
decode 10 bytes of malicious CBOR data to `[]byte` (with default settings):
|
||||
|
||||
| Codec | Speed (ns/op) | Memory | Allocs |
|
||||
| :---- | ------------: | -----: | -----: |
|
||||
| fxamacker/cbor 2.5.0 | 44 ± 5% | 32 B/op | 2 allocs/op |
|
||||
| ugorji/go 1.2.11 | 5353261 ± 4% | 67111321 B/op | 13 allocs/op |
|
||||
|
||||
<details><summary>Benchmark details</summary><p/>
|
||||
|
||||
Latest comparison used:
|
||||
- Input: `[]byte{0x9B, 0x00, 0x00, 0x42, 0xFA, 0x42, 0xFA, 0x42, 0xFA, 0x42}`
|
||||
- go1.19.10, linux/amd64, i5-13600K (disabled all e-cores, DDR4 @2933)
|
||||
- go test -bench=. -benchmem -count=20
|
||||
|
||||
#### Prior comparisons
|
||||
|
||||
| Codec | Speed (ns/op) | Memory | Allocs |
|
||||
| :---- | ------------: | -----: | -----: |
|
||||
| fxamacker/cbor 2.5.0-beta2 | 44.33 ± 2% | 32 B/op | 2 allocs/op |
|
||||
| fxamacker/cbor 0.1.0 - 2.4.0 | ~44.68 ± 6% | 32 B/op | 2 allocs/op |
|
||||
| ugorji/go 1.2.10 | 5524792.50 ± 3% | 67110491 B/op | 12 allocs/op |
|
||||
| ugorji/go 1.1.0 - 1.2.6 | 💥 runtime: | out of memory: | cannot allocate |
|
||||
|
||||
- Input: `[]byte{0x9B, 0x00, 0x00, 0x42, 0xFA, 0x42, 0xFA, 0x42, 0xFA, 0x42}`
|
||||
- go1.19.6, linux/amd64, i5-13600K (DDR4)
|
||||
- go test -bench=. -benchmem -count=20
|
||||
|
||||
<hr/>
|
||||
|
||||
</details>
|
||||
|
||||
### Smaller Encodings with Struct Tags
|
||||
|
||||
Struct tags (`toarray`, `keyasint`, `omitempty`) reduce encoded size of structs.
|
||||
|
||||
<details><summary>Example encoding 3-level nested Go struct to 1 byte CBOR</summary><p/>
|
||||
|
||||
https://go.dev/play/p/YxwvfPdFQG2
|
||||
|
||||
```Go
|
||||
// Example encoding nested struct (with omitempty tag)
|
||||
// - encoding/json: 18 byte JSON
|
||||
// - fxamacker/cbor: 1 byte CBOR
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/fxamacker/cbor/v2"
|
||||
)
|
||||
|
||||
type GrandChild struct {
|
||||
Quux int `json:",omitempty"`
|
||||
}
|
||||
|
||||
type Child struct {
|
||||
Baz int `json:",omitempty"`
|
||||
Qux GrandChild `json:",omitempty"`
|
||||
}
|
||||
|
||||
type Parent struct {
|
||||
Foo Child `json:",omitempty"`
|
||||
Bar int `json:",omitempty"`
|
||||
}
|
||||
|
||||
func cb() {
|
||||
results, _ := cbor.Marshal(Parent{})
|
||||
fmt.Println("hex(CBOR): " + hex.EncodeToString(results))
|
||||
|
||||
text, _ := cbor.Diagnose(results) // Diagnostic Notation
|
||||
fmt.Println("DN: " + text)
|
||||
}
|
||||
|
||||
func js() {
|
||||
results, _ := json.Marshal(Parent{})
|
||||
fmt.Println("hex(JSON): " + hex.EncodeToString(results))
|
||||
|
||||
text := string(results) // JSON
|
||||
fmt.Println("JSON: " + text)
|
||||
}
|
||||
|
||||
func main() {
|
||||
cb()
|
||||
fmt.Println("-------------")
|
||||
js()
|
||||
}
|
||||
```
|
||||
|
||||
Output (DN is Diagnostic Notation):
|
||||
```
|
||||
hex(CBOR): a0
|
||||
DN: {}
|
||||
-------------
|
||||
hex(JSON): 7b22466f6f223a7b22517578223a7b7d7d7d
|
||||
JSON: {"Foo":{"Qux":{}}}
|
||||
```
|
||||
|
||||
<hr/>
|
||||
|
||||
</details>
|
||||
|
||||
Example using different struct tags together:
|
||||
As a special case, struct field tag "-" omits the field.
|
||||
|
||||

|
||||
|
||||
API is mostly same as `encoding/json`, plus interfaces that simplify concurrency for CBOR options.
|
||||
> [!NOTE]
|
||||
> `fxamacker/cbor` can encode a 3-level nested Go struct to 1 byte!
|
||||
> - `encoding/json`: 18 bytes of JSON
|
||||
> - `fxamacker/cbor`: 1 byte of CBOR
|
||||
>
|
||||
> <details><summary> 🔎 Encoding 3-level nested Go struct with omitempty</summary><p/>
|
||||
>
|
||||
> https://go.dev/play/p/YxwvfPdFQG2
|
||||
>
|
||||
> ```Go
|
||||
> // Example encoding nested struct (with omitempty tag)
|
||||
> // - encoding/json: 18 byte JSON
|
||||
> // - fxamacker/cbor: 1 byte CBOR
|
||||
>
|
||||
> package main
|
||||
>
|
||||
> import (
|
||||
> "encoding/hex"
|
||||
> "encoding/json"
|
||||
> "fmt"
|
||||
>
|
||||
> "github.com/fxamacker/cbor/v2"
|
||||
> )
|
||||
>
|
||||
> type GrandChild struct {
|
||||
> Quux int `json:",omitempty"`
|
||||
> }
|
||||
>
|
||||
> type Child struct {
|
||||
> Baz int `json:",omitempty"`
|
||||
> Qux GrandChild `json:",omitempty"`
|
||||
> }
|
||||
>
|
||||
> type Parent struct {
|
||||
> Foo Child `json:",omitempty"`
|
||||
> Bar int `json:",omitempty"`
|
||||
> }
|
||||
>
|
||||
> func cb() {
|
||||
> results, _ := cbor.Marshal(Parent{})
|
||||
> fmt.Println("hex(CBOR): " + hex.EncodeToString(results))
|
||||
>
|
||||
> text, _ := cbor.Diagnose(results) // Diagnostic Notation
|
||||
> fmt.Println("DN: " + text)
|
||||
> }
|
||||
>
|
||||
> func js() {
|
||||
> results, _ := json.Marshal(Parent{})
|
||||
> fmt.Println("hex(JSON): " + hex.EncodeToString(results))
|
||||
>
|
||||
> text := string(results) // JSON
|
||||
> fmt.Println("JSON: " + text)
|
||||
> }
|
||||
>
|
||||
> func main() {
|
||||
> cb()
|
||||
> fmt.Println("-------------")
|
||||
> js()
|
||||
> }
|
||||
> ```
|
||||
>
|
||||
> Output (DN is Diagnostic Notation):
|
||||
> ```
|
||||
> hex(CBOR): a0
|
||||
> DN: {}
|
||||
> -------------
|
||||
> hex(JSON): 7b22466f6f223a7b22517578223a7b7d7d7d
|
||||
> JSON: {"Foo":{"Qux":{}}}
|
||||
> ```
|
||||
>
|
||||
> </details>
|
||||
|
||||
|
||||
## Quick Start
|
||||
|
||||
__Install__: `go get github.com/fxamacker/cbor/v2` and `import "github.com/fxamacker/cbor/v2"`.
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> Tinygo users can try beta/experimental branch [feature/cbor-tinygo-beta](https://github.com/fxamacker/cbor/tree/feature/cbor-tinygo-beta).
|
||||
>
|
||||
> <details><summary> 🔎 More about tinygo feature branch</summary>
|
||||
>
|
||||
> ### Tinygo
|
||||
>
|
||||
> Branch [feature/cbor-tinygo-beta](https://github.com/fxamacker/cbor/tree/feature/cbor-tinygo-beta) is based on fxamacker/cbor v2.7.0 and it can be compiled using tinygo v0.33 (also compiles with golang/go).
|
||||
>
|
||||
> It passes unit tests (with both go1.22 and tinygo v0.33) and is considered beta/experimental for tinygo.
|
||||
>
|
||||
> :warning: The `feature/cbor-tinygo-beta` branch does not get fuzz tested yet.
|
||||
>
|
||||
> Changes in this feature branch only affect tinygo compiled software. Summary of changes:
|
||||
> - default `DecOptions.MaxNestedLevels` is reduced to 16 (was 32). User can specify higher limit but 24+ crashes tests when compiled with tinygo v0.33.
|
||||
> - disabled decoding CBOR tag data to Go interface because tinygo v0.33 is missing needed feature.
|
||||
> - encoding error message can be different when encoding function type.
|
||||
>
|
||||
> Related tinygo issues:
|
||||
> - https://github.com/tinygo-org/tinygo/issues/4277
|
||||
> - https://github.com/tinygo-org/tinygo/issues/4458
|
||||
>
|
||||
> </details>
|
||||
|
||||
|
||||
### Key Points
|
||||
|
||||
This library can encode and decode CBOR (RFC 8949) and CBOR Sequences (RFC 8742).
|
||||
@ -252,16 +292,17 @@ rest, err = cbor.UnmarshalFirst(b, &v) // decode []byte b to v
|
||||
// DiagnoseFirst translates first CBOR data item to text and returns remaining bytes.
|
||||
text, rest, err = cbor.DiagnoseFirst(b) // decode []byte b to Diagnostic Notation text
|
||||
|
||||
// NOTE: Unmarshal returns ExtraneousDataError if there are remaining bytes,
|
||||
// but new funcs UnmarshalFirst and DiagnoseFirst do not.
|
||||
// NOTE: Unmarshal() returns ExtraneousDataError if there are remaining bytes, but
|
||||
// UnmarshalFirst() and DiagnoseFirst() allow trailing bytes.
|
||||
```
|
||||
|
||||
__IMPORTANT__: 👉 CBOR settings allow trade-offs between speed, security, encoding size, etc.
|
||||
|
||||
- Different CBOR libraries may use different default settings.
|
||||
- CBOR-based formats or protocols usually require specific settings.
|
||||
|
||||
For example, WebAuthn uses "CTAP2 Canonical CBOR" which is available as a preset.
|
||||
> [!IMPORTANT]
|
||||
> CBOR settings allow trade-offs between speed, security, encoding size, etc.
|
||||
>
|
||||
> - Different CBOR libraries may use different default settings.
|
||||
> - CBOR-based formats or protocols usually require specific settings.
|
||||
>
|
||||
> For example, WebAuthn uses "CTAP2 Canonical CBOR" which is available as a preset.
|
||||
|
||||
### Presets
|
||||
|
||||
@ -312,9 +353,63 @@ err = em.MarshalToBuffer(v, &buf) // encode v to provided buf
|
||||
|
||||
### Struct Tags
|
||||
|
||||
Struct tags (`toarray`, `keyasint`, `omitempty`) reduce encoded size of structs.
|
||||
Struct tag options (`toarray`, `keyasint`, `omitempty`, `omitzero`) reduce encoded size of structs.
|
||||
|
||||
<details><summary>Example encoding 3-level nested Go struct to 1 byte CBOR</summary><p/>
|
||||
As a special case, struct field tag "-" omits the field.
|
||||
|
||||
<details><summary> 🔎 Example encoding with struct field tag "-"</summary><p/>
|
||||
|
||||
https://go.dev/play/p/aWEIFxd7InX
|
||||
|
||||
```Go
|
||||
// https://github.com/fxamacker/cbor/issues/652
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/fxamacker/cbor/v2"
|
||||
)
|
||||
|
||||
// The `cbor:"-"` tag omits the Type field when encoding to CBOR.
|
||||
type Entity struct {
|
||||
_ struct{} `cbor:",toarray"`
|
||||
ID uint64 `json:"id"`
|
||||
Type string `cbor:"-" json:"typeOf"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
entity := Entity{
|
||||
ID: 1,
|
||||
Type: "int64",
|
||||
Name: "Identifier",
|
||||
}
|
||||
|
||||
c, _ := cbor.Marshal(entity)
|
||||
diag, _ := cbor.Diagnose(c)
|
||||
fmt.Printf("CBOR in hex: %x\n", c)
|
||||
fmt.Printf("CBOR in edn: %s\n", diag)
|
||||
|
||||
j, _ := json.Marshal(entity)
|
||||
fmt.Printf("JSON: %s\n", string(j))
|
||||
|
||||
fmt.Printf("JSON encoding is %d bytes\n", len(j))
|
||||
fmt.Printf("CBOR encoding is %d bytes\n", len(c))
|
||||
|
||||
// Output:
|
||||
// CBOR in hex: 82016a4964656e746966696572
|
||||
// CBOR in edn: [1, "Identifier"]
|
||||
// JSON: {"id":1,"typeOf":"int64","name":"Identifier"}
|
||||
// JSON encoding is 45 bytes
|
||||
// CBOR encoding is 13 bytes
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary> 🔎 Example encoding 3-level nested Go struct to 1 byte CBOR</summary><p/>
|
||||
|
||||
https://go.dev/play/p/YxwvfPdFQG2
|
||||
|
||||
@ -382,13 +477,13 @@ JSON: {"Foo":{"Qux":{}}}
|
||||
|
||||
</details>
|
||||
|
||||
<details><summary>Example using several struct tags</summary><p/>
|
||||
<details><summary> 🔎 Example using struct tag options</summary><p/>
|
||||
|
||||

|
||||
|
||||
</details>
|
||||
|
||||
Struct tags simplify use of CBOR-based protocols that require CBOR arrays or maps with integer keys.
|
||||
Struct tag options simplify use of CBOR-based protocols that require CBOR arrays or maps with integer keys.
|
||||
|
||||
### CBOR Tags
|
||||
|
||||
@ -404,7 +499,7 @@ em, err := opts.EncModeWithSharedTags(ts) // mutable shared CBOR tags
|
||||
|
||||
`TagSet` and modes using it are safe for concurrent use. Equivalent API is available for `DecMode`.
|
||||
|
||||
<details><summary>Example using TagSet and TagOptions</summary><p/>
|
||||
<details><summary> 🔎 Example using TagSet and TagOptions</summary><p/>
|
||||
|
||||
```go
|
||||
// Use signedCWT struct defined in "Decoding CWT" example.
|
||||
@ -430,16 +525,149 @@ if err := dm.Unmarshal(data, &v); err != nil {
|
||||
em, _ := cbor.EncOptions{}.EncModeWithTags(tags)
|
||||
|
||||
// Marshal signedCWT with tag number.
|
||||
if data, err := cbor.Marshal(v); err != nil {
|
||||
if data, err := em.Marshal(v); err != nil {
|
||||
return err
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
👉 `fxamacker/cbor` allows user apps to use almost any current or future CBOR tag number by implementing `cbor.Marshaler` and `cbor.Unmarshaler` interfaces.
|
||||
|
||||
Basically, `MarshalCBOR` and `UnmarshalCBOR` functions can be implemented by user apps and those functions will automatically be called by this CBOR codec's `Marshal`, `Unmarshal`, etc.
|
||||
|
||||
The following [example](https://github.com/fxamacker/cbor/blob/master/example_embedded_json_tag_for_cbor_test.go) shows how to encode and decode a tagged CBOR data item with tag number 262. The tag content is a JSON object "embedded" as a CBOR byte string (major type 2).
|
||||
|
||||
<details><summary> 🔎 Example using Embedded JSON Tag for CBOR (tag 262)</summary>
|
||||
|
||||
```go
|
||||
// https://github.com/fxamacker/cbor/issues/657
|
||||
|
||||
package cbor_test
|
||||
|
||||
// NOTE: RFC 8949 does not mention tag number 262. IANA assigned
|
||||
// CBOR tag number 262 as "Embedded JSON Object" specified by the
|
||||
// document Embedded JSON Tag for CBOR:
|
||||
//
|
||||
// "Tag 262 can be applied to a byte string (major type 2) to indicate
|
||||
// that the byte string is a JSON Object. The length of the byte string
|
||||
// indicates the content."
|
||||
//
|
||||
// For more info, see Embedded JSON Tag for CBOR at:
|
||||
// https://github.com/toravir/CBOR-Tag-Specs/blob/master/embeddedJSON.md
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/fxamacker/cbor/v2"
|
||||
)
|
||||
|
||||
// cborTagNumForEmbeddedJSON is the CBOR tag number 262.
|
||||
const cborTagNumForEmbeddedJSON = 262
|
||||
|
||||
// EmbeddedJSON represents a Go value to be encoded as a tagged CBOR data item
|
||||
// with tag number 262 and the tag content is a JSON object "embedded" as a
|
||||
// CBOR byte string (major type 2).
|
||||
type EmbeddedJSON struct {
|
||||
any
|
||||
}
|
||||
|
||||
func NewEmbeddedJSON(val any) EmbeddedJSON {
|
||||
return EmbeddedJSON{val}
|
||||
}
|
||||
|
||||
// MarshalCBOR encodes EmbeddedJSON to a tagged CBOR data item with the
|
||||
// tag number 262 and the tag content is a JSON object that is
|
||||
// "embedded" as a CBOR byte string.
|
||||
func (v EmbeddedJSON) MarshalCBOR() ([]byte, error) {
|
||||
// Encode v to JSON object.
|
||||
data, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create cbor.Tag representing a tagged CBOR data item.
|
||||
tag := cbor.Tag{
|
||||
Number: cborTagNumForEmbeddedJSON,
|
||||
Content: data,
|
||||
}
|
||||
|
||||
// Marshal to a tagged CBOR data item.
|
||||
return cbor.Marshal(tag)
|
||||
}
|
||||
|
||||
// UnmarshalCBOR decodes a tagged CBOR data item to EmbeddedJSON.
|
||||
// The byte slice provided to this function must contain a single
|
||||
// tagged CBOR data item with the tag number 262 and tag content
|
||||
// must be a JSON object "embedded" as a CBOR byte string.
|
||||
func (v *EmbeddedJSON) UnmarshalCBOR(b []byte) error {
|
||||
// Unmarshal tagged CBOR data item.
|
||||
var tag cbor.Tag
|
||||
if err := cbor.Unmarshal(b, &tag); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check tag number.
|
||||
if tag.Number != cborTagNumForEmbeddedJSON {
|
||||
return fmt.Errorf("got tag number %d, expect tag number %d", tag.Number, cborTagNumForEmbeddedJSON)
|
||||
}
|
||||
|
||||
// Check tag content.
|
||||
jsonData, isByteString := tag.Content.([]byte)
|
||||
if !isByteString {
|
||||
return fmt.Errorf("got tag content type %T, expect tag content []byte", tag.Content)
|
||||
}
|
||||
|
||||
// Unmarshal JSON object.
|
||||
return json.Unmarshal(jsonData, v)
|
||||
}
|
||||
|
||||
// MarshalJSON encodes EmbeddedJSON to a JSON object.
|
||||
func (v EmbeddedJSON) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(v.any)
|
||||
}
|
||||
|
||||
// UnmarshalJSON decodes a JSON object.
|
||||
func (v *EmbeddedJSON) UnmarshalJSON(b []byte) error {
|
||||
dec := json.NewDecoder(bytes.NewReader(b))
|
||||
dec.UseNumber()
|
||||
return dec.Decode(&v.any)
|
||||
}
|
||||
|
||||
func Example_embeddedJSONTagForCBOR() {
|
||||
value := NewEmbeddedJSON(map[string]any{
|
||||
"name": "gopher",
|
||||
"id": json.Number("42"),
|
||||
})
|
||||
|
||||
data, err := cbor.Marshal(value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("cbor: %x\n", data)
|
||||
|
||||
var v EmbeddedJSON
|
||||
err = cbor.Unmarshal(data, &v)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("%+v\n", v.any)
|
||||
for k, v := range v.any.(map[string]any) {
|
||||
fmt.Printf(" %s: %v (%T)\n", k, v, v)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
### Functions and Interfaces
|
||||
|
||||
<details><summary>Functions and interfaces at a glance</summary><p/>
|
||||
<details><summary> 🔎 Functions and interfaces at a glance</summary><p/>
|
||||
|
||||
Common functions with same API as `encoding/json`:
|
||||
- `Marshal`, `Unmarshal`
|
||||
@ -453,7 +681,7 @@ because RFC 8949 treats CBOR data item with remaining bytes as malformed.
|
||||
Other useful functions:
|
||||
- `Diagnose`, `DiagnoseFirst` produce human-readable [Extended Diagnostic Notation](https://www.rfc-editor.org/rfc/rfc8610.html#appendix-G) from CBOR data.
|
||||
- `UnmarshalFirst` decodes first CBOR data item and return any remaining bytes.
|
||||
- `Wellformed` returns true if the the CBOR data item is well-formed.
|
||||
- `Wellformed` returns true if the CBOR data item is well-formed.
|
||||
|
||||
Interfaces identical or comparable to Go `encoding` packages include:
|
||||
`Marshaler`, `Unmarshaler`, `BinaryMarshaler`, and `BinaryUnmarshaler`.
|
||||
@ -472,11 +700,24 @@ Default limits may need to be increased for systems handling very large data (e.
|
||||
|
||||
## Status
|
||||
|
||||
v2.7.0 (June 23, 2024) adds features and improvements that help large projects (e.g. Kubernetes) use CBOR as an alternative to JSON and Protocol Buffers. Other improvements include speedups, improved memory use, bug fixes, new serialization options, etc. It passed fuzz tests (5+ billion executions) and is production quality.
|
||||
v2.8.0 (March 30, 2025) is a small release primarily to add `omitzero` option to struct field tags and fix bugs. It passed fuzz tests (billions of executions) and is production quality.
|
||||
|
||||
v2.8.0 and v2.7.1 fixes these 3 functions (when called directly by user apps) to use same error handling on bad inputs as `cbor.Unmarshal()`:
|
||||
- `ByteString.UnmarshalCBOR()`
|
||||
- `RawTag.UnmarshalCBOR()`
|
||||
- `SimpleValue.UnmarshalCBOR()`
|
||||
|
||||
The above 3 `UnmarshalCBOR()` functions were initially created for internal use and are deprecated now, so please use `Unmarshal()` or `UnmarshalFirst()` instead. To preserve backward compatibility, these deprecated functions were added to fuzz tests and will not be removed in v2.
|
||||
|
||||
The minimum version of Go required to build:
|
||||
- v2.8.0 requires go 1.20.
|
||||
- v2.7.1 and older releases require go 1.17.
|
||||
|
||||
For more details, see [release notes](https://github.com/fxamacker/cbor/releases).
|
||||
|
||||
### Prior Release
|
||||
### Prior Releases
|
||||
|
||||
v2.7.0 (June 23, 2024) adds features and improvements that help large projects (e.g. Kubernetes) use CBOR as an alternative to JSON and Protocol Buffers. Other improvements include speedups, improved memory use, bug fixes, new serialization options, etc. It passed fuzz tests (5+ billion executions) and is production quality.
|
||||
|
||||
[v2.6.0](https://github.com/fxamacker/cbor/releases/tag/v2.6.0) (February 2024) adds important new features, optimizations, and bug fixes. It is especially useful to systems that need to convert data between CBOR and JSON. New options and optimizations improve handling of bignum, integers, maps, and strings.
|
||||
|
||||
@ -489,7 +730,7 @@ See [v2.5.0 release notes](https://github.com/fxamacker/cbor/releases/tag/v2.5.0
|
||||
See ["Version and API Changes"](https://github.com/fxamacker/cbor#versions-and-api-changes) section for more info about version numbering, etc.
|
||||
|
||||
<!--
|
||||
<details><summary>👉 Benchmark Comparison: v2.4.0 vs v2.5.0</summary><p/>
|
||||
<details><summary> 🔎 Benchmark Comparison: v2.4.0 vs v2.5.0</summary><p/>
|
||||
|
||||
TODO: Update to v2.4.0 vs 2.5.0 (not beta2).
|
||||
|
||||
@ -549,7 +790,7 @@ geomean 2.782
|
||||
|
||||
## Who uses fxamacker/cbor
|
||||
|
||||
`fxamacker/cbor` is used in projects by Arm Ltd., Berlin Institute of Health at Charité, Chainlink, Cisco, Confidential Computing Consortium, ConsenSys, Dapper Labs, EdgeX Foundry, F5, FIDO Alliance, Fraunhofer‑AISEC, Kubernetes, Let's Encrypt (ISRG), Linux Foundation, Matrix.org, Microsoft, Mozilla, National Cybersecurity Agency of France (govt), Netherlands (govt), Oasis Protocol, Smallstep, Tailscale, Taurus SA, Teleport, TIBCO, and others.
|
||||
`fxamacker/cbor` is used in projects by Arm Ltd., Berlin Institute of Health at Charité, Chainlink, Cisco, Confidential Computing Consortium, ConsenSys, EdgeX Foundry, F5, Flow Foundation, Fraunhofer‑AISEC, IBM, Kubernetes, Let's Encrypt (ISRG), Linux Foundation, Matrix.org, Microsoft, Mozilla, National Cybersecurity Agency of France (govt), Netherlands (govt), Oasis Protocol, Smallstep, Tailscale, Taurus SA, Teleport, TIBCO, and others.
|
||||
|
||||
`fxamacker/cbor` passed multiple confidential security assessments. A [nonconfidential security assessment](https://github.com/veraison/go-cose/blob/v1.0.0-rc.1/reports/NCC_Microsoft-go-cose-Report_2022-05-26_v1.0.pdf) (prepared by NCC Group for Microsoft Corporation) includes a subset of fxamacker/cbor v2.4.0 in its scope.
|
||||
|
||||
@ -588,7 +829,7 @@ By default, decoder treats time values of floating-point NaN and Infinity as if
|
||||
__Click to expand topic:__
|
||||
|
||||
<details>
|
||||
<summary>Duplicate Map Keys</summary><p>
|
||||
<summary> 🔎 Duplicate Map Keys</summary><p>
|
||||
|
||||
This library provides options for fast detection and rejection of duplicate map keys based on applying a Go-specific data model to CBOR's extended generic data model in order to determine duplicate vs distinct map keys. Detection relies on whether the CBOR map key would be a duplicate "key" when decoded and applied to the user-provided Go map or struct.
|
||||
|
||||
@ -601,7 +842,7 @@ APF suffix means "Allow Partial Fill" so the destination map or struct can conta
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Tag Validity</summary><p>
|
||||
<summary> 🔎 Tag Validity</summary><p>
|
||||
|
||||
This library checks tag validity for built-in tags (currently tag numbers 0, 1, 2, 3, and 55799):
|
||||
|
||||
|
||||
318
bench_test.go
318
bench_test.go
@ -45,7 +45,7 @@ type coseKey struct {
|
||||
IV []byte `cbor:"5,keyasint,omitempty"`
|
||||
CrvOrNOrK RawMessage `cbor:"-1,keyasint,omitempty"` // K for symmetric keys, Crv for elliptic curve keys, N for RSA modulus
|
||||
XOrE RawMessage `cbor:"-2,keyasint,omitempty"` // X for curve x-coordinate, E for RSA public exponent
|
||||
Y RawMessage `cbor:"-3,keyasint,omitempty"` // Y for curve y-cooridate
|
||||
Y RawMessage `cbor:"-3,keyasint,omitempty"` // Y for curve y-coordinate
|
||||
D []byte `cbor:"-4,keyasint,omitempty"`
|
||||
}
|
||||
|
||||
@ -171,33 +171,113 @@ var decodeBenchmarks = []struct {
|
||||
data []byte
|
||||
decodeToTypes []reflect.Type
|
||||
}{
|
||||
{"bool", hexDecode("f5"), []reflect.Type{typeIntf, typeBool}}, // true
|
||||
{"positive int", hexDecode("1bffffffffffffffff"), []reflect.Type{typeIntf, typeUint64}}, // uint64(18446744073709551615)
|
||||
{"negative int", hexDecode("3903e7"), []reflect.Type{typeIntf, typeInt64}}, // int64(-1000)
|
||||
{"float", hexDecode("fbc010666666666666"), []reflect.Type{typeIntf, typeFloat64}}, // float64(-4.1)
|
||||
{"bytes", hexDecode("581a0102030405060708090a0b0c0d0e0f101112131415161718191a"), []reflect.Type{typeIntf, typeByteSlice}}, // []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}
|
||||
{"bytes indef len", hexDecode("5f410141024103410441054106410741084109410a410b410c410d410e410f4110411141124113411441154116411741184119411aff"), []reflect.Type{typeIntf, typeByteSlice}}, // []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}
|
||||
{"text", hexDecode("782b54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f67"), []reflect.Type{typeIntf, typeString}}, // "The quick brown fox jumps over the lazy dog"
|
||||
{"text indef len", hexDecode("7f61546168616561206171617561696163616b612061626172616f6177616e61206166616f61786120616a6175616d617061736120616f61766165617261206174616861656120616c6161617a617961206164616f6167ff"), []reflect.Type{typeIntf, typeString}}, // "The quick brown fox jumps over the lazy dog"
|
||||
{"array", hexDecode("981a0102030405060708090a0b0c0d0e0f101112131415161718181819181a"), []reflect.Type{typeIntf, typeIntSlice}}, // []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}
|
||||
{"array indef len", hexDecode("9f0102030405060708090a0b0c0d0e0f101112131415161718181819181aff"), []reflect.Type{typeIntf, typeIntSlice}}, // []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", hexDecode("ad616161416162614261636143616461446165614561666146616761476168614861696149616a614a616c614c616d614d616e614e"), []reflect.Type{typeIntf, typeMapStringIntf, typeMapStringString}}, // 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"}}
|
||||
{"map indef len", hexDecode("bf616161416162614261636143616461446165614561666146616761476168614861696149616a614a616b614b616c614c616d614d616e614eff"), []reflect.Type{typeIntf, typeMapStringIntf, typeMapStringString}}, // 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"}}
|
||||
{
|
||||
name: "bool",
|
||||
data: mustHexDecode("f5"),
|
||||
decodeToTypes: []reflect.Type{typeIntf, typeBool},
|
||||
}, // true
|
||||
{
|
||||
name: "positive int",
|
||||
data: mustHexDecode("1bffffffffffffffff"),
|
||||
decodeToTypes: []reflect.Type{typeIntf, typeUint64},
|
||||
}, // uint64(18446744073709551615)
|
||||
{
|
||||
name: "negative int",
|
||||
data: mustHexDecode("3903e7"),
|
||||
decodeToTypes: []reflect.Type{typeIntf, typeInt64},
|
||||
}, // int64(-1000)
|
||||
{
|
||||
name: "float",
|
||||
data: mustHexDecode("fbc010666666666666"),
|
||||
decodeToTypes: []reflect.Type{typeIntf, typeFloat64},
|
||||
}, // float64(-4.1)
|
||||
{
|
||||
name: "bytes",
|
||||
data: mustHexDecode("581a0102030405060708090a0b0c0d0e0f101112131415161718191a"),
|
||||
decodeToTypes: []reflect.Type{typeIntf, typeByteSlice},
|
||||
}, // []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}
|
||||
{
|
||||
name: "bytes indef len",
|
||||
data: mustHexDecode("5f410141024103410441054106410741084109410a410b410c410d410e410f4110411141124113411441154116411741184119411aff"),
|
||||
decodeToTypes: []reflect.Type{typeIntf, typeByteSlice},
|
||||
}, // []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}
|
||||
{
|
||||
name: "text",
|
||||
data: mustHexDecode("782b54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f67"),
|
||||
decodeToTypes: []reflect.Type{typeIntf, typeString},
|
||||
}, // "The quick brown fox jumps over the lazy dog"
|
||||
{
|
||||
name: "text indef len",
|
||||
data: mustHexDecode("7f61546168616561206171617561696163616b612061626172616f6177616e61206166616f61786120616a6175616d617061736120616f61766165617261206174616861656120616c6161617a617961206164616f6167ff"),
|
||||
decodeToTypes: []reflect.Type{typeIntf, typeString},
|
||||
}, // "The quick brown fox jumps over the lazy dog"
|
||||
{
|
||||
name: "array",
|
||||
data: mustHexDecode("981a0102030405060708090a0b0c0d0e0f101112131415161718181819181a"),
|
||||
decodeToTypes: []reflect.Type{typeIntf, typeIntSlice},
|
||||
}, // []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}
|
||||
{
|
||||
name: "array indef len",
|
||||
data: mustHexDecode("9f0102030405060708090a0b0c0d0e0f101112131415161718181819181aff"),
|
||||
decodeToTypes: []reflect.Type{typeIntf, typeIntSlice},
|
||||
}, // []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}
|
||||
{
|
||||
name: "map",
|
||||
data: mustHexDecode("ad616161416162614261636143616461446165614561666146616761476168614861696149616a614a616c614c616d614d616e614e"),
|
||||
decodeToTypes: []reflect.Type{typeIntf, typeMapStringIntf, typeMapStringString},
|
||||
}, // 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"}}
|
||||
{
|
||||
name: "map indef len",
|
||||
data: mustHexDecode("bf616161416162614261636143616461446165614561666146616761476168614861696149616a614a616b614b616c614c616d614d616e614eff"),
|
||||
decodeToTypes: []reflect.Type{typeIntf, typeMapStringIntf, typeMapStringString},
|
||||
}, // 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"}}
|
||||
}
|
||||
|
||||
var encodeBenchmarks = []struct {
|
||||
name string
|
||||
data []byte
|
||||
values []interface{}
|
||||
values []any
|
||||
}{
|
||||
{"bool", hexDecode("f5"), []interface{}{true}},
|
||||
{"positive int", hexDecode("1bffffffffffffffff"), []interface{}{uint64(18446744073709551615)}},
|
||||
{"negative int", hexDecode("3903e7"), []interface{}{int64(-1000)}},
|
||||
{"float", hexDecode("fbc010666666666666"), []interface{}{float64(-4.1)}},
|
||||
{"bytes", hexDecode("581a0102030405060708090a0b0c0d0e0f101112131415161718191a"), []interface{}{[]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}}},
|
||||
{"text", hexDecode("782b54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f67"), []interface{}{"The quick brown fox jumps over the lazy dog"}},
|
||||
{"array", hexDecode("981a0102030405060708090a0b0c0d0e0f101112131415161718181819181a"), []interface{}{[]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", 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"}}},
|
||||
{
|
||||
name: "bool",
|
||||
data: mustHexDecode("f5"),
|
||||
values: []any{true},
|
||||
},
|
||||
{
|
||||
name: "positive int",
|
||||
data: mustHexDecode("1bffffffffffffffff"),
|
||||
values: []any{uint64(18446744073709551615)},
|
||||
},
|
||||
{
|
||||
name: "negative int",
|
||||
data: mustHexDecode("3903e7"),
|
||||
values: []any{int64(-1000)},
|
||||
},
|
||||
{
|
||||
name: "float",
|
||||
data: mustHexDecode("fbc010666666666666"),
|
||||
values: []any{float64(-4.1)},
|
||||
},
|
||||
{
|
||||
name: "bytes",
|
||||
data: mustHexDecode("581a0102030405060708090a0b0c0d0e0f101112131415161718191a"),
|
||||
values: []any{[]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}},
|
||||
},
|
||||
{
|
||||
name: "text",
|
||||
data: mustHexDecode("782b54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f67"),
|
||||
values: []any{"The quick brown fox jumps over the lazy dog"},
|
||||
},
|
||||
{
|
||||
name: "array",
|
||||
data: mustHexDecode("981a0102030405060708090a0b0c0d0e0f101112131415161718181819181a"),
|
||||
values: []any{[]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}},
|
||||
},
|
||||
{
|
||||
name: "map",
|
||||
data: mustHexDecode("ad616161416162614261636143616461446165614561666146616761476168614861696149616a614a616c614c616d614d616e614e"),
|
||||
values: []any{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"}},
|
||||
},
|
||||
}
|
||||
|
||||
func BenchmarkUnmarshal(b *testing.B) {
|
||||
@ -224,39 +304,39 @@ func BenchmarkUnmarshal(b *testing.B) {
|
||||
}{
|
||||
// Unmarshal CBOR map with string key to map[string]interface{}.
|
||||
{
|
||||
"CBOR map to Go map[string]interface{}",
|
||||
hexDecode("a86154f56255691bffffffffffffffff61493903e76146fbc0106666666666666142581a0102030405060708090a0b0c0d0e0f101112131415161718191a6153782b54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f6764536c6369981a0102030405060708090a0b0c0d0e0f101112131415161718181819181a634d7373ad6163614361656145616661466167614761686148616e614e616d614d61616141616261426164614461696149616a614a616c614c"),
|
||||
reflect.TypeOf(map[string]interface{}{}),
|
||||
name: "CBOR map to Go map[string]interface{}",
|
||||
data: mustHexDecode("a86154f56255691bffffffffffffffff61493903e76146fbc0106666666666666142581a0102030405060708090a0b0c0d0e0f101112131415161718191a6153782b54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f6764536c6369981a0102030405060708090a0b0c0d0e0f101112131415161718181819181a634d7373ad6163614361656145616661466167614761686148616e614e616d614d61616141616261426164614461696149616a614a616c614c"),
|
||||
decodeToType: reflect.TypeOf(map[string]any{}),
|
||||
},
|
||||
// Unmarshal CBOR map with string key to struct.
|
||||
{
|
||||
"CBOR map to Go struct",
|
||||
hexDecode("a86154f56255491bffffffffffffffff61493903e76146fbc0106666666666666142581a0102030405060708090a0b0c0d0e0f101112131415161718191a6153782b54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f6764536c6369981a0102030405060708090a0b0c0d0e0f101112131415161718181819181a634d7373ad6163614361656145616661466167614761686148616e614e616d614d61616141616261426164614461696149616a614a616c614c"),
|
||||
reflect.TypeOf(T1{}),
|
||||
name: "CBOR map to Go struct",
|
||||
data: mustHexDecode("a86154f56255491bffffffffffffffff61493903e76146fbc0106666666666666142581a0102030405060708090a0b0c0d0e0f101112131415161718191a6153782b54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f6764536c6369981a0102030405060708090a0b0c0d0e0f101112131415161718181819181a634d7373ad6163614361656145616661466167614761686148616e614e616d614d61616141616261426164614461696149616a614a616c614c"),
|
||||
decodeToType: reflect.TypeOf(T1{}),
|
||||
},
|
||||
// Unmarshal CBOR map with integer key, such as COSE Key and SenML, to map[int]interface{}.
|
||||
{
|
||||
"CBOR map to Go map[int]interface{}",
|
||||
hexDecode("a801f5021bffffffffffffffff033903e704fbc01066666666666605581a0102030405060708090a0b0c0d0e0f101112131415161718191a06782b54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f6707981a0102030405060708090a0b0c0d0e0f101112131415161718181819181a08ad61646144616661466167614761686148616d614d616e614e6161614161626142616361436165614561696149616a614a616c614c"),
|
||||
reflect.TypeOf(map[int]interface{}{}),
|
||||
name: "CBOR map to Go map[int]interface{}",
|
||||
data: mustHexDecode("a801f5021bffffffffffffffff033903e704fbc01066666666666605581a0102030405060708090a0b0c0d0e0f101112131415161718191a06782b54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f6707981a0102030405060708090a0b0c0d0e0f101112131415161718181819181a08ad61646144616661466167614761686148616d614d616e614e6161614161626142616361436165614561696149616a614a616c614c"),
|
||||
decodeToType: reflect.TypeOf(map[int]any{}),
|
||||
},
|
||||
// Unmarshal CBOR map with integer key, such as COSE Key and SenML, to struct.
|
||||
{
|
||||
"CBOR map to Go struct keyasint",
|
||||
hexDecode("a801f5021bffffffffffffffff033903e704fbc01066666666666605581a0102030405060708090a0b0c0d0e0f101112131415161718191a06782b54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f6707981a0102030405060708090a0b0c0d0e0f101112131415161718181819181a08ad61646144616661466167614761686148616d614d616e614e6161614161626142616361436165614561696149616a614a616c614c"),
|
||||
reflect.TypeOf(T2{}),
|
||||
name: "CBOR map to Go struct keyasint",
|
||||
data: mustHexDecode("a801f5021bffffffffffffffff033903e704fbc01066666666666605581a0102030405060708090a0b0c0d0e0f101112131415161718191a06782b54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f6707981a0102030405060708090a0b0c0d0e0f101112131415161718181819181a08ad61646144616661466167614761686148616d614d616e614e6161614161626142616361436165614561696149616a614a616c614c"),
|
||||
decodeToType: reflect.TypeOf(T2{}),
|
||||
},
|
||||
// Unmarshal CBOR array of known sequence of data types, such as signed/maced/encrypted CWT, to []interface{}.
|
||||
{
|
||||
"CBOR array to Go []interface{}",
|
||||
hexDecode("88f51bffffffffffffffff3903e7fbc010666666666666581a0102030405060708090a0b0c0d0e0f101112131415161718191a782b54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f67981a0102030405060708090a0b0c0d0e0f101112131415161718181819181aad616261426163614361646144616561456166614661696149616e614e616161416167614761686148616a614a616c614c616d614d"),
|
||||
reflect.TypeOf([]interface{}{}),
|
||||
name: "CBOR array to Go []interface{}",
|
||||
data: mustHexDecode("88f51bffffffffffffffff3903e7fbc010666666666666581a0102030405060708090a0b0c0d0e0f101112131415161718191a782b54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f67981a0102030405060708090a0b0c0d0e0f101112131415161718181819181aad616261426163614361646144616561456166614661696149616e614e616161416167614761686148616a614a616c614c616d614d"),
|
||||
decodeToType: reflect.TypeOf([]any{}),
|
||||
},
|
||||
// Unmarshal CBOR array of known sequence of data types, such as signed/maced/encrypted CWT, to struct.
|
||||
{
|
||||
"CBOR array to Go struct toarray",
|
||||
hexDecode("88f51bffffffffffffffff3903e7fbc010666666666666581a0102030405060708090a0b0c0d0e0f101112131415161718191a782b54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f67981a0102030405060708090a0b0c0d0e0f101112131415161718181819181aad616261426163614361646144616561456166614661696149616e614e616161416167614761686148616a614a616c614c616d614d"),
|
||||
reflect.TypeOf(T3{}),
|
||||
name: "CBOR array to Go struct toarray",
|
||||
data: mustHexDecode("88f51bffffffffffffffff3903e7fbc010666666666666581a0102030405060708090a0b0c0d0e0f101112131415161718191a782b54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f67981a0102030405060708090a0b0c0d0e0f101112131415161718181819181aad616261426163614361646144616561456166614661696149616e614e616161416167614761686148616a614a616c614c616d614d"),
|
||||
decodeToType: reflect.TypeOf(T3{}),
|
||||
},
|
||||
}
|
||||
for _, bm := range moreBenchmarks {
|
||||
@ -273,7 +353,7 @@ func BenchmarkUnmarshal(b *testing.B) {
|
||||
|
||||
func BenchmarkUnmarshalFirst(b *testing.B) {
|
||||
// Random trailing data
|
||||
trailingData := hexDecode("4a6b0f4718c73f391091ea1c")
|
||||
trailingData := mustHexDecode("4a6b0f4718c73f391091ea1c")
|
||||
for _, bm := range decodeBenchmarks {
|
||||
for _, t := range bm.decodeToTypes {
|
||||
name := "CBOR " + bm.name + " to Go " + t.String()
|
||||
@ -297,7 +377,7 @@ func BenchmarkUnmarshalFirst(b *testing.B) {
|
||||
|
||||
func BenchmarkUnmarshalFirstViaDecoder(b *testing.B) {
|
||||
// Random trailing data
|
||||
trailingData := hexDecode("4a6b0f4718c73f391091ea1c")
|
||||
trailingData := mustHexDecode("4a6b0f4718c73f391091ea1c")
|
||||
for _, bm := range decodeBenchmarks {
|
||||
for _, t := range bm.decodeToTypes {
|
||||
name := "CBOR " + bm.name + " to Go " + t.String()
|
||||
@ -384,7 +464,7 @@ func BenchmarkMarshal(b *testing.B) {
|
||||
}
|
||||
}
|
||||
// Marshal map[string]interface{} to CBOR map
|
||||
m1 := map[string]interface{}{ //nolint:dupl
|
||||
m1 := map[string]any{
|
||||
"T": true,
|
||||
"UI": uint(18446744073709551615),
|
||||
"I": -1000,
|
||||
@ -406,7 +486,7 @@ func BenchmarkMarshal(b *testing.B) {
|
||||
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"},
|
||||
}
|
||||
// Marshal map[int]interface{} to CBOR map
|
||||
m2 := map[int]interface{}{ //nolint:dupl
|
||||
m2 := map[int]any{
|
||||
1: true,
|
||||
2: uint(18446744073709551615),
|
||||
3: -1000,
|
||||
@ -428,7 +508,7 @@ func BenchmarkMarshal(b *testing.B) {
|
||||
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"},
|
||||
}
|
||||
// Marshal []interface to CBOR array.
|
||||
slc := []interface{}{
|
||||
slc := []any{
|
||||
true,
|
||||
uint(18446744073709551615),
|
||||
-1000,
|
||||
@ -451,25 +531,61 @@ func BenchmarkMarshal(b *testing.B) {
|
||||
}
|
||||
var moreBenchmarks = []struct {
|
||||
name string
|
||||
value interface{}
|
||||
value any
|
||||
}{
|
||||
{"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},
|
||||
{"Go struct toarray to CBOR array", v3},
|
||||
{
|
||||
name: "Go map[string]interface{} to CBOR map",
|
||||
value: m1,
|
||||
},
|
||||
{
|
||||
name: "Go struct to CBOR map",
|
||||
value: v1,
|
||||
},
|
||||
{
|
||||
name: "Go struct many fields all omitempty all empty to CBOR map",
|
||||
value: ManyFieldsAllOmitEmpty{},
|
||||
},
|
||||
{
|
||||
name: "Go struct some fields all omitempty all empty to CBOR map",
|
||||
value: SomeFieldsAllOmitEmpty{},
|
||||
},
|
||||
{
|
||||
name: "Go struct many fields all omitempty all nonempty to CBOR map",
|
||||
value: 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,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Go struct some fields all omitempty all nonempty to CBOR map",
|
||||
value: SomeFieldsAllOmitEmpty{
|
||||
F01: 1, F02: 1, F03: 1, F04: 1, F05: 1, F06: 1, F07: 1, F08: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Go struct many fields one omitempty to CBOR map",
|
||||
value: ManyFieldsOneOmitEmpty{},
|
||||
},
|
||||
{
|
||||
name: "Go struct some fields one omitempty to CBOR map",
|
||||
value: SomeFieldsOneOmitEmpty{},
|
||||
},
|
||||
{
|
||||
name: "Go map[int]interface{} to CBOR map",
|
||||
value: m2,
|
||||
},
|
||||
{
|
||||
name: "Go struct keyasint to CBOR map",
|
||||
value: v2,
|
||||
},
|
||||
{
|
||||
name: "Go []interface{} to CBOR map",
|
||||
value: slc,
|
||||
},
|
||||
{
|
||||
name: "Go struct toarray to CBOR array",
|
||||
value: v3,
|
||||
},
|
||||
}
|
||||
for _, bm := range moreBenchmarks {
|
||||
b.Run(bm.name, func(b *testing.B) {
|
||||
@ -501,9 +617,17 @@ func BenchmarkMarshalCanonical(b *testing.B) {
|
||||
for _, bm := range []struct {
|
||||
name string
|
||||
data []byte
|
||||
values []interface{}
|
||||
values []any
|
||||
}{
|
||||
{"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 */}},
|
||||
{
|
||||
name: "map",
|
||||
data: mustHexDecode("ad616161416162614261636143616461446165614561666146616761476168614861696149616a614a616c614c616d614d616e614e"),
|
||||
values: []any{
|
||||
map[string]string{"a": "A", "b": "B", "c": "C", "d": "D", "e": "E", "f": "F", "g": "G", "h": "H", "i": "I", "j": "J", "l": "L", "m": "M", "n": "N"},
|
||||
strc{A: "A", B: "B", C: "C", D: "D", E: "E", F: "F", G: "G", H: "H", I: "I", J: "J", L: "L", M: "M", N: "N"},
|
||||
map[int]int{0: 0}, /* single-entry map */
|
||||
},
|
||||
},
|
||||
} {
|
||||
for _, v := range bm.values {
|
||||
name := "Go " + reflect.TypeOf(v).String() + " to CBOR " + bm.name
|
||||
@ -598,9 +722,18 @@ func BenchmarkUnmarshalCOSE(b *testing.B) {
|
||||
name string
|
||||
data []byte
|
||||
}{
|
||||
{"128-Bit Symmetric Key", hexDecode("a42050231f4c4d4d3051fdc2ec0a3851d5b3830104024c53796d6d6574726963313238030a")},
|
||||
{"256-Bit Symmetric Key", hexDecode("a4205820403697de87af64611c1d32a05dab0fe1fcb715a86ab435f1ec99192d795693880104024c53796d6d6574726963323536030a")},
|
||||
{"ECDSA P256 256-Bit Key", hexDecode("a72358206c1382765aec5358f117733d281c1c7bdc39884d04a45a1e6c67c858bc206c1922582060f7f1a780d8a783bfb7a2dd6b2796e8128dbbcef9d3d168db9529971a36e7b9215820143329cce7868e416927599cf65a34f3ce2ffda55a7eca69ed8919a394d42f0f2001010202524173796d6d657472696345434453413235360326")},
|
||||
{
|
||||
name: "128-Bit Symmetric Key",
|
||||
data: mustHexDecode("a42050231f4c4d4d3051fdc2ec0a3851d5b3830104024c53796d6d6574726963313238030a"),
|
||||
},
|
||||
{
|
||||
name: "256-Bit Symmetric Key",
|
||||
data: mustHexDecode("a4205820403697de87af64611c1d32a05dab0fe1fcb715a86ab435f1ec99192d795693880104024c53796d6d6574726963323536030a"),
|
||||
},
|
||||
{
|
||||
name: "ECDSA P256 256-Bit Key",
|
||||
data: mustHexDecode("a72358206c1382765aec5358f117733d281c1c7bdc39884d04a45a1e6c67c858bc206c1922582060f7f1a780d8a783bfb7a2dd6b2796e8128dbbcef9d3d168db9529971a36e7b9215820143329cce7868e416927599cf65a34f3ce2ffda55a7eca69ed8919a394d42f0f2001010202524173796d6d657472696345434453413235360326"),
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
@ -620,9 +753,18 @@ func BenchmarkMarshalCOSE(b *testing.B) {
|
||||
name string
|
||||
data []byte
|
||||
}{
|
||||
{"128-Bit Symmetric Key", hexDecode("a42050231f4c4d4d3051fdc2ec0a3851d5b3830104024c53796d6d6574726963313238030a")},
|
||||
{"256-Bit Symmetric Key", hexDecode("a4205820403697de87af64611c1d32a05dab0fe1fcb715a86ab435f1ec99192d795693880104024c53796d6d6574726963323536030a")},
|
||||
{"ECDSA P256 256-Bit Key", hexDecode("a72358206c1382765aec5358f117733d281c1c7bdc39884d04a45a1e6c67c858bc206c1922582060f7f1a780d8a783bfb7a2dd6b2796e8128dbbcef9d3d168db9529971a36e7b9215820143329cce7868e416927599cf65a34f3ce2ffda55a7eca69ed8919a394d42f0f2001010202524173796d6d657472696345434453413235360326")},
|
||||
{
|
||||
name: "128-Bit Symmetric Key",
|
||||
data: mustHexDecode("a42050231f4c4d4d3051fdc2ec0a3851d5b3830104024c53796d6d6574726963313238030a"),
|
||||
},
|
||||
{
|
||||
name: "256-Bit Symmetric Key",
|
||||
data: mustHexDecode("a4205820403697de87af64611c1d32a05dab0fe1fcb715a86ab435f1ec99192d795693880104024c53796d6d6574726963323536030a"),
|
||||
},
|
||||
{
|
||||
name: "ECDSA P256 256-Bit Key",
|
||||
data: mustHexDecode("a72358206c1382765aec5358f117733d281c1c7bdc39884d04a45a1e6c67c858bc206c1922582060f7f1a780d8a783bfb7a2dd6b2796e8128dbbcef9d3d168db9529971a36e7b9215820143329cce7868e416927599cf65a34f3ce2ffda55a7eca69ed8919a394d42f0f2001010202524173796d6d657472696345434453413235360326"),
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
var v coseKey
|
||||
@ -641,7 +783,7 @@ 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")
|
||||
data := mustHexDecode("a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b71")
|
||||
for i := 0; i < b.N; i++ {
|
||||
var v claims
|
||||
if err := Unmarshal(data, &v); err != nil {
|
||||
@ -652,7 +794,7 @@ 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")
|
||||
data := mustHexDecode("a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b71")
|
||||
dm, _ := DecOptions{DupMapKey: DupMapKeyEnforcedAPF}.DecMode()
|
||||
for i := 0; i < b.N; i++ {
|
||||
var v claims
|
||||
@ -664,7 +806,7 @@ 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")
|
||||
data := mustHexDecode("a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b71")
|
||||
var v claims
|
||||
if err := Unmarshal(data, &v); err != nil {
|
||||
b.Fatal("Unmarshal:", err)
|
||||
@ -678,7 +820,7 @@ func BenchmarkMarshalCWTClaims(b *testing.B) {
|
||||
|
||||
func BenchmarkUnmarshalSenML(b *testing.B) {
|
||||
// Data from https://tools.ietf.org/html/rfc8428#section-6
|
||||
data := hexDecode("87a721781b75726e3a6465763a6f773a3130653230373361303130383030363a22fb41d303a15b00106223614120050067766f6c7461676501615602fb405e066666666666a3006763757272656e74062402fb3ff3333333333333a3006763757272656e74062302fb3ff4cccccccccccda3006763757272656e74062202fb3ff6666666666666a3006763757272656e74062102f93e00a3006763757272656e74062002fb3ff999999999999aa3006763757272656e74060002fb3ffb333333333333")
|
||||
data := mustHexDecode("87a721781b75726e3a6465763a6f773a3130653230373361303130383030363a22fb41d303a15b00106223614120050067766f6c7461676501615602fb405e066666666666a3006763757272656e74062402fb3ff3333333333333a3006763757272656e74062302fb3ff4cccccccccccda3006763757272656e74062202fb3ff6666666666666a3006763757272656e74062102f93e00a3006763757272656e74062002fb3ff999999999999aa3006763757272656e74060002fb3ffb333333333333")
|
||||
for i := 0; i < b.N; i++ {
|
||||
var v []SenMLRecord
|
||||
if err := Unmarshal(data, &v); err != nil {
|
||||
@ -689,7 +831,7 @@ func BenchmarkUnmarshalSenML(b *testing.B) {
|
||||
|
||||
func BenchmarkMarshalSenML(b *testing.B) {
|
||||
// Data from https://tools.ietf.org/html/rfc8428#section-6
|
||||
data := hexDecode("87a721781b75726e3a6465763a6f773a3130653230373361303130383030363a22fb41d303a15b00106223614120050067766f6c7461676501615602fb405e066666666666a3006763757272656e74062402fb3ff3333333333333a3006763757272656e74062302fb3ff4cccccccccccda3006763757272656e74062202fb3ff6666666666666a3006763757272656e74062102f93e00a3006763757272656e74062002fb3ff999999999999aa3006763757272656e74060002fb3ffb333333333333")
|
||||
data := mustHexDecode("87a721781b75726e3a6465763a6f773a3130653230373361303130383030363a22fb41d303a15b00106223614120050067766f6c7461676501615602fb405e066666666666a3006763757272656e74062402fb3ff3333333333333a3006763757272656e74062302fb3ff4cccccccccccda3006763757272656e74062202fb3ff6666666666666a3006763757272656e74062102f93e00a3006763757272656e74062002fb3ff999999999999aa3006763757272656e74060002fb3ffb333333333333")
|
||||
var v []SenMLRecord
|
||||
if err := Unmarshal(data, &v); err != nil {
|
||||
b.Fatal("Unmarshal:", err)
|
||||
@ -703,7 +845,7 @@ func BenchmarkMarshalSenML(b *testing.B) {
|
||||
|
||||
func BenchmarkMarshalSenMLShortestFloat16(b *testing.B) {
|
||||
// Data from https://tools.ietf.org/html/rfc8428#section-6
|
||||
data := hexDecode("87a721781b75726e3a6465763a6f773a3130653230373361303130383030363a22fb41d303a15b00106223614120050067766f6c7461676501615602fb405e066666666666a3006763757272656e74062402fb3ff3333333333333a3006763757272656e74062302fb3ff4cccccccccccda3006763757272656e74062202fb3ff6666666666666a3006763757272656e74062102f93e00a3006763757272656e74062002fb3ff999999999999aa3006763757272656e74060002fb3ffb333333333333")
|
||||
data := mustHexDecode("87a721781b75726e3a6465763a6f773a3130653230373361303130383030363a22fb41d303a15b00106223614120050067766f6c7461676501615602fb405e066666666666a3006763757272656e74062402fb3ff3333333333333a3006763757272656e74062302fb3ff4cccccccccccda3006763757272656e74062202fb3ff6666666666666a3006763757272656e74062102f93e00a3006763757272656e74062002fb3ff999999999999aa3006763757272656e74060002fb3ffb333333333333")
|
||||
var v []SenMLRecord
|
||||
if err := Unmarshal(data, &v); err != nil {
|
||||
b.Fatal("Unmarshal:", err)
|
||||
@ -718,7 +860,7 @@ func BenchmarkMarshalSenMLShortestFloat16(b *testing.B) {
|
||||
|
||||
func BenchmarkUnmarshalWebAuthn(b *testing.B) {
|
||||
// Data generated from Yubico security key
|
||||
data := hexDecode("a363666d74686669646f2d7532666761747453746d74a26373696758483046022100e7ab373cfbd99fcd55fd59b0f6f17fef5b77a20ddec3db7f7e4d55174e366236022100828336b4822125fb56541fb14a8a273876acd339395ec2dad95cf41c1dd2a9ae637835638159024e3082024a30820132a0030201020204124a72fe300d06092a864886f70d01010b0500302e312c302a0603550403132359756269636f2055324620526f6f742043412053657269616c203435373230303633313020170d3134303830313030303030305a180f32303530303930343030303030305a302c312a302806035504030c2159756269636f205532462045452053657269616c203234393431343937323135383059301306072a8648ce3d020106082a8648ce3d030107034200043d8b1bbd2fcbf6086e107471601468484153c1c6d3b4b68a5e855e6e40757ee22bcd8988bf3befd7cdf21cb0bf5d7a150d844afe98103c6c6607d9faae287c02a33b3039302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e313013060b2b0601040182e51c020101040403020520300d06092a864886f70d01010b05000382010100a14f1eea0076f6b8476a10a2be72e60d0271bb465b2dfbfc7c1bd12d351989917032631d795d097fa30a26a325634e85721bc2d01a86303f6bc075e5997319e122148b0496eec8d1f4f94cf4110de626c289443d1f0f5bbb239ca13e81d1d5aa9df5af8e36126475bfc23af06283157252762ff68879bcf0ef578d55d67f951b4f32b63c8aea5b0f99c67d7d814a7ff5a6f52df83e894a3a5d9c8b82e7f8bc8daf4c80175ff8972fda79333ec465d806eacc948f1bab22045a95558a48c20226dac003d41fbc9e05ea28a6bb5e10a49de060a0a4f6a2676a34d68c4abe8c61874355b9027e828ca9e064b002d62e8d8cf0744921753d35e3c87c5d5779453e7768617574684461746158c449960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d976341000000000000000000000000000000000000000000408903fd7dfd2c9770e98cae0123b13a2c27828a106349bc6277140e7290b7e9eb7976aa3c04ed347027caf7da3a2fa76304751c02208acfc4e7fc6c7ebbc375c8a5010203262001215820ad7f7992c335b90d882b2802061b97a4fabca7e2ee3e7a51e728b8055e4eb9c7225820e0966ba7005987fece6f0e0e13447aa98cec248e4000a594b01b74c1cb1d40b3")
|
||||
data := mustHexDecode("a363666d74686669646f2d7532666761747453746d74a26373696758483046022100e7ab373cfbd99fcd55fd59b0f6f17fef5b77a20ddec3db7f7e4d55174e366236022100828336b4822125fb56541fb14a8a273876acd339395ec2dad95cf41c1dd2a9ae637835638159024e3082024a30820132a0030201020204124a72fe300d06092a864886f70d01010b0500302e312c302a0603550403132359756269636f2055324620526f6f742043412053657269616c203435373230303633313020170d3134303830313030303030305a180f32303530303930343030303030305a302c312a302806035504030c2159756269636f205532462045452053657269616c203234393431343937323135383059301306072a8648ce3d020106082a8648ce3d030107034200043d8b1bbd2fcbf6086e107471601468484153c1c6d3b4b68a5e855e6e40757ee22bcd8988bf3befd7cdf21cb0bf5d7a150d844afe98103c6c6607d9faae287c02a33b3039302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e313013060b2b0601040182e51c020101040403020520300d06092a864886f70d01010b05000382010100a14f1eea0076f6b8476a10a2be72e60d0271bb465b2dfbfc7c1bd12d351989917032631d795d097fa30a26a325634e85721bc2d01a86303f6bc075e5997319e122148b0496eec8d1f4f94cf4110de626c289443d1f0f5bbb239ca13e81d1d5aa9df5af8e36126475bfc23af06283157252762ff68879bcf0ef578d55d67f951b4f32b63c8aea5b0f99c67d7d814a7ff5a6f52df83e894a3a5d9c8b82e7f8bc8daf4c80175ff8972fda79333ec465d806eacc948f1bab22045a95558a48c20226dac003d41fbc9e05ea28a6bb5e10a49de060a0a4f6a2676a34d68c4abe8c61874355b9027e828ca9e064b002d62e8d8cf0744921753d35e3c87c5d5779453e7768617574684461746158c449960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d976341000000000000000000000000000000000000000000408903fd7dfd2c9770e98cae0123b13a2c27828a106349bc6277140e7290b7e9eb7976aa3c04ed347027caf7da3a2fa76304751c02208acfc4e7fc6c7ebbc375c8a5010203262001215820ad7f7992c335b90d882b2802061b97a4fabca7e2ee3e7a51e728b8055e4eb9c7225820e0966ba7005987fece6f0e0e13447aa98cec248e4000a594b01b74c1cb1d40b3")
|
||||
for i := 0; i < b.N; i++ {
|
||||
var v attestationObject
|
||||
if err := Unmarshal(data, &v); err != nil {
|
||||
@ -729,7 +871,7 @@ func BenchmarkUnmarshalWebAuthn(b *testing.B) {
|
||||
|
||||
func BenchmarkMarshalWebAuthn(b *testing.B) {
|
||||
// Data generated from Yubico security key
|
||||
data := hexDecode("a363666d74686669646f2d7532666761747453746d74a26373696758483046022100e7ab373cfbd99fcd55fd59b0f6f17fef5b77a20ddec3db7f7e4d55174e366236022100828336b4822125fb56541fb14a8a273876acd339395ec2dad95cf41c1dd2a9ae637835638159024e3082024a30820132a0030201020204124a72fe300d06092a864886f70d01010b0500302e312c302a0603550403132359756269636f2055324620526f6f742043412053657269616c203435373230303633313020170d3134303830313030303030305a180f32303530303930343030303030305a302c312a302806035504030c2159756269636f205532462045452053657269616c203234393431343937323135383059301306072a8648ce3d020106082a8648ce3d030107034200043d8b1bbd2fcbf6086e107471601468484153c1c6d3b4b68a5e855e6e40757ee22bcd8988bf3befd7cdf21cb0bf5d7a150d844afe98103c6c6607d9faae287c02a33b3039302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e313013060b2b0601040182e51c020101040403020520300d06092a864886f70d01010b05000382010100a14f1eea0076f6b8476a10a2be72e60d0271bb465b2dfbfc7c1bd12d351989917032631d795d097fa30a26a325634e85721bc2d01a86303f6bc075e5997319e122148b0496eec8d1f4f94cf4110de626c289443d1f0f5bbb239ca13e81d1d5aa9df5af8e36126475bfc23af06283157252762ff68879bcf0ef578d55d67f951b4f32b63c8aea5b0f99c67d7d814a7ff5a6f52df83e894a3a5d9c8b82e7f8bc8daf4c80175ff8972fda79333ec465d806eacc948f1bab22045a95558a48c20226dac003d41fbc9e05ea28a6bb5e10a49de060a0a4f6a2676a34d68c4abe8c61874355b9027e828ca9e064b002d62e8d8cf0744921753d35e3c87c5d5779453e7768617574684461746158c449960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d976341000000000000000000000000000000000000000000408903fd7dfd2c9770e98cae0123b13a2c27828a106349bc6277140e7290b7e9eb7976aa3c04ed347027caf7da3a2fa76304751c02208acfc4e7fc6c7ebbc375c8a5010203262001215820ad7f7992c335b90d882b2802061b97a4fabca7e2ee3e7a51e728b8055e4eb9c7225820e0966ba7005987fece6f0e0e13447aa98cec248e4000a594b01b74c1cb1d40b3")
|
||||
data := mustHexDecode("a363666d74686669646f2d7532666761747453746d74a26373696758483046022100e7ab373cfbd99fcd55fd59b0f6f17fef5b77a20ddec3db7f7e4d55174e366236022100828336b4822125fb56541fb14a8a273876acd339395ec2dad95cf41c1dd2a9ae637835638159024e3082024a30820132a0030201020204124a72fe300d06092a864886f70d01010b0500302e312c302a0603550403132359756269636f2055324620526f6f742043412053657269616c203435373230303633313020170d3134303830313030303030305a180f32303530303930343030303030305a302c312a302806035504030c2159756269636f205532462045452053657269616c203234393431343937323135383059301306072a8648ce3d020106082a8648ce3d030107034200043d8b1bbd2fcbf6086e107471601468484153c1c6d3b4b68a5e855e6e40757ee22bcd8988bf3befd7cdf21cb0bf5d7a150d844afe98103c6c6607d9faae287c02a33b3039302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e313013060b2b0601040182e51c020101040403020520300d06092a864886f70d01010b05000382010100a14f1eea0076f6b8476a10a2be72e60d0271bb465b2dfbfc7c1bd12d351989917032631d795d097fa30a26a325634e85721bc2d01a86303f6bc075e5997319e122148b0496eec8d1f4f94cf4110de626c289443d1f0f5bbb239ca13e81d1d5aa9df5af8e36126475bfc23af06283157252762ff68879bcf0ef578d55d67f951b4f32b63c8aea5b0f99c67d7d814a7ff5a6f52df83e894a3a5d9c8b82e7f8bc8daf4c80175ff8972fda79333ec465d806eacc948f1bab22045a95558a48c20226dac003d41fbc9e05ea28a6bb5e10a49de060a0a4f6a2676a34d68c4abe8c61874355b9027e828ca9e064b002d62e8d8cf0744921753d35e3c87c5d5779453e7768617574684461746158c449960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d976341000000000000000000000000000000000000000000408903fd7dfd2c9770e98cae0123b13a2c27828a106349bc6277140e7290b7e9eb7976aa3c04ed347027caf7da3a2fa76304751c02208acfc4e7fc6c7ebbc375c8a5010203262001215820ad7f7992c335b90d882b2802061b97a4fabca7e2ee3e7a51e728b8055e4eb9c7225820e0966ba7005987fece6f0e0e13447aa98cec248e4000a594b01b74c1cb1d40b3")
|
||||
var v attestationObject
|
||||
if err := Unmarshal(data, &v); err != nil {
|
||||
b.Fatal("Unmarshal:", err)
|
||||
@ -743,7 +885,7 @@ 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")
|
||||
data := mustHexDecode("d83dd18443a10104a1044c53796d6d65747269633235365850a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b7148093101ef6d789200")
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
var v macedCOSE
|
||||
@ -755,7 +897,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")
|
||||
data := mustHexDecode("d83dd18443a10104a1044c53796d6d65747269633235365850a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b7148093101ef6d789200")
|
||||
|
||||
// Register tag CBOR Web Token (CWT) 61 and COSE_Mac0 17 with macedCOSE type
|
||||
tags := NewTagSet()
|
||||
@ -775,7 +917,7 @@ func BenchmarkUnmarshalCOSEMACWithTag(b *testing.B) {
|
||||
}
|
||||
func BenchmarkMarshalCOSEMAC(b *testing.B) {
|
||||
// Data from https://tools.ietf.org/html/rfc8392#appendix-A section A.4
|
||||
data := hexDecode("d83dd18443a10104a1044c53796d6d65747269633235365850a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b7148093101ef6d789200")
|
||||
data := mustHexDecode("d83dd18443a10104a1044c53796d6d65747269633235365850a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b7148093101ef6d789200")
|
||||
|
||||
var v macedCOSE
|
||||
if err := Unmarshal(data, &v); err != nil {
|
||||
@ -791,7 +933,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")
|
||||
data := mustHexDecode("d83dd18443a10104a1044c53796d6d65747269633235365850a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b7148093101ef6d789200")
|
||||
|
||||
// Register tag CBOR Web Token (CWT) 61 and COSE_Mac0 17 with macedCOSE type
|
||||
tags := NewTagSet()
|
||||
@ -821,10 +963,10 @@ func BenchmarkUnmarshalMapToStruct(b *testing.B) {
|
||||
}
|
||||
|
||||
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, ...}
|
||||
allKnownFields = mustHexDecode("ad6141f56142f56143f56144f56145f56146f56147f56148f56149f5614af5614bf5614cf5614df5") // {"A": true, ... "M": true }
|
||||
allKnownDuplicateFields = mustHexDecode("ad6141f56141f56141f56141f56141f56141f56141f56141f56141f56141f56141f56141f56141f5") // {"A": true, "A": true, "A": true, ...}
|
||||
allUnknownFields = mustHexDecode("ad614ef5614ff56150f56151f56152f56153f56154f56155f56156f56157f56158f56159f5615af5") // {"N": true, ... "Z": true }
|
||||
allUnknownDuplicateFields = mustHexDecode("ad614ef5614ef5614ef5614ef5614ef5614ef5614ef5614ef5614ef5614ef5614ef5614ef5614ef5") // {"N": true, "N": true, "N": true, ...}
|
||||
)
|
||||
|
||||
type ManyFields struct {
|
||||
@ -860,7 +1002,7 @@ func BenchmarkUnmarshalMapToStruct(b *testing.B) {
|
||||
type input struct {
|
||||
name string
|
||||
data []byte
|
||||
into interface{}
|
||||
into any
|
||||
reject bool
|
||||
}
|
||||
|
||||
|
||||
@ -38,11 +38,38 @@ func (bs ByteString) MarshalCBOR() ([]byte, error) {
|
||||
|
||||
// UnmarshalCBOR decodes CBOR byte string (major type 2) to ByteString.
|
||||
// Decoding CBOR null and CBOR undefined sets ByteString to be empty.
|
||||
//
|
||||
// Deprecated: No longer used by this codec; kept for compatibility
|
||||
// with user apps that directly call this function.
|
||||
func (bs *ByteString) UnmarshalCBOR(data []byte) error {
|
||||
if bs == nil {
|
||||
return errors.New("cbor.ByteString: UnmarshalCBOR on nil pointer")
|
||||
}
|
||||
|
||||
d := decoder{data: data, dm: defaultDecMode}
|
||||
|
||||
// Check well-formedness of CBOR data item.
|
||||
// ByteString.UnmarshalCBOR() is exported, so
|
||||
// the codec needs to support same behavior for:
|
||||
// - Unmarshal(data, *ByteString)
|
||||
// - ByteString.UnmarshalCBOR(data)
|
||||
err := d.wellformed(false, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return bs.unmarshalCBOR(data)
|
||||
}
|
||||
|
||||
// unmarshalCBOR decodes CBOR byte string (major type 2) to ByteString.
|
||||
// Decoding CBOR null and CBOR undefined sets ByteString to be empty.
|
||||
// This function assumes data is well-formed, and does not perform bounds checking.
|
||||
// This function is called by Unmarshal().
|
||||
func (bs *ByteString) unmarshalCBOR(data []byte) error {
|
||||
if bs == nil {
|
||||
return errors.New("cbor.ByteString: UnmarshalCBOR on nil pointer")
|
||||
}
|
||||
|
||||
// Decoding CBOR null and CBOR undefined to ByteString resets data.
|
||||
// This behavior is similar to decoding CBOR null and CBOR undefined to []byte.
|
||||
if len(data) == 1 && (data[0] == 0xf6 || data[0] == 0xf7) {
|
||||
|
||||
@ -3,7 +3,11 @@
|
||||
|
||||
package cbor
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestByteString(t *testing.T) {
|
||||
type s1 struct {
|
||||
@ -26,72 +30,72 @@ func TestByteString(t *testing.T) {
|
||||
{
|
||||
name: "empty",
|
||||
obj: emptybs,
|
||||
wantCborData: hexDecode("40"),
|
||||
wantCborData: mustHexDecode("40"),
|
||||
},
|
||||
{
|
||||
name: "not empty",
|
||||
obj: bs,
|
||||
wantCborData: hexDecode("4401020304"),
|
||||
wantCborData: mustHexDecode("4401020304"),
|
||||
},
|
||||
{
|
||||
name: "array",
|
||||
obj: []ByteString{bs},
|
||||
wantCborData: hexDecode("814401020304"),
|
||||
wantCborData: mustHexDecode("814401020304"),
|
||||
},
|
||||
{
|
||||
name: "map with ByteString key",
|
||||
obj: map[ByteString]bool{bs: true},
|
||||
wantCborData: hexDecode("a14401020304f5"),
|
||||
wantCborData: mustHexDecode("a14401020304f5"),
|
||||
},
|
||||
{
|
||||
name: "empty ByteString field",
|
||||
obj: s1{},
|
||||
wantCborData: hexDecode("a1616140"),
|
||||
wantCborData: mustHexDecode("a1616140"),
|
||||
},
|
||||
{
|
||||
name: "not empty ByteString field",
|
||||
obj: s1{A: bs},
|
||||
wantCborData: hexDecode("a161614401020304"),
|
||||
wantCborData: mustHexDecode("a161614401020304"),
|
||||
},
|
||||
{
|
||||
name: "nil *ByteString field",
|
||||
obj: s2{},
|
||||
wantCborData: hexDecode("a16161f6"),
|
||||
wantCborData: mustHexDecode("a16161f6"),
|
||||
},
|
||||
{
|
||||
name: "empty *ByteString field",
|
||||
obj: s2{A: &emptybs},
|
||||
wantCborData: hexDecode("a1616140"),
|
||||
wantCborData: mustHexDecode("a1616140"),
|
||||
},
|
||||
{
|
||||
name: "not empty *ByteString field",
|
||||
obj: s2{A: &bs},
|
||||
wantCborData: hexDecode("a161614401020304"),
|
||||
wantCborData: mustHexDecode("a161614401020304"),
|
||||
},
|
||||
{
|
||||
name: "empty ByteString field with omitempty option",
|
||||
obj: s3{},
|
||||
wantCborData: hexDecode("a0"),
|
||||
wantCborData: mustHexDecode("a0"),
|
||||
},
|
||||
{
|
||||
name: "not empty ByteString field with omitempty option",
|
||||
obj: s3{A: bs},
|
||||
wantCborData: hexDecode("a161614401020304"),
|
||||
wantCborData: mustHexDecode("a161614401020304"),
|
||||
},
|
||||
{
|
||||
name: "nil *ByteString field with omitempty option",
|
||||
obj: s4{},
|
||||
wantCborData: hexDecode("a0"),
|
||||
wantCborData: mustHexDecode("a0"),
|
||||
},
|
||||
{
|
||||
name: "empty *ByteString field with omitempty option",
|
||||
obj: s4{A: &emptybs},
|
||||
wantCborData: hexDecode("a1616140"),
|
||||
wantCborData: mustHexDecode("a1616140"),
|
||||
},
|
||||
{
|
||||
name: "not empty *ByteString field with omitempty option",
|
||||
obj: s4{A: &bs},
|
||||
wantCborData: hexDecode("a161614401020304"),
|
||||
wantCborData: mustHexDecode("a161614401020304"),
|
||||
},
|
||||
}
|
||||
|
||||
@ -99,3 +103,110 @@ func TestByteString(t *testing.T) {
|
||||
dm, _ := DecOptions{}.DecMode()
|
||||
testRoundTrip(t, testCases, em, dm)
|
||||
}
|
||||
|
||||
func TestUnmarshalByteStringOnBadData(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
data []byte
|
||||
errMsg string
|
||||
}{
|
||||
// Empty data
|
||||
{
|
||||
name: "nil data",
|
||||
data: nil,
|
||||
errMsg: io.EOF.Error(),
|
||||
},
|
||||
{
|
||||
name: "empty data",
|
||||
data: []byte{},
|
||||
errMsg: io.EOF.Error(),
|
||||
},
|
||||
|
||||
// Wrong CBOR types
|
||||
{
|
||||
name: "uint type",
|
||||
data: mustHexDecode("01"),
|
||||
errMsg: "cbor: cannot unmarshal positive integer into Go value of type cbor.ByteString",
|
||||
},
|
||||
{
|
||||
name: "int type",
|
||||
data: mustHexDecode("20"),
|
||||
errMsg: "cbor: cannot unmarshal negative integer into Go value of type cbor.ByteString",
|
||||
},
|
||||
{
|
||||
name: "string type",
|
||||
data: mustHexDecode("60"),
|
||||
errMsg: "cbor: cannot unmarshal UTF-8 text string into Go value of type cbor.ByteString",
|
||||
},
|
||||
{
|
||||
name: "array type",
|
||||
data: mustHexDecode("80"),
|
||||
errMsg: "cbor: cannot unmarshal array into Go value of type cbor.ByteString",
|
||||
},
|
||||
{
|
||||
name: "map type",
|
||||
data: mustHexDecode("a0"),
|
||||
errMsg: "cbor: cannot unmarshal map into Go value of type cbor.ByteString",
|
||||
},
|
||||
{
|
||||
name: "tag type",
|
||||
data: mustHexDecode("c074323031332d30332d32315432303a30343a30305a"),
|
||||
errMsg: "cbor: cannot unmarshal tag into Go value of type cbor.ByteString",
|
||||
},
|
||||
{
|
||||
name: "float type",
|
||||
data: mustHexDecode("f90000"),
|
||||
errMsg: "cbor: cannot unmarshal primitives into Go value of type cbor.ByteString",
|
||||
},
|
||||
|
||||
// Truncated CBOR data
|
||||
{
|
||||
name: "truncated head",
|
||||
data: mustHexDecode("18"),
|
||||
errMsg: io.ErrUnexpectedEOF.Error(),
|
||||
},
|
||||
|
||||
// Truncated CBOR byte string
|
||||
{
|
||||
name: "truncated byte string",
|
||||
data: mustHexDecode("44010203"),
|
||||
errMsg: io.ErrUnexpectedEOF.Error(),
|
||||
},
|
||||
|
||||
// Extraneous CBOR data
|
||||
{
|
||||
name: "extraneous data",
|
||||
data: mustHexDecode("c074323031332d30332d32315432303a30343a30305a00"),
|
||||
errMsg: "cbor: 1 bytes of extraneous data starting at index 22",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// Test ByteString.UnmarshalCBOR(data)
|
||||
{
|
||||
var v ByteString
|
||||
|
||||
err := v.UnmarshalCBOR(tc.data)
|
||||
if err == nil {
|
||||
t.Errorf("UnmarshalCBOR(%x) didn't return error", tc.data)
|
||||
}
|
||||
if !strings.HasPrefix(err.Error(), tc.errMsg) {
|
||||
t.Errorf("UnmarshalCBOR(%x) returned error %q, want %q", tc.data, err.Error(), tc.errMsg)
|
||||
}
|
||||
}
|
||||
// Test Unmarshal(data, *ByteString), which calls ByteString.unmarshalCBOR() under the hood
|
||||
{
|
||||
var v ByteString
|
||||
|
||||
err := Unmarshal(tc.data, &v)
|
||||
if err == nil {
|
||||
t.Errorf("Unmarshal(%x) didn't return error", tc.data)
|
||||
}
|
||||
if !strings.HasPrefix(err.Error(), tc.errMsg) {
|
||||
t.Errorf("Unmarshal(%x) returned error %q, want %q", tc.data, err.Error(), tc.errMsg)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
25
cache.go
25
cache.go
@ -17,6 +17,7 @@ import (
|
||||
type encodeFuncs struct {
|
||||
ef encodeFunc
|
||||
ief isEmptyFunc
|
||||
izf isZeroFunc
|
||||
}
|
||||
|
||||
var (
|
||||
@ -31,10 +32,12 @@ type specialType int
|
||||
const (
|
||||
specialTypeNone specialType = iota
|
||||
specialTypeUnmarshalerIface
|
||||
specialTypeUnexportedUnmarshalerIface
|
||||
specialTypeEmptyIface
|
||||
specialTypeIface
|
||||
specialTypeTag
|
||||
specialTypeTime
|
||||
specialTypeJSONUnmarshalerIface
|
||||
)
|
||||
|
||||
type typeInfo struct {
|
||||
@ -50,7 +53,7 @@ type typeInfo struct {
|
||||
func newTypeInfo(t reflect.Type) *typeInfo {
|
||||
tInfo := typeInfo{typ: t, kind: t.Kind()}
|
||||
|
||||
for t.Kind() == reflect.Ptr {
|
||||
for t.Kind() == reflect.Pointer {
|
||||
t = t.Elem()
|
||||
}
|
||||
|
||||
@ -69,8 +72,12 @@ func newTypeInfo(t reflect.Type) *typeInfo {
|
||||
tInfo.spclType = specialTypeTag
|
||||
} else if t == typeTime {
|
||||
tInfo.spclType = specialTypeTime
|
||||
} else if reflect.PtrTo(t).Implements(typeUnmarshaler) {
|
||||
} else if reflect.PointerTo(t).Implements(typeUnexportedUnmarshaler) {
|
||||
tInfo.spclType = specialTypeUnexportedUnmarshalerIface
|
||||
} else if reflect.PointerTo(t).Implements(typeUnmarshaler) {
|
||||
tInfo.spclType = specialTypeUnmarshalerIface
|
||||
} else if reflect.PointerTo(t).Implements(typeJSONUnmarshaler) {
|
||||
tInfo.spclType = specialTypeJSONUnmarshalerIface
|
||||
}
|
||||
|
||||
switch k {
|
||||
@ -237,7 +244,7 @@ func getEncodingStructType(t reflect.Type) (*encodingStructType, error) {
|
||||
e := getEncodeBuffer()
|
||||
for i := 0; i < len(flds); i++ {
|
||||
// Get field's encodeFunc
|
||||
flds[i].ef, flds[i].ief = getEncodeFunc(flds[i].typ)
|
||||
flds[i].ef, flds[i].ief, flds[i].izf = getEncodeFunc(flds[i].typ)
|
||||
if flds[i].ef == nil {
|
||||
err = &UnsupportedTypeError{t}
|
||||
break
|
||||
@ -321,7 +328,7 @@ func getEncodingStructType(t reflect.Type) (*encodingStructType, error) {
|
||||
func getEncodingStructToArrayType(t reflect.Type, flds fields) (*encodingStructType, error) {
|
||||
for i := 0; i < len(flds); i++ {
|
||||
// Get field's encodeFunc
|
||||
flds[i].ef, flds[i].ief = getEncodeFunc(flds[i].typ)
|
||||
flds[i].ef, flds[i].ief, flds[i].izf = getEncodeFunc(flds[i].typ)
|
||||
if flds[i].ef == nil {
|
||||
structType := &encodingStructType{err: &UnsupportedTypeError{t}}
|
||||
encodingStructTypeCache.Store(t, structType)
|
||||
@ -337,14 +344,14 @@ func getEncodingStructToArrayType(t reflect.Type, flds fields) (*encodingStructT
|
||||
return structType, structType.err
|
||||
}
|
||||
|
||||
func getEncodeFunc(t reflect.Type) (encodeFunc, isEmptyFunc) {
|
||||
func getEncodeFunc(t reflect.Type) (encodeFunc, isEmptyFunc, isZeroFunc) {
|
||||
if v, _ := encodeFuncCache.Load(t); v != nil {
|
||||
fs := v.(encodeFuncs)
|
||||
return fs.ef, fs.ief
|
||||
return fs.ef, fs.ief, fs.izf
|
||||
}
|
||||
ef, ief := getEncodeFuncInternal(t)
|
||||
encodeFuncCache.Store(t, encodeFuncs{ef, ief})
|
||||
return ef, ief
|
||||
ef, ief, izf := getEncodeFuncInternal(t)
|
||||
encodeFuncCache.Store(t, encodeFuncs{ef, ief, izf})
|
||||
return ef, ief, izf
|
||||
}
|
||||
|
||||
func getTypeInfo(t reflect.Type) *typeInfo {
|
||||
|
||||
@ -5,6 +5,7 @@ package cbor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
@ -180,3 +181,11 @@ func validBuiltinTag(tagNum uint64, contentHead byte) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Transcoder is a scheme for transcoding a single CBOR encoded data item to or from a different
|
||||
// data format.
|
||||
type Transcoder interface {
|
||||
// Transcode reads the data item in its source format from a Reader and writes a
|
||||
// corresponding representation in its destination format to a Writer.
|
||||
Transcode(dst io.Writer, src io.Reader) error
|
||||
}
|
||||
|
||||
394
decode.go
394
decode.go
@ -4,6 +4,7 @@
|
||||
package cbor
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
@ -94,7 +95,7 @@ import (
|
||||
//
|
||||
// To unmarshal CBOR null (0xf6) and undefined (0xf7) values into a
|
||||
// slice/map/pointer, Unmarshal sets Go value to nil. Because null is often
|
||||
// used to mean "not present", unmarshalling CBOR null and undefined value
|
||||
// used to mean "not present", unmarshaling CBOR null and undefined value
|
||||
// into any other Go type has no effect and returns no error.
|
||||
//
|
||||
// Unmarshal supports CBOR tag 55799 (self-describe CBOR), tag 0 and 1 (time),
|
||||
@ -104,7 +105,7 @@ import (
|
||||
// if there are any remaining bytes following the first valid CBOR data item.
|
||||
// See UnmarshalFirst, if you want to unmarshal only the first
|
||||
// CBOR data item without ExtraneousDataError caused by remaining bytes.
|
||||
func Unmarshal(data []byte, v interface{}) error {
|
||||
func Unmarshal(data []byte, v any) error {
|
||||
return defaultDecMode.Unmarshal(data, v)
|
||||
}
|
||||
|
||||
@ -114,7 +115,7 @@ func Unmarshal(data []byte, v interface{}) error {
|
||||
// If v is nil, not a pointer, or a nil pointer, UnmarshalFirst returns an error.
|
||||
//
|
||||
// See the documentation for Unmarshal for details.
|
||||
func UnmarshalFirst(data []byte, v interface{}) (rest []byte, err error) {
|
||||
func UnmarshalFirst(data []byte, v any) (rest []byte, err error) {
|
||||
return defaultDecMode.UnmarshalFirst(data, v)
|
||||
}
|
||||
|
||||
@ -151,6 +152,10 @@ type Unmarshaler interface {
|
||||
UnmarshalCBOR([]byte) error
|
||||
}
|
||||
|
||||
type unmarshaler interface {
|
||||
unmarshalCBOR([]byte) error
|
||||
}
|
||||
|
||||
// InvalidUnmarshalError describes an invalid argument passed to Unmarshal.
|
||||
type InvalidUnmarshalError struct {
|
||||
s string
|
||||
@ -193,12 +198,12 @@ func (e *InvalidMapKeyTypeError) Error() string {
|
||||
|
||||
// DupMapKeyError describes detected duplicate map key in CBOR map.
|
||||
type DupMapKeyError struct {
|
||||
Key interface{}
|
||||
Key any
|
||||
Index int
|
||||
}
|
||||
|
||||
func (e *DupMapKeyError) Error() string {
|
||||
return fmt.Sprintf("cbor: found duplicate map key \"%v\" at map element index %d", e.Key, e.Index)
|
||||
return fmt.Sprintf("cbor: found duplicate map key %#v at map element index %d", e.Key, e.Index)
|
||||
}
|
||||
|
||||
// UnknownFieldError describes detected unknown field in CBOR map when decoding to Go struct.
|
||||
@ -383,7 +388,7 @@ const (
|
||||
// - return UnmarshalTypeError if value doesn't fit into int64
|
||||
IntDecConvertSignedOrFail
|
||||
|
||||
// IntDecConvertSigned affects how CBOR integers (major type 0 and 1) decode to Go interface{}.
|
||||
// IntDecConvertSignedOrBigInt affects how CBOR integers (major type 0 and 1) decode to Go interface{}.
|
||||
// It makes CBOR integers (major type 0 and 1) decode to:
|
||||
// - int64 if value fits
|
||||
// - big.Int or *big.Int (see BigIntDecMode) if value doesn't fit into int64
|
||||
@ -489,11 +494,11 @@ type BigIntDecMode int
|
||||
|
||||
const (
|
||||
// BigIntDecodeValue makes CBOR bignum decode to big.Int (instead of *big.Int)
|
||||
// when unmarshalling into a Go interface{}.
|
||||
// when unmarshaling into a Go interface{}.
|
||||
BigIntDecodeValue BigIntDecMode = iota
|
||||
|
||||
// BigIntDecodePointer makes CBOR bignum decode to *big.Int when
|
||||
// unmarshalling into a Go interface{}.
|
||||
// unmarshaling into a Go interface{}.
|
||||
BigIntDecodePointer
|
||||
|
||||
maxBigIntDecMode
|
||||
@ -745,6 +750,25 @@ func (bum BinaryUnmarshalerMode) valid() bool {
|
||||
return bum >= 0 && bum < maxBinaryUnmarshalerMode
|
||||
}
|
||||
|
||||
// TextUnmarshalerMode specifies how to decode into types that implement
|
||||
// encoding.TextUnmarshaler.
|
||||
type TextUnmarshalerMode int
|
||||
|
||||
const (
|
||||
// TextUnmarshalerNone does not recognize TextUnmarshaler implementations during decode.
|
||||
TextUnmarshalerNone TextUnmarshalerMode = iota
|
||||
|
||||
// TextUnmarshalerTextString will invoke UnmarshalText on the contents of a CBOR text
|
||||
// string when decoding into a value that implements TextUnmarshaler.
|
||||
TextUnmarshalerTextString
|
||||
|
||||
maxTextUnmarshalerMode
|
||||
)
|
||||
|
||||
func (tum TextUnmarshalerMode) valid() bool {
|
||||
return tum >= 0 && tum < maxTextUnmarshalerMode
|
||||
}
|
||||
|
||||
// DecOptions specifies decoding options.
|
||||
type DecOptions struct {
|
||||
// DupMapKey specifies whether to enforce duplicate map key.
|
||||
@ -793,7 +817,7 @@ type DecOptions struct {
|
||||
// TagsMd specifies whether to allow CBOR tags (major type 6).
|
||||
TagsMd TagsMode
|
||||
|
||||
// IntDec specifies which Go integer type (int64 or uint64) to use
|
||||
// IntDec specifies which Go integer type (int64, uint64, or [big.Int]) to use
|
||||
// when decoding CBOR int (major type 0 and 1) to Go interface{}.
|
||||
IntDec IntDecMode
|
||||
|
||||
@ -807,7 +831,7 @@ type DecOptions struct {
|
||||
ExtraReturnErrors ExtraDecErrorCond
|
||||
|
||||
// DefaultMapType specifies Go map type to create and decode to
|
||||
// when unmarshalling CBOR into an empty interface value.
|
||||
// when unmarshaling CBOR into an empty interface value.
|
||||
// By default, unmarshal uses map[interface{}]interface{}.
|
||||
DefaultMapType reflect.Type
|
||||
|
||||
@ -879,6 +903,15 @@ type DecOptions struct {
|
||||
// BinaryUnmarshaler specifies how to decode into types that implement
|
||||
// encoding.BinaryUnmarshaler.
|
||||
BinaryUnmarshaler BinaryUnmarshalerMode
|
||||
|
||||
// TextUnmarshaler specifies how to decode into types that implement
|
||||
// encoding.TextUnmarshaler.
|
||||
TextUnmarshaler TextUnmarshalerMode
|
||||
|
||||
// JSONUnmarshalerTranscoder sets the transcoding scheme used to unmarshal types that
|
||||
// implement json.Unmarshaler but do not also implement cbor.Unmarshaler. If nil, decoding
|
||||
// behavior is not influenced by whether or not a type implements json.Unmarshaler.
|
||||
JSONUnmarshalerTranscoder Transcoder
|
||||
}
|
||||
|
||||
// DecMode returns DecMode with immutable options and no tags (safe for concurrency).
|
||||
@ -1091,33 +1124,39 @@ func (opts DecOptions) decMode() (*decMode, error) { //nolint:gocritic // ignore
|
||||
return nil, errors.New("cbor: invalid BinaryUnmarshaler " + strconv.Itoa(int(opts.BinaryUnmarshaler)))
|
||||
}
|
||||
|
||||
if !opts.TextUnmarshaler.valid() {
|
||||
return nil, errors.New("cbor: invalid TextUnmarshaler " + strconv.Itoa(int(opts.TextUnmarshaler)))
|
||||
}
|
||||
|
||||
dm := decMode{
|
||||
dupMapKey: opts.DupMapKey,
|
||||
timeTag: opts.TimeTag,
|
||||
maxNestedLevels: opts.MaxNestedLevels,
|
||||
maxArrayElements: opts.MaxArrayElements,
|
||||
maxMapPairs: opts.MaxMapPairs,
|
||||
indefLength: opts.IndefLength,
|
||||
tagsMd: opts.TagsMd,
|
||||
intDec: opts.IntDec,
|
||||
mapKeyByteString: opts.MapKeyByteString,
|
||||
extraReturnErrors: opts.ExtraReturnErrors,
|
||||
defaultMapType: opts.DefaultMapType,
|
||||
utf8: opts.UTF8,
|
||||
fieldNameMatching: opts.FieldNameMatching,
|
||||
bigIntDec: opts.BigIntDec,
|
||||
defaultByteStringType: opts.DefaultByteStringType,
|
||||
byteStringToString: opts.ByteStringToString,
|
||||
fieldNameByteString: opts.FieldNameByteString,
|
||||
unrecognizedTagToAny: opts.UnrecognizedTagToAny,
|
||||
timeTagToAny: opts.TimeTagToAny,
|
||||
simpleValues: simpleValues,
|
||||
nanDec: opts.NaN,
|
||||
infDec: opts.Inf,
|
||||
byteStringToTime: opts.ByteStringToTime,
|
||||
byteStringExpectedFormat: opts.ByteStringExpectedFormat,
|
||||
bignumTag: opts.BignumTag,
|
||||
binaryUnmarshaler: opts.BinaryUnmarshaler,
|
||||
dupMapKey: opts.DupMapKey,
|
||||
timeTag: opts.TimeTag,
|
||||
maxNestedLevels: opts.MaxNestedLevels,
|
||||
maxArrayElements: opts.MaxArrayElements,
|
||||
maxMapPairs: opts.MaxMapPairs,
|
||||
indefLength: opts.IndefLength,
|
||||
tagsMd: opts.TagsMd,
|
||||
intDec: opts.IntDec,
|
||||
mapKeyByteString: opts.MapKeyByteString,
|
||||
extraReturnErrors: opts.ExtraReturnErrors,
|
||||
defaultMapType: opts.DefaultMapType,
|
||||
utf8: opts.UTF8,
|
||||
fieldNameMatching: opts.FieldNameMatching,
|
||||
bigIntDec: opts.BigIntDec,
|
||||
defaultByteStringType: opts.DefaultByteStringType,
|
||||
byteStringToString: opts.ByteStringToString,
|
||||
fieldNameByteString: opts.FieldNameByteString,
|
||||
unrecognizedTagToAny: opts.UnrecognizedTagToAny,
|
||||
timeTagToAny: opts.TimeTagToAny,
|
||||
simpleValues: simpleValues,
|
||||
nanDec: opts.NaN,
|
||||
infDec: opts.Inf,
|
||||
byteStringToTime: opts.ByteStringToTime,
|
||||
byteStringExpectedFormat: opts.ByteStringExpectedFormat,
|
||||
bignumTag: opts.BignumTag,
|
||||
binaryUnmarshaler: opts.BinaryUnmarshaler,
|
||||
textUnmarshaler: opts.TextUnmarshaler,
|
||||
jsonUnmarshalerTranscoder: opts.JSONUnmarshalerTranscoder,
|
||||
}
|
||||
|
||||
return &dm, nil
|
||||
@ -1130,7 +1169,7 @@ type DecMode interface {
|
||||
// Unmarshal returns an error.
|
||||
//
|
||||
// See the documentation for Unmarshal for details.
|
||||
Unmarshal(data []byte, v interface{}) error
|
||||
Unmarshal(data []byte, v any) error
|
||||
|
||||
// UnmarshalFirst parses the first CBOR data item into the value pointed to by v
|
||||
// using the decoding mode. Any remaining bytes are returned in rest.
|
||||
@ -1138,7 +1177,7 @@ type DecMode interface {
|
||||
// If v is nil, not a pointer, or a nil pointer, UnmarshalFirst returns an error.
|
||||
//
|
||||
// See the documentation for Unmarshal for details.
|
||||
UnmarshalFirst(data []byte, v interface{}) (rest []byte, err error)
|
||||
UnmarshalFirst(data []byte, v any) (rest []byte, err error)
|
||||
|
||||
// Valid checks whether data is a well-formed encoded CBOR data item and
|
||||
// that it complies with configurable restrictions such as MaxNestedLevels,
|
||||
@ -1170,33 +1209,35 @@ type DecMode interface {
|
||||
}
|
||||
|
||||
type decMode struct {
|
||||
tags tagProvider
|
||||
dupMapKey DupMapKeyMode
|
||||
timeTag DecTagMode
|
||||
maxNestedLevels int
|
||||
maxArrayElements int
|
||||
maxMapPairs int
|
||||
indefLength IndefLengthMode
|
||||
tagsMd TagsMode
|
||||
intDec IntDecMode
|
||||
mapKeyByteString MapKeyByteStringMode
|
||||
extraReturnErrors ExtraDecErrorCond
|
||||
defaultMapType reflect.Type
|
||||
utf8 UTF8Mode
|
||||
fieldNameMatching FieldNameMatchingMode
|
||||
bigIntDec BigIntDecMode
|
||||
defaultByteStringType reflect.Type
|
||||
byteStringToString ByteStringToStringMode
|
||||
fieldNameByteString FieldNameByteStringMode
|
||||
unrecognizedTagToAny UnrecognizedTagToAnyMode
|
||||
timeTagToAny TimeTagToAnyMode
|
||||
simpleValues *SimpleValueRegistry
|
||||
nanDec NaNMode
|
||||
infDec InfMode
|
||||
byteStringToTime ByteStringToTimeMode
|
||||
byteStringExpectedFormat ByteStringExpectedFormatMode
|
||||
bignumTag BignumTagMode
|
||||
binaryUnmarshaler BinaryUnmarshalerMode
|
||||
tags tagProvider
|
||||
dupMapKey DupMapKeyMode
|
||||
timeTag DecTagMode
|
||||
maxNestedLevels int
|
||||
maxArrayElements int
|
||||
maxMapPairs int
|
||||
indefLength IndefLengthMode
|
||||
tagsMd TagsMode
|
||||
intDec IntDecMode
|
||||
mapKeyByteString MapKeyByteStringMode
|
||||
extraReturnErrors ExtraDecErrorCond
|
||||
defaultMapType reflect.Type
|
||||
utf8 UTF8Mode
|
||||
fieldNameMatching FieldNameMatchingMode
|
||||
bigIntDec BigIntDecMode
|
||||
defaultByteStringType reflect.Type
|
||||
byteStringToString ByteStringToStringMode
|
||||
fieldNameByteString FieldNameByteStringMode
|
||||
unrecognizedTagToAny UnrecognizedTagToAnyMode
|
||||
timeTagToAny TimeTagToAnyMode
|
||||
simpleValues *SimpleValueRegistry
|
||||
nanDec NaNMode
|
||||
infDec InfMode
|
||||
byteStringToTime ByteStringToTimeMode
|
||||
byteStringExpectedFormat ByteStringExpectedFormatMode
|
||||
bignumTag BignumTagMode
|
||||
binaryUnmarshaler BinaryUnmarshalerMode
|
||||
textUnmarshaler TextUnmarshalerMode
|
||||
jsonUnmarshalerTranscoder Transcoder
|
||||
}
|
||||
|
||||
var defaultDecMode, _ = DecOptions{}.decMode()
|
||||
@ -1211,32 +1252,34 @@ func (dm *decMode) DecOptions() DecOptions {
|
||||
}
|
||||
|
||||
return DecOptions{
|
||||
DupMapKey: dm.dupMapKey,
|
||||
TimeTag: dm.timeTag,
|
||||
MaxNestedLevels: dm.maxNestedLevels,
|
||||
MaxArrayElements: dm.maxArrayElements,
|
||||
MaxMapPairs: dm.maxMapPairs,
|
||||
IndefLength: dm.indefLength,
|
||||
TagsMd: dm.tagsMd,
|
||||
IntDec: dm.intDec,
|
||||
MapKeyByteString: dm.mapKeyByteString,
|
||||
ExtraReturnErrors: dm.extraReturnErrors,
|
||||
DefaultMapType: dm.defaultMapType,
|
||||
UTF8: dm.utf8,
|
||||
FieldNameMatching: dm.fieldNameMatching,
|
||||
BigIntDec: dm.bigIntDec,
|
||||
DefaultByteStringType: dm.defaultByteStringType,
|
||||
ByteStringToString: dm.byteStringToString,
|
||||
FieldNameByteString: dm.fieldNameByteString,
|
||||
UnrecognizedTagToAny: dm.unrecognizedTagToAny,
|
||||
TimeTagToAny: dm.timeTagToAny,
|
||||
SimpleValues: simpleValues,
|
||||
NaN: dm.nanDec,
|
||||
Inf: dm.infDec,
|
||||
ByteStringToTime: dm.byteStringToTime,
|
||||
ByteStringExpectedFormat: dm.byteStringExpectedFormat,
|
||||
BignumTag: dm.bignumTag,
|
||||
BinaryUnmarshaler: dm.binaryUnmarshaler,
|
||||
DupMapKey: dm.dupMapKey,
|
||||
TimeTag: dm.timeTag,
|
||||
MaxNestedLevels: dm.maxNestedLevels,
|
||||
MaxArrayElements: dm.maxArrayElements,
|
||||
MaxMapPairs: dm.maxMapPairs,
|
||||
IndefLength: dm.indefLength,
|
||||
TagsMd: dm.tagsMd,
|
||||
IntDec: dm.intDec,
|
||||
MapKeyByteString: dm.mapKeyByteString,
|
||||
ExtraReturnErrors: dm.extraReturnErrors,
|
||||
DefaultMapType: dm.defaultMapType,
|
||||
UTF8: dm.utf8,
|
||||
FieldNameMatching: dm.fieldNameMatching,
|
||||
BigIntDec: dm.bigIntDec,
|
||||
DefaultByteStringType: dm.defaultByteStringType,
|
||||
ByteStringToString: dm.byteStringToString,
|
||||
FieldNameByteString: dm.fieldNameByteString,
|
||||
UnrecognizedTagToAny: dm.unrecognizedTagToAny,
|
||||
TimeTagToAny: dm.timeTagToAny,
|
||||
SimpleValues: simpleValues,
|
||||
NaN: dm.nanDec,
|
||||
Inf: dm.infDec,
|
||||
ByteStringToTime: dm.byteStringToTime,
|
||||
ByteStringExpectedFormat: dm.byteStringExpectedFormat,
|
||||
BignumTag: dm.bignumTag,
|
||||
BinaryUnmarshaler: dm.binaryUnmarshaler,
|
||||
TextUnmarshaler: dm.textUnmarshaler,
|
||||
JSONUnmarshalerTranscoder: dm.jsonUnmarshalerTranscoder,
|
||||
}
|
||||
}
|
||||
|
||||
@ -1245,7 +1288,7 @@ func (dm *decMode) DecOptions() DecOptions {
|
||||
// Unmarshal returns an error.
|
||||
//
|
||||
// See the documentation for Unmarshal for details.
|
||||
func (dm *decMode) Unmarshal(data []byte, v interface{}) error {
|
||||
func (dm *decMode) Unmarshal(data []byte, v any) error {
|
||||
d := decoder{data: data, dm: dm}
|
||||
|
||||
// Check well-formedness.
|
||||
@ -1265,7 +1308,7 @@ func (dm *decMode) Unmarshal(data []byte, v interface{}) error {
|
||||
// If v is nil, not a pointer, or a nil pointer, UnmarshalFirst returns an error.
|
||||
//
|
||||
// See the documentation for Unmarshal for details.
|
||||
func (dm *decMode) UnmarshalFirst(data []byte, v interface{}) (rest []byte, err error) {
|
||||
func (dm *decMode) UnmarshalFirst(data []byte, v any) (rest []byte, err error) {
|
||||
d := decoder{data: data, dm: dm}
|
||||
|
||||
// check well-formedness.
|
||||
@ -1341,13 +1384,13 @@ type decoder struct {
|
||||
// If CBOR data item fails to be decoded into v,
|
||||
// error is returned and offset is moved to the next CBOR data item.
|
||||
// Precondition: d.data contains at least one well-formed CBOR data item.
|
||||
func (d *decoder) value(v interface{}) error {
|
||||
func (d *decoder) value(v any) error {
|
||||
// v can't be nil, non-pointer, or nil pointer value.
|
||||
if v == nil {
|
||||
return &InvalidUnmarshalError{"cbor: Unmarshal(nil)"}
|
||||
}
|
||||
rv := reflect.ValueOf(v)
|
||||
if rv.Kind() != reflect.Ptr {
|
||||
if rv.Kind() != reflect.Pointer {
|
||||
return &InvalidUnmarshalError{"cbor: Unmarshal(non-pointer " + rv.Type().String() + ")"}
|
||||
} else if rv.IsNil() {
|
||||
return &InvalidUnmarshalError{"cbor: Unmarshal(nil " + rv.Type().String() + ")"}
|
||||
@ -1361,9 +1404,9 @@ func (d *decoder) value(v interface{}) error {
|
||||
func (d *decoder) parseToValue(v reflect.Value, tInfo *typeInfo) error { //nolint:gocyclo
|
||||
|
||||
// Decode CBOR nil or CBOR undefined to pointer value by setting pointer value to nil.
|
||||
if d.nextCBORNil() && v.Kind() == reflect.Ptr {
|
||||
if d.nextCBORNil() && v.Kind() == reflect.Pointer {
|
||||
d.skip()
|
||||
v.Set(reflect.Zero(v.Type()))
|
||||
v.SetZero()
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -1387,7 +1430,7 @@ func (d *decoder) parseToValue(v reflect.Value, tInfo *typeInfo) error { //nolin
|
||||
registeredType := d.dm.tags.getTypeFromTagNum(tagNums)
|
||||
if registeredType != nil {
|
||||
if registeredType.Implements(tInfo.nonPtrType) ||
|
||||
reflect.PtrTo(registeredType).Implements(tInfo.nonPtrType) {
|
||||
reflect.PointerTo(registeredType).Implements(tInfo.nonPtrType) {
|
||||
v.Set(reflect.New(registeredType))
|
||||
v = v.Elem()
|
||||
tInfo = getTypeInfo(registeredType)
|
||||
@ -1399,7 +1442,7 @@ func (d *decoder) parseToValue(v reflect.Value, tInfo *typeInfo) error { //nolin
|
||||
|
||||
// Create new value for the pointer v to point to.
|
||||
// At this point, CBOR value is not nil/undefined if v is a pointer.
|
||||
for v.Kind() == reflect.Ptr {
|
||||
for v.Kind() == reflect.Pointer {
|
||||
if v.IsNil() {
|
||||
if !v.CanSet() {
|
||||
d.skip()
|
||||
@ -1460,6 +1503,17 @@ func (d *decoder) parseToValue(v reflect.Value, tInfo *typeInfo) error { //nolin
|
||||
|
||||
case specialTypeUnmarshalerIface:
|
||||
return d.parseToUnmarshaler(v)
|
||||
|
||||
case specialTypeUnexportedUnmarshalerIface:
|
||||
return d.parseToUnexportedUnmarshaler(v)
|
||||
|
||||
case specialTypeJSONUnmarshalerIface:
|
||||
// This special type implies that the type does not also implement
|
||||
// cbor.Umarshaler.
|
||||
if d.dm.jsonUnmarshalerTranscoder == nil {
|
||||
break
|
||||
}
|
||||
return d.parseToJSONUnmarshaler(v)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1523,7 +1577,7 @@ func (d *decoder) parseToValue(v reflect.Value, tInfo *typeInfo) error { //nolin
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fillTextString(t, b, v)
|
||||
return fillTextString(t, b, v, d.dm.textUnmarshaler)
|
||||
|
||||
case cborTypePrimitives:
|
||||
_, ai, val := d.getHead()
|
||||
@ -1788,12 +1842,12 @@ func (d *decoder) parseToTime() (time.Time, bool, error) {
|
||||
// parseToUnmarshaler parses CBOR data to value implementing Unmarshaler interface.
|
||||
// It assumes data is well-formed, and does not perform bounds checking.
|
||||
func (d *decoder) parseToUnmarshaler(v reflect.Value) error {
|
||||
if d.nextCBORNil() && v.Kind() == reflect.Ptr && v.IsNil() {
|
||||
if d.nextCBORNil() && v.Kind() == reflect.Pointer && v.IsNil() {
|
||||
d.skip()
|
||||
return nil
|
||||
}
|
||||
|
||||
if v.Kind() != reflect.Ptr && v.CanAddr() {
|
||||
if v.Kind() != reflect.Pointer && v.CanAddr() {
|
||||
v = v.Addr()
|
||||
}
|
||||
if u, ok := v.Interface().(Unmarshaler); ok {
|
||||
@ -1805,9 +1859,55 @@ func (d *decoder) parseToUnmarshaler(v reflect.Value) error {
|
||||
return errors.New("cbor: failed to assert " + v.Type().String() + " as cbor.Unmarshaler")
|
||||
}
|
||||
|
||||
// parseToUnexportedUnmarshaler parses CBOR data to value implementing unmarshaler interface.
|
||||
// It assumes data is well-formed, and does not perform bounds checking.
|
||||
func (d *decoder) parseToUnexportedUnmarshaler(v reflect.Value) error {
|
||||
if d.nextCBORNil() && v.Kind() == reflect.Pointer && v.IsNil() {
|
||||
d.skip()
|
||||
return nil
|
||||
}
|
||||
|
||||
if v.Kind() != reflect.Pointer && v.CanAddr() {
|
||||
v = v.Addr()
|
||||
}
|
||||
if u, ok := v.Interface().(unmarshaler); ok {
|
||||
start := d.off
|
||||
d.skip()
|
||||
return u.unmarshalCBOR(d.data[start:d.off])
|
||||
}
|
||||
d.skip()
|
||||
return errors.New("cbor: failed to assert " + v.Type().String() + " as cbor.unmarshaler")
|
||||
}
|
||||
|
||||
// parseToJSONUnmarshaler parses CBOR data to be transcoded to JSON and passed to the value's
|
||||
// implementation of the json.Unmarshaler interface. It assumes data is well-formed, and does not
|
||||
// perform bounds checking.
|
||||
func (d *decoder) parseToJSONUnmarshaler(v reflect.Value) error {
|
||||
if d.nextCBORNil() && v.Kind() == reflect.Pointer && v.IsNil() {
|
||||
d.skip()
|
||||
return nil
|
||||
}
|
||||
|
||||
if v.Kind() != reflect.Pointer && v.CanAddr() {
|
||||
v = v.Addr()
|
||||
}
|
||||
if u, ok := v.Interface().(jsonUnmarshaler); ok {
|
||||
start := d.off
|
||||
d.skip()
|
||||
e := getEncodeBuffer()
|
||||
defer putEncodeBuffer(e)
|
||||
if err := d.dm.jsonUnmarshalerTranscoder.Transcode(e, bytes.NewReader(d.data[start:d.off])); err != nil {
|
||||
return &TranscodeError{err: err, rtype: v.Type(), sourceFormat: "cbor", targetFormat: "json"}
|
||||
}
|
||||
return u.UnmarshalJSON(e.Bytes())
|
||||
}
|
||||
d.skip()
|
||||
return errors.New("cbor: failed to assert " + v.Type().String() + " as json.Unmarshaler")
|
||||
}
|
||||
|
||||
// parse parses CBOR data and returns value in default Go type.
|
||||
// It assumes data is well-formed, and does not perform bounds checking.
|
||||
func (d *decoder) parse(skipSelfDescribedTag bool) (interface{}, error) { //nolint:gocyclo
|
||||
func (d *decoder) parse(skipSelfDescribedTag bool) (any, error) { //nolint:gocyclo
|
||||
// Strip self-described CBOR tag number.
|
||||
if skipSelfDescribedTag {
|
||||
for d.nextCBORType() == cborTypeTag {
|
||||
@ -2224,15 +2324,15 @@ func (d *decoder) parseTextString() ([]byte, error) {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (d *decoder) parseArray() ([]interface{}, error) {
|
||||
func (d *decoder) parseArray() ([]any, error) {
|
||||
_, _, val, indefiniteLength := d.getHeadWithIndefiniteLengthFlag()
|
||||
hasSize := !indefiniteLength
|
||||
count := int(val)
|
||||
if !hasSize {
|
||||
count = d.numOfItemsUntilBreak() // peek ahead to get array size to preallocate slice for better performance
|
||||
}
|
||||
v := make([]interface{}, count)
|
||||
var e interface{}
|
||||
v := make([]any, count)
|
||||
var e any
|
||||
var err, lastErr error
|
||||
for i := 0; (hasSize && i < count) || (!hasSize && !d.foundBreak()); i++ {
|
||||
if e, lastErr = d.parse(true); lastErr != nil {
|
||||
@ -2290,20 +2390,19 @@ func (d *decoder) parseArrayToArray(v reflect.Value, tInfo *typeInfo) error {
|
||||
}
|
||||
// Set remaining Go array elements to zero values.
|
||||
if gi < vLen {
|
||||
zeroV := reflect.Zero(tInfo.elemTypeInfo.typ)
|
||||
for ; gi < vLen; gi++ {
|
||||
v.Index(gi).Set(zeroV)
|
||||
v.Index(gi).SetZero()
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *decoder) parseMap() (interface{}, error) {
|
||||
func (d *decoder) parseMap() (any, error) {
|
||||
_, _, val, indefiniteLength := d.getHeadWithIndefiniteLengthFlag()
|
||||
hasSize := !indefiniteLength
|
||||
count := int(val)
|
||||
m := make(map[interface{}]interface{})
|
||||
var k, e interface{}
|
||||
m := make(map[any]any)
|
||||
var k, e any
|
||||
var err, lastErr error
|
||||
keyCount := 0
|
||||
for i := 0; (hasSize && i < count) || (!hasSize && !d.foundBreak()); i++ {
|
||||
@ -2376,13 +2475,13 @@ func (d *decoder) parseMapToMap(v reflect.Value, tInfo *typeInfo) error { //noli
|
||||
}
|
||||
keyType, eleType := tInfo.keyTypeInfo.typ, tInfo.elemTypeInfo.typ
|
||||
reuseKey, reuseEle := isImmutableKind(tInfo.keyTypeInfo.kind), isImmutableKind(tInfo.elemTypeInfo.kind)
|
||||
var keyValue, eleValue, zeroKeyValue, zeroEleValue reflect.Value
|
||||
var keyValue, eleValue reflect.Value
|
||||
keyIsInterfaceType := keyType == typeIntf // If key type is interface{}, need to check if key value is hashable.
|
||||
var err, lastErr error
|
||||
keyCount := v.Len()
|
||||
var existingKeys map[interface{}]bool // Store existing map keys, used for detecting duplicate map key.
|
||||
var existingKeys map[any]bool // Store existing map keys, used for detecting duplicate map key.
|
||||
if d.dm.dupMapKey == DupMapKeyEnforcedAPF {
|
||||
existingKeys = make(map[interface{}]bool, keyCount)
|
||||
existingKeys = make(map[any]bool, keyCount)
|
||||
if keyCount > 0 {
|
||||
vKeys := v.MapKeys()
|
||||
for i := 0; i < len(vKeys); i++ {
|
||||
@ -2395,10 +2494,7 @@ func (d *decoder) parseMapToMap(v reflect.Value, tInfo *typeInfo) error { //noli
|
||||
if !keyValue.IsValid() {
|
||||
keyValue = reflect.New(keyType).Elem()
|
||||
} else if !reuseKey {
|
||||
if !zeroKeyValue.IsValid() {
|
||||
zeroKeyValue = reflect.Zero(keyType)
|
||||
}
|
||||
keyValue.Set(zeroKeyValue)
|
||||
keyValue.SetZero()
|
||||
}
|
||||
if lastErr = d.parseToValue(keyValue, tInfo.keyTypeInfo); lastErr != nil {
|
||||
if err == nil {
|
||||
@ -2413,7 +2509,7 @@ func (d *decoder) parseMapToMap(v reflect.Value, tInfo *typeInfo) error { //noli
|
||||
if !isHashableValue(keyValue.Elem()) {
|
||||
var converted bool
|
||||
if d.dm.mapKeyByteString == MapKeyByteStringAllowed {
|
||||
var k interface{}
|
||||
var k any
|
||||
k, converted = convertByteSliceToByteString(keyValue.Elem().Interface())
|
||||
if converted {
|
||||
keyValue.Set(reflect.ValueOf(k))
|
||||
@ -2433,10 +2529,7 @@ func (d *decoder) parseMapToMap(v reflect.Value, tInfo *typeInfo) error { //noli
|
||||
if !eleValue.IsValid() {
|
||||
eleValue = reflect.New(eleType).Elem()
|
||||
} else if !reuseEle {
|
||||
if !zeroEleValue.IsValid() {
|
||||
zeroEleValue = reflect.Zero(eleType)
|
||||
}
|
||||
eleValue.Set(zeroEleValue)
|
||||
eleValue.SetZero()
|
||||
}
|
||||
if lastErr := d.parseToValue(eleValue, tInfo.elemTypeInfo); lastErr != nil {
|
||||
if err == nil {
|
||||
@ -2584,7 +2677,7 @@ func (d *decoder) parseMapToStruct(v reflect.Value, tInfo *typeInfo) error { //n
|
||||
|
||||
// Keeps track of CBOR map keys to detect duplicate map key
|
||||
keyCount := 0
|
||||
var mapKeys map[interface{}]struct{}
|
||||
var mapKeys map[any]struct{}
|
||||
|
||||
errOnUnknownField := (d.dm.extraReturnErrors & ExtraDecErrorUnknownField) > 0
|
||||
|
||||
@ -2594,7 +2687,7 @@ MapEntryLoop:
|
||||
|
||||
// If duplicate field detection is enabled and the key at index j did not match any
|
||||
// field, k will hold the map key.
|
||||
var k interface{}
|
||||
var k any
|
||||
|
||||
t := d.nextCBORType()
|
||||
if t == cborTypeTextString || (t == cborTypeByteString && d.dm.fieldNameByteString == FieldNameByteStringAllowed) {
|
||||
@ -2764,7 +2857,7 @@ MapEntryLoop:
|
||||
// check is never reached.
|
||||
if d.dm.dupMapKey == DupMapKeyEnforcedAPF {
|
||||
if mapKeys == nil {
|
||||
mapKeys = make(map[interface{}]struct{}, 1)
|
||||
mapKeys = make(map[any]struct{}, 1)
|
||||
}
|
||||
mapKeys[k] = struct{}{}
|
||||
newKeyCount := len(mapKeys)
|
||||
@ -2968,20 +3061,25 @@ func (d *decoder) nextCBORNil() bool {
|
||||
return d.data[d.off] == 0xf6 || d.data[d.off] == 0xf7
|
||||
}
|
||||
|
||||
type jsonUnmarshaler interface{ UnmarshalJSON([]byte) error }
|
||||
|
||||
var (
|
||||
typeIntf = reflect.TypeOf([]interface{}(nil)).Elem()
|
||||
typeTime = reflect.TypeOf(time.Time{})
|
||||
typeBigInt = reflect.TypeOf(big.Int{})
|
||||
typeUnmarshaler = reflect.TypeOf((*Unmarshaler)(nil)).Elem()
|
||||
typeBinaryUnmarshaler = reflect.TypeOf((*encoding.BinaryUnmarshaler)(nil)).Elem()
|
||||
typeString = reflect.TypeOf("")
|
||||
typeByteSlice = reflect.TypeOf([]byte(nil))
|
||||
typeIntf = reflect.TypeOf([]any(nil)).Elem()
|
||||
typeTime = reflect.TypeOf(time.Time{})
|
||||
typeBigInt = reflect.TypeOf(big.Int{})
|
||||
typeUnmarshaler = reflect.TypeOf((*Unmarshaler)(nil)).Elem()
|
||||
typeUnexportedUnmarshaler = reflect.TypeOf((*unmarshaler)(nil)).Elem()
|
||||
typeBinaryUnmarshaler = reflect.TypeOf((*encoding.BinaryUnmarshaler)(nil)).Elem()
|
||||
typeTextUnmarshaler = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
|
||||
typeJSONUnmarshaler = reflect.TypeOf((*jsonUnmarshaler)(nil)).Elem()
|
||||
typeString = reflect.TypeOf("")
|
||||
typeByteSlice = reflect.TypeOf([]byte(nil))
|
||||
)
|
||||
|
||||
func fillNil(_ cborType, v reflect.Value) error {
|
||||
switch v.Kind() {
|
||||
case reflect.Slice, reflect.Map, reflect.Interface, reflect.Ptr:
|
||||
v.Set(reflect.Zero(v.Type()))
|
||||
case reflect.Slice, reflect.Map, reflect.Interface, reflect.Pointer:
|
||||
v.SetZero()
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
@ -3083,7 +3181,7 @@ func fillFloat(t cborType, val float64, v reflect.Value) error {
|
||||
}
|
||||
|
||||
func fillByteString(t cborType, val []byte, shared bool, v reflect.Value, bsts ByteStringToStringMode, bum BinaryUnmarshalerMode) error {
|
||||
if bum == BinaryUnmarshalerByteString && reflect.PtrTo(v.Type()).Implements(typeBinaryUnmarshaler) {
|
||||
if bum == BinaryUnmarshalerByteString && reflect.PointerTo(v.Type()).Implements(typeBinaryUnmarshaler) {
|
||||
if v.CanAddr() {
|
||||
v = v.Addr()
|
||||
if u, ok := v.Interface().(encoding.BinaryUnmarshaler); ok {
|
||||
@ -3117,9 +3215,8 @@ func fillByteString(t cborType, val []byte, shared bool, v reflect.Value, bsts B
|
||||
}
|
||||
// Set remaining Go array elements to zero values.
|
||||
if i < vLen {
|
||||
zeroV := reflect.Zero(reflect.TypeOf(byte(0)))
|
||||
for ; i < vLen; i++ {
|
||||
v.Index(i).Set(zeroV)
|
||||
v.Index(i).SetZero()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@ -3127,11 +3224,28 @@ func fillByteString(t cborType, val []byte, shared bool, v reflect.Value, bsts B
|
||||
return &UnmarshalTypeError{CBORType: t.String(), GoType: v.Type().String()}
|
||||
}
|
||||
|
||||
func fillTextString(t cborType, val []byte, v reflect.Value) error {
|
||||
func fillTextString(t cborType, val []byte, v reflect.Value, tum TextUnmarshalerMode) error {
|
||||
// Check if the value implements TextUnmarshaler and the mode allows it
|
||||
if tum == TextUnmarshalerTextString && reflect.PointerTo(v.Type()).Implements(typeTextUnmarshaler) {
|
||||
if v.CanAddr() {
|
||||
v = v.Addr()
|
||||
if u, ok := v.Interface().(encoding.TextUnmarshaler); ok {
|
||||
// The contract of TextUnmarshaler forbids retaining the input
|
||||
// bytes, so no copying is required even if val is shared.
|
||||
if err := u.UnmarshalText(val); err != nil {
|
||||
return fmt.Errorf("cbor: cannot unmarshal text for %s: %w", v.Type(), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errors.New("cbor: cannot set new value for " + v.Type().String())
|
||||
}
|
||||
|
||||
if v.Kind() == reflect.String {
|
||||
v.SetString(string(val))
|
||||
return nil
|
||||
}
|
||||
|
||||
return &UnmarshalTypeError{CBORType: t.String(), GoType: v.Type().String()}
|
||||
}
|
||||
|
||||
@ -3172,7 +3286,7 @@ func isHashableValue(rv reflect.Value) bool {
|
||||
// This function also handles nested tags.
|
||||
// CBOR data is already verified to be well-formed before this function is used,
|
||||
// so the recursion won't exceed max nested levels.
|
||||
func convertByteSliceToByteString(v interface{}) (interface{}, bool) {
|
||||
func convertByteSliceToByteString(v any) (any, bool) {
|
||||
switch v := v.(type) {
|
||||
case []byte:
|
||||
return ByteString(v), true
|
||||
|
||||
2949
decode_test.go
2949
decode_test.go
File diff suppressed because it is too large
Load Diff
804
diagnose_test.go
804
diagnose_test.go
File diff suppressed because it is too large
Load Diff
51
doc.go
51
doc.go
@ -2,15 +2,15 @@
|
||||
// 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,
|
||||
Package cbor is a modern CBOR codec (RFC 8949 & RFC 8742) with CBOR tags,
|
||||
Go struct tag options (toarray/keyasint/omitempty/omitzero), Core Deterministic Encoding,
|
||||
CTAP2, Canonical CBOR, float64->32->16, and duplicate map key detection.
|
||||
|
||||
Encoding options allow "preferred serialization" by encoding integers and floats
|
||||
to their smallest forms (e.g. float16) when values fit.
|
||||
|
||||
Struct tags like "keyasint", "toarray" and "omitempty" make CBOR data smaller
|
||||
and easier to use with structs.
|
||||
Struct tag options "keyasint", "toarray", "omitempty", and "omitzero" reduce encoding size
|
||||
and reduce programming effort.
|
||||
|
||||
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.
|
||||
@ -23,11 +23,19 @@ The Quick Start guide is at https://github.com/fxamacker/cbor#quick-start
|
||||
|
||||
Function signatures identical to encoding/json include:
|
||||
|
||||
Marshal, Unmarshal, NewEncoder, NewDecoder, (*Encoder).Encode, (*Decoder).Decode.
|
||||
Marshal, Unmarshal, NewEncoder, NewDecoder, (*Encoder).Encode, (*Decoder).Decode
|
||||
|
||||
Standard interfaces include:
|
||||
|
||||
BinaryMarshaler, BinaryUnmarshaler, Marshaler, and Unmarshaler.
|
||||
BinaryMarshaler, BinaryUnmarshaler, Marshaler, and Unmarshaler
|
||||
|
||||
Diagnostic functions translate CBOR data item into Diagnostic Notation:
|
||||
|
||||
Diagnose, DiagnoseFirst
|
||||
|
||||
Functions that simplify using CBOR Sequences (RFC 8742) include:
|
||||
|
||||
UnmarshalFirst
|
||||
|
||||
Custom encoding and decoding is possible by implementing standard interfaces for
|
||||
user-defined Go types.
|
||||
@ -50,19 +58,19 @@ 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 {
|
||||
// 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 {
|
||||
// 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
|
||||
|
||||
@ -78,6 +86,16 @@ Using Default Decoding Mode
|
||||
decoder := cbor.NewDecoder(r)
|
||||
err = decoder.Decode(&v)
|
||||
|
||||
Using Default Mode of UnmarshalFirst to Decode CBOR Sequences
|
||||
|
||||
// Decode the first CBOR data item and return remaining bytes:
|
||||
rest, err = cbor.UnmarshalFirst(b, &v) // decode []byte b to v
|
||||
|
||||
Using Extended Diagnostic Notation (EDN) to represent CBOR data
|
||||
|
||||
// Translate the first CBOR data item into text and return remaining bytes.
|
||||
text, rest, err = cbor.DiagnoseFirst(b) // decode []byte b to text
|
||||
|
||||
Creating and Using Encoding Modes
|
||||
|
||||
// Create EncOptions using either struct literal or a function.
|
||||
@ -111,15 +129,20 @@ Decoding Options: https://github.com/fxamacker/cbor#decoding-options
|
||||
Struct tags like `cbor:"name,omitempty"` and `json:"name,omitempty"` work as expected.
|
||||
If both struct tags are specified then `cbor` is used.
|
||||
|
||||
Struct tags like "keyasint", "toarray", and "omitempty" make it easy to use
|
||||
Struct tag options like "keyasint", "toarray", "omitempty", and "omitzero" make it easy to use
|
||||
very compact formats like COSE and CWT (CBOR Web Tokens) with structs.
|
||||
|
||||
The "omitzero" option omits zero values from encoding, matching
|
||||
[stdlib encoding/json behavior](https://pkg.go.dev/encoding/json#Marshal).
|
||||
When specified in the `cbor` tag, the option is always honored.
|
||||
When specified in the `json` tag, the option is honored when building with Go 1.24+.
|
||||
|
||||
For example, "toarray" makes struct fields encode to array elements. And "keyasint"
|
||||
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
|
||||
Struct tag options are listed at https://github.com/fxamacker/cbor#struct-tags-1
|
||||
|
||||
# Tests and Fuzzing
|
||||
|
||||
|
||||
442
encode.go
442
encode.go
@ -58,8 +58,10 @@ import (
|
||||
//
|
||||
// Marshal supports format string stored under the "cbor" key in the struct
|
||||
// field's tag. CBOR format string can specify the name of the field,
|
||||
// "omitempty" and "keyasint" options, and special case "-" for field omission.
|
||||
// If "cbor" key is absent, Marshal uses "json" key.
|
||||
// "omitempty", "omitzero" and "keyasint" options, and special case "-" for
|
||||
// field omission. If "cbor" key is absent, Marshal uses "json" key.
|
||||
// When using the "json" key, the "omitzero" option is honored when building
|
||||
// with Go 1.24+ to match stdlib encoding/json behavior.
|
||||
//
|
||||
// Struct field name is treated as integer if it has "keyasint" option in
|
||||
// its format string. The format string must specify an integer as its
|
||||
@ -67,8 +69,8 @@ import (
|
||||
//
|
||||
// Special struct field "_" is used to specify struct level options, such as
|
||||
// "toarray". "toarray" option enables Go struct to be encoded as CBOR array.
|
||||
// "omitempty" is disabled by "toarray" to ensure that the same number
|
||||
// of elements are encoded every time.
|
||||
// "omitempty" and "omitzero" are disabled by "toarray" to ensure that the
|
||||
// same number of elements are encoded every time.
|
||||
//
|
||||
// Anonymous struct fields are marshaled as if their exported fields
|
||||
// were fields in the outer struct. Marshal follows the same struct fields
|
||||
@ -92,7 +94,7 @@ import (
|
||||
//
|
||||
// Values of other types cannot be encoded in CBOR. Attempting
|
||||
// to encode such a value causes Marshal to return an UnsupportedTypeError.
|
||||
func Marshal(v interface{}) ([]byte, error) {
|
||||
func Marshal(v any) ([]byte, error) {
|
||||
return defaultEncMode.Marshal(v)
|
||||
}
|
||||
|
||||
@ -103,7 +105,7 @@ func Marshal(v interface{}) ([]byte, error) {
|
||||
// partially encoded data if error is returned.
|
||||
//
|
||||
// See Marshal for more details.
|
||||
func MarshalToBuffer(v interface{}, buf *bytes.Buffer) error {
|
||||
func MarshalToBuffer(v any, buf *bytes.Buffer) error {
|
||||
return defaultEncMode.MarshalToBuffer(v, buf)
|
||||
}
|
||||
|
||||
@ -130,6 +132,20 @@ func (e *MarshalerError) Unwrap() error {
|
||||
return e.err
|
||||
}
|
||||
|
||||
type TranscodeError struct {
|
||||
err error
|
||||
rtype reflect.Type
|
||||
sourceFormat, targetFormat string
|
||||
}
|
||||
|
||||
func (e TranscodeError) Error() string {
|
||||
return "cbor: cannot transcode from " + e.sourceFormat + " to " + e.targetFormat + ": " + e.err.Error()
|
||||
}
|
||||
|
||||
func (e TranscodeError) Unwrap() error {
|
||||
return e.err
|
||||
}
|
||||
|
||||
// UnsupportedTypeError is returned by Marshal when attempting to encode value
|
||||
// of an unsupported type.
|
||||
type UnsupportedTypeError struct {
|
||||
@ -291,24 +307,51 @@ func (icm InfConvertMode) valid() bool {
|
||||
return icm >= 0 && icm < maxInfConvert
|
||||
}
|
||||
|
||||
// TimeMode specifies how to encode time.Time values.
|
||||
// TimeMode specifies how to encode time.Time values in compliance with RFC 8949 (CBOR):
|
||||
// - Section 3.4.1: Standard Date/Time String
|
||||
// - Section 3.4.2: Epoch-Based Date/Time
|
||||
// For more info, see:
|
||||
// - https://www.rfc-editor.org/rfc/rfc8949.html
|
||||
// NOTE: User applications that prefer to encode time with fractional seconds to an integer
|
||||
// (instead of floating point or text string) can use a CBOR tag number not assigned by IANA:
|
||||
// 1. Define a user-defined type in Go with just a time.Time or int64 as its data.
|
||||
// 2. Implement the cbor.Marshaler and cbor.Unmarshaler interface for that user-defined type
|
||||
// to encode or decode the tagged data item with an enclosed integer content.
|
||||
type TimeMode int
|
||||
|
||||
const (
|
||||
// TimeUnix causes time.Time to be encoded as epoch time in integer with second precision.
|
||||
// TimeUnix causes time.Time to encode to a CBOR time (tag 1) with an integer content
|
||||
// representing seconds elapsed (with 1-second precision) since UNIX Epoch UTC.
|
||||
// The TimeUnix option is location independent and has a clear precision guarantee.
|
||||
TimeUnix TimeMode = iota
|
||||
|
||||
// TimeUnixMicro causes time.Time to be encoded as epoch time in float-point rounded to microsecond precision.
|
||||
// TimeUnixMicro causes time.Time to encode to a CBOR time (tag 1) with a floating point content
|
||||
// representing seconds elapsed (with up to 1-microsecond precision) since UNIX Epoch UTC.
|
||||
// NOTE: The floating point content is encoded to the shortest floating-point encoding that preserves
|
||||
// the 64-bit floating point value. I.e., the floating point encoding can be IEEE 764:
|
||||
// binary64, binary32, or binary16 depending on the content's value.
|
||||
TimeUnixMicro
|
||||
|
||||
// TimeUnixDynamic causes time.Time to be encoded as integer if time.Time doesn't have fractional seconds,
|
||||
// otherwise float-point rounded to microsecond precision.
|
||||
// TimeUnixDynamic causes time.Time to encode to a CBOR time (tag 1) with either an integer content or
|
||||
// a floating point content, depending on the content's value. This option is equivalent to dynamically
|
||||
// choosing TimeUnix if time.Time doesn't have fractional seconds, and using TimeUnixMicro if time.Time
|
||||
// has fractional seconds.
|
||||
TimeUnixDynamic
|
||||
|
||||
// TimeRFC3339 causes time.Time to be encoded as RFC3339 formatted string with second precision.
|
||||
// TimeRFC3339 causes time.Time to encode to a CBOR time (tag 0) with a text string content
|
||||
// representing the time using 1-second precision in RFC3339 format. If the time.Time has a
|
||||
// non-UTC timezone then a "localtime - UTC" numeric offset will be included as specified in RFC3339.
|
||||
// NOTE: User applications can avoid including the RFC3339 numeric offset by:
|
||||
// - providing a time.Time value set to UTC, or
|
||||
// - using the TimeUnix, TimeUnixMicro, or TimeUnixDynamic option instead of TimeRFC3339.
|
||||
TimeRFC3339
|
||||
|
||||
// TimeRFC3339Nano causes time.Time to be encoded as RFC3339 formatted string with nanosecond precision.
|
||||
// TimeRFC3339Nano causes time.Time to encode to a CBOR time (tag 0) with a text string content
|
||||
// representing the time using 1-nanosecond precision in RFC3339 format. If the time.Time has a
|
||||
// non-UTC timezone then a "localtime - UTC" numeric offset will be included as specified in RFC3339.
|
||||
// NOTE: User applications can avoid including the RFC3339 numeric offset by:
|
||||
// - providing a time.Time value set to UTC, or
|
||||
// - using the TimeUnix, TimeUnixMicro, or TimeUnixDynamic option instead of TimeRFC3339Nano.
|
||||
TimeRFC3339Nano
|
||||
|
||||
maxTimeMode
|
||||
@ -481,6 +524,24 @@ func (bmm BinaryMarshalerMode) valid() bool {
|
||||
return bmm >= 0 && bmm < maxBinaryMarshalerMode
|
||||
}
|
||||
|
||||
// TextMarshalerMode specifies how to encode types that implement encoding.TextMarshaler.
|
||||
type TextMarshalerMode int
|
||||
|
||||
const (
|
||||
// TextMarshalerNone does not recognize TextMarshaler implementations during encode.
|
||||
// This is the default behavior.
|
||||
TextMarshalerNone TextMarshalerMode = iota
|
||||
|
||||
// TextMarshalerTextString encodes the output of MarshalText to a CBOR text string.
|
||||
TextMarshalerTextString
|
||||
|
||||
maxTextMarshalerMode
|
||||
)
|
||||
|
||||
func (tmm TextMarshalerMode) valid() bool {
|
||||
return tmm >= 0 && tmm < maxTextMarshalerMode
|
||||
}
|
||||
|
||||
// EncOptions specifies encoding options.
|
||||
type EncOptions struct {
|
||||
// Sort specifies sorting order.
|
||||
@ -538,6 +599,14 @@ type EncOptions struct {
|
||||
|
||||
// BinaryMarshaler specifies how to encode types that implement encoding.BinaryMarshaler.
|
||||
BinaryMarshaler BinaryMarshalerMode
|
||||
|
||||
// TextMarshaler specifies how to encode types that implement encoding.TextMarshaler.
|
||||
TextMarshaler TextMarshalerMode
|
||||
|
||||
// JSONMarshalerTranscoder sets the transcoding scheme used to marshal types that implement
|
||||
// json.Marshaler but do not also implement cbor.Marshaler. If nil, encoding behavior is not
|
||||
// influenced by whether or not a type implements json.Marshaler.
|
||||
JSONMarshalerTranscoder Transcoder
|
||||
}
|
||||
|
||||
// CanonicalEncOptions returns EncOptions for "Canonical CBOR" encoding,
|
||||
@ -748,6 +817,9 @@ func (opts EncOptions) encMode() (*encMode, error) { //nolint:gocritic // ignore
|
||||
if !opts.BinaryMarshaler.valid() {
|
||||
return nil, errors.New("cbor: invalid BinaryMarshaler " + strconv.Itoa(int(opts.BinaryMarshaler)))
|
||||
}
|
||||
if !opts.TextMarshaler.valid() {
|
||||
return nil, errors.New("cbor: invalid TextMarshaler " + strconv.Itoa(int(opts.TextMarshaler)))
|
||||
}
|
||||
em := encMode{
|
||||
sort: opts.Sort,
|
||||
shortestFloat: opts.ShortestFloat,
|
||||
@ -767,13 +839,15 @@ func (opts EncOptions) encMode() (*encMode, error) { //nolint:gocritic // ignore
|
||||
byteSliceLaterEncodingTag: byteSliceLaterEncodingTag,
|
||||
byteArray: opts.ByteArray,
|
||||
binaryMarshaler: opts.BinaryMarshaler,
|
||||
textMarshaler: opts.TextMarshaler,
|
||||
jsonMarshalerTranscoder: opts.JSONMarshalerTranscoder,
|
||||
}
|
||||
return &em, nil
|
||||
}
|
||||
|
||||
// EncMode is the main interface for CBOR encoding.
|
||||
type EncMode interface {
|
||||
Marshal(v interface{}) ([]byte, error)
|
||||
Marshal(v any) ([]byte, error)
|
||||
NewEncoder(w io.Writer) *Encoder
|
||||
EncOptions() EncOptions
|
||||
}
|
||||
@ -783,7 +857,7 @@ type EncMode interface {
|
||||
// into the built-in buffer pool.
|
||||
type UserBufferEncMode interface {
|
||||
EncMode
|
||||
MarshalToBuffer(v interface{}, buf *bytes.Buffer) error
|
||||
MarshalToBuffer(v any, buf *bytes.Buffer) error
|
||||
|
||||
// This private method is to prevent users implementing
|
||||
// this interface and so future additions to it will
|
||||
@ -812,6 +886,8 @@ type encMode struct {
|
||||
byteSliceLaterEncodingTag uint64
|
||||
byteArray ByteArrayMode
|
||||
binaryMarshaler BinaryMarshalerMode
|
||||
textMarshaler TextMarshalerMode
|
||||
jsonMarshalerTranscoder Transcoder
|
||||
}
|
||||
|
||||
var defaultEncMode, _ = EncOptions{}.encMode()
|
||||
@ -888,22 +964,24 @@ func getMarshalerDecMode(indefLength IndefLengthMode, tagsMd TagsMode) *decMode
|
||||
// EncOptions returns user specified options used to create this EncMode.
|
||||
func (em *encMode) EncOptions() EncOptions {
|
||||
return EncOptions{
|
||||
Sort: em.sort,
|
||||
ShortestFloat: em.shortestFloat,
|
||||
NaNConvert: em.nanConvert,
|
||||
InfConvert: em.infConvert,
|
||||
BigIntConvert: em.bigIntConvert,
|
||||
Time: em.time,
|
||||
TimeTag: em.timeTag,
|
||||
IndefLength: em.indefLength,
|
||||
NilContainers: em.nilContainers,
|
||||
TagsMd: em.tagsMd,
|
||||
OmitEmpty: em.omitEmpty,
|
||||
String: em.stringType,
|
||||
FieldName: em.fieldName,
|
||||
ByteSliceLaterFormat: em.byteSliceLaterFormat,
|
||||
ByteArray: em.byteArray,
|
||||
BinaryMarshaler: em.binaryMarshaler,
|
||||
Sort: em.sort,
|
||||
ShortestFloat: em.shortestFloat,
|
||||
NaNConvert: em.nanConvert,
|
||||
InfConvert: em.infConvert,
|
||||
BigIntConvert: em.bigIntConvert,
|
||||
Time: em.time,
|
||||
TimeTag: em.timeTag,
|
||||
IndefLength: em.indefLength,
|
||||
NilContainers: em.nilContainers,
|
||||
TagsMd: em.tagsMd,
|
||||
OmitEmpty: em.omitEmpty,
|
||||
String: em.stringType,
|
||||
FieldName: em.fieldName,
|
||||
ByteSliceLaterFormat: em.byteSliceLaterFormat,
|
||||
ByteArray: em.byteArray,
|
||||
BinaryMarshaler: em.binaryMarshaler,
|
||||
TextMarshaler: em.textMarshaler,
|
||||
JSONMarshalerTranscoder: em.jsonMarshalerTranscoder,
|
||||
}
|
||||
}
|
||||
|
||||
@ -921,7 +999,7 @@ func (em *encMode) encTagBytes(t reflect.Type) []byte {
|
||||
// Marshal returns the CBOR encoding of v using em encoding mode.
|
||||
//
|
||||
// See the documentation for Marshal for details.
|
||||
func (em *encMode) Marshal(v interface{}) ([]byte, error) {
|
||||
func (em *encMode) Marshal(v any) ([]byte, error) {
|
||||
e := getEncodeBuffer()
|
||||
|
||||
if err := encode(e, em, reflect.ValueOf(v)); err != nil {
|
||||
@ -943,7 +1021,7 @@ func (em *encMode) Marshal(v interface{}) ([]byte, error) {
|
||||
// partially encoded data if error is returned.
|
||||
//
|
||||
// See Marshal for more details.
|
||||
func (em *encMode) MarshalToBuffer(v interface{}, buf *bytes.Buffer) error {
|
||||
func (em *encMode) MarshalToBuffer(v any, buf *bytes.Buffer) error {
|
||||
if buf == nil {
|
||||
return fmt.Errorf("cbor: encoding buffer provided by user is nil")
|
||||
}
|
||||
@ -957,7 +1035,7 @@ func (em *encMode) NewEncoder(w io.Writer) *Encoder {
|
||||
|
||||
// encodeBufferPool caches unused bytes.Buffer objects for later reuse.
|
||||
var encodeBufferPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
New: func() any {
|
||||
e := new(bytes.Buffer)
|
||||
e.Grow(32) // TODO: make this configurable
|
||||
return e
|
||||
@ -975,6 +1053,7 @@ func putEncodeBuffer(e *bytes.Buffer) {
|
||||
|
||||
type encodeFunc func(e *bytes.Buffer, em *encMode, v reflect.Value) error
|
||||
type isEmptyFunc func(em *encMode, v reflect.Value) (empty bool, err error)
|
||||
type isZeroFunc func(v reflect.Value) (zero bool, err error)
|
||||
|
||||
func encode(e *bytes.Buffer, em *encMode, v reflect.Value) error {
|
||||
if !v.IsValid() {
|
||||
@ -983,7 +1062,7 @@ func encode(e *bytes.Buffer, em *encMode, v reflect.Value) error {
|
||||
return nil
|
||||
}
|
||||
vt := v.Type()
|
||||
f, _ := getEncodeFunc(vt)
|
||||
f, _, _ := getEncodeFunc(vt)
|
||||
if f == nil {
|
||||
return &UnsupportedTypeError{vt}
|
||||
}
|
||||
@ -1483,6 +1562,15 @@ func encodeStruct(e *bytes.Buffer, em *encMode, v reflect.Value) (err error) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if f.omitZero {
|
||||
zero, err := f.izf(fv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if zero {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if !f.keyAsInt && em.fieldName == FieldNameToByteString {
|
||||
e.Write(f.cborNameByteString)
|
||||
@ -1665,6 +1753,107 @@ func (bme binaryMarshalerEncoder) isEmpty(em *encMode, v reflect.Value) (bool, e
|
||||
return len(data) == 0, nil
|
||||
}
|
||||
|
||||
type textMarshalerEncoder struct {
|
||||
alternateEncode encodeFunc
|
||||
alternateIsEmpty isEmptyFunc
|
||||
}
|
||||
|
||||
func (tme textMarshalerEncoder) encode(e *bytes.Buffer, em *encMode, v reflect.Value) error {
|
||||
if em.textMarshaler == TextMarshalerNone {
|
||||
return tme.alternateEncode(e, em, v)
|
||||
}
|
||||
|
||||
vt := v.Type()
|
||||
m, ok := v.Interface().(encoding.TextMarshaler)
|
||||
if !ok {
|
||||
pv := reflect.New(vt)
|
||||
pv.Elem().Set(v)
|
||||
m = pv.Interface().(encoding.TextMarshaler)
|
||||
}
|
||||
data, err := m.MarshalText()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cbor: cannot marshal text for %s: %w", vt, err)
|
||||
}
|
||||
if b := em.encTagBytes(vt); b != nil {
|
||||
e.Write(b)
|
||||
}
|
||||
|
||||
encodeHead(e, byte(cborTypeTextString), uint64(len(data)))
|
||||
e.Write(data)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tme textMarshalerEncoder) isEmpty(em *encMode, v reflect.Value) (bool, error) {
|
||||
if em.textMarshaler == TextMarshalerNone {
|
||||
return tme.alternateIsEmpty(em, v)
|
||||
}
|
||||
|
||||
m, ok := v.Interface().(encoding.TextMarshaler)
|
||||
if !ok {
|
||||
pv := reflect.New(v.Type())
|
||||
pv.Elem().Set(v)
|
||||
m = pv.Interface().(encoding.TextMarshaler)
|
||||
}
|
||||
data, err := m.MarshalText()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("cbor: cannot marshal text for %s: %w", v.Type(), err)
|
||||
}
|
||||
return len(data) == 0, nil
|
||||
}
|
||||
|
||||
type jsonMarshalerEncoder struct {
|
||||
alternateEncode encodeFunc
|
||||
alternateIsEmpty isEmptyFunc
|
||||
}
|
||||
|
||||
func (jme jsonMarshalerEncoder) encode(e *bytes.Buffer, em *encMode, v reflect.Value) error {
|
||||
if em.jsonMarshalerTranscoder == nil {
|
||||
return jme.alternateEncode(e, em, v)
|
||||
}
|
||||
|
||||
vt := v.Type()
|
||||
m, ok := v.Interface().(jsonMarshaler)
|
||||
if !ok {
|
||||
pv := reflect.New(vt)
|
||||
pv.Elem().Set(v)
|
||||
m = pv.Interface().(jsonMarshaler)
|
||||
}
|
||||
|
||||
json, err := m.MarshalJSON()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
offset := e.Len()
|
||||
|
||||
if b := em.encTagBytes(vt); b != nil {
|
||||
e.Write(b)
|
||||
}
|
||||
|
||||
if err := em.jsonMarshalerTranscoder.Transcode(e, bytes.NewReader(json)); err != nil {
|
||||
return &TranscodeError{err: err, rtype: vt, sourceFormat: "json", targetFormat: "cbor"}
|
||||
}
|
||||
|
||||
// Validate that the transcode function has written exactly one well-formed data item.
|
||||
d := decoder{data: e.Bytes()[offset:], dm: getMarshalerDecMode(em.indefLength, em.tagsMd)}
|
||||
if err := d.wellformed(false, true); err != nil {
|
||||
e.Truncate(offset)
|
||||
return &TranscodeError{err: err, rtype: vt, sourceFormat: "json", targetFormat: "cbor"}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (jme jsonMarshalerEncoder) isEmpty(em *encMode, v reflect.Value) (bool, error) {
|
||||
if em.jsonMarshalerTranscoder == nil {
|
||||
return jme.alternateIsEmpty(em, v)
|
||||
}
|
||||
|
||||
// As with types implementing cbor.Marshaler, transcoded json.Marshaler values always encode
|
||||
// as exactly one complete CBOR data item.
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func encodeMarshalerType(e *bytes.Buffer, em *encMode, v reflect.Value) error {
|
||||
if em.tagsMd == TagsForbidden && v.Type() == typeRawTag {
|
||||
return errors.New("cbor: cannot encode cbor.RawTag when TagsMd is TagsForbidden")
|
||||
@ -1768,41 +1957,45 @@ func encodeHead(e *bytes.Buffer, t byte, n uint64) int {
|
||||
return headSize
|
||||
}
|
||||
|
||||
type jsonMarshaler interface{ MarshalJSON() ([]byte, error) }
|
||||
|
||||
var (
|
||||
typeMarshaler = reflect.TypeOf((*Marshaler)(nil)).Elem()
|
||||
typeBinaryMarshaler = reflect.TypeOf((*encoding.BinaryMarshaler)(nil)).Elem()
|
||||
typeTextMarshaler = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem()
|
||||
typeJSONMarshaler = reflect.TypeOf((*jsonMarshaler)(nil)).Elem()
|
||||
typeRawMessage = reflect.TypeOf(RawMessage(nil))
|
||||
typeByteString = reflect.TypeOf(ByteString(""))
|
||||
)
|
||||
|
||||
func getEncodeFuncInternal(t reflect.Type) (ef encodeFunc, ief isEmptyFunc) {
|
||||
func getEncodeFuncInternal(t reflect.Type) (ef encodeFunc, ief isEmptyFunc, izf isZeroFunc) {
|
||||
k := t.Kind()
|
||||
if k == reflect.Ptr {
|
||||
return getEncodeIndirectValueFunc(t), isEmptyPtr
|
||||
if k == reflect.Pointer {
|
||||
return getEncodeIndirectValueFunc(t), isEmptyPtr, getIsZeroFunc(t)
|
||||
}
|
||||
switch t {
|
||||
case typeSimpleValue:
|
||||
return encodeMarshalerType, isEmptyUint
|
||||
return encodeMarshalerType, isEmptyUint, getIsZeroFunc(t)
|
||||
|
||||
case typeTag:
|
||||
return encodeTag, alwaysNotEmpty
|
||||
return encodeTag, alwaysNotEmpty, getIsZeroFunc(t)
|
||||
|
||||
case typeTime:
|
||||
return encodeTime, alwaysNotEmpty
|
||||
return encodeTime, alwaysNotEmpty, getIsZeroFunc(t)
|
||||
|
||||
case typeBigInt:
|
||||
return encodeBigInt, alwaysNotEmpty
|
||||
return encodeBigInt, alwaysNotEmpty, getIsZeroFunc(t)
|
||||
|
||||
case typeRawMessage:
|
||||
return encodeMarshalerType, isEmptySlice
|
||||
return encodeMarshalerType, isEmptySlice, getIsZeroFunc(t)
|
||||
|
||||
case typeByteString:
|
||||
return encodeMarshalerType, isEmptyString
|
||||
return encodeMarshalerType, isEmptyString, getIsZeroFunc(t)
|
||||
}
|
||||
if reflect.PtrTo(t).Implements(typeMarshaler) {
|
||||
return encodeMarshalerType, alwaysNotEmpty
|
||||
if reflect.PointerTo(t).Implements(typeMarshaler) {
|
||||
return encodeMarshalerType, alwaysNotEmpty, getIsZeroFunc(t)
|
||||
}
|
||||
if reflect.PtrTo(t).Implements(typeBinaryMarshaler) {
|
||||
if reflect.PointerTo(t).Implements(typeBinaryMarshaler) {
|
||||
defer func() {
|
||||
// capture encoding method used for modes that disable BinaryMarshaler
|
||||
bme := binaryMarshalerEncoder{
|
||||
@ -1813,41 +2006,65 @@ func getEncodeFuncInternal(t reflect.Type) (ef encodeFunc, ief isEmptyFunc) {
|
||||
ief = bme.isEmpty
|
||||
}()
|
||||
}
|
||||
if reflect.PointerTo(t).Implements(typeTextMarshaler) {
|
||||
defer func() {
|
||||
// capture encoding method used for modes that disable TextMarshaler
|
||||
tme := textMarshalerEncoder{
|
||||
alternateEncode: ef,
|
||||
alternateIsEmpty: ief,
|
||||
}
|
||||
ef = tme.encode
|
||||
ief = tme.isEmpty
|
||||
}()
|
||||
}
|
||||
if reflect.PointerTo(t).Implements(typeJSONMarshaler) {
|
||||
defer func() {
|
||||
// capture encoding method used for modes that don't support transcoding
|
||||
// from types that implement json.Marshaler.
|
||||
jme := jsonMarshalerEncoder{
|
||||
alternateEncode: ef,
|
||||
alternateIsEmpty: ief,
|
||||
}
|
||||
ef = jme.encode
|
||||
ief = jme.isEmpty
|
||||
}()
|
||||
}
|
||||
|
||||
switch k {
|
||||
case reflect.Bool:
|
||||
return encodeBool, isEmptyBool
|
||||
return encodeBool, isEmptyBool, getIsZeroFunc(t)
|
||||
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return encodeInt, isEmptyInt
|
||||
return encodeInt, isEmptyInt, getIsZeroFunc(t)
|
||||
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
return encodeUint, isEmptyUint
|
||||
return encodeUint, isEmptyUint, getIsZeroFunc(t)
|
||||
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return encodeFloat, isEmptyFloat
|
||||
return encodeFloat, isEmptyFloat, getIsZeroFunc(t)
|
||||
|
||||
case reflect.String:
|
||||
return encodeString, isEmptyString
|
||||
return encodeString, isEmptyString, getIsZeroFunc(t)
|
||||
|
||||
case reflect.Slice:
|
||||
if t.Elem().Kind() == reflect.Uint8 {
|
||||
return encodeByteString, isEmptySlice
|
||||
return encodeByteString, isEmptySlice, getIsZeroFunc(t)
|
||||
}
|
||||
fallthrough
|
||||
|
||||
case reflect.Array:
|
||||
f, _ := getEncodeFunc(t.Elem())
|
||||
f, _, _ := getEncodeFunc(t.Elem())
|
||||
if f == nil {
|
||||
return nil, nil
|
||||
return nil, nil, nil
|
||||
}
|
||||
return arrayEncodeFunc{f: f}.encode, isEmptySlice
|
||||
return arrayEncodeFunc{f: f}.encode, isEmptySlice, getIsZeroFunc(t)
|
||||
|
||||
case reflect.Map:
|
||||
f := getEncodeMapFunc(t)
|
||||
if f == nil {
|
||||
return nil, nil
|
||||
return nil, nil, nil
|
||||
}
|
||||
return f, isEmptyMap
|
||||
return f, isEmptyMap, getIsZeroFunc(t)
|
||||
|
||||
case reflect.Struct:
|
||||
// Get struct's special field "_" tag options
|
||||
@ -1855,31 +2072,31 @@ func getEncodeFuncInternal(t reflect.Type) (ef encodeFunc, ief isEmptyFunc) {
|
||||
tag := f.Tag.Get("cbor")
|
||||
if tag != "-" {
|
||||
if hasToArrayOption(tag) {
|
||||
return encodeStructToArray, isEmptyStruct
|
||||
return encodeStructToArray, isEmptyStruct, isZeroFieldStruct
|
||||
}
|
||||
}
|
||||
}
|
||||
return encodeStruct, isEmptyStruct
|
||||
return encodeStruct, isEmptyStruct, getIsZeroFunc(t)
|
||||
|
||||
case reflect.Interface:
|
||||
return encodeIntf, isEmptyIntf
|
||||
return encodeIntf, isEmptyIntf, getIsZeroFunc(t)
|
||||
}
|
||||
return nil, nil
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
func getEncodeIndirectValueFunc(t reflect.Type) encodeFunc {
|
||||
for t.Kind() == reflect.Ptr {
|
||||
for t.Kind() == reflect.Pointer {
|
||||
t = t.Elem()
|
||||
}
|
||||
f, _ := getEncodeFunc(t)
|
||||
f, _, _ := getEncodeFunc(t)
|
||||
if f == nil {
|
||||
return nil
|
||||
}
|
||||
return func(e *bytes.Buffer, em *encMode, v reflect.Value) error {
|
||||
for v.Kind() == reflect.Ptr && !v.IsNil() {
|
||||
for v.Kind() == reflect.Pointer && !v.IsNil() {
|
||||
v = v.Elem()
|
||||
}
|
||||
if v.Kind() == reflect.Ptr && v.IsNil() {
|
||||
if v.Kind() == reflect.Pointer && v.IsNil() {
|
||||
e.Write(cborNil)
|
||||
return nil
|
||||
}
|
||||
@ -1987,3 +2204,96 @@ func float32NaNFromReflectValue(v reflect.Value) float32 {
|
||||
f32 := p.Convert(reflect.TypeOf((*float32)(nil))).Elem().Interface().(float32)
|
||||
return f32
|
||||
}
|
||||
|
||||
type isZeroer interface {
|
||||
IsZero() bool
|
||||
}
|
||||
|
||||
var isZeroerType = reflect.TypeOf((*isZeroer)(nil)).Elem()
|
||||
|
||||
// getIsZeroFunc returns a function for the given type that can be called to determine if a given value is zero.
|
||||
// Types that implement `IsZero() bool` are delegated to for non-nil values.
|
||||
// Types that do not implement `IsZero() bool` use the reflect.Value#IsZero() implementation.
|
||||
// The returned function matches behavior of stdlib encoding/json behavior in Go 1.24+.
|
||||
func getIsZeroFunc(t reflect.Type) isZeroFunc {
|
||||
// Provide a function that uses a type's IsZero method if defined.
|
||||
switch {
|
||||
case t == nil:
|
||||
return isZeroDefault
|
||||
case t.Kind() == reflect.Interface && t.Implements(isZeroerType):
|
||||
return isZeroInterfaceCustom
|
||||
case t.Kind() == reflect.Pointer && t.Implements(isZeroerType):
|
||||
return isZeroPointerCustom
|
||||
case t.Implements(isZeroerType):
|
||||
return isZeroCustom
|
||||
case reflect.PointerTo(t).Implements(isZeroerType):
|
||||
return isZeroAddrCustom
|
||||
default:
|
||||
return isZeroDefault
|
||||
}
|
||||
}
|
||||
|
||||
// isZeroInterfaceCustom returns true for nil or pointer-to-nil values,
|
||||
// and delegates to the custom IsZero() implementation otherwise.
|
||||
func isZeroInterfaceCustom(v reflect.Value) (bool, error) {
|
||||
kind := v.Kind()
|
||||
|
||||
switch kind {
|
||||
case reflect.Chan, reflect.Func, reflect.Map, reflect.Pointer, reflect.Interface, reflect.Slice:
|
||||
if v.IsNil() {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
switch kind {
|
||||
case reflect.Interface, reflect.Pointer:
|
||||
if elem := v.Elem(); elem.Kind() == reflect.Pointer && elem.IsNil() {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return v.Interface().(isZeroer).IsZero(), nil
|
||||
}
|
||||
|
||||
// isZeroPointerCustom returns true for nil values,
|
||||
// and delegates to the custom IsZero() implementation otherwise.
|
||||
func isZeroPointerCustom(v reflect.Value) (bool, error) {
|
||||
if v.IsNil() {
|
||||
return true, nil
|
||||
}
|
||||
return v.Interface().(isZeroer).IsZero(), nil
|
||||
}
|
||||
|
||||
// isZeroCustom delegates to the custom IsZero() implementation.
|
||||
func isZeroCustom(v reflect.Value) (bool, error) {
|
||||
return v.Interface().(isZeroer).IsZero(), nil
|
||||
}
|
||||
|
||||
// isZeroAddrCustom delegates to the custom IsZero() implementation of the addr of the value.
|
||||
func isZeroAddrCustom(v reflect.Value) (bool, error) {
|
||||
if !v.CanAddr() {
|
||||
// Temporarily box v so we can take the address.
|
||||
v2 := reflect.New(v.Type()).Elem()
|
||||
v2.Set(v)
|
||||
v = v2
|
||||
}
|
||||
return v.Addr().Interface().(isZeroer).IsZero(), nil
|
||||
}
|
||||
|
||||
// isZeroDefault calls reflect.Value#IsZero()
|
||||
func isZeroDefault(v reflect.Value) (bool, error) {
|
||||
if !v.IsValid() {
|
||||
// v is zero value
|
||||
return true, nil
|
||||
}
|
||||
return v.IsZero(), nil
|
||||
}
|
||||
|
||||
// isZeroFieldStruct is used to determine whether to omit toarray structs
|
||||
func isZeroFieldStruct(v reflect.Value) (bool, error) {
|
||||
structType, err := getEncodingStructType(v.Type())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return len(structType.fields) == 0, nil
|
||||
}
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
// 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
|
||||
|
||||
package cbor
|
||||
|
||||
import (
|
||||
@ -67,8 +65,8 @@ func (me *mapKeyValueEncodeFunc) encodeKeyValues(e *bytes.Buffer, em *encMode, v
|
||||
}
|
||||
|
||||
func getEncodeMapFunc(t reflect.Type) encodeFunc {
|
||||
kf, _ := getEncodeFunc(t.Key())
|
||||
ef, _ := getEncodeFunc(t.Elem())
|
||||
kf, _, _ := getEncodeFunc(t.Key())
|
||||
ef, _, _ := getEncodeFunc(t.Elem())
|
||||
if kf == nil || ef == nil {
|
||||
return nil
|
||||
}
|
||||
@ -76,13 +74,13 @@ func getEncodeMapFunc(t reflect.Type) encodeFunc {
|
||||
kf: kf,
|
||||
ef: ef,
|
||||
kpool: sync.Pool{
|
||||
New: func() interface{} {
|
||||
New: func() any {
|
||||
rk := reflect.New(t.Key()).Elem()
|
||||
return &rk
|
||||
},
|
||||
},
|
||||
vpool: sync.Pool{
|
||||
New: func() interface{} {
|
||||
New: func() any {
|
||||
rv := reflect.New(t.Elem()).Elem()
|
||||
return &rv
|
||||
},
|
||||
|
||||
@ -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
|
||||
|
||||
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
|
||||
}
|
||||
3342
encode_test.go
3342
encode_test.go
File diff suppressed because it is too large
Load Diff
130
example_embedded_json_tag_for_cbor_test.go
Normal file
130
example_embedded_json_tag_for_cbor_test.go
Normal file
@ -0,0 +1,130 @@
|
||||
// Copyright (c) Faye Amacker. All rights reserved.
|
||||
// Licensed under the MIT License. See LICENSE in the project root for license information.
|
||||
|
||||
package cbor_test
|
||||
|
||||
// fxamacker/cbor allows user apps to use almost any current or future
|
||||
// CBOR tag number by implementing cbor.Marshaler and cbor.Unmarshaler
|
||||
// interfaces. Essentially, MarshalCBOR and UnmarshalCBOR functions that
|
||||
// are implemented by user apps will automatically be called by this
|
||||
// CBOR codec's Marshal, Unmarshal, etc.
|
||||
//
|
||||
// This example shows how to encode and decode a tagged CBOR data item with
|
||||
// tag number 262 and the tag content is a JSON object "embedded" as a
|
||||
// CBOR byte string (major type 2).
|
||||
//
|
||||
// NOTE: RFC 8949 does not mention tag number 262. IANA assigned
|
||||
// CBOR tag number 262 as "Embedded JSON Object" specified by the
|
||||
// document Embedded JSON Tag for CBOR:
|
||||
//
|
||||
// "Tag 262 can be applied to a byte string (major type 2) to indicate
|
||||
// that the byte string is a JSON Object. The length of the byte string
|
||||
// indicates the content."
|
||||
//
|
||||
// For more info, see Embedded JSON Tag for CBOR at:
|
||||
// https://github.com/toravir/CBOR-Tag-Specs/blob/master/embeddedJSON.md
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/fxamacker/cbor/v2"
|
||||
)
|
||||
|
||||
// cborTagNumForEmbeddedJSON is the CBOR tag number 262.
|
||||
const cborTagNumForEmbeddedJSON = 262
|
||||
|
||||
// EmbeddedJSON represents a Go value to be encoded as a tagged CBOR data item
|
||||
// with tag number 262 and the tag content is a JSON object "embedded" as a
|
||||
// CBOR byte string (major type 2).
|
||||
type EmbeddedJSON struct {
|
||||
any
|
||||
}
|
||||
|
||||
func NewEmbeddedJSON(val any) EmbeddedJSON {
|
||||
return EmbeddedJSON{val}
|
||||
}
|
||||
|
||||
// MarshalCBOR encodes EmbeddedJSON to a tagged CBOR data item with the
|
||||
// tag number 262 and the tag content is a JSON object that is
|
||||
// "embedded" as a CBOR byte string.
|
||||
func (v EmbeddedJSON) MarshalCBOR() ([]byte, error) {
|
||||
// Encode v to JSON object.
|
||||
data, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create cbor.Tag representing a tagged CBOR data item.
|
||||
tag := cbor.Tag{
|
||||
Number: cborTagNumForEmbeddedJSON,
|
||||
Content: data,
|
||||
}
|
||||
|
||||
// Marshal to a tagged CBOR data item.
|
||||
return cbor.Marshal(tag)
|
||||
}
|
||||
|
||||
// UnmarshalCBOR decodes a tagged CBOR data item to EmbeddedJSON.
|
||||
// The byte slice provided to this function must contain a single
|
||||
// tagged CBOR data item with the tag number 262 and tag content
|
||||
// must be a JSON object "embedded" as a CBOR byte string.
|
||||
func (v *EmbeddedJSON) UnmarshalCBOR(b []byte) error {
|
||||
// Unmarshal tagged CBOR data item.
|
||||
var tag cbor.Tag
|
||||
if err := cbor.Unmarshal(b, &tag); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check tag number.
|
||||
if tag.Number != cborTagNumForEmbeddedJSON {
|
||||
return fmt.Errorf("got tag number %d, expect tag number %d", tag.Number, cborTagNumForEmbeddedJSON)
|
||||
}
|
||||
|
||||
// Check tag content.
|
||||
jsonData, isByteString := tag.Content.([]byte)
|
||||
if !isByteString {
|
||||
return fmt.Errorf("got tag content type %T, expect tag content []byte", tag.Content)
|
||||
}
|
||||
|
||||
// Unmarshal JSON object.
|
||||
return json.Unmarshal(jsonData, v)
|
||||
}
|
||||
|
||||
// MarshalJSON encodes EmbeddedJSON to a JSON object.
|
||||
func (v EmbeddedJSON) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(v.any)
|
||||
}
|
||||
|
||||
// UnmarshalJSON decodes a JSON object.
|
||||
func (v *EmbeddedJSON) UnmarshalJSON(b []byte) error {
|
||||
dec := json.NewDecoder(bytes.NewReader(b))
|
||||
dec.UseNumber()
|
||||
return dec.Decode(&v.any)
|
||||
}
|
||||
|
||||
func Example_embeddedJSONTagForCBOR() {
|
||||
value := NewEmbeddedJSON(map[string]any{
|
||||
"name": "gopher",
|
||||
"id": json.Number("42"),
|
||||
})
|
||||
|
||||
data, err := cbor.Marshal(value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("cbor: %x\n", data)
|
||||
|
||||
var v EmbeddedJSON
|
||||
err = cbor.Unmarshal(data, &v)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("%+v\n", v.any)
|
||||
for k, v := range v.any.(map[string]any) {
|
||||
fmt.Printf(" %s: %v (%T)\n", k, v, v)
|
||||
}
|
||||
}
|
||||
@ -82,7 +82,7 @@ func ExampleMarshal_canonical() {
|
||||
// a46341676504644d616c65f4644e616d656543616e647968436f6e7461637473a2634a6f656c3232322d3232322d32323232644d6172796c3131312d3131312d31313131
|
||||
}
|
||||
|
||||
// This example uses "toarray" struct tag to encode struct as CBOR array.
|
||||
// This example uses "toarray" struct tag option to encode struct as CBOR array.
|
||||
func ExampleMarshal_toarray() {
|
||||
type Record struct {
|
||||
_ struct{} `cbor:",toarray"`
|
||||
@ -100,8 +100,8 @@ func ExampleMarshal_toarray() {
|
||||
// 836763757272656e74615601
|
||||
}
|
||||
|
||||
// This example uses "keyasint" struct tag to encode struct's fiele names as integer.
|
||||
// This feautre is very useful in handling COSE, CWT, SenML data.
|
||||
// This example uses "keyasint" struct tag option to encode struct's field names as integer.
|
||||
// This feature is very useful in handling COSE, CWT, SenML data.
|
||||
func ExampleMarshal_keyasint() {
|
||||
type Record struct {
|
||||
Name string `cbor:"1,keyasint"`
|
||||
@ -353,7 +353,7 @@ func ExampleDecoder() {
|
||||
}
|
||||
|
||||
func Example_cWT() {
|
||||
// Use "keyasint" struct tag to encode/decode struct to/from CBOR map.
|
||||
// Use "keyasint" struct tag option to encode/decode struct to/from CBOR map.
|
||||
type claims struct {
|
||||
Iss string `cbor:"1,keyasint"`
|
||||
Sub string `cbor:"2,keyasint"`
|
||||
@ -403,14 +403,14 @@ func Example_cWTWithDupMapKeyOption() {
|
||||
}
|
||||
|
||||
func Example_signedCWT() {
|
||||
// Use "keyasint" struct tag to encode/decode struct to/from CBOR map.
|
||||
// Use "keyasint" struct tag option to encode/decode struct to/from CBOR map.
|
||||
// Partial COSE header definition
|
||||
type coseHeader struct {
|
||||
Alg int `cbor:"1,keyasint,omitempty"`
|
||||
Kid []byte `cbor:"4,keyasint,omitempty"`
|
||||
IV []byte `cbor:"5,keyasint,omitempty"`
|
||||
}
|
||||
// Use "toarray" struct tag to encode/decode struct to/from CBOR array.
|
||||
// Use "toarray" struct tag option to encode/decode struct to/from CBOR array.
|
||||
type signedCWT struct {
|
||||
_ struct{} `cbor:",toarray"`
|
||||
Protected []byte
|
||||
@ -433,14 +433,14 @@ func Example_signedCWT() {
|
||||
}
|
||||
|
||||
func Example_signedCWTWithTag() {
|
||||
// Use "keyasint" struct tag to encode/decode struct to/from CBOR map.
|
||||
// Use "keyasint" struct tag option to encode/decode struct to/from CBOR map.
|
||||
// Partial COSE header definition
|
||||
type coseHeader struct {
|
||||
Alg int `cbor:"1,keyasint,omitempty"`
|
||||
Kid []byte `cbor:"4,keyasint,omitempty"`
|
||||
IV []byte `cbor:"5,keyasint,omitempty"`
|
||||
}
|
||||
// Use "toarray" struct tag to encode/decode struct to/from CBOR array.
|
||||
// Use "toarray" struct tag option to encode/decode struct to/from CBOR array.
|
||||
type signedCWT struct {
|
||||
_ struct{} `cbor:",toarray"`
|
||||
Protected []byte
|
||||
@ -478,7 +478,7 @@ func Example_signedCWTWithTag() {
|
||||
}
|
||||
|
||||
func Example_cOSE() {
|
||||
// Use "keyasint" struct tag to encode/decode struct to/from CBOR map.
|
||||
// Use "keyasint" struct tag option to encode/decode struct to/from CBOR map.
|
||||
// Use cbor.RawMessage to delay unmarshaling (CrvOrNOrK's data type depends on Kty's value).
|
||||
type coseKey struct {
|
||||
Kty int `cbor:"1,keyasint,omitempty"`
|
||||
@ -488,7 +488,7 @@ func Example_cOSE() {
|
||||
IV []byte `cbor:"5,keyasint,omitempty"`
|
||||
CrvOrNOrK cbor.RawMessage `cbor:"-1,keyasint,omitempty"` // K for symmetric keys, Crv for elliptic curve keys, N for RSA modulus
|
||||
XOrE cbor.RawMessage `cbor:"-2,keyasint,omitempty"` // X for curve x-coordinate, E for RSA public exponent
|
||||
Y cbor.RawMessage `cbor:"-3,keyasint,omitempty"` // Y for curve y-cooridate
|
||||
Y cbor.RawMessage `cbor:"-3,keyasint,omitempty"` // Y for curve y-coordinate
|
||||
D []byte `cbor:"-4,keyasint,omitempty"`
|
||||
}
|
||||
// Data from https://tools.ietf.org/html/rfc8392#appendix-A section A.2
|
||||
@ -507,7 +507,7 @@ func Example_cOSE() {
|
||||
}
|
||||
|
||||
func Example_senML() {
|
||||
// Use "keyasint" struct tag to encode/decode struct to/from CBOR map.
|
||||
// Use "keyasint" struct tag option to encode/decode struct to/from CBOR map.
|
||||
type SenMLRecord struct {
|
||||
BaseName string `cbor:"-2,keyasint,omitempty"`
|
||||
BaseTime float64 `cbor:"-3,keyasint,omitempty"`
|
||||
|
||||
85
example_transcoding_test.go
Normal file
85
example_transcoding_test.go
Normal file
@ -0,0 +1,85 @@
|
||||
// Copyright (c) Faye Amacker. All rights reserved.
|
||||
// Licensed under the MIT License. See LICENSE in the project root for license information.
|
||||
|
||||
package cbor_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
|
||||
"github.com/fxamacker/cbor/v2"
|
||||
)
|
||||
|
||||
type TranscoderFunc func(io.Writer, io.Reader) error
|
||||
|
||||
func (f TranscoderFunc) Transcode(w io.Writer, r io.Reader) error {
|
||||
return f(w, r)
|
||||
}
|
||||
|
||||
func ExampleTranscoder_fromJSON() {
|
||||
enc, _ := cbor.EncOptions{
|
||||
JSONMarshalerTranscoder: TranscoderFunc(func(w io.Writer, r io.Reader) error {
|
||||
d := json.NewDecoder(r)
|
||||
|
||||
for {
|
||||
token, err := d.Token()
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch token {
|
||||
case json.Delim('['):
|
||||
if _, err := w.Write([]byte{0x9f}); err != nil {
|
||||
return err
|
||||
}
|
||||
case json.Delim('{'):
|
||||
if _, err := w.Write([]byte{0xbf}); err != nil {
|
||||
return err
|
||||
}
|
||||
case json.Delim(']'), json.Delim('}'):
|
||||
if _, err := w.Write([]byte{0xff}); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
b, err := cbor.Marshal(token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := w.Write(b); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
}.EncMode()
|
||||
|
||||
got, _ := enc.Marshal(json.RawMessage(`{"a": [true, "z", {"y": 3.14}], "b": {"c": null}}`))
|
||||
diag, _ := cbor.Diagnose(got)
|
||||
fmt.Println(diag)
|
||||
// Output: {_ "a": [_ true, "z", {_ "y": 3.14}], "b": {_ "c": null}}
|
||||
}
|
||||
|
||||
func ExampleTranscoder_toJSON() {
|
||||
var dec cbor.DecMode
|
||||
dec, _ = cbor.DecOptions{
|
||||
DefaultMapType: reflect.TypeOf(map[string]any{}),
|
||||
JSONUnmarshalerTranscoder: TranscoderFunc(func(w io.Writer, r io.Reader) error {
|
||||
var tmp any
|
||||
if err := dec.NewDecoder(r).Decode(&tmp); err != nil {
|
||||
return err
|
||||
}
|
||||
return json.NewEncoder(w).Encode(tmp)
|
||||
}),
|
||||
}.DecMode()
|
||||
|
||||
var got json.RawMessage
|
||||
if err := dec.Unmarshal(cbor.RawMessage{0xa2, 0x61, 'a', 0x01, 0x61, 'b', 0x83, 0xf4, 0xf5, 0xf6}, &got); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(string(got))
|
||||
// Output: {"a":1,"b":[false,true,null]}
|
||||
}
|
||||
2
go.mod
2
go.mod
@ -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.20
|
||||
|
||||
require github.com/x448/float16 v0.8.4
|
||||
|
||||
@ -1,3 +1,6 @@
|
||||
// Copyright (c) Faye Amacker. All rights reserved.
|
||||
// Licensed under the MIT License. See LICENSE in the project root for license information.
|
||||
|
||||
package cbor_test
|
||||
|
||||
import (
|
||||
@ -34,7 +37,7 @@ func TestStdlibJSONCompatibility(t *testing.T) {
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
original interface{}
|
||||
original any
|
||||
ifaceEqual bool // require equal intermediate interface{} values from both protocols
|
||||
}{
|
||||
{
|
||||
@ -67,14 +70,14 @@ func TestStdlibJSONCompatibility(t *testing.T) {
|
||||
}
|
||||
t.Logf("original to cbor: %s", diag1)
|
||||
|
||||
var jintf interface{}
|
||||
var jintf any
|
||||
err = json.Unmarshal(j1, &jintf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("json to interface{} (%T): %#v", jintf, jintf)
|
||||
|
||||
var cintf interface{}
|
||||
var cintf any
|
||||
err = dec.Unmarshal(c1, &cintf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
8
omitzero_go124.go
Normal file
8
omitzero_go124.go
Normal file
@ -0,0 +1,8 @@
|
||||
// Copyright (c) Faye Amacker. All rights reserved.
|
||||
// Licensed under the MIT License. See LICENSE in the project root for license information.
|
||||
|
||||
//go:build go1.24
|
||||
|
||||
package cbor
|
||||
|
||||
var jsonStdlibSupportsOmitzero = true
|
||||
8
omitzero_pre_go124.go
Normal file
8
omitzero_pre_go124.go
Normal file
@ -0,0 +1,8 @@
|
||||
// Copyright (c) Faye Amacker. All rights reserved.
|
||||
// Licensed under the MIT License. See LICENSE in the project root for license information.
|
||||
|
||||
//go:build !go1.24
|
||||
|
||||
package cbor
|
||||
|
||||
var jsonStdlibSupportsOmitzero = false
|
||||
@ -1,3 +1,6 @@
|
||||
// Copyright (c) Faye Amacker. All rights reserved.
|
||||
// Licensed under the MIT License. See LICENSE in the project root for license information.
|
||||
|
||||
package cbor
|
||||
|
||||
import (
|
||||
@ -45,6 +48,9 @@ func (sv SimpleValue) MarshalCBOR() ([]byte, error) {
|
||||
}
|
||||
|
||||
// UnmarshalCBOR decodes CBOR simple value (major type 7) to SimpleValue.
|
||||
//
|
||||
// Deprecated: No longer used by this codec; kept for compatibility
|
||||
// with user apps that directly call this function.
|
||||
func (sv *SimpleValue) UnmarshalCBOR(data []byte) error {
|
||||
if sv == nil {
|
||||
return errors.New("cbor.SimpleValue: UnmarshalCBOR on nil pointer")
|
||||
@ -52,6 +58,29 @@ func (sv *SimpleValue) UnmarshalCBOR(data []byte) error {
|
||||
|
||||
d := decoder{data: data, dm: defaultDecMode}
|
||||
|
||||
// Check well-formedness of CBOR data item.
|
||||
// SimpleValue.UnmarshalCBOR() is exported, so
|
||||
// the codec needs to support same behavior for:
|
||||
// - Unmarshal(data, *SimpleValue)
|
||||
// - SimpleValue.UnmarshalCBOR(data)
|
||||
err := d.wellformed(false, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return sv.unmarshalCBOR(data)
|
||||
}
|
||||
|
||||
// unmarshalCBOR decodes CBOR simple value (major type 7) to SimpleValue.
|
||||
// This function assumes data is well-formed, and does not perform bounds checking.
|
||||
// This function is called by Unmarshal().
|
||||
func (sv *SimpleValue) unmarshalCBOR(data []byte) error {
|
||||
if sv == nil {
|
||||
return errors.New("cbor.SimpleValue: UnmarshalCBOR on nil pointer")
|
||||
}
|
||||
|
||||
d := decoder{data: data, dm: defaultDecMode}
|
||||
|
||||
typ, ai, val := d.getHead()
|
||||
|
||||
if typ != cborTypePrimitives {
|
||||
|
||||
@ -5,7 +5,9 @@ package cbor
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@ -51,8 +53,127 @@ func TestUnmarshalSimpleValue(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestUnmarshalSimpleValueOnBadData(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
data []byte
|
||||
errMsg string
|
||||
}{
|
||||
// Empty data
|
||||
{
|
||||
name: "nil data",
|
||||
data: nil,
|
||||
errMsg: io.EOF.Error(),
|
||||
},
|
||||
{
|
||||
name: "empty data",
|
||||
data: []byte{},
|
||||
errMsg: io.EOF.Error(),
|
||||
},
|
||||
|
||||
// Wrong CBOR types
|
||||
{
|
||||
name: "uint type",
|
||||
data: mustHexDecode("01"),
|
||||
errMsg: "cbor: cannot unmarshal positive integer into Go value of type SimpleValue",
|
||||
},
|
||||
{
|
||||
name: "int type",
|
||||
data: mustHexDecode("20"),
|
||||
errMsg: "cbor: cannot unmarshal negative integer into Go value of type SimpleValue",
|
||||
},
|
||||
{
|
||||
name: "byte string type",
|
||||
data: mustHexDecode("40"),
|
||||
errMsg: "cbor: cannot unmarshal byte string into Go value of type SimpleValue",
|
||||
},
|
||||
{
|
||||
name: "string type",
|
||||
data: mustHexDecode("60"),
|
||||
errMsg: "cbor: cannot unmarshal UTF-8 text string into Go value of type SimpleValue",
|
||||
},
|
||||
{
|
||||
name: "array type",
|
||||
data: mustHexDecode("80"),
|
||||
errMsg: "cbor: cannot unmarshal array into Go value of type SimpleValue",
|
||||
},
|
||||
{
|
||||
name: "map type",
|
||||
data: mustHexDecode("a0"),
|
||||
errMsg: "cbor: cannot unmarshal map into Go value of type SimpleValue",
|
||||
},
|
||||
{
|
||||
name: "tag type",
|
||||
data: mustHexDecode("c074323031332d30332d32315432303a30343a30305a"),
|
||||
errMsg: "cbor: cannot unmarshal tag into Go value of type SimpleValue",
|
||||
},
|
||||
{
|
||||
name: "float type",
|
||||
data: mustHexDecode("f90000"),
|
||||
errMsg: "cbor: cannot unmarshal primitives into Go value of type SimpleValue",
|
||||
},
|
||||
|
||||
// Truncated CBOR data
|
||||
{
|
||||
name: "truncated head",
|
||||
data: mustHexDecode("18"),
|
||||
errMsg: io.ErrUnexpectedEOF.Error(),
|
||||
},
|
||||
|
||||
// Truncated CBOR simple value
|
||||
{
|
||||
name: "truncated simple value",
|
||||
data: mustHexDecode("f8"),
|
||||
errMsg: io.ErrUnexpectedEOF.Error(),
|
||||
},
|
||||
|
||||
// Invalid simple value
|
||||
{
|
||||
name: "invalid simple value",
|
||||
data: mustHexDecode("f800"),
|
||||
errMsg: "cbor: invalid simple value 0 for type primitives",
|
||||
},
|
||||
|
||||
// Extraneous CBOR data
|
||||
{
|
||||
name: "extraneous data",
|
||||
data: mustHexDecode("f4f5"),
|
||||
errMsg: "cbor: 1 bytes of extraneous data starting at index 1",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// Test SimpleValue.UnmarshalCBOR(data)
|
||||
{
|
||||
var v SimpleValue
|
||||
|
||||
err := v.UnmarshalCBOR(tc.data)
|
||||
if err == nil {
|
||||
t.Errorf("UnmarshalCBOR(%x) didn't return error", tc.data)
|
||||
}
|
||||
if !strings.HasPrefix(err.Error(), tc.errMsg) {
|
||||
t.Errorf("UnmarshalCBOR(%x) returned error %q, want %q", tc.data, err.Error(), tc.errMsg)
|
||||
}
|
||||
}
|
||||
// Test Unmarshal(data, *SimpleValue), which calls SimpleValue.unmarshalCBOR() under the hood
|
||||
{
|
||||
var v SimpleValue
|
||||
|
||||
err := Unmarshal(tc.data, &v)
|
||||
if err == nil {
|
||||
t.Errorf("Unmarshal(%x) didn't return error", tc.data)
|
||||
}
|
||||
if !strings.HasPrefix(err.Error(), tc.errMsg) {
|
||||
t.Errorf("Unmarshal(%x) returned error %q, want %q", tc.data, err.Error(), tc.errMsg)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testUnmarshalInvalidSimpleValueToEmptyInterface(t *testing.T, data []byte) {
|
||||
var v interface{}
|
||||
var v any
|
||||
if err := Unmarshal(data, v); err == nil {
|
||||
t.Errorf("Unmarshal(0x%x) didn't return an error", data)
|
||||
} else if _, ok := err.(*SyntaxError); !ok {
|
||||
@ -69,8 +190,8 @@ func testUnmarshalInvalidSimpleValue(t *testing.T, data []byte) {
|
||||
}
|
||||
}
|
||||
|
||||
func testUnmarshalSimpleValueToEmptyInterface(t *testing.T, data []byte, want interface{}) {
|
||||
var v interface{}
|
||||
func testUnmarshalSimpleValueToEmptyInterface(t *testing.T, data []byte, want any) {
|
||||
var v any
|
||||
if err := Unmarshal(data, &v); err != nil {
|
||||
t.Errorf("Unmarshal(0x%x) returned error %v", data, err)
|
||||
return
|
||||
|
||||
@ -26,7 +26,7 @@ func NewDecoder(r io.Reader) *Decoder {
|
||||
}
|
||||
|
||||
// Decode reads CBOR value and decodes it into the value pointed to by v.
|
||||
func (dec *Decoder) Decode(v interface{}) error {
|
||||
func (dec *Decoder) Decode(v any) error {
|
||||
_, err := dec.readNext()
|
||||
if err != nil {
|
||||
// Return validation error or read error.
|
||||
@ -170,7 +170,7 @@ func NewEncoder(w io.Writer) *Encoder {
|
||||
}
|
||||
|
||||
// Encode writes the CBOR encoding of v.
|
||||
func (enc *Encoder) Encode(v interface{}) error {
|
||||
func (enc *Encoder) Encode(v any) error {
|
||||
if len(enc.indefTypes) > 0 && v != nil {
|
||||
indefType := enc.indefTypes[len(enc.indefTypes)-1]
|
||||
if indefType == cborTypeTextString {
|
||||
|
||||
228
stream_test.go
228
stream_test.go
@ -26,9 +26,18 @@ func TestDecoder(t *testing.T) {
|
||||
name string
|
||||
reader io.Reader
|
||||
}{
|
||||
{"bytes.Buffer", &buf},
|
||||
{"1 byte reader", newNBytesReader(buf.Bytes(), 1)},
|
||||
{"toggled reader", newToggledReader(buf.Bytes(), 1)},
|
||||
{
|
||||
name: "bytes.Buffer",
|
||||
reader: &buf,
|
||||
},
|
||||
{
|
||||
name: "1 byte reader",
|
||||
reader: newNBytesReader(buf.Bytes(), 1),
|
||||
},
|
||||
{
|
||||
name: "toggled reader",
|
||||
reader: newToggledReader(buf.Bytes(), 1),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
@ -37,7 +46,7 @@ func TestDecoder(t *testing.T) {
|
||||
bytesRead := 0
|
||||
for i := 0; i < 5; i++ {
|
||||
for _, tc := range unmarshalTests {
|
||||
var v interface{}
|
||||
var v any
|
||||
if err := decoder.Decode(&v); err != nil {
|
||||
t.Fatalf("Decode() returned error %v", err)
|
||||
}
|
||||
@ -56,7 +65,7 @@ func TestDecoder(t *testing.T) {
|
||||
}
|
||||
for i := 0; i < 2; i++ {
|
||||
// no more data
|
||||
var v interface{}
|
||||
var v any
|
||||
err := decoder.Decode(&v)
|
||||
if v != nil {
|
||||
t.Errorf("Decode() = %v (%T), want nil (no more data)", v, v)
|
||||
@ -83,9 +92,18 @@ func TestDecoderUnmarshalTypeError(t *testing.T) {
|
||||
name string
|
||||
reader io.Reader
|
||||
}{
|
||||
{"bytes.Buffer", &buf},
|
||||
{"1 byte reader", newNBytesReader(buf.Bytes(), 1)},
|
||||
{"toggled reader", newToggledReader(buf.Bytes(), 1)},
|
||||
{
|
||||
name: "bytes.Buffer",
|
||||
reader: &buf,
|
||||
},
|
||||
{
|
||||
name: "1 byte reader",
|
||||
reader: newNBytesReader(buf.Bytes(), 1),
|
||||
},
|
||||
{
|
||||
name: "toggled reader",
|
||||
reader: newToggledReader(buf.Bytes(), 1),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
@ -106,7 +124,7 @@ func TestDecoderUnmarshalTypeError(t *testing.T) {
|
||||
t.Errorf("NumBytesRead() = %v, want %v", decoder.NumBytesRead(), bytesRead)
|
||||
}
|
||||
|
||||
var vi interface{}
|
||||
var vi any
|
||||
if err := decoder.Decode(&vi); err != nil {
|
||||
t.Errorf("Decode() returned error %v", err)
|
||||
}
|
||||
@ -126,7 +144,7 @@ func TestDecoderUnmarshalTypeError(t *testing.T) {
|
||||
}
|
||||
for i := 0; i < 2; i++ {
|
||||
// no more data
|
||||
var v interface{}
|
||||
var v any
|
||||
err := decoder.Decode(&v)
|
||||
if v != nil {
|
||||
t.Errorf("Decode() = %v (%T), want nil (no more data)", v, v)
|
||||
@ -150,9 +168,18 @@ func TestDecoderUnexpectedEOFError(t *testing.T) {
|
||||
name string
|
||||
reader io.Reader
|
||||
}{
|
||||
{"bytes.Buffer", &buf},
|
||||
{"1 byte reader", newNBytesReader(buf.Bytes(), 1)},
|
||||
{"toggled reader", newToggledReader(buf.Bytes(), 1)},
|
||||
{
|
||||
name: "bytes.Buffer",
|
||||
reader: &buf,
|
||||
},
|
||||
{
|
||||
name: "1 byte reader",
|
||||
reader: newNBytesReader(buf.Bytes(), 1),
|
||||
},
|
||||
{
|
||||
name: "toggled reader",
|
||||
reader: newToggledReader(buf.Bytes(), 1),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
@ -162,7 +189,7 @@ func TestDecoderUnexpectedEOFError(t *testing.T) {
|
||||
bytesRead := 0
|
||||
for i := 0; i < len(unmarshalTests)-1; i++ {
|
||||
tc := unmarshalTests[i]
|
||||
var v interface{}
|
||||
var v any
|
||||
if err := decoder.Decode(&v); err != nil {
|
||||
t.Fatalf("Decode() returned error %v", err)
|
||||
}
|
||||
@ -180,7 +207,7 @@ func TestDecoderUnexpectedEOFError(t *testing.T) {
|
||||
}
|
||||
for i := 0; i < 2; i++ {
|
||||
// truncated data
|
||||
var v interface{}
|
||||
var v any
|
||||
err := decoder.Decode(&v)
|
||||
if v != nil {
|
||||
t.Errorf("Decode() = %v (%T), want nil (no more data)", v, v)
|
||||
@ -206,9 +233,18 @@ func TestDecoderReadError(t *testing.T) {
|
||||
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)},
|
||||
{
|
||||
name: "byte reader",
|
||||
reader: newNBytesReaderWithError(buf.Bytes(), 512, readerErr),
|
||||
},
|
||||
{
|
||||
name: "1 byte reader",
|
||||
reader: newNBytesReaderWithError(buf.Bytes(), 1, readerErr),
|
||||
},
|
||||
{
|
||||
name: "toggled reader",
|
||||
reader: newToggledReaderWithError(buf.Bytes(), 1, readerErr),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
@ -217,7 +253,7 @@ func TestDecoderReadError(t *testing.T) {
|
||||
bytesRead := 0
|
||||
for i := 0; i < len(unmarshalTests)-1; i++ {
|
||||
tc := unmarshalTests[i]
|
||||
var v interface{}
|
||||
var v any
|
||||
if err := decoder.Decode(&v); err != nil {
|
||||
t.Fatalf("Decode() returned error %v", err)
|
||||
}
|
||||
@ -235,7 +271,7 @@ func TestDecoderReadError(t *testing.T) {
|
||||
}
|
||||
for i := 0; i < 2; i++ {
|
||||
// truncated data because Reader returned error
|
||||
var v interface{}
|
||||
var v any
|
||||
err := decoder.Decode(&v)
|
||||
if v != nil {
|
||||
t.Errorf("Decode() = %v (%T), want nil (no more data)", v, v)
|
||||
@ -256,16 +292,28 @@ func TestDecoderNoData(t *testing.T) {
|
||||
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},
|
||||
{
|
||||
name: "byte.Buffer",
|
||||
reader: new(bytes.Buffer),
|
||||
wantErr: io.EOF,
|
||||
},
|
||||
{
|
||||
name: "1 byte reader",
|
||||
reader: newNBytesReaderWithError(nil, 0, readerErr),
|
||||
wantErr: readerErr,
|
||||
},
|
||||
{
|
||||
name: "toggled reader",
|
||||
reader: newToggledReaderWithError(nil, 0, readerErr),
|
||||
wantErr: 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{}
|
||||
var v any
|
||||
err := decoder.Decode(&v)
|
||||
if v != nil {
|
||||
t.Errorf("Decode() = %v (%T), want nil", v, v)
|
||||
@ -279,13 +327,13 @@ func TestDecoderNoData(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDecoderRecoverableReadError(t *testing.T) {
|
||||
data := hexDecode("83010203") // [1,2,3]
|
||||
wantValue := []interface{}{uint64(1), uint64(2), uint64(3)}
|
||||
data := mustHexDecode("83010203") // [1,2,3]
|
||||
wantValue := []any{uint64(1), uint64(2), uint64(3)}
|
||||
recoverableReaderErr := errors.New("recoverable reader error")
|
||||
|
||||
decoder := NewDecoder(newRecoverableReader(data, 1, recoverableReaderErr))
|
||||
|
||||
var v interface{}
|
||||
var v any
|
||||
err := decoder.Decode(&v)
|
||||
if err != recoverableReaderErr {
|
||||
t.Fatalf("Decode() returned error %v, want error %v", err, recoverableReaderErr)
|
||||
@ -303,7 +351,7 @@ func TestDecoderRecoverableReadError(t *testing.T) {
|
||||
}
|
||||
|
||||
// no more data
|
||||
v = interface{}(nil)
|
||||
v = any(nil)
|
||||
err = decoder.Decode(&v)
|
||||
if v != nil {
|
||||
t.Errorf("Decode() = %v (%T), want nil (no more data)", v, v)
|
||||
@ -317,13 +365,13 @@ func TestDecoderInvalidData(t *testing.T) {
|
||||
data := []byte{0x01, 0x3e}
|
||||
decoder := NewDecoder(bytes.NewReader(data))
|
||||
|
||||
var v1 interface{}
|
||||
var v1 any
|
||||
err := decoder.Decode(&v1)
|
||||
if err != nil {
|
||||
t.Errorf("Decode() returned error %v when decoding valid data item", err)
|
||||
}
|
||||
|
||||
var v2 interface{}
|
||||
var v2 any
|
||||
err = decoder.Decode(&v2)
|
||||
if err == nil {
|
||||
t.Errorf("Decode() didn't return error when decoding invalid data item")
|
||||
@ -344,9 +392,18 @@ func TestDecoderSkip(t *testing.T) {
|
||||
name string
|
||||
reader io.Reader
|
||||
}{
|
||||
{"bytes.Buffer", &buf},
|
||||
{"1 byte reader", newNBytesReader(buf.Bytes(), 1)},
|
||||
{"toggled reader", newToggledReader(buf.Bytes(), 1)},
|
||||
{
|
||||
name: "bytes.Buffer",
|
||||
reader: &buf,
|
||||
},
|
||||
{
|
||||
name: "1 byte reader",
|
||||
reader: newNBytesReader(buf.Bytes(), 1),
|
||||
},
|
||||
{
|
||||
name: "toggled reader",
|
||||
reader: newToggledReader(buf.Bytes(), 1),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
@ -386,9 +443,18 @@ func TestDecoderSkipInvalidDataError(t *testing.T) {
|
||||
name string
|
||||
reader io.Reader
|
||||
}{
|
||||
{"bytes.Buffer", &buf},
|
||||
{"1 byte reader", newNBytesReader(buf.Bytes(), 1)},
|
||||
{"toggled reader", newToggledReader(buf.Bytes(), 1)},
|
||||
{
|
||||
name: "bytes.Buffer",
|
||||
reader: &buf,
|
||||
},
|
||||
{
|
||||
name: "1 byte reader",
|
||||
reader: newNBytesReader(buf.Bytes(), 1),
|
||||
},
|
||||
{
|
||||
name: "toggled reader",
|
||||
reader: newToggledReader(buf.Bytes(), 1),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
@ -429,9 +495,18 @@ func TestDecoderSkipUnexpectedEOFError(t *testing.T) {
|
||||
name string
|
||||
reader io.Reader
|
||||
}{
|
||||
{"bytes.Buffer", &buf},
|
||||
{"1 byte reader", newNBytesReader(buf.Bytes(), 1)},
|
||||
{"toggled reader", newToggledReader(buf.Bytes(), 1)},
|
||||
{
|
||||
name: "bytes.Buffer",
|
||||
reader: &buf,
|
||||
},
|
||||
{
|
||||
name: "1 byte reader",
|
||||
reader: newNBytesReader(buf.Bytes(), 1),
|
||||
},
|
||||
{
|
||||
name: "toggled reader",
|
||||
reader: newToggledReader(buf.Bytes(), 1),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
@ -472,9 +547,18 @@ func TestDecoderSkipReadError(t *testing.T) {
|
||||
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)},
|
||||
{
|
||||
name: "byte reader",
|
||||
reader: newNBytesReaderWithError(buf.Bytes(), 512, readerErr),
|
||||
},
|
||||
{
|
||||
name: "1 byte reader",
|
||||
reader: newNBytesReaderWithError(buf.Bytes(), 1, readerErr),
|
||||
},
|
||||
{
|
||||
name: "toggled reader",
|
||||
reader: newToggledReaderWithError(buf.Bytes(), 1, readerErr),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
@ -510,9 +594,21 @@ func TestDecoderSkipNoData(t *testing.T) {
|
||||
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},
|
||||
{
|
||||
name: "byte.Buffer",
|
||||
reader: new(bytes.Buffer),
|
||||
wantErr: io.EOF,
|
||||
},
|
||||
{
|
||||
name: "1 byte reader",
|
||||
reader: newNBytesReaderWithError(nil, 0, readerErr),
|
||||
wantErr: readerErr,
|
||||
},
|
||||
{
|
||||
name: "toggled reader",
|
||||
reader: newToggledReaderWithError(nil, 0, readerErr),
|
||||
wantErr: readerErr,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
@ -529,7 +625,7 @@ func TestDecoderSkipNoData(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDecoderSkipRecoverableReadError(t *testing.T) {
|
||||
data := hexDecode("83010203") // [1,2,3]
|
||||
data := mustHexDecode("83010203") // [1,2,3]
|
||||
recoverableReaderErr := errors.New("recoverable reader error")
|
||||
|
||||
decoder := NewDecoder(newRecoverableReader(data, 1, recoverableReaderErr))
|
||||
@ -565,7 +661,7 @@ func TestDecoderStructTag(t *testing.T) {
|
||||
B: "B",
|
||||
C: "C",
|
||||
}
|
||||
data := hexDecode("a36161614161626142617a6143") // {"a":"A", "b":"B", "z":"C"}
|
||||
data := mustHexDecode("a36161614161626142617a6143") // {"a":"A", "b":"B", "z":"C"}
|
||||
|
||||
var v strc
|
||||
dec := NewDecoder(bytes.NewReader(data))
|
||||
@ -645,7 +741,7 @@ func TestDecoderBuffered(t *testing.T) {
|
||||
t.Errorf("Buffered() = 0x%x (%d bytes), want 0 bytes", buffered, len(buffered))
|
||||
}
|
||||
|
||||
var v interface{}
|
||||
var v any
|
||||
err = decoder.Decode(&v)
|
||||
if err != tc.decodeErr {
|
||||
t.Errorf("Decode() returned error %v, want %v", err, tc.decodeErr)
|
||||
@ -688,12 +784,24 @@ func TestEncoder(t *testing.T) {
|
||||
func TestEncoderError(t *testing.T) {
|
||||
testcases := []struct {
|
||||
name string
|
||||
value interface{}
|
||||
value any
|
||||
wantErrorMsg string
|
||||
}{
|
||||
{"channel cannot be marshaled", make(chan bool), "cbor: unsupported type: chan bool"},
|
||||
{"function cannot be marshaled", func(i int) int { return i * i }, "cbor: unsupported type: func(int) int"},
|
||||
{"complex cannot be marshaled", complex(100, 8), "cbor: unsupported type: complex128"},
|
||||
{
|
||||
name: "channel cannot be marshaled",
|
||||
value: make(chan bool),
|
||||
wantErrorMsg: "cbor: unsupported type: chan bool",
|
||||
},
|
||||
{
|
||||
name: "function cannot be marshaled",
|
||||
value: func(i int) int { return i * i },
|
||||
wantErrorMsg: "cbor: unsupported type: func(int) int",
|
||||
},
|
||||
{
|
||||
name: "complex cannot be marshaled",
|
||||
value: complex(100, 8),
|
||||
wantErrorMsg: "cbor: unsupported type: complex128",
|
||||
},
|
||||
}
|
||||
var w bytes.Buffer
|
||||
encoder := NewEncoder(&w)
|
||||
@ -716,7 +824,7 @@ func TestEncoderError(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIndefiniteByteString(t *testing.T) {
|
||||
want := hexDecode("5f42010243030405ff")
|
||||
want := mustHexDecode("5f42010243030405ff")
|
||||
var w bytes.Buffer
|
||||
encoder := NewEncoder(&w)
|
||||
if err := encoder.StartIndefiniteByteString(); err != nil {
|
||||
@ -755,7 +863,7 @@ func TestIndefiniteByteStringError(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIndefiniteTextString(t *testing.T) {
|
||||
want := hexDecode("7f657374726561646d696e67ff")
|
||||
want := mustHexDecode("7f657374726561646d696e67ff")
|
||||
var w bytes.Buffer
|
||||
encoder := NewEncoder(&w)
|
||||
if err := encoder.StartIndefiniteTextString(); err != nil {
|
||||
@ -789,7 +897,7 @@ func TestIndefiniteTextStringError(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIndefiniteArray(t *testing.T) {
|
||||
want := hexDecode("9f018202039f0405ffff")
|
||||
want := mustHexDecode("9f018202039f0405ffff")
|
||||
var w bytes.Buffer
|
||||
encoder := NewEncoder(&w)
|
||||
if err := encoder.StartIndefiniteArray(); err != nil {
|
||||
@ -822,7 +930,7 @@ func TestIndefiniteArray(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIndefiniteMap(t *testing.T) {
|
||||
want := hexDecode("bf61610161629f0203ffff")
|
||||
want := mustHexDecode("bf61610161629f0203ffff")
|
||||
var w bytes.Buffer
|
||||
em, err := EncOptions{Sort: SortCanonical}.EncMode()
|
||||
if err != nil {
|
||||
@ -886,7 +994,7 @@ func TestEncoderStructTag(t *testing.T) {
|
||||
B: "B",
|
||||
C: "C",
|
||||
}
|
||||
want := hexDecode("a36161614161626142617a6143") // {"a":"A", "b":"B", "z":"C"}
|
||||
want := mustHexDecode("a36161614161626142617a6143") // {"a":"A", "b":"B", "z":"C"}
|
||||
|
||||
var w bytes.Buffer
|
||||
encoder := NewEncoder(&w)
|
||||
@ -904,8 +1012,8 @@ func TestRawMessage(t *testing.T) {
|
||||
B *RawMessage `cbor:"b"`
|
||||
C *RawMessage `cbor:"c"`
|
||||
}
|
||||
data := hexDecode("a361610161628202036163f6") // {"a": 1, "b": [2, 3], "c": nil},
|
||||
r := RawMessage(hexDecode("820203"))
|
||||
data := mustHexDecode("a361610161628202036163f6") // {"a": 1, "b": [2, 3], "c": nil},
|
||||
r := RawMessage(mustHexDecode("820203"))
|
||||
want := strc{
|
||||
A: RawMessage([]byte{0x01}),
|
||||
B: &r,
|
||||
@ -967,7 +1075,7 @@ func TestEmptyRawMessage(t *testing.T) {
|
||||
func TestNilRawMessageUnmarshalCBORError(t *testing.T) {
|
||||
wantErrorMsg := "cbor.RawMessage: UnmarshalCBOR on nil pointer"
|
||||
var r *RawMessage
|
||||
data := hexDecode("01")
|
||||
data := mustHexDecode("01")
|
||||
if err := r.UnmarshalCBOR(data); err == nil {
|
||||
t.Errorf("UnmarshalCBOR() didn't return error")
|
||||
} else if err.Error() != wantErrorMsg {
|
||||
|
||||
@ -18,9 +18,11 @@ type field struct {
|
||||
typ reflect.Type
|
||||
ef encodeFunc
|
||||
ief isEmptyFunc
|
||||
izf isZeroFunc
|
||||
typInfo *typeInfo // used to decoder to reuse type info
|
||||
tagged bool // used to choose dominant field (at the same level tagged fields dominate untagged fields)
|
||||
omitEmpty bool // used to skip empty field
|
||||
omitZero bool // used to skip zero field
|
||||
keyAsInt bool // used to encode/decode field name as int
|
||||
}
|
||||
|
||||
@ -157,7 +159,7 @@ func appendFields(
|
||||
f := t.Field(i)
|
||||
|
||||
ft := f.Type
|
||||
for ft.Kind() == reflect.Ptr {
|
||||
for ft.Kind() == reflect.Pointer {
|
||||
ft = ft.Elem()
|
||||
}
|
||||
|
||||
@ -165,9 +167,11 @@ func appendFields(
|
||||
continue
|
||||
}
|
||||
|
||||
cborTag := true
|
||||
tag := f.Tag.Get("cbor")
|
||||
if tag == "" {
|
||||
tag = f.Tag.Get("json")
|
||||
cborTag = false
|
||||
}
|
||||
if tag == "-" {
|
||||
continue
|
||||
@ -177,7 +181,7 @@ func appendFields(
|
||||
|
||||
// Parse field tag options
|
||||
var tagFieldName string
|
||||
var omitempty, keyasint bool
|
||||
var omitempty, omitzero, keyasint bool
|
||||
for j := 0; tag != ""; j++ {
|
||||
var token string
|
||||
idx := strings.IndexByte(tag, ',')
|
||||
@ -192,6 +196,10 @@ func appendFields(
|
||||
switch token {
|
||||
case "omitempty":
|
||||
omitempty = true
|
||||
case "omitzero":
|
||||
if cborTag || jsonStdlibSupportsOmitzero {
|
||||
omitzero = true
|
||||
}
|
||||
case "keyasint":
|
||||
keyasint = true
|
||||
}
|
||||
@ -213,6 +221,7 @@ func appendFields(
|
||||
idx: fIdx,
|
||||
typ: f.Type,
|
||||
omitEmpty: omitempty,
|
||||
omitZero: omitzero,
|
||||
keyAsInt: keyasint,
|
||||
tagged: tagged})
|
||||
} else {
|
||||
@ -230,8 +239,7 @@ func appendFields(
|
||||
// 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)
|
||||
return f.IsExported() || (f.Anonymous && fk == reflect.Struct)
|
||||
}
|
||||
|
||||
type embeddedFieldNullPtrFunc func(reflect.Value) (reflect.Value, error)
|
||||
@ -244,7 +252,7 @@ func getFieldValue(v reflect.Value, idx []int, f embeddedFieldNullPtrFunc) (fv r
|
||||
fv = fv.Field(n)
|
||||
|
||||
if i < len(idx)-1 {
|
||||
if fv.Kind() == reflect.Ptr && fv.Type().Elem().Kind() == reflect.Struct {
|
||||
if fv.Kind() == reflect.Pointer && fv.Type().Elem().Kind() == reflect.Struct {
|
||||
if fv.IsNil() {
|
||||
// Null pointer to embedded struct field
|
||||
fv, err = f(fv)
|
||||
|
||||
48
tag.go
48
tag.go
@ -1,3 +1,6 @@
|
||||
// Copyright (c) Faye Amacker. All rights reserved.
|
||||
// Licensed under the MIT License. See LICENSE in the project root for license information.
|
||||
|
||||
package cbor
|
||||
|
||||
import (
|
||||
@ -7,27 +10,54 @@ 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 a tagged data item (CBOR major type 6), comprising a tag number and the unmarshaled tag content.
|
||||
// NOTE: The same encoding and decoding options that apply to untagged CBOR data items also applies to tag content
|
||||
// during encoding and decoding.
|
||||
type Tag struct {
|
||||
Number uint64
|
||||
Content interface{}
|
||||
Content any
|
||||
}
|
||||
|
||||
// RawTag represents CBOR tag data, including tag number and raw tag content.
|
||||
// RawTag implements Unmarshaler and Marshaler interfaces.
|
||||
// RawTag represents a tagged data item (CBOR major type 6), comprising a tag number and the raw tag content.
|
||||
// The raw tag content (enclosed data item) is a CBOR-encoded data item.
|
||||
// RawTag can be used to delay decoding a CBOR data item or precompute encoding a CBOR data item.
|
||||
type RawTag struct {
|
||||
Number uint64
|
||||
Content RawMessage
|
||||
}
|
||||
|
||||
// UnmarshalCBOR sets *t with tag number and raw tag content copied from data.
|
||||
// UnmarshalCBOR sets *t with the tag number and the raw tag content copied from data.
|
||||
//
|
||||
// Deprecated: No longer used by this codec; kept for compatibility
|
||||
// with user apps that directly call this function.
|
||||
func (t *RawTag) UnmarshalCBOR(data []byte) error {
|
||||
if t == nil {
|
||||
return errors.New("cbor.RawTag: UnmarshalCBOR on nil pointer")
|
||||
}
|
||||
|
||||
d := decoder{data: data, dm: defaultDecMode}
|
||||
|
||||
// Check if data is a well-formed CBOR data item.
|
||||
// RawTag.UnmarshalCBOR() is exported, so
|
||||
// the codec needs to support same behavior for:
|
||||
// - Unmarshal(data, *RawTag)
|
||||
// - RawTag.UnmarshalCBOR(data)
|
||||
err := d.wellformed(false, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return t.unmarshalCBOR(data)
|
||||
}
|
||||
|
||||
// unmarshalCBOR sets *t with the tag number and the raw tag content copied from data.
|
||||
// This function assumes data is well-formed, and does not perform bounds checking.
|
||||
// This function is called by Unmarshal().
|
||||
func (t *RawTag) unmarshalCBOR(data []byte) error {
|
||||
if t == nil {
|
||||
return errors.New("cbor.RawTag: UnmarshalCBOR on nil pointer")
|
||||
}
|
||||
|
||||
// Decoding CBOR null and undefined to cbor.RawTag is no-op.
|
||||
if len(data) == 1 && (data[0] == 0xf6 || data[0] == 0xf7) {
|
||||
return nil
|
||||
@ -193,7 +223,7 @@ func (t *syncTagSet) Add(opts TagOptions, contentType reflect.Type, num uint64,
|
||||
if contentType == nil {
|
||||
return errors.New("cbor: cannot add nil content type to TagSet")
|
||||
}
|
||||
for contentType.Kind() == reflect.Ptr {
|
||||
for contentType.Kind() == reflect.Pointer {
|
||||
contentType = contentType.Elem()
|
||||
}
|
||||
tag, err := newTagItem(opts, contentType, num, nestedNum...)
|
||||
@ -216,7 +246,7 @@ func (t *syncTagSet) Add(opts TagOptions, contentType reflect.Type, num uint64,
|
||||
|
||||
// Remove removes given tag content type from TagSet.
|
||||
func (t *syncTagSet) Remove(contentType reflect.Type) {
|
||||
for contentType.Kind() == reflect.Ptr {
|
||||
for contentType.Kind() == reflect.Pointer {
|
||||
contentType = contentType.Elem()
|
||||
}
|
||||
t.Lock()
|
||||
|
||||
257
tag_test.go
257
tag_test.go
@ -1,3 +1,6 @@
|
||||
// Copyright (c) Faye Amacker. All rights reserved.
|
||||
// Licensed under the MIT License. See LICENSE in the project root for license information.
|
||||
|
||||
package cbor
|
||||
|
||||
import (
|
||||
@ -67,92 +70,92 @@ func TestTagNewTypeWithBuiltinUnderlyingType(t *testing.T) {
|
||||
{
|
||||
name: "bool",
|
||||
obj: myBool(true),
|
||||
wantCborData: hexDecode("d864f5"),
|
||||
wantCborData: mustHexDecode("d864f5"),
|
||||
},
|
||||
{
|
||||
name: "uint",
|
||||
obj: myUint(0),
|
||||
wantCborData: hexDecode("d86500"),
|
||||
wantCborData: mustHexDecode("d86500"),
|
||||
},
|
||||
{
|
||||
name: "uint8",
|
||||
obj: myUint8(0),
|
||||
wantCborData: hexDecode("d86600"),
|
||||
wantCborData: mustHexDecode("d86600"),
|
||||
},
|
||||
{
|
||||
name: "uint16",
|
||||
obj: myUint16(1000),
|
||||
wantCborData: hexDecode("d8671903e8"),
|
||||
wantCborData: mustHexDecode("d8671903e8"),
|
||||
},
|
||||
{
|
||||
name: "uint32",
|
||||
obj: myUint32(1000000),
|
||||
wantCborData: hexDecode("d8681a000f4240"),
|
||||
wantCborData: mustHexDecode("d8681a000f4240"),
|
||||
},
|
||||
{
|
||||
name: "uint64",
|
||||
obj: myUint64(1000000000000),
|
||||
wantCborData: hexDecode("d8691b000000e8d4a51000"),
|
||||
wantCborData: mustHexDecode("d8691b000000e8d4a51000"),
|
||||
},
|
||||
{
|
||||
name: "int",
|
||||
obj: myInt(-1),
|
||||
wantCborData: hexDecode("d86a20"),
|
||||
wantCborData: mustHexDecode("d86a20"),
|
||||
},
|
||||
{
|
||||
name: "int8",
|
||||
obj: myInt8(-1),
|
||||
wantCborData: hexDecode("d86b20"),
|
||||
wantCborData: mustHexDecode("d86b20"),
|
||||
},
|
||||
{
|
||||
name: "int16",
|
||||
obj: myInt16(-1000),
|
||||
wantCborData: hexDecode("d86c3903e7"),
|
||||
wantCborData: mustHexDecode("d86c3903e7"),
|
||||
},
|
||||
{
|
||||
name: "int32",
|
||||
obj: myInt32(-1000),
|
||||
wantCborData: hexDecode("d86d3903e7"),
|
||||
wantCborData: mustHexDecode("d86d3903e7"),
|
||||
},
|
||||
{
|
||||
name: "int64",
|
||||
obj: myInt64(-1000),
|
||||
wantCborData: hexDecode("d86e3903e7"),
|
||||
wantCborData: mustHexDecode("d86e3903e7"),
|
||||
},
|
||||
{
|
||||
name: "float32",
|
||||
obj: myFloat32(100000.0),
|
||||
wantCborData: hexDecode("d86ffa47c35000"),
|
||||
wantCborData: mustHexDecode("d86ffa47c35000"),
|
||||
},
|
||||
{
|
||||
name: "float64",
|
||||
obj: myFloat64(1.1),
|
||||
wantCborData: hexDecode("d870fb3ff199999999999a"),
|
||||
wantCborData: mustHexDecode("d870fb3ff199999999999a"),
|
||||
},
|
||||
{
|
||||
name: "string",
|
||||
obj: myString("a"),
|
||||
wantCborData: hexDecode("d8716161"),
|
||||
wantCborData: mustHexDecode("d8716161"),
|
||||
},
|
||||
{
|
||||
name: "[]byte",
|
||||
obj: myByteSlice([]byte{1, 2, 3, 4}),
|
||||
wantCborData: hexDecode("d8724401020304"),
|
||||
wantCborData: mustHexDecode("d8724401020304"),
|
||||
},
|
||||
{
|
||||
name: "[]int",
|
||||
obj: myIntSlice([]int{1, 2, 3, 4}),
|
||||
wantCborData: hexDecode("d8738401020304"),
|
||||
wantCborData: mustHexDecode("d8738401020304"),
|
||||
},
|
||||
{
|
||||
name: "[4]int",
|
||||
obj: myIntArray([...]int{1, 2, 3, 4}),
|
||||
wantCborData: hexDecode("d8748401020304"),
|
||||
wantCborData: mustHexDecode("d8748401020304"),
|
||||
},
|
||||
{
|
||||
name: "map[int]int",
|
||||
obj: myMapIntInt(map[int]int{1: 2, 3: 4}),
|
||||
wantCborData: hexDecode("d875a201020304"),
|
||||
wantCborData: mustHexDecode("d875a201020304"),
|
||||
},
|
||||
}
|
||||
|
||||
@ -178,12 +181,12 @@ func TestTagBinaryMarshalerUnmarshaler(t *testing.T) {
|
||||
{
|
||||
name: "primitive obj",
|
||||
obj: number(1234567890),
|
||||
wantCborData: hexDecode("d87b4800000000499602d2"),
|
||||
wantCborData: mustHexDecode("d87b4800000000499602d2"),
|
||||
},
|
||||
{
|
||||
name: "struct obj",
|
||||
obj: stru{a: "a", b: "b", c: "c"},
|
||||
wantCborData: hexDecode("d87c45612C622C63"),
|
||||
wantCborData: mustHexDecode("d87c45612C622C63"),
|
||||
},
|
||||
}
|
||||
|
||||
@ -205,7 +208,7 @@ func TestTagStruct(t *testing.T) {
|
||||
em, _ := EncOptions{}.EncModeWithTags(tags)
|
||||
dm, _ := DecOptions{}.DecModeWithTags(tags)
|
||||
|
||||
data := hexDecode("d864a0") // {}
|
||||
data := mustHexDecode("d864a0") // {}
|
||||
var v T
|
||||
if err := dm.Unmarshal(data, &v); err != nil {
|
||||
t.Errorf("Unmarshal() returned error %v", err)
|
||||
@ -234,7 +237,7 @@ func TestTagFixedLengthStruct(t *testing.T) {
|
||||
em, _ := EncOptions{}.EncModeWithTags(tags)
|
||||
dm, _ := DecOptions{}.DecModeWithTags(tags)
|
||||
|
||||
data := hexDecode("d864a1617360") // {"s":""}
|
||||
data := mustHexDecode("d864a1617360") // {"s":""}
|
||||
var v T
|
||||
if err := dm.Unmarshal(data, &v); err != nil {
|
||||
t.Errorf("Unmarshal() returned error %v", err)
|
||||
@ -273,7 +276,7 @@ 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")
|
||||
data := mustHexDecode("d28443a10126a104524173796d6d657472696345434453413235365850a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b7158405427c1ff28d23fbad1f29c4c7c6a555e601d6fa29f9179bc3d7438bacaca5acd08c8d4d4f96131680c429a01f85951ecee743a52b9b63632c57209120e1c9e30")
|
||||
var v signedCWT
|
||||
if err := dm.Unmarshal(data, &v); err != nil {
|
||||
t.Errorf("Unmarshal() returned error %v", err)
|
||||
@ -313,7 +316,7 @@ 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")
|
||||
data := mustHexDecode("d83dd18443a10104a1044c53796d6d65747269633235365850a70175636f61703a2f2f61732e6578616d706c652e636f6d02656572696b77037818636f61703a2f2f6c696768742e6578616d706c652e636f6d041a5612aeb0051a5610d9f0061a5610d9f007420b7148093101ef6d789200")
|
||||
var v macedCOSE
|
||||
if err := dm.Unmarshal(data, &v); err != nil {
|
||||
t.Errorf("Unmarshal() returned error %v", err)
|
||||
@ -744,22 +747,22 @@ func TestDecodeTagData(t *testing.T) {
|
||||
{
|
||||
name: "BinaryMarshaler non-struct",
|
||||
obj: number(1234567890),
|
||||
wantCborData: hexDecode("d87b4800000000499602d2"),
|
||||
wantCborData: mustHexDecode("d87b4800000000499602d2"),
|
||||
},
|
||||
{
|
||||
name: "BinaryMarshaler struct",
|
||||
obj: stru{a: "a", b: "b", c: "c"},
|
||||
wantCborData: hexDecode("d87c45612C622C63"),
|
||||
wantCborData: mustHexDecode("d87c45612C622C63"),
|
||||
},
|
||||
{
|
||||
name: "non-struct",
|
||||
obj: myInt(1),
|
||||
wantCborData: hexDecode("d87d01"),
|
||||
wantCborData: mustHexDecode("d87d01"),
|
||||
},
|
||||
{
|
||||
name: "struct",
|
||||
obj: s{A: "A", B: "B", C: "C"},
|
||||
wantCborData: hexDecode("d87ea3616161416162614261636143"), // {"a":"A", "b":"B", "c":"C"}
|
||||
wantCborData: mustHexDecode("d87ea3616161416162614261636143"), // {"a":"A", "b":"B", "c":"C"}
|
||||
},
|
||||
}
|
||||
for _, tag := range tags {
|
||||
@ -814,22 +817,22 @@ func TestDecodeNoTagData(t *testing.T) {
|
||||
{
|
||||
name: "BinaryMarshaler non-struct",
|
||||
obj: number(1234567890),
|
||||
wantCborData: hexDecode("4800000000499602d2"),
|
||||
wantCborData: mustHexDecode("4800000000499602d2"),
|
||||
},
|
||||
{
|
||||
name: "BinaryMarshaler struct",
|
||||
obj: stru{a: "a", b: "b", c: "c"},
|
||||
wantCborData: hexDecode("45612C622C63"),
|
||||
wantCborData: mustHexDecode("45612C622C63"),
|
||||
},
|
||||
{
|
||||
name: "non-struct",
|
||||
obj: myInt(1),
|
||||
wantCborData: hexDecode("01"),
|
||||
wantCborData: mustHexDecode("01"),
|
||||
},
|
||||
{
|
||||
name: "struct",
|
||||
obj: s{A: "A", B: "B", C: "C"},
|
||||
wantCborData: hexDecode("a3616161416162614261636143"), // {"a":"A", "b":"B", "c":"C"}
|
||||
wantCborData: mustHexDecode("a3616161416162614261636143"), // {"a":"A", "b":"B", "c":"C"}
|
||||
},
|
||||
}
|
||||
|
||||
@ -905,32 +908,32 @@ func TestDecodeWrongTag(t *testing.T) {
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
obj interface{}
|
||||
obj any
|
||||
data []byte
|
||||
wantErrorMsg string
|
||||
}{
|
||||
{
|
||||
name: "BinaryMarshaler non-struct",
|
||||
obj: number(1234567890),
|
||||
data: hexDecode("d87d4800000000499602d2"),
|
||||
data: mustHexDecode("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"),
|
||||
data: mustHexDecode("d87d45612C622C63"),
|
||||
wantErrorMsg: "cbor: wrong tag number for cbor.stru, got [125], expected [124]",
|
||||
},
|
||||
{
|
||||
name: "non-struct",
|
||||
obj: myInt(1),
|
||||
data: hexDecode("d87d01"),
|
||||
data: mustHexDecode("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"}
|
||||
data: mustHexDecode("d87ea3616161416162614261636143"), // {"a":"A", "b":"B", "c":"C"}
|
||||
wantErrorMsg: "cbor: wrong tag number for cbor.s, got [126], expected [101 102]",
|
||||
},
|
||||
}
|
||||
@ -989,7 +992,7 @@ func TestEncodeSharedTag(t *testing.T) {
|
||||
|
||||
// Encode myInt with tag number 123
|
||||
v := myInt(1)
|
||||
wantCborData := hexDecode("d87b01")
|
||||
wantCborData := mustHexDecode("d87b01")
|
||||
b, err := em.Marshal(v)
|
||||
if err != nil {
|
||||
t.Errorf("Marshal(%v) returned error %v", v, err)
|
||||
@ -1003,7 +1006,7 @@ func TestEncodeSharedTag(t *testing.T) {
|
||||
|
||||
// Encode myInt without tag number 123
|
||||
v = myInt(2)
|
||||
wantCborData = hexDecode("02")
|
||||
wantCborData = mustHexDecode("02")
|
||||
b, err = em.Marshal(v)
|
||||
if err != nil {
|
||||
t.Errorf("Marshal(%v) returned error %v", v, err)
|
||||
@ -1019,7 +1022,7 @@ func TestEncodeSharedTag(t *testing.T) {
|
||||
|
||||
// Encode myInt with tag number 234
|
||||
v = myInt(3)
|
||||
wantCborData = hexDecode("d8ea03")
|
||||
wantCborData = mustHexDecode("d8ea03")
|
||||
b, err = em.Marshal(v)
|
||||
if err != nil {
|
||||
t.Errorf("Marshal(%v) returned error %v", v, err)
|
||||
@ -1050,7 +1053,7 @@ func TestDecodeSharedTag(t *testing.T) {
|
||||
// Decode myInt with tag number 123
|
||||
var v myInt
|
||||
wantV := myInt(1)
|
||||
data := hexDecode("d87b01")
|
||||
data := mustHexDecode("d87b01")
|
||||
if err = dm.Unmarshal(data, &v); err != nil {
|
||||
t.Errorf("Unmarshal(0x%x) returned error %v", data, err)
|
||||
}
|
||||
@ -1063,7 +1066,7 @@ func TestDecodeSharedTag(t *testing.T) {
|
||||
|
||||
// Decode myInt without tag number
|
||||
wantV = myInt(2)
|
||||
data = hexDecode("02")
|
||||
data = mustHexDecode("02")
|
||||
if err := dm.Unmarshal(data, &v); err != nil {
|
||||
t.Errorf("Unmarshal(0x%x) returned error %v", data, err)
|
||||
}
|
||||
@ -1078,7 +1081,7 @@ func TestDecodeSharedTag(t *testing.T) {
|
||||
|
||||
// Decode myInt with tag number 234
|
||||
wantV = myInt(3)
|
||||
data = hexDecode("d8ea03")
|
||||
data = mustHexDecode("d8ea03")
|
||||
if err := dm.Unmarshal(data, &v); err != nil {
|
||||
t.Errorf("Unmarshal(0x%x) returned error %v", data, err)
|
||||
}
|
||||
@ -1162,7 +1165,7 @@ func TestEncModeWithTagsError(t *testing.T) {
|
||||
func TestNilRawTagUnmarshalCBORError(t *testing.T) {
|
||||
wantErrorMsg := "cbor.RawTag: UnmarshalCBOR on nil pointer"
|
||||
var tag *RawTag
|
||||
data := hexDecode("c249010000000000000000")
|
||||
data := mustHexDecode("c249010000000000000000")
|
||||
if err := tag.UnmarshalCBOR(data); err == nil {
|
||||
t.Errorf("UnmarshalCBOR() didn't return error")
|
||||
} else if err.Error() != wantErrorMsg {
|
||||
@ -1171,7 +1174,7 @@ func TestNilRawTagUnmarshalCBORError(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestTagUnmarshalError(t *testing.T) {
|
||||
data := hexDecode("d87b61fe") // invalid UTF-8 string
|
||||
data := mustHexDecode("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)
|
||||
@ -1216,8 +1219,8 @@ func TestMarshalUninitializedRawTag(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMarshalTagWithEmptyContent(t *testing.T) {
|
||||
v := Tag{Number: 100} // Tag.Content is empty
|
||||
want := hexDecode("d864f6") // 100(null)
|
||||
v := Tag{Number: 100} // Tag.Content is empty
|
||||
want := mustHexDecode("d864f6") // 100(null)
|
||||
b, err := Marshal(v)
|
||||
if err != nil {
|
||||
t.Errorf("Marshal(%v) returned error %v", v, err)
|
||||
@ -1228,8 +1231,8 @@ func TestMarshalTagWithEmptyContent(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMarshalRawTagWithEmptyContent(t *testing.T) {
|
||||
v := RawTag{Number: 100} // RawTag.Content is empty
|
||||
want := hexDecode("d864f6") // 100(null)
|
||||
v := RawTag{Number: 100} // RawTag.Content is empty
|
||||
want := mustHexDecode("d864f6") // 100(null)
|
||||
b, err := Marshal(v)
|
||||
if err != nil {
|
||||
t.Errorf("Marshal(%v) returned error %v", v, err)
|
||||
@ -1240,7 +1243,7 @@ func TestMarshalRawTagWithEmptyContent(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEncodeTag(t *testing.T) {
|
||||
m := make(map[interface{}]bool)
|
||||
m := make(map[any]bool)
|
||||
m[10] = true
|
||||
m[100] = true
|
||||
m[-1] = true
|
||||
@ -1252,8 +1255,8 @@ func TestEncodeTag(t *testing.T) {
|
||||
|
||||
v := Tag{100, m}
|
||||
|
||||
lenFirstSortedCborData := hexDecode("d864a80af520f5f4f51864f5617af58120f5626161f5811864f5") // tag number: 100, value: map with sorted keys: 10, -1, false, 100, "z", [-1], "aa", [100]
|
||||
bytewiseSortedCborData := hexDecode("d864a80af51864f520f5617af5626161f5811864f58120f5f4f5") // tag number: 100, value: map with sorted keys: 10, 100, -1, "z", "aa", [100], [-1], false
|
||||
lenFirstSortedCborData := mustHexDecode("d864a80af520f5f4f51864f5617af58120f5626161f5811864f5") // tag number: 100, value: map with sorted keys: 10, -1, false, 100, "z", [-1], "aa", [100]
|
||||
bytewiseSortedCborData := mustHexDecode("d864a80af51864f520f5617af5626161f5811864f58120f5f4f5") // tag number: 100, value: map with sorted keys: 10, 100, -1, "z", "aa", [100], [-1], false
|
||||
|
||||
em, _ := EncOptions{Sort: SortLengthFirst}.EncMode()
|
||||
b, err := em.Marshal(v)
|
||||
@ -1295,38 +1298,38 @@ func TestDecodeTagToEmptyIface(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
data []byte
|
||||
wantObj interface{}
|
||||
wantObj any
|
||||
}{
|
||||
{
|
||||
name: "registered myBool",
|
||||
data: hexDecode("d864f5"), // 100(true)
|
||||
data: mustHexDecode("d864f5"), // 100(true)
|
||||
wantObj: myBool(true),
|
||||
},
|
||||
{
|
||||
name: "registered myUint",
|
||||
data: hexDecode("d865d86600"), // 101(102(0))
|
||||
data: mustHexDecode("d865d86600"), // 101(102(0))
|
||||
wantObj: myUint(0),
|
||||
},
|
||||
{
|
||||
name: "not registered bool",
|
||||
data: hexDecode("d865f5"), // 101(true)
|
||||
data: mustHexDecode("d865f5"), // 101(true)
|
||||
wantObj: Tag{101, true},
|
||||
},
|
||||
{
|
||||
name: "not registered uint",
|
||||
data: hexDecode("d865d86700"), // 101(103(0))
|
||||
data: mustHexDecode("d865d86700"), // 101(103(0))
|
||||
wantObj: Tag{101, Tag{103, uint64(0)}},
|
||||
},
|
||||
{
|
||||
name: "not registered uint",
|
||||
data: hexDecode("d865d866d86700"), // 101(102(103(0)))
|
||||
data: mustHexDecode("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{}
|
||||
var v1 any
|
||||
if err := dm.Unmarshal(tc.data, &v1); err != nil {
|
||||
t.Errorf("Unmarshal() returned error %v", err)
|
||||
}
|
||||
@ -1334,7 +1337,7 @@ func TestDecodeTagToEmptyIface(t *testing.T) {
|
||||
t.Errorf("Unmarshal to interface{} returned different values: %v, %v", tc.wantObj, v1)
|
||||
}
|
||||
|
||||
var v2 interface{}
|
||||
var v2 any
|
||||
if err := dmSharedTags.Unmarshal(tc.data, &v2); err != nil {
|
||||
t.Errorf("Unmarshal() returned error %v", err)
|
||||
}
|
||||
@ -1357,9 +1360,9 @@ func TestDecodeRegisteredTagToEmptyIfaceError(t *testing.T) {
|
||||
|
||||
dm, _ := DecOptions{}.DecModeWithTags(tags)
|
||||
|
||||
data := hexDecode("d865d8663bffffffffffffffff") // 101(102(-18446744073709551616))
|
||||
data := mustHexDecode("d865d8663bffffffffffffffff") // 101(102(-18446744073709551616))
|
||||
|
||||
var v interface{}
|
||||
var v any
|
||||
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 {
|
||||
@ -1408,14 +1411,14 @@ func TestDecodeRegisterTagForUnmarshaler(t *testing.T) {
|
||||
t.Fatalf("TagSet.Add(%s, %d) returned error %v", typ, 100, err)
|
||||
}
|
||||
|
||||
data := hexDecode("d864a1636e756d01") // 100({"num": 1})
|
||||
data := mustHexDecode("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{}
|
||||
var v1 any
|
||||
if err := dm.Unmarshal(data, &v1); err != nil {
|
||||
t.Errorf("Unmarshal() returned error %v", err)
|
||||
}
|
||||
@ -1448,7 +1451,7 @@ func TestDecodeRegisterTagForUnmarshaler(t *testing.T) {
|
||||
func TestMarshalRawTagContainingMalformedCBORData(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
value interface{}
|
||||
value any
|
||||
wantErrorMsg string
|
||||
}{
|
||||
// Nil RawMessage and empty RawMessage are encoded as CBOR nil.
|
||||
@ -1516,7 +1519,7 @@ func TestEncodeBuiltinTag(t *testing.T) {
|
||||
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"),
|
||||
want: mustHexDecode("c074323031332d30332d32315432303a30343a30305a"),
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
@ -1536,3 +1539,125 @@ func TestEncodeBuiltinTag(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalRawTagOnBadData(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
data []byte
|
||||
errMsg string
|
||||
}{
|
||||
// Empty data
|
||||
{
|
||||
name: "nil data",
|
||||
data: nil,
|
||||
errMsg: io.EOF.Error(),
|
||||
},
|
||||
{
|
||||
name: "empty data",
|
||||
data: []byte{},
|
||||
errMsg: io.EOF.Error(),
|
||||
},
|
||||
|
||||
// Wrong CBOR types
|
||||
{
|
||||
name: "uint type",
|
||||
data: mustHexDecode("01"),
|
||||
errMsg: "cbor: cannot unmarshal positive integer into Go value of type cbor.RawTag",
|
||||
},
|
||||
{
|
||||
name: "int type",
|
||||
data: mustHexDecode("20"),
|
||||
errMsg: "cbor: cannot unmarshal negative integer into Go value of type cbor.RawTag",
|
||||
},
|
||||
{
|
||||
name: "byte string type",
|
||||
data: mustHexDecode("40"),
|
||||
errMsg: "cbor: cannot unmarshal byte string into Go value of type cbor.RawTag",
|
||||
},
|
||||
{
|
||||
name: "string type",
|
||||
data: mustHexDecode("60"),
|
||||
errMsg: "cbor: cannot unmarshal UTF-8 text string into Go value of type cbor.RawTag",
|
||||
},
|
||||
{
|
||||
name: "array type",
|
||||
data: mustHexDecode("80"),
|
||||
errMsg: "cbor: cannot unmarshal array into Go value of type cbor.RawTag",
|
||||
},
|
||||
{
|
||||
name: "map type",
|
||||
data: mustHexDecode("a0"),
|
||||
errMsg: "cbor: cannot unmarshal map into Go value of type cbor.RawTag",
|
||||
},
|
||||
{
|
||||
name: "primitive type",
|
||||
data: mustHexDecode("f4"),
|
||||
errMsg: "cbor: cannot unmarshal primitives into Go value of type cbor.RawTag",
|
||||
},
|
||||
{
|
||||
name: "float type",
|
||||
data: mustHexDecode("f90000"),
|
||||
errMsg: "cbor: cannot unmarshal primitives into Go value of type cbor.RawTag",
|
||||
},
|
||||
|
||||
// Truncated CBOR data
|
||||
{
|
||||
name: "truncated head",
|
||||
data: mustHexDecode("18"),
|
||||
errMsg: io.ErrUnexpectedEOF.Error(),
|
||||
},
|
||||
|
||||
// Truncated CBOR tag data
|
||||
{
|
||||
name: "truncated tag number",
|
||||
data: mustHexDecode("d8"),
|
||||
errMsg: io.ErrUnexpectedEOF.Error(),
|
||||
},
|
||||
{
|
||||
name: "tag number not followed by tag content",
|
||||
data: mustHexDecode("da"),
|
||||
errMsg: io.ErrUnexpectedEOF.Error(),
|
||||
},
|
||||
{
|
||||
name: "truncated tag content",
|
||||
data: mustHexDecode("c074323031332d30332d32315432303a30343a3030"),
|
||||
errMsg: io.ErrUnexpectedEOF.Error(),
|
||||
},
|
||||
|
||||
// Extraneous CBOR data
|
||||
{
|
||||
name: "extraneous data",
|
||||
data: mustHexDecode("c074323031332d30332d32315432303a30343a30305a00"),
|
||||
errMsg: "cbor: 1 bytes of extraneous data starting at index 22",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// Test RawTag.UnmarshalCBOR(data)
|
||||
{
|
||||
var v RawTag
|
||||
|
||||
err := v.UnmarshalCBOR(tc.data)
|
||||
if err == nil {
|
||||
t.Errorf("UnmarshalCBOR(%x) didn't return error", tc.data)
|
||||
}
|
||||
if !strings.HasPrefix(err.Error(), tc.errMsg) {
|
||||
t.Errorf("UnmarshalCBOR(%x) returned error %q, want %q", tc.data, err.Error(), tc.errMsg)
|
||||
}
|
||||
}
|
||||
// Test Unmarshal(data, *RawTag), which calls RawTag.unmarshalCBOR() under the hood
|
||||
{
|
||||
var v RawTag
|
||||
|
||||
err := Unmarshal(tc.data, &v)
|
||||
if err == nil {
|
||||
t.Errorf("Unmarshal(%x) didn't return error", tc.data)
|
||||
}
|
||||
if !strings.HasPrefix(err.Error(), tc.errMsg) {
|
||||
t.Errorf("Unmarshal(%x) returned error %q, want %q", tc.data, err.Error(), tc.errMsg)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
259
valid_test.go
259
valid_test.go
@ -32,9 +32,24 @@ func TestValidExtraneousData(t *testing.T) {
|
||||
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},
|
||||
{
|
||||
name: "two numbers",
|
||||
data: []byte{0x00, 0x01},
|
||||
extraneousDataNumOfBytes: 1,
|
||||
extraneousDataIndex: 1,
|
||||
}, // 0, 1
|
||||
{
|
||||
name: "bytestring and int",
|
||||
data: []byte{0x44, 0x01, 0x02, 0x03, 0x04, 0x00},
|
||||
extraneousDataNumOfBytes: 1,
|
||||
extraneousDataIndex: 5,
|
||||
}, // h'01020304', 0
|
||||
{
|
||||
name: "int and partial array",
|
||||
data: []byte{0x00, 0x83, 0x01, 0x02},
|
||||
extraneousDataNumOfBytes: 3,
|
||||
extraneousDataIndex: 1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
@ -75,38 +90,166 @@ func TestDepth(t *testing.T) {
|
||||
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")))
|
||||
{"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")))
|
||||
{
|
||||
name: "uint",
|
||||
data: mustHexDecode("00"),
|
||||
wantDepth: 0,
|
||||
}, // 0
|
||||
{
|
||||
name: "int",
|
||||
data: mustHexDecode("20"),
|
||||
wantDepth: 0,
|
||||
}, // -1
|
||||
{
|
||||
name: "bool",
|
||||
data: mustHexDecode("f4"),
|
||||
wantDepth: 0,
|
||||
}, // false
|
||||
{
|
||||
name: "nil",
|
||||
data: mustHexDecode("f6"),
|
||||
wantDepth: 0,
|
||||
}, // nil
|
||||
{
|
||||
name: "float",
|
||||
data: mustHexDecode("fa47c35000"),
|
||||
wantDepth: 0,
|
||||
}, // 100000.0
|
||||
{
|
||||
name: "byte string",
|
||||
data: mustHexDecode("40"),
|
||||
wantDepth: 0,
|
||||
}, // []byte{}
|
||||
{
|
||||
name: "indefinite length byte string",
|
||||
data: mustHexDecode("5f42010243030405ff"),
|
||||
wantDepth: 0,
|
||||
}, // []byte{1, 2, 3, 4, 5}
|
||||
{
|
||||
name: "text string",
|
||||
data: mustHexDecode("60"),
|
||||
wantDepth: 0,
|
||||
}, // ""
|
||||
{
|
||||
name: "indefinite length text string",
|
||||
data: mustHexDecode("7f657374726561646d696e67ff"),
|
||||
wantDepth: 0,
|
||||
}, // "streaming"
|
||||
{
|
||||
name: "empty array",
|
||||
data: mustHexDecode("80"),
|
||||
wantDepth: 1,
|
||||
}, // []
|
||||
{
|
||||
name: "indefinite length empty array",
|
||||
data: mustHexDecode("9fff"),
|
||||
wantDepth: 1,
|
||||
}, // []
|
||||
{
|
||||
name: "array",
|
||||
data: mustHexDecode("98190102030405060708090a0b0c0d0e0f101112131415161718181819"),
|
||||
wantDepth: 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]
|
||||
{
|
||||
name: "indefinite length array",
|
||||
data: mustHexDecode("9f0102030405060708090a0b0c0d0e0f101112131415161718181819ff"),
|
||||
wantDepth: 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]
|
||||
{
|
||||
name: "nested array",
|
||||
data: mustHexDecode("8301820203820405"),
|
||||
wantDepth: 2,
|
||||
}, // [1,[2,3],[4,5]]
|
||||
{
|
||||
name: "indefinite length nested array",
|
||||
data: mustHexDecode("83018202039f0405ff"),
|
||||
wantDepth: 2,
|
||||
}, // [1,[2,3],[4,5]]
|
||||
{
|
||||
name: "array and map",
|
||||
data: mustHexDecode("826161a161626163"),
|
||||
wantDepth: 2,
|
||||
}, // [a", {"b": "c"}]
|
||||
{
|
||||
name: "indefinite length array and map",
|
||||
data: mustHexDecode("826161bf61626163ff"),
|
||||
wantDepth: 2,
|
||||
}, // [a", {"b": "c"}]
|
||||
{
|
||||
name: "empty map",
|
||||
data: mustHexDecode("a0"),
|
||||
wantDepth: 1,
|
||||
}, // {}
|
||||
{
|
||||
name: "indefinite length empty map",
|
||||
data: mustHexDecode("bfff"),
|
||||
wantDepth: 1,
|
||||
}, // {}
|
||||
{
|
||||
name: "map",
|
||||
data: mustHexDecode("a201020304"),
|
||||
wantDepth: 1,
|
||||
}, // {1:2, 3:4}
|
||||
{
|
||||
name: "nested map",
|
||||
data: mustHexDecode("a26161016162820203"),
|
||||
wantDepth: 2,
|
||||
}, // {"a": 1, "b": [2, 3]}
|
||||
{
|
||||
name: "indefinite length nested map",
|
||||
data: mustHexDecode("bf61610161629f0203ffff"),
|
||||
wantDepth: 2,
|
||||
}, // {"a": 1, "b": [2, 3]}
|
||||
{
|
||||
name: "tag",
|
||||
data: mustHexDecode("c074323031332d30332d32315432303a30343a30305a"),
|
||||
wantDepth: 0,
|
||||
}, // 0("2013-03-21T20:04:00Z")
|
||||
{
|
||||
name: "tagged map",
|
||||
data: mustHexDecode("d864a26161016162820203"),
|
||||
wantDepth: 2,
|
||||
}, // 100({"a": 1, "b": [2, 3]})
|
||||
{
|
||||
name: "tagged map and array",
|
||||
data: mustHexDecode("d864a26161016162d865820203"),
|
||||
wantDepth: 2,
|
||||
}, // 100({"a": 1, "b": 101([2, 3])})
|
||||
{
|
||||
name: "tagged map and array",
|
||||
data: mustHexDecode("d864a26161016162d865d866820203"),
|
||||
wantDepth: 3,
|
||||
}, // 100({"a": 1, "b": 101(102([2, 3]))})
|
||||
{
|
||||
name: "nested tag",
|
||||
data: mustHexDecode("d864d865d86674323031332d30332d32315432303a30343a30305a"),
|
||||
wantDepth: 2,
|
||||
}, // 100(101(102("2013-03-21T20:04:00Z")))
|
||||
{
|
||||
name: "32-level array",
|
||||
data: mustHexDecode("82018181818181818181818181818181818181818181818181818181818181818101"),
|
||||
wantDepth: 32,
|
||||
},
|
||||
{
|
||||
name: "32-level indefinite length array",
|
||||
data: mustHexDecode("9f018181818181818181818181818181818181818181818181818181818181818101ff"),
|
||||
wantDepth: 32,
|
||||
},
|
||||
{
|
||||
name: "32-level map",
|
||||
data: mustHexDecode("a1018181818181818181818181818181818181818181818181818181818181818101"),
|
||||
wantDepth: 32,
|
||||
},
|
||||
{
|
||||
name: "32-level indefinite length map",
|
||||
data: mustHexDecode("bf018181818181818181818181818181818181818181818181818181818181818101ff"),
|
||||
wantDepth: 32,
|
||||
},
|
||||
{
|
||||
name: "32-level tag",
|
||||
data: mustHexDecode("d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d86474323031332d30332d32315432303a30343a30305a"),
|
||||
wantDepth: 32,
|
||||
}, // 100(100(...("2013-03-21T20:04:00Z")))
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
@ -131,43 +274,43 @@ func TestDepthError(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "33-level array",
|
||||
data: hexDecode("82018181818181818181818181818181818181818181818181818181818181818101"),
|
||||
data: mustHexDecode("82018181818181818181818181818181818181818181818181818181818181818101"),
|
||||
opts: DecOptions{MaxNestedLevels: 4},
|
||||
wantErrorMsg: "cbor: exceeded max nested level 4",
|
||||
},
|
||||
{
|
||||
name: "33-level array",
|
||||
data: hexDecode("82018181818181818181818181818181818181818181818181818181818181818101"),
|
||||
data: mustHexDecode("82018181818181818181818181818181818181818181818181818181818181818101"),
|
||||
opts: DecOptions{MaxNestedLevels: 10},
|
||||
wantErrorMsg: "cbor: exceeded max nested level 10",
|
||||
},
|
||||
{
|
||||
name: "33-level array",
|
||||
data: hexDecode("8201818181818181818181818181818181818181818181818181818181818181818101"),
|
||||
data: mustHexDecode("8201818181818181818181818181818181818181818181818181818181818181818101"),
|
||||
opts: DecOptions{},
|
||||
wantErrorMsg: "cbor: exceeded max nested level 32",
|
||||
},
|
||||
{
|
||||
name: "33-level indefinite length array",
|
||||
data: hexDecode("9f01818181818181818181818181818181818181818181818181818181818181818101ff"),
|
||||
data: mustHexDecode("9f01818181818181818181818181818181818181818181818181818181818181818101ff"),
|
||||
opts: DecOptions{},
|
||||
wantErrorMsg: "cbor: exceeded max nested level 32",
|
||||
},
|
||||
{
|
||||
name: "33-level map",
|
||||
data: hexDecode("a101818181818181818181818181818181818181818181818181818181818181818101"),
|
||||
data: mustHexDecode("a101818181818181818181818181818181818181818181818181818181818181818101"),
|
||||
opts: DecOptions{},
|
||||
wantErrorMsg: "cbor: exceeded max nested level 32",
|
||||
},
|
||||
{
|
||||
name: "33-level indefinite length map",
|
||||
data: hexDecode("bf01818181818181818181818181818181818181818181818181818181818181818101ff"),
|
||||
data: mustHexDecode("bf01818181818181818181818181818181818181818181818181818181818181818101ff"),
|
||||
opts: DecOptions{},
|
||||
wantErrorMsg: "cbor: exceeded max nested level 32",
|
||||
},
|
||||
{
|
||||
name: "33-level tag",
|
||||
data: hexDecode("d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d86474323031332d30332d32315432303a30343a30305a"),
|
||||
data: mustHexDecode("d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d864d86474323031332d30332d32315432303a30343a30305a"),
|
||||
opts: DecOptions{},
|
||||
wantErrorMsg: "cbor: exceeded max nested level 32",
|
||||
},
|
||||
@ -194,35 +337,35 @@ func TestValidBuiltinTagTest(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "tag 0",
|
||||
data: hexDecode("c074323031332d30332d32315432303a30343a30305a"),
|
||||
data: mustHexDecode("c074323031332d30332d32315432303a30343a30305a"),
|
||||
},
|
||||
{
|
||||
name: "tag 1",
|
||||
data: hexDecode("c11a514b67b0"),
|
||||
data: mustHexDecode("c11a514b67b0"),
|
||||
},
|
||||
{
|
||||
name: "tag 2",
|
||||
data: hexDecode("c249010000000000000000"),
|
||||
data: mustHexDecode("c249010000000000000000"),
|
||||
},
|
||||
{
|
||||
name: "tag 3",
|
||||
data: hexDecode("c349010000000000000000"),
|
||||
data: mustHexDecode("c349010000000000000000"),
|
||||
},
|
||||
{
|
||||
name: "nested tag 0",
|
||||
data: hexDecode("d9d9f7c074323031332d30332d32315432303a30343a30305a"),
|
||||
data: mustHexDecode("d9d9f7c074323031332d30332d32315432303a30343a30305a"),
|
||||
},
|
||||
{
|
||||
name: "nested tag 1",
|
||||
data: hexDecode("d9d9f7c11a514b67b0"),
|
||||
data: mustHexDecode("d9d9f7c11a514b67b0"),
|
||||
},
|
||||
{
|
||||
name: "nested tag 2",
|
||||
data: hexDecode("d9d9f7c249010000000000000000"),
|
||||
data: mustHexDecode("d9d9f7c249010000000000000000"),
|
||||
},
|
||||
{
|
||||
name: "nested tag 3",
|
||||
data: hexDecode("d9d9f7c349010000000000000000"),
|
||||
data: mustHexDecode("d9d9f7c349010000000000000000"),
|
||||
},
|
||||
}
|
||||
|
||||
@ -244,42 +387,42 @@ func TestInvalidBuiltinTagTest(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "tag 0",
|
||||
data: hexDecode("c01a514b67b0"),
|
||||
data: mustHexDecode("c01a514b67b0"),
|
||||
wantErrorMsg: "cbor: tag number 0 must be followed by text string, got positive integer",
|
||||
},
|
||||
{
|
||||
name: "tag 1",
|
||||
data: hexDecode("c174323031332d30332d32315432303a30343a30305a"),
|
||||
data: mustHexDecode("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"),
|
||||
data: mustHexDecode("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"),
|
||||
data: mustHexDecode("c300"),
|
||||
wantErrorMsg: "cbor: tag number 2 or 3 must be followed by byte string, got positive integer",
|
||||
},
|
||||
{
|
||||
name: "nested tag 0",
|
||||
data: hexDecode("d9d9f7c01a514b67b0"),
|
||||
data: mustHexDecode("d9d9f7c01a514b67b0"),
|
||||
wantErrorMsg: "cbor: tag number 0 must be followed by text string, got positive integer",
|
||||
},
|
||||
{
|
||||
name: "nested tag 1",
|
||||
data: hexDecode("d9d9f7c174323031332d30332d32315432303a30343a30305a"),
|
||||
data: mustHexDecode("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"),
|
||||
data: mustHexDecode("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"),
|
||||
data: mustHexDecode("d9d9f7c300"),
|
||||
wantErrorMsg: "cbor: tag number 2 or 3 must be followed by byte string, got positive integer",
|
||||
},
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user