From 8d14d64ae6fcbfa7b567175cb1a3f2c8916e02f8 Mon Sep 17 00:00:00 2001 From: Alex Richards Date: Wed, 25 Sep 2024 12:45:42 +1200 Subject: [PATCH] tiny-cbor initial commit --- .github/workflows/build-and-test.yml | 27 + .gitignore | 2 + LICENSE | 202 +++++++ Makefile | 14 + README.md | 7 + cbor.go | 64 +++ cbor_test.go | 87 +++ comparison_test.go | 75 +++ cover.out | 360 +++++++++++++ go.mod | 10 + go.sum | 6 + io.go | 46 ++ io_test.go | 53 ++ numbers.go | 16 + numbers_test.go | 63 +++ read.go | 446 +++++++++++++++ read_any.go | 172 ++++++ read_any_test.go | 50 ++ read_over.go | 122 +++++ read_raw.go | 134 +++++ read_test.go | 774 +++++++++++++++++++++++++++ util_test.go | 17 + write.go | 121 +++++ write_test.go | 568 ++++++++++++++++++++ 24 files changed, 3436 insertions(+) create mode 100644 .github/workflows/build-and-test.yml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 cbor.go create mode 100644 cbor_test.go create mode 100644 comparison_test.go create mode 100644 cover.out create mode 100644 go.mod create mode 100644 go.sum create mode 100644 io.go create mode 100644 io_test.go create mode 100644 numbers.go create mode 100644 numbers_test.go create mode 100644 read.go create mode 100644 read_any.go create mode 100644 read_any_test.go create mode 100644 read_over.go create mode 100644 read_raw.go create mode 100644 read_test.go create mode 100644 util_test.go create mode 100644 write.go create mode 100644 write_test.go diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml new file mode 100644 index 0000000..069057a --- /dev/null +++ b/.github/workflows/build-and-test.yml @@ -0,0 +1,27 @@ +name: "Build & Test" + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + + build: + name: "Build & Test" + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + + - name: Build + run: make build + + - name: Test + run: make test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ce5598e --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.out +.idea/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..fe7c7f9 --- /dev/null +++ b/Makefile @@ -0,0 +1,14 @@ +build: + go build + +build-verbose: + go build -gcflags '-m -l' + +test: + go test + +benchmark: + go test -run NONE -bench . -benchmem + +compare: + go test -run NONE -bench . -benchmem -tags cbor_comparison diff --git a/README.md b/README.md new file mode 100644 index 0000000..d893853 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# Tiny-CBOR + +CBOR without the reflection. + +The long term plan here is to generate Go read / write methods from the struct +tags in [go-cbor](https://github.com/fxamacker/cbor). + diff --git a/cbor.go b/cbor.go new file mode 100644 index 0000000..fc731fa --- /dev/null +++ b/cbor.go @@ -0,0 +1,64 @@ +package cbor + +import "errors" + +type MajorType byte + +const ( + MajorTypeUInt MajorType = 0 << 5 + MajorTypeNInt = 1 << 5 + MajorTypeBstr = 2 << 5 + MajorTypeTstr = 3 << 5 + MajorTypeArray = 4 << 5 + MajorTypeMap = 5 << 5 + MajorTypeTagged = 6 << 5 + MajorTypeSimpleFloat = 7 << 5 +) + +const ( + majorTypeMask = 0b111_00000 +) + +type Arg byte + +const ( + Arg8 Arg = 24 + Arg16 = 25 + Arg32 = 26 + Arg64 = 27 + // 28..30 reserved + ArgIndefinite = 31 +) + +const ( + argMask = 0b000_11111 +) + +const ( + SimpleFalse byte = 20 + SimpleTrue = 21 + SimpleNull = 22 + SimpleUndefined = 23 + SimpleUint8 = Arg8 // 24 + SimpleFloat16 = Arg16 // 25 + SimpleFloat32 = Arg32 // 26 + SimpleFloat64 = Arg64 // 27 + // 28..30 reserved + SimpleBreak = 31 +) + +var ( + ErrUnsupportedMajorType = errors.New("cbor: unsupported major type") + ErrUnsupportedValue = errors.New("cbor: unsupported value") + ErrNotWellFormed = errors.New("cbor: not well formed") + ErrOverflow = errors.New("cbor: overflow") + ErrNestedIndefinite = errors.New("cbor: nested indefinite") +) + +const ( + valueBreak = MajorTypeSimpleFloat | SimpleBreak +) + +const lenSharedBuffer = 64 + +var sharedBuffer [lenSharedBuffer]byte diff --git a/cbor_test.go b/cbor_test.go new file mode 100644 index 0000000..f8636e9 --- /dev/null +++ b/cbor_test.go @@ -0,0 +1,87 @@ +package cbor + +var tests_ExampleEncoded = []struct { + encoded string +}{ + {encoded: "00"}, + {encoded: "01"}, + {encoded: "0a"}, + {encoded: "17"}, + {encoded: "1818"}, + {encoded: "1819"}, + {encoded: "1864"}, + {encoded: "1903e8"}, + {encoded: "1a000f4240"}, + {encoded: "1b000000e8d4a51000"}, + {encoded: "1bffffffffffffffff"}, + {encoded: "c249010000000000000000"}, + {encoded: "3bffffffffffffffff"}, + {encoded: "c349010000000000000000"}, + {encoded: "20"}, + {encoded: "29"}, + {encoded: "3863"}, + {encoded: "3903e7"}, + {encoded: "f90000"}, + {encoded: "f98000"}, + {encoded: "f93c00"}, + {encoded: "fb3ff199999999999a"}, + {encoded: "f93e00"}, + {encoded: "f97bff"}, + {encoded: "fa47c35000"}, + {encoded: "fa7f7fffff"}, + {encoded: "fb7e37e43c8800759c"}, + {encoded: "f90001"}, + {encoded: "f90400"}, + {encoded: "f9c400"}, + {encoded: "fbc010666666666666"}, + {encoded: "f97c00"}, + {encoded: "f97e00"}, + {encoded: "f9fc00"}, + {encoded: "fa7f800000"}, + {encoded: "fa7fc00000"}, + {encoded: "faff800000"}, + {encoded: "fb7ff0000000000000"}, + {encoded: "fb7ff8000000000000"}, + {encoded: "fbfff0000000000000"}, + {encoded: "f4"}, + {encoded: "f5"}, + {encoded: "f6"}, + {encoded: "f7"}, + {encoded: "f0"}, + {encoded: "f8ff"}, + {encoded: "c074323031332d30332d32315432303a30343a30305a"}, + {encoded: "c11a514b67b0"}, + {encoded: "c1fb41d452d9ec200000"}, + {encoded: "d74401020304"}, + {encoded: "d818456449455446"}, + {encoded: "d82076687474703a2f2f7777772e6578616d706c652e636f6d"}, + {encoded: "40"}, + {encoded: "4401020304"}, + {encoded: "60"}, + {encoded: "6161"}, + {encoded: "6449455446"}, + {encoded: "62225c"}, + {encoded: "62c3bc"}, + {encoded: "63e6b0b4"}, + {encoded: "64f0908591"}, + {encoded: "80"}, + {encoded: "83010203"}, + {encoded: "8301820203820405"}, + {encoded: "98190102030405060708090a0b0c0d0e0f101112131415161718181819"}, + {encoded: "a0"}, + {encoded: "a201020304"}, + {encoded: "a26161016162820203"}, + {encoded: "826161a161626163"}, + {encoded: "a56161614161626142616361436164614461656145"}, + {encoded: "5f42010243030405ff"}, + {encoded: "7f657374726561646d696e67ff"}, + {encoded: "9fff"}, + {encoded: "9f018202039f0405ffff"}, + {encoded: "9f01820203820405ff"}, + {encoded: "83018202039f0405ff"}, + {encoded: "83019f0203ff820405"}, + {encoded: "9f0102030405060708090a0b0c0d0e0f101112131415161718181819ff"}, + {encoded: "bf61610161629f0203ffff"}, + {encoded: "826161bf61626163ff"}, + {encoded: "bf6346756ef563416d7421ff"}, +} diff --git a/comparison_test.go b/comparison_test.go new file mode 100644 index 0000000..8e18fe4 --- /dev/null +++ b/comparison_test.go @@ -0,0 +1,75 @@ +//go:build cbor_comparison + +package cbor + +import ( + "bytes" + "fmt" + "io" + "testing" + + fxcbor "github.com/fxamacker/cbor/v2" + "github.com/google/go-cmp/cmp" +) + +func Test_ReadAny_Comparison(t *testing.T) { + for _, tt := range tests_ExampleEncoded { + t.Run(tt.encoded, func(t *testing.T) { + encoded := decodeHex(t, tt.encoded) + in := bytes.NewReader(encoded) + + var err error + var out any + var outFx any + + { + in.Seek(0, io.SeekStart) + out, err = ReadAny(in) + if err != nil { + t.Fatal(err) + } + } + + { + in.Seek(0, io.SeekStart) + err = fxcbor.Unmarshal(encoded, &outFx) + if err != nil { + t.Fatal(err) + } + } + + if diff := cmp.Diff(out, outFx); diff != "" { + t.Fatal(diff) + } + }) + } +} +func Benchmark_ReadAny_Comparison(b *testing.B) { + for _, tt := range tests_ExampleEncoded { + encoded := decodeHex(b, tt.encoded) + in := bytes.NewReader(encoded) + + var out any + var err error + + b.Run(fmt.Sprintf("%s", tt.encoded), func(b *testing.B) { + for range b.N { + in.Seek(0, io.SeekStart) + out, err = ReadAny(in) + if err != nil { + b.Fatal(err) + } + } + }) + + b.Run(fmt.Sprintf("fx %s", tt.encoded), func(b *testing.B) { + for range b.N { + in.Seek(0, io.SeekStart) + err = fxcbor.Unmarshal(encoded, &out) + if err != nil { + b.Fatal(err) + } + } + }) + } +} diff --git a/cover.out b/cover.out new file mode 100644 index 0000000..81c8ebe --- /dev/null +++ b/cover.out @@ -0,0 +1,360 @@ +mode: set +github.com/alex-richards/tiny-cbor/io.go:11.52,13.13 2 1 +github.com/alex-richards/tiny-cbor/io.go:13.13,15.3 1 0 +github.com/alex-richards/tiny-cbor/io.go:17.2,17.10 1 1 +github.com/alex-richards/tiny-cbor/io.go:17.10,20.14 3 1 +github.com/alex-richards/tiny-cbor/io.go:20.14,22.4 1 1 +github.com/alex-richards/tiny-cbor/io.go:24.3,25.20 2 1 +github.com/alex-richards/tiny-cbor/io.go:28.2,28.22 1 1 +github.com/alex-richards/tiny-cbor/io.go:31.47,32.10 1 1 +github.com/alex-richards/tiny-cbor/io.go:32.10,34.3 1 0 +github.com/alex-richards/tiny-cbor/io.go:36.2,39.12 3 1 +github.com/alex-richards/tiny-cbor/io.go:39.12,41.3 1 0 +github.com/alex-richards/tiny-cbor/io.go:43.2,45.17 3 1 +github.com/alex-richards/tiny-cbor/numbers.go:3.104,5.19 2 1 +github.com/alex-richards/tiny-cbor/numbers.go:5.19,7.3 1 1 +github.com/alex-richards/tiny-cbor/numbers.go:8.2,8.8 1 1 +github.com/alex-richards/tiny-cbor/numbers.go:11.103,13.19 2 1 +github.com/alex-richards/tiny-cbor/numbers.go:13.19,15.3 1 1 +github.com/alex-richards/tiny-cbor/read.go:12.66,15.12 3 1 +github.com/alex-richards/tiny-cbor/read.go:15.12,17.3 1 0 +github.com/alex-richards/tiny-cbor/read.go:19.2,21.16 3 1 +github.com/alex-richards/tiny-cbor/read.go:21.16,23.3 1 0 +github.com/alex-richards/tiny-cbor/read.go:25.2,25.11 1 1 +github.com/alex-richards/tiny-cbor/read.go:25.11,28.18 3 1 +github.com/alex-richards/tiny-cbor/read.go:28.18,30.4 1 0 +github.com/alex-richards/tiny-cbor/read.go:31.3,31.32 1 1 +github.com/alex-richards/tiny-cbor/read.go:34.2,34.31 1 1 +github.com/alex-richards/tiny-cbor/read.go:39.44,43.2 3 1 +github.com/alex-richards/tiny-cbor/read.go:45.53,46.9 1 1 +github.com/alex-richards/tiny-cbor/read.go:47.18,48.32 1 1 +github.com/alex-richards/tiny-cbor/read.go:49.19,50.24 1 1 +github.com/alex-richards/tiny-cbor/read.go:51.20,52.24 1 1 +github.com/alex-richards/tiny-cbor/read.go:53.20,54.24 1 1 +github.com/alex-richards/tiny-cbor/read.go:55.20,56.24 1 1 +github.com/alex-richards/tiny-cbor/read.go:57.28,58.24 1 1 +github.com/alex-richards/tiny-cbor/read.go:59.10,60.35 1 0 +github.com/alex-richards/tiny-cbor/read.go:65.44,67.16 2 1 +github.com/alex-richards/tiny-cbor/read.go:67.16,69.3 1 0 +github.com/alex-richards/tiny-cbor/read.go:71.2,71.34 1 1 +github.com/alex-richards/tiny-cbor/read.go:71.34,73.3 1 0 +github.com/alex-richards/tiny-cbor/read.go:75.2,75.19 1 1 +github.com/alex-richards/tiny-cbor/read.go:78.80,80.16 2 1 +github.com/alex-richards/tiny-cbor/read.go:80.16,82.3 1 0 +github.com/alex-richards/tiny-cbor/read.go:84.2,84.47 1 1 +github.com/alex-richards/tiny-cbor/read.go:87.110,90.61 1 1 +github.com/alex-richards/tiny-cbor/read.go:90.61,93.25 2 1 +github.com/alex-richards/tiny-cbor/read.go:93.25,95.4 1 1 +github.com/alex-richards/tiny-cbor/read.go:97.3,97.23 1 1 +github.com/alex-richards/tiny-cbor/read.go:100.2,100.35 1 1 +github.com/alex-richards/tiny-cbor/read.go:103.74,105.16 2 1 +github.com/alex-richards/tiny-cbor/read.go:105.16,107.3 1 0 +github.com/alex-richards/tiny-cbor/read.go:109.2,109.45 1 1 +github.com/alex-richards/tiny-cbor/read.go:112.104,116.61 1 1 +github.com/alex-richards/tiny-cbor/read.go:116.61,119.29 2 1 +github.com/alex-richards/tiny-cbor/read.go:119.29,121.4 1 1 +github.com/alex-richards/tiny-cbor/read.go:123.3,123.33 1 1 +github.com/alex-richards/tiny-cbor/read.go:123.33,125.4 1 1 +github.com/alex-richards/tiny-cbor/read.go:127.3,127.23 1 1 +github.com/alex-richards/tiny-cbor/read.go:130.2,130.35 1 1 +github.com/alex-richards/tiny-cbor/read.go:133.62,135.16 2 1 +github.com/alex-richards/tiny-cbor/read.go:135.16,137.3 1 0 +github.com/alex-richards/tiny-cbor/read.go:139.2,139.44 1 1 +github.com/alex-richards/tiny-cbor/read.go:142.92,144.61 1 1 +github.com/alex-richards/tiny-cbor/read.go:144.61,146.3 1 1 +github.com/alex-richards/tiny-cbor/read.go:148.2,148.32 1 1 +github.com/alex-richards/tiny-cbor/read.go:148.32,150.3 1 0 +github.com/alex-richards/tiny-cbor/read.go:152.2,152.39 1 1 +github.com/alex-richards/tiny-cbor/read.go:152.39,154.25 2 1 +github.com/alex-richards/tiny-cbor/read.go:154.25,156.4 1 1 +github.com/alex-richards/tiny-cbor/read.go:158.3,158.27 1 1 +github.com/alex-richards/tiny-cbor/read.go:158.27,160.4 1 1 +github.com/alex-richards/tiny-cbor/read.go:162.3,162.27 1 1 +github.com/alex-richards/tiny-cbor/read.go:162.27,164.4 1 1 +github.com/alex-richards/tiny-cbor/read.go:166.3,166.27 1 1 +github.com/alex-richards/tiny-cbor/read.go:166.27,168.4 1 1 +github.com/alex-richards/tiny-cbor/read.go:170.3,170.32 1 0 +github.com/alex-richards/tiny-cbor/read.go:173.2,173.35 1 0 +github.com/alex-richards/tiny-cbor/read.go:176.43,178.16 2 1 +github.com/alex-richards/tiny-cbor/read.go:178.16,180.3 1 0 +github.com/alex-richards/tiny-cbor/read.go:182.2,182.35 1 1 +github.com/alex-richards/tiny-cbor/read.go:185.64,186.39 1 1 +github.com/alex-richards/tiny-cbor/read.go:186.39,187.33 1 1 +github.com/alex-richards/tiny-cbor/read.go:187.33,189.4 1 1 +github.com/alex-richards/tiny-cbor/read.go:191.3,191.32 1 1 +github.com/alex-richards/tiny-cbor/read.go:191.32,193.4 1 1 +github.com/alex-richards/tiny-cbor/read.go:195.3,195.36 1 1 +github.com/alex-richards/tiny-cbor/read.go:198.2,198.39 1 1 +github.com/alex-richards/tiny-cbor/read.go:205.9,207.16 2 1 +github.com/alex-richards/tiny-cbor/read.go:207.16,209.3 1 0 +github.com/alex-richards/tiny-cbor/read.go:211.2,211.62 1 1 +github.com/alex-richards/tiny-cbor/read.go:221.9,222.62 1 1 +github.com/alex-richards/tiny-cbor/read.go:222.62,224.3 1 0 +github.com/alex-richards/tiny-cbor/read.go:226.2,229.16 3 1 +github.com/alex-richards/tiny-cbor/read.go:229.16,231.3 1 0 +github.com/alex-richards/tiny-cbor/read.go:233.2,233.16 1 1 +github.com/alex-richards/tiny-cbor/read.go:233.16,234.7 1 1 +github.com/alex-richards/tiny-cbor/read.go:234.7,236.18 2 1 +github.com/alex-richards/tiny-cbor/read.go:236.18,238.5 1 0 +github.com/alex-richards/tiny-cbor/read.go:240.4,240.63 1 1 +github.com/alex-richards/tiny-cbor/read.go:240.63,241.10 1 1 +github.com/alex-richards/tiny-cbor/read.go:244.4,244.64 1 1 +github.com/alex-richards/tiny-cbor/read.go:244.64,246.5 1 0 +github.com/alex-richards/tiny-cbor/read.go:248.4,248.28 1 1 +github.com/alex-richards/tiny-cbor/read.go:248.28,250.5 1 0 +github.com/alex-richards/tiny-cbor/read.go:252.4,253.18 2 1 +github.com/alex-richards/tiny-cbor/read.go:253.18,255.5 1 0 +github.com/alex-richards/tiny-cbor/read.go:257.8,259.17 2 1 +github.com/alex-richards/tiny-cbor/read.go:259.17,261.4 1 0 +github.com/alex-richards/tiny-cbor/read.go:264.2,264.12 1 1 +github.com/alex-richards/tiny-cbor/read.go:271.9,276.17 5 1 +github.com/alex-richards/tiny-cbor/read.go:276.17,280.13 4 1 +github.com/alex-richards/tiny-cbor/read.go:280.13,282.4 1 0 +github.com/alex-richards/tiny-cbor/read.go:283.3,284.13 2 1 +github.com/alex-richards/tiny-cbor/read.go:284.13,286.4 1 0 +github.com/alex-richards/tiny-cbor/read.go:288.3,288.22 1 1 +github.com/alex-richards/tiny-cbor/read.go:291.2,291.12 1 1 +github.com/alex-richards/tiny-cbor/read.go:298.9,300.16 2 1 +github.com/alex-richards/tiny-cbor/read.go:300.16,302.3 1 0 +github.com/alex-richards/tiny-cbor/read.go:304.2,304.67 1 1 +github.com/alex-richards/tiny-cbor/read.go:314.9,315.33 1 1 +github.com/alex-richards/tiny-cbor/read.go:315.33,317.3 1 0 +github.com/alex-richards/tiny-cbor/read.go:319.2,322.16 3 1 +github.com/alex-richards/tiny-cbor/read.go:322.16,324.3 1 0 +github.com/alex-richards/tiny-cbor/read.go:326.2,326.16 1 1 +github.com/alex-richards/tiny-cbor/read.go:326.16,328.7 2 1 +github.com/alex-richards/tiny-cbor/read.go:328.7,330.18 2 1 +github.com/alex-richards/tiny-cbor/read.go:330.18,332.5 1 0 +github.com/alex-richards/tiny-cbor/read.go:333.4,333.23 1 1 +github.com/alex-richards/tiny-cbor/read.go:333.23,334.10 1 1 +github.com/alex-richards/tiny-cbor/read.go:337.4,338.18 2 1 +github.com/alex-richards/tiny-cbor/read.go:338.18,340.5 1 0 +github.com/alex-richards/tiny-cbor/read.go:342.8,343.38 1 1 +github.com/alex-richards/tiny-cbor/read.go:343.38,345.18 2 1 +github.com/alex-richards/tiny-cbor/read.go:345.18,347.5 1 0 +github.com/alex-richards/tiny-cbor/read.go:350.2,350.12 1 1 +github.com/alex-richards/tiny-cbor/read.go:357.9,359.16 2 1 +github.com/alex-richards/tiny-cbor/read.go:359.16,361.3 1 0 +github.com/alex-richards/tiny-cbor/read.go:363.2,363.69 1 1 +github.com/alex-richards/tiny-cbor/read.go:373.9,374.31 1 1 +github.com/alex-richards/tiny-cbor/read.go:374.31,376.3 1 0 +github.com/alex-richards/tiny-cbor/read.go:378.2,381.16 3 1 +github.com/alex-richards/tiny-cbor/read.go:381.16,383.3 1 0 +github.com/alex-richards/tiny-cbor/read.go:385.2,385.16 1 1 +github.com/alex-richards/tiny-cbor/read.go:385.16,387.7 2 0 +github.com/alex-richards/tiny-cbor/read.go:387.7,389.18 2 0 +github.com/alex-richards/tiny-cbor/read.go:389.18,391.5 1 0 +github.com/alex-richards/tiny-cbor/read.go:392.4,392.23 1 0 +github.com/alex-richards/tiny-cbor/read.go:392.23,393.10 1 0 +github.com/alex-richards/tiny-cbor/read.go:396.4,397.18 2 0 +github.com/alex-richards/tiny-cbor/read.go:397.18,399.5 1 0 +github.com/alex-richards/tiny-cbor/read.go:401.8,402.38 1 1 +github.com/alex-richards/tiny-cbor/read.go:402.38,404.18 2 1 +github.com/alex-richards/tiny-cbor/read.go:404.18,406.5 1 0 +github.com/alex-richards/tiny-cbor/read.go:410.2,410.12 1 1 +github.com/alex-richards/tiny-cbor/read_any.go:13.41,15.16 2 1 +github.com/alex-richards/tiny-cbor/read_any.go:15.16,17.3 1 0 +github.com/alex-richards/tiny-cbor/read_any.go:19.2,19.43 1 1 +github.com/alex-richards/tiny-cbor/read_any.go:22.85,24.19 2 1 +github.com/alex-richards/tiny-cbor/read_any.go:25.21,26.10 1 1 +github.com/alex-richards/tiny-cbor/read_any.go:27.20,28.53 1 1 +github.com/alex-richards/tiny-cbor/read_any.go:29.21,30.54 1 1 +github.com/alex-richards/tiny-cbor/read_any.go:31.21,32.54 1 1 +github.com/alex-richards/tiny-cbor/read_any.go:33.21,34.54 1 1 +github.com/alex-richards/tiny-cbor/read_any.go:35.11,36.32 1 0 +github.com/alex-richards/tiny-cbor/read_any.go:39.21,40.10 1 1 +github.com/alex-richards/tiny-cbor/read_any.go:41.20,42.50 1 1 +github.com/alex-richards/tiny-cbor/read_any.go:43.21,44.51 1 1 +github.com/alex-richards/tiny-cbor/read_any.go:45.21,46.51 1 0 +github.com/alex-richards/tiny-cbor/read_any.go:47.21,48.51 1 1 +github.com/alex-richards/tiny-cbor/read_any.go:49.11,50.32 1 0 +github.com/alex-richards/tiny-cbor/read_any.go:53.21,56.47 2 1 +github.com/alex-richards/tiny-cbor/read_any.go:56.47,59.5 2 1 +github.com/alex-richards/tiny-cbor/read_any.go:62.3,62.17 1 1 +github.com/alex-richards/tiny-cbor/read_any.go:62.17,64.4 1 0 +github.com/alex-richards/tiny-cbor/read_any.go:65.3,65.24 1 1 +github.com/alex-richards/tiny-cbor/read_any.go:67.21,70.47 2 1 +github.com/alex-richards/tiny-cbor/read_any.go:70.47,73.5 2 1 +github.com/alex-richards/tiny-cbor/read_any.go:76.3,76.17 1 1 +github.com/alex-richards/tiny-cbor/read_any.go:76.17,78.4 1 0 +github.com/alex-richards/tiny-cbor/read_any.go:79.3,79.32 1 1 +github.com/alex-richards/tiny-cbor/read_any.go:81.22,83.27 2 1 +github.com/alex-richards/tiny-cbor/read_any.go:83.27,84.8 1 1 +github.com/alex-richards/tiny-cbor/read_any.go:84.8,86.19 2 1 +github.com/alex-richards/tiny-cbor/read_any.go:86.19,88.6 1 0 +github.com/alex-richards/tiny-cbor/read_any.go:90.5,90.64 1 1 +github.com/alex-richards/tiny-cbor/read_any.go:90.64,91.11 1 1 +github.com/alex-richards/tiny-cbor/read_any.go:94.5,95.19 2 1 +github.com/alex-richards/tiny-cbor/read_any.go:95.19,97.6 1 0 +github.com/alex-richards/tiny-cbor/read_any.go:98.5,98.21 1 1 +github.com/alex-richards/tiny-cbor/read_any.go:100.9,101.39 1 1 +github.com/alex-richards/tiny-cbor/read_any.go:101.39,103.19 2 1 +github.com/alex-richards/tiny-cbor/read_any.go:103.19,105.6 1 0 +github.com/alex-richards/tiny-cbor/read_any.go:106.5,106.13 1 1 +github.com/alex-richards/tiny-cbor/read_any.go:109.3,109.16 1 1 +github.com/alex-richards/tiny-cbor/read_any.go:111.20,113.27 2 1 +github.com/alex-richards/tiny-cbor/read_any.go:113.27,114.8 1 1 +github.com/alex-richards/tiny-cbor/read_any.go:114.8,116.19 2 1 +github.com/alex-richards/tiny-cbor/read_any.go:116.19,118.6 1 0 +github.com/alex-richards/tiny-cbor/read_any.go:120.5,120.64 1 1 +github.com/alex-richards/tiny-cbor/read_any.go:120.64,121.11 1 1 +github.com/alex-richards/tiny-cbor/read_any.go:124.5,125.19 2 1 +github.com/alex-richards/tiny-cbor/read_any.go:125.19,127.6 1 0 +github.com/alex-richards/tiny-cbor/read_any.go:128.5,129.19 2 1 +github.com/alex-richards/tiny-cbor/read_any.go:129.19,131.6 1 0 +github.com/alex-richards/tiny-cbor/read_any.go:132.5,132.13 1 1 +github.com/alex-richards/tiny-cbor/read_any.go:134.9,135.39 1 1 +github.com/alex-richards/tiny-cbor/read_any.go:135.39,137.19 2 1 +github.com/alex-richards/tiny-cbor/read_any.go:137.19,139.6 1 0 +github.com/alex-richards/tiny-cbor/read_any.go:140.5,141.19 2 1 +github.com/alex-richards/tiny-cbor/read_any.go:141.19,143.6 1 0 +github.com/alex-richards/tiny-cbor/read_any.go:144.5,144.13 1 1 +github.com/alex-richards/tiny-cbor/read_any.go:147.3,147.16 1 1 +github.com/alex-richards/tiny-cbor/read_any.go:149.23,150.21 1 1 +github.com/alex-richards/tiny-cbor/read_any.go:152.10,153.10 1 1 +github.com/alex-richards/tiny-cbor/read_any.go:154.48,155.53 1 1 +github.com/alex-richards/tiny-cbor/read_any.go:156.74,157.37 1 1 +github.com/alex-richards/tiny-cbor/read_any.go:158.70,159.19 1 1 +github.com/alex-richards/tiny-cbor/read_any.go:160.27,161.53 1 1 +github.com/alex-richards/tiny-cbor/read_any.go:162.29,163.52 1 1 +github.com/alex-richards/tiny-cbor/read_any.go:164.29,165.52 1 1 +github.com/alex-richards/tiny-cbor/read_any.go:166.29,167.52 1 1 +github.com/alex-richards/tiny-cbor/read_any.go:168.11,169.32 1 0 +github.com/alex-richards/tiny-cbor/read_over.go:6.35,8.16 2 1 +github.com/alex-richards/tiny-cbor/read_over.go:8.16,10.3 1 0 +github.com/alex-richards/tiny-cbor/read_over.go:12.2,12.44 1 1 +github.com/alex-richards/tiny-cbor/read_over.go:16.79,17.19 1 1 +github.com/alex-richards/tiny-cbor/read_over.go:20.24,21.13 1 1 +github.com/alex-richards/tiny-cbor/read_over.go:24.17,25.27 1 1 +github.com/alex-richards/tiny-cbor/read_over.go:25.27,26.8 1 1 +github.com/alex-richards/tiny-cbor/read_over.go:26.8,28.19 2 1 +github.com/alex-richards/tiny-cbor/read_over.go:28.19,30.6 1 0 +github.com/alex-richards/tiny-cbor/read_over.go:32.5,32.64 1 1 +github.com/alex-richards/tiny-cbor/read_over.go:32.64,33.11 1 1 +github.com/alex-richards/tiny-cbor/read_over.go:36.5,36.19 1 1 +github.com/alex-richards/tiny-cbor/read_over.go:36.19,39.16 3 1 +github.com/alex-richards/tiny-cbor/read_over.go:39.16,41.7 1 0 +github.com/alex-richards/tiny-cbor/read_over.go:42.6,42.24 1 1 +github.com/alex-richards/tiny-cbor/read_over.go:45.4,45.14 1 1 +github.com/alex-richards/tiny-cbor/read_over.go:46.9,47.18 1 1 +github.com/alex-richards/tiny-cbor/read_over.go:47.18,50.15 3 1 +github.com/alex-richards/tiny-cbor/read_over.go:50.15,52.6 1 0 +github.com/alex-richards/tiny-cbor/read_over.go:53.5,53.23 1 1 +github.com/alex-richards/tiny-cbor/read_over.go:55.4,55.14 1 1 +github.com/alex-richards/tiny-cbor/read_over.go:58.22,59.27 1 1 +github.com/alex-richards/tiny-cbor/read_over.go:59.27,60.8 1 1 +github.com/alex-richards/tiny-cbor/read_over.go:60.8,62.19 2 1 +github.com/alex-richards/tiny-cbor/read_over.go:62.19,64.6 1 0 +github.com/alex-richards/tiny-cbor/read_over.go:66.5,66.64 1 1 +github.com/alex-richards/tiny-cbor/read_over.go:66.64,67.11 1 1 +github.com/alex-richards/tiny-cbor/read_over.go:70.5,70.62 1 1 +github.com/alex-richards/tiny-cbor/read_over.go:70.62,72.6 1 0 +github.com/alex-richards/tiny-cbor/read_over.go:74.4,74.14 1 1 +github.com/alex-richards/tiny-cbor/read_over.go:75.9,76.39 1 1 +github.com/alex-richards/tiny-cbor/read_over.go:76.39,77.40 1 1 +github.com/alex-richards/tiny-cbor/read_over.go:77.40,79.6 1 0 +github.com/alex-richards/tiny-cbor/read_over.go:81.4,81.14 1 1 +github.com/alex-richards/tiny-cbor/read_over.go:84.20,85.27 1 1 +github.com/alex-richards/tiny-cbor/read_over.go:85.27,86.8 1 1 +github.com/alex-richards/tiny-cbor/read_over.go:86.8,88.19 2 1 +github.com/alex-richards/tiny-cbor/read_over.go:88.19,90.6 1 0 +github.com/alex-richards/tiny-cbor/read_over.go:92.5,92.64 1 1 +github.com/alex-richards/tiny-cbor/read_over.go:92.64,93.11 1 1 +github.com/alex-richards/tiny-cbor/read_over.go:96.5,96.62 1 1 +github.com/alex-richards/tiny-cbor/read_over.go:96.62,98.6 1 0 +github.com/alex-richards/tiny-cbor/read_over.go:99.5,99.39 1 1 +github.com/alex-richards/tiny-cbor/read_over.go:99.39,101.6 1 0 +github.com/alex-richards/tiny-cbor/read_over.go:103.4,103.14 1 1 +github.com/alex-richards/tiny-cbor/read_over.go:104.9,105.39 1 1 +github.com/alex-richards/tiny-cbor/read_over.go:105.39,106.40 1 1 +github.com/alex-richards/tiny-cbor/read_over.go:106.40,108.6 1 0 +github.com/alex-richards/tiny-cbor/read_over.go:109.5,109.40 1 1 +github.com/alex-richards/tiny-cbor/read_over.go:109.40,111.6 1 0 +github.com/alex-richards/tiny-cbor/read_over.go:113.4,113.14 1 1 +github.com/alex-richards/tiny-cbor/read_over.go:116.23,117.22 1 1 +github.com/alex-richards/tiny-cbor/read_over.go:119.10,120.33 1 0 +github.com/alex-richards/tiny-cbor/read_raw.go:10.9,12.12 2 1 +github.com/alex-richards/tiny-cbor/read_raw.go:12.12,14.3 1 0 +github.com/alex-richards/tiny-cbor/read_raw.go:16.2,18.16 3 1 +github.com/alex-richards/tiny-cbor/read_raw.go:18.16,20.3 1 0 +github.com/alex-richards/tiny-cbor/read_raw.go:22.2,23.11 2 1 +github.com/alex-richards/tiny-cbor/read_raw.go:23.11,25.18 2 1 +github.com/alex-richards/tiny-cbor/read_raw.go:25.18,27.4 1 0 +github.com/alex-richards/tiny-cbor/read_raw.go:28.3,28.49 1 1 +github.com/alex-richards/tiny-cbor/read_raw.go:31.2,32.18 2 1 +github.com/alex-richards/tiny-cbor/read_raw.go:32.18,34.3 1 0 +github.com/alex-richards/tiny-cbor/read_raw.go:36.2,36.19 1 1 +github.com/alex-richards/tiny-cbor/read_raw.go:37.58,38.13 1 1 +github.com/alex-richards/tiny-cbor/read_raw.go:40.36,41.27 1 1 +github.com/alex-richards/tiny-cbor/read_raw.go:41.27,43.8 2 1 +github.com/alex-richards/tiny-cbor/read_raw.go:43.8,45.19 2 1 +github.com/alex-richards/tiny-cbor/read_raw.go:45.19,47.6 1 0 +github.com/alex-richards/tiny-cbor/read_raw.go:49.5,49.24 1 1 +github.com/alex-richards/tiny-cbor/read_raw.go:49.24,51.11 2 1 +github.com/alex-richards/tiny-cbor/read_raw.go:54.5,55.19 2 1 +github.com/alex-richards/tiny-cbor/read_raw.go:55.19,57.6 1 0 +github.com/alex-richards/tiny-cbor/read_raw.go:59.4,59.14 1 1 +github.com/alex-richards/tiny-cbor/read_raw.go:60.9,62.48 1 1 +github.com/alex-richards/tiny-cbor/read_raw.go:62.48,62.62 1 1 +github.com/alex-richards/tiny-cbor/read_raw.go:67.22,68.27 1 1 +github.com/alex-richards/tiny-cbor/read_raw.go:68.27,70.8 2 1 +github.com/alex-richards/tiny-cbor/read_raw.go:70.8,72.19 2 1 +github.com/alex-richards/tiny-cbor/read_raw.go:72.19,74.6 1 0 +github.com/alex-richards/tiny-cbor/read_raw.go:76.5,76.24 1 1 +github.com/alex-richards/tiny-cbor/read_raw.go:76.24,78.11 2 1 +github.com/alex-richards/tiny-cbor/read_raw.go:81.5,82.19 2 1 +github.com/alex-richards/tiny-cbor/read_raw.go:82.19,84.6 1 0 +github.com/alex-richards/tiny-cbor/read_raw.go:86.4,86.14 1 1 +github.com/alex-richards/tiny-cbor/read_raw.go:87.9,88.16 1 1 +github.com/alex-richards/tiny-cbor/read_raw.go:88.16,90.19 2 1 +github.com/alex-richards/tiny-cbor/read_raw.go:90.19,92.6 1 0 +github.com/alex-richards/tiny-cbor/read_raw.go:94.4,94.14 1 1 +github.com/alex-richards/tiny-cbor/read_raw.go:97.20,98.27 1 1 +github.com/alex-richards/tiny-cbor/read_raw.go:98.27,100.8 2 1 +github.com/alex-richards/tiny-cbor/read_raw.go:100.8,102.19 2 1 +github.com/alex-richards/tiny-cbor/read_raw.go:102.19,104.6 1 0 +github.com/alex-richards/tiny-cbor/read_raw.go:106.5,106.24 1 1 +github.com/alex-richards/tiny-cbor/read_raw.go:106.24,108.11 2 1 +github.com/alex-richards/tiny-cbor/read_raw.go:111.5,111.17 1 1 +github.com/alex-richards/tiny-cbor/read_raw.go:111.17,113.20 2 1 +github.com/alex-richards/tiny-cbor/read_raw.go:113.20,115.7 1 0 +github.com/alex-richards/tiny-cbor/read_raw.go:118.4,118.14 1 1 +github.com/alex-richards/tiny-cbor/read_raw.go:119.9,120.16 1 1 +github.com/alex-richards/tiny-cbor/read_raw.go:120.16,121.17 1 1 +github.com/alex-richards/tiny-cbor/read_raw.go:121.17,123.20 2 1 +github.com/alex-richards/tiny-cbor/read_raw.go:123.20,125.7 1 0 +github.com/alex-richards/tiny-cbor/read_raw.go:128.4,128.14 1 1 +github.com/alex-richards/tiny-cbor/read_raw.go:131.10,132.26 1 1 +github.com/alex-richards/tiny-cbor/write.go:11.84,14.26 2 1 +github.com/alex-richards/tiny-cbor/write.go:14.26,17.3 2 1 +github.com/alex-richards/tiny-cbor/write.go:19.2,20.9 2 1 +github.com/alex-richards/tiny-cbor/write.go:21.22,23.8 2 1 +github.com/alex-richards/tiny-cbor/write.go:24.25,26.8 2 1 +github.com/alex-richards/tiny-cbor/write.go:27.31,29.8 2 1 +github.com/alex-richards/tiny-cbor/write.go:30.10,32.8 2 1 +github.com/alex-richards/tiny-cbor/write.go:35.2,36.41 2 1 +github.com/alex-richards/tiny-cbor/write.go:39.93,41.2 1 1 +github.com/alex-richards/tiny-cbor/write.go:43.87,44.16 1 1 +github.com/alex-richards/tiny-cbor/write.go:44.16,46.3 1 1 +github.com/alex-richards/tiny-cbor/write.go:47.2,47.61 1 1 +github.com/alex-richards/tiny-cbor/write.go:50.93,53.11 2 1 +github.com/alex-richards/tiny-cbor/write.go:54.9,58.38 4 1 +github.com/alex-richards/tiny-cbor/write.go:60.9,62.64 2 1 +github.com/alex-richards/tiny-cbor/write.go:62.64,64.4 1 1 +github.com/alex-richards/tiny-cbor/write.go:66.3,68.38 3 1 +github.com/alex-richards/tiny-cbor/write.go:70.9,73.24 3 1 +github.com/alex-richards/tiny-cbor/write.go:73.24,75.4 1 1 +github.com/alex-richards/tiny-cbor/write.go:77.3,79.38 3 1 +github.com/alex-richards/tiny-cbor/write.go:81.10,82.35 1 0 +github.com/alex-richards/tiny-cbor/write.go:86.56,87.11 1 1 +github.com/alex-richards/tiny-cbor/write.go:87.11,89.3 1 1 +github.com/alex-richards/tiny-cbor/write.go:90.2,90.71 1 1 +github.com/alex-richards/tiny-cbor/write.go:93.57,95.2 1 1 +github.com/alex-richards/tiny-cbor/write.go:97.59,101.16 4 1 +github.com/alex-richards/tiny-cbor/write.go:101.16,103.3 1 0 +github.com/alex-richards/tiny-cbor/write.go:104.2,106.16 3 1 +github.com/alex-richards/tiny-cbor/write.go:109.60,113.16 4 1 +github.com/alex-richards/tiny-cbor/write.go:113.16,115.3 1 0 +github.com/alex-richards/tiny-cbor/write.go:116.2,118.16 3 1 +github.com/alex-richards/tiny-cbor/write.go:121.66,123.2 1 1 +github.com/alex-richards/tiny-cbor/write.go:125.64,127.2 1 1 diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..e995700 --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module github.com/alex-richards/tiny-cbor + +go 1.22.6 + +require github.com/x448/float16 v0.8.4 + +require ( + github.com/fxamacker/cbor/v2 v2.7.0 // test + github.com/google/go-cmp v0.6.0 // test +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..978c159 --- /dev/null +++ b/go.sum @@ -0,0 +1,6 @@ +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= diff --git a/io.go b/io.go new file mode 100644 index 0000000..d1db1c9 --- /dev/null +++ b/io.go @@ -0,0 +1,46 @@ +package cbor + +import "io" + +type peekReader struct { + r io.Reader // wrapped reader + p byte // peeked byte + pv bool // peeded valid +} + +func (r *peekReader) Read(out []byte) (int, error) { + ol := len(out) + if ol == 0 { + return 0, nil + } + + if r.pv { + out[0] = r.p + r.pv = false + if ol == 1 { + return 1, nil + } + + n, err := r.r.Read(out[1:ol]) + return n + 1, err + } + + return r.r.Read(out) +} + +func (r *peekReader) PeekByte() (byte, error) { + if r.pv { + return r.p, nil + } + + var pb = []byte{0} + n, err := r.r.Read(pb) + + if n != 1 { + return 0, err + } + + r.p = pb[0] + r.pv = true + return r.p, nil +} diff --git a/io_test.go b/io_test.go new file mode 100644 index 0000000..e261302 --- /dev/null +++ b/io_test.go @@ -0,0 +1,53 @@ +package cbor + +import ( + "bytes" + "testing" + + "github.com/google/go-cmp/cmp" +) + +func Test_peekReader(t *testing.T) { + t.Run("Read", func(t *testing.T) { + in := []byte{1, 2, 3, 4, 5, 6} + out := make([]byte, len(in)) + + reader := peekReader{r: bytes.NewReader(in)} + + _, err := reader.Read(out) + if err != nil { + t.Fatal(err) + } + + if diff := cmp.Diff(in, out); diff != "" { + t.Fatal(diff) + } + }) + + t.Run("Read Peak", func(t *testing.T) { + in := []byte{1, 2, 3, 4, 5, 6} + out := make([]byte, len(in)) + + reader := peekReader{r: bytes.NewReader(in)} + + n, err := reader.Read(out[:3]) + if n != 3 { + t.Log(out) + t.Fatal(err) + } + + p, err := reader.PeekByte() + if p != 4 { + t.Fatal("unexpected value") + } + + n, err = reader.Read(out[3:]) + if n != 3 { + t.Fatal(err) + } + + if diff := cmp.Diff(in, out); diff != "" { + t.Fatal(diff) + } + }) +} diff --git a/numbers.go b/numbers.go new file mode 100644 index 0000000..ffa2ea4 --- /dev/null +++ b/numbers.go @@ -0,0 +1,16 @@ +package cbor + +func shiftBytesInto[T int8 | int16 | int32 | int64 | uint8 | uint16 | uint32 | uint64](b []byte) (o T) { + n := len(b) + for i := range n { + o |= T(b[i]) << (8 * (n - (i + 1))) + } + return +} + +func shiftBytesFrom[T int8 | int16 | int32 | int64 | uint8 | uint16 | uint32 | uint64](v T, b []byte) { + n := len(b) + for i := range n { + b[i] = byte(v >> (8 * ((n - i) - 1))) + } +} diff --git a/numbers_test.go b/numbers_test.go new file mode 100644 index 0000000..d9076f9 --- /dev/null +++ b/numbers_test.go @@ -0,0 +1,63 @@ +package cbor + +import ( + "encoding/hex" + "testing" + + "github.com/google/go-cmp/cmp" +) + +func Test_shiftBytesInto(t *testing.T) { + tests := []struct { + got string + want uint64 + }{ + { + got: "00", + want: 0x00, + }, + { + got: "0102030405060708", + want: 0x0102030405060708, + }, + { + got: "01020304050607080910", + want: 0x0304050607080910, + }, + } + + for _, tt := range tests { + t.Run(tt.got, func(t *testing.T) { + got := shiftBytesInto[uint64](decodeHex(t, tt.got)) + if diff := cmp.Diff(tt.want, got); diff != "" { + t.Fatal(diff) + } + }) + } +} + +func Test_shiftBytesFrom(t *testing.T) { + tests := []struct { + got uint64 + want string + }{ + { + got: 0x00, + want: "0000000000000000", + }, + { + got: 0x0102030405060708, + want: "0102030405060708", + }, + } + + for _, tt := range tests { + t.Run(tt.want, func(t *testing.T) { + got := make([]byte, 8) + shiftBytesFrom(tt.got, got) + if diff := cmp.Diff(tt.want, hex.EncodeToString(got)); diff != "" { + t.Fatal(diff) + } + }) + } +} diff --git a/read.go b/read.go new file mode 100644 index 0000000..e9338aa --- /dev/null +++ b/read.go @@ -0,0 +1,446 @@ +package cbor + +import ( + "github.com/x448/float16" + "io" + "math" +) + +// readMajorType reads the major type and any header arguments from [in]. +func readMajorType(in io.Reader) (MajorType, Arg, uint64, error) { + b := sharedBuffer[:1] + n, err := in.Read(b) + if n != 1 { + return 0, 0, 0, err + } + + majorType, arg := decodePrefix(b[0]) + arg, l, v, err := decodeArg(arg) + if err != nil { + return 0, 0, 0, err + } + + if l > 0 { + b = sharedBuffer[1 : 1+l] + n, err = in.Read(b) + if n != int(l) { + return 0, 0, 0, err + } + v = shiftBytesInto[uint64](b) + } + + return majorType, arg, v, nil +} + +// decodePrefix returns the major type, argument, and the length, in bytes, of +// the remaining part of the header if any. +func decodePrefix(p byte) (MajorType, Arg) { + majorType := MajorType(p & majorTypeMask) + arg := Arg(p & argMask) + return majorType, arg +} + +func decodeArg(arg Arg) (Arg, uint8, uint64, error) { + switch { + case arg < Arg8: + return 0, 0, uint64(arg), nil + case arg == Arg8: + return arg, 1, 0, nil + case arg == Arg16: + return arg, 2, 0, nil + case arg == Arg32: + return arg, 4, 0, nil + case arg == Arg64: + return arg, 8, 0, nil + case arg == ArgIndefinite: + return arg, 0, 0, nil + default: + return 0, 0, 0, ErrNotWellFormed + } +} + +// ReadTag reads the next object from [in] as a tag and returns the value. +func ReadTag(in io.Reader) (uint64, error) { + majorType, _, value, err := readMajorType(in) + if err != nil { + return 0, err + } + + if majorType != MajorTypeTagged { + return 0, ErrUnsupportedMajorType + } + + return value, nil +} + +func ReadUnsigned[T uint8 | uint16 | uint32 | uint64](in io.Reader) (T, error) { + majorType, arg, value, err := readMajorType(in) + if err != nil { + return 0, err + } + + return readUnsigned[T](majorType, arg, value) +} + +func readUnsigned[T uint8 | uint16 | uint32 | uint64](majorType MajorType, arg Arg, value uint64) (T, error) { + if majorType == MajorTypeUInt || + (majorType == MajorTypeSimpleFloat && arg == 0 && value < uint64(SimpleFalse)) || + (majorType == MajorTypeSimpleFloat && arg == SimpleUint8) { + + switch any(T(0)).(type) { + case uint8: + if (value & 0xffffffffffffff00) != 0 { + return 0, ErrOverflow + } + case uint16: + if (value & 0xffffffffffff0000) != 0 { + return 0, ErrOverflow + } + case uint32: + if (value & 0xffffffff00000000) != 0 { + return 0, ErrOverflow + } + case uint64: + // noop + default: + panic("unreachable") + } + + return T(value), nil + } + + return 0, ErrUnsupportedMajorType +} + +func ReadSigned[T int8 | int16 | int32 | int64](in io.Reader) (T, error) { + majorType, arg, value, err := readMajorType(in) + if err != nil { + return 0, err + } + + return readSigned[T](majorType, arg, value) +} + +func readSigned[T int8 | int16 | int32 | int64](majorType MajorType, arg Arg, value uint64) (T, error) { + if majorType == MajorTypeUInt || + majorType == MajorTypeNInt || + (majorType == MajorTypeSimpleFloat && arg == 0 && value < uint64(SimpleFalse)) || + (majorType == MajorTypeSimpleFloat && arg == SimpleUint8) { + + switch any(T(0)).(type) { + case int8: + if (value & 0xffffffffffffff80) != 0 { + return 0, ErrOverflow + } + case int16: + if (value & 0xffffffffffff8000) != 0 { + return 0, ErrOverflow + } + case int32: + if (value & 0xffffffff80000000) != 0 { + return 0, ErrOverflow + } + case int64: + if (value & 0x8000000000000000) != 0 { + return 0, ErrOverflow + } + default: + panic("unreachable") + } + + if majorType == MajorTypeNInt { + return -1 - T(value), nil + } + + return T(value), nil + } + + return 0, ErrUnsupportedMajorType +} + +func ReadFloat[T float32 | float64](in io.Reader) (T, error) { + majorType, arg, value, err := readMajorType(in) + if err != nil { + return 0, err + } + + return readFloat[T](majorType, arg, value) +} + +func readFloat[T float32 | float64](majorType MajorType, arg Arg, value uint64) (T, error) { + if majorType == MajorTypeUInt || + (majorType == MajorTypeSimpleFloat && arg == SimpleUint8) { + return T(value), nil + } + + if majorType == MajorTypeNInt { + return -1 - T(value), nil + } + + if majorType == MajorTypeSimpleFloat { + if arg == SimpleFloat16 { + return T(float16.Frombits(uint16(value)).Float32()), nil + } + + if arg == SimpleFloat32 { + return T(math.Float32frombits(uint32(value))), nil + } + + if arg == SimpleFloat64 { + v := math.Float64frombits(value) + switch any(T(0)).(type) { + case float32: + v32 := T(v) + if v != float64(v32) { + return 0, ErrOverflow + } + return v32, nil + case float64: + return T(v), nil + default: + panic("unreachable") + } + } + + return 0, ErrUnsupportedValue + } + + return 0, ErrUnsupportedMajorType +} + +func ReadBool(in io.Reader) (bool, error) { + majorType, _, value, err := readMajorType(in) + if err != nil { + return false, err + } + + return readBool(majorType, value) +} + +func readBool(majorType MajorType, value uint64) (bool, error) { + if majorType == MajorTypeSimpleFloat { + if byte(value) == SimpleFalse { + return false, nil + } + + if byte(value) == SimpleTrue { + return true, nil + } + + return false, ErrUnsupportedValue + } + + return false, ErrUnsupportedMajorType +} + +func ReadBytes( + in io.Reader, + readLength func(indefinite bool, length uint64) error, + out io.Writer, +) error { + majorType, arg, value, err := readMajorType(in) + if err != nil { + return err + } + + return readBytes(in, majorType, arg, value, readLength, out) +} + +func readBytes( + in io.Reader, + majorType MajorType, + arg Arg, + value uint64, + readLength func(indefinite bool, length uint64) error, + out io.Writer, +) error { + if majorType != MajorTypeBstr && majorType != MajorTypeTstr { + return ErrUnsupportedMajorType + } + + indefinite := arg == ArgIndefinite + + err := readLength(indefinite, value) + if err != nil { + return err + } + + if indefinite { + for { + majorType, arg, value, err := readMajorType(in) + if err != nil { + return err + } + + if majorType == MajorTypeSimpleFloat && arg == SimpleBreak { + break + } + + if majorType != MajorTypeBstr && majorType != MajorTypeTstr { + return ErrUnsupportedMajorType + } + + if arg == ArgIndefinite { + return ErrNestedIndefinite + } + + err = readByteChunks(in, value, out) + if err != nil { + return err + } + } + } else { + err = readByteChunks(in, value, out) + if err != nil { + return err + } + } + + return nil +} + +func readByteChunks( + in io.Reader, + length uint64, + out io.Writer, +) error { + var l int + var b []byte + var n int + var err error + for length > 0 { + l = int(min(lenSharedBuffer, length)) + b = sharedBuffer[:l] + n, err = in.Read(b) + if n != l { + return err + } + n, err = out.Write(b[:n]) + if n != l { + return err + } + + length -= uint64(n) + } + + return nil +} + +func ReadArray( + in io.Reader, + readLength func(indefinite bool, length uint64) error, + readItem func(in io.Reader) error, +) error { + majorType, arg, value, err := readMajorType(in) + if err != nil { + return err + } + + return readArray(in, majorType, arg, value, readLength, readItem) +} + +func readArray( + in io.Reader, + majorType MajorType, + arg Arg, + value uint64, + readLength func(indefinite bool, length uint64) error, + readItem func(in io.Reader) error, +) error { + if majorType != MajorTypeArray { + return ErrUnsupportedMajorType + } + + indefinite := arg == ArgIndefinite + + err := readLength(indefinite, value) + if err != nil { + return err + } + + if indefinite { + pin := &peekReader{r: in} + for { + r, err := pin.PeekByte() + if err != nil { + return err + } + if r == valueBreak { + break + } + + err = readItem(pin) + if err != nil { + return err + } + } + } else { + for i := uint64(0); i < value; i++ { + err = readItem(in) + if err != nil { + return err + } + } + } + return nil +} + +func ReadMap( + in io.Reader, + readLength func(indefinite bool, length uint64) error, + readKeyValue func(in io.Reader) error, +) error { + majorType, arg, value, err := readMajorType(in) + if err != nil { + return err + } + + return readMap(in, majorType, arg, value, readLength, readKeyValue) +} + +func readMap( + in io.Reader, + majorType MajorType, + arg Arg, + value uint64, + readLength func(indefinite bool, length uint64) error, + readKeyValue func(in io.Reader) error, +) error { + if majorType != MajorTypeMap { + return ErrUnsupportedMajorType + } + + indefinite := arg == ArgIndefinite + + err := readLength(indefinite, value) + if err != nil { + return err + } + + if indefinite { + pin := &peekReader{r: in} + for { + r, err := pin.PeekByte() + if err != nil { + return err + } + if r == valueBreak { + break + } + + err = readKeyValue(pin) + if err != nil { + return err + } + } + } else { + for i := uint64(0); i < value; i++ { + err = readKeyValue(in) + if err != nil { + return err + } + } + } + + return nil +} diff --git a/read_any.go b/read_any.go new file mode 100644 index 0000000..8b74204 --- /dev/null +++ b/read_any.go @@ -0,0 +1,172 @@ +//go:build !cbor_no_readany + +package cbor + +import ( + "bytes" + "io" +) + +// ReadAny returns the next object form [in] regardless of type. +// Outputs can be any of int64, uint64, bool, []byte, string, []any, +// map[any]any, float32, float64, nil. +func ReadAny(in io.Reader) (any, error) { + majorType, arg, value, err := readMajorType(in) + if err != nil { + return 0, err + } + + return readAny(in, majorType, arg, value) +} + +func readAny(in io.Reader, majorType MajorType, arg Arg, value uint64) (any, error) { + var err error + switch majorType { + case MajorTypeUInt: + switch { + case arg <= Arg8: + return readUnsigned[uint8](majorType, arg, value) + case arg == Arg16: + return readUnsigned[uint16](majorType, arg, value) + case arg == Arg32: + return readUnsigned[uint32](majorType, arg, value) + case arg == Arg64: + return readUnsigned[uint64](majorType, arg, value) + default: + return nil, ErrNotWellFormed + } + + case MajorTypeNInt: + switch { + case arg <= Arg8: + return readSigned[int8](majorType, arg, value) + case arg == Arg16: + return readSigned[int16](majorType, arg, value) + case arg == Arg32: + return readSigned[int32](majorType, arg, value) + case arg == Arg64: + return readSigned[int64](majorType, arg, value) + default: + return nil, ErrNotWellFormed + } + + case MajorTypeBstr: + b := bytes.NewBuffer(nil) + err = readBytes(in, majorType, arg, value, + func(indefinite bool, length uint64) error { + b.Grow(int(length)) + return nil + }, + b, + ) + if err != nil { + return nil, err + } + return b.Bytes(), nil + + case MajorTypeTstr: + b := bytes.NewBuffer(nil) + err = readBytes(in, majorType, arg, value, + func(indefinite bool, length uint64) error { + b.Grow(int(length)) + return nil + }, + b, + ) + if err != nil { + return nil, err + } + return string(b.Bytes()), nil + + case MajorTypeArray: + a := make([]any, value) + if arg == ArgIndefinite { + for { + majorType, arg, value, err := readMajorType(in) + if err != nil { + return nil, err + } + + if majorType == MajorTypeSimpleFloat && arg == SimpleBreak { + break + } + + v, err := readAny(in, majorType, arg, value) + if err != nil { + return nil, err + } + a = append(a, v) + } + } else { + for i := uint64(0); i < value; i++ { + v, err := ReadAny(in) + if err != nil { + return nil, err + } + a[i] = v + } + } + return a, nil + + case MajorTypeMap: + m := make(map[any]any, value) + if arg == ArgIndefinite { + for { + majorType, arg, value, err := readMajorType(in) + if err != nil { + return nil, err + } + + if majorType == MajorTypeSimpleFloat && arg == SimpleBreak { + break + } + + k, err := readAny(in, majorType, arg, value) + if err != nil { + return nil, err + } + v, err := ReadAny(in) + if err != nil { + return nil, err + } + m[k] = v + } + } else { + for i := uint64(0); i < value; i++ { + k, err := ReadAny(in) + if err != nil { + return nil, err + } + v, err := ReadAny(in) + if err != nil { + return nil, err + } + m[k] = v + } + } + return m, nil + + case MajorTypeTagged: + return ReadAny(in) + + default: // MajorTypeSimpleFloat: + switch { + case arg == 0 && value < uint64(SimpleFalse): + return readUnsigned[uint8](majorType, arg, value) + case arg == 0 && (value == uint64(SimpleFalse) || value == SimpleTrue): + return readBool(majorType, value) + case arg == 0 && (value == SimpleNull || value == SimpleUndefined): + return nil, nil + case arg == SimpleUint8: + return readUnsigned[uint8](majorType, arg, value) + case arg == SimpleFloat16: + return readFloat[float32](majorType, arg, value) + case arg == SimpleFloat32: + return readFloat[float32](majorType, arg, value) + case arg == SimpleFloat64: + return readFloat[float64](majorType, arg, value) + default: // SimpleBreak + return nil, ErrNotWellFormed + } + } +} diff --git a/read_any_test.go b/read_any_test.go new file mode 100644 index 0000000..1ac6989 --- /dev/null +++ b/read_any_test.go @@ -0,0 +1,50 @@ +//go:build !cbor_no_readany + +package cbor + +import ( + "bytes" + "encoding/hex" + "io" + "testing" +) + +func Test_ReadAny(t *testing.T) { + for _, tt := range tests_ExampleEncoded { + t.Run(tt.encoded, func(t *testing.T) { + decoded := decodeHex(t, tt.encoded) + + in := bytes.NewReader(decoded) + + v, err := ReadAny(in) + if err != nil && err != ErrOverflow { + t.Fatal(err) + } + + n := in.Len() + if n != 0 { + rem := make([]byte, n) + in.Read(rem) + t.Fatalf("trailing data - %v - %s", v, hex.EncodeToString(rem)) + } + }) + } +} + +func Benchmark_ReadAny(b *testing.B) { + for _, tt := range tests_ExampleEncoded { + encoded := decodeHex(b, tt.encoded) + + in := bytes.NewReader(encoded) + + b.Run(tt.encoded, func(b *testing.B) { + for range b.N { + in.Seek(0, io.SeekStart) + _, err := ReadAny(in) + if err != nil { + b.Fatal(err) + } + } + }) + } +} diff --git a/read_over.go b/read_over.go new file mode 100644 index 0000000..bbf5115 --- /dev/null +++ b/read_over.go @@ -0,0 +1,122 @@ +package cbor + +import "io" + +// ReadOver skips the next object in [in]. +func ReadOver(in io.Reader) error { + majorType, arg, value, err := readMajorType(in) + if err != nil { + return err + } + + return readOver(in, majorType, arg, value) +} + +// readOver skips the next object in [in], starting from after the header. +func readOver(in io.Reader, majorType MajorType, arg Arg, value uint64) error { + switch majorType { + case MajorTypeUInt, + MajorTypeNInt, + MajorTypeSimpleFloat: + return nil + + case MajorTypeBstr, + MajorTypeTstr: + if arg == ArgIndefinite { + for { + majorType, arg, value, err := readMajorType(in) + if err != nil { + return err + } + + if majorType == MajorTypeSimpleFloat && arg == SimpleBreak { + break + } + + for value > 0 { + l := int(min(lenSharedBuffer, value)) + n, err := in.Read(sharedBuffer[:l]) + if n != l { + return err + } + value -= uint64(n) + } + } + return nil + } else { + for value > 0 { + l := int(min(lenSharedBuffer, value)) + n, err := in.Read(sharedBuffer[0:l]) + if n != l { + return err + } + value -= uint64(n) + } + return nil + } + + case MajorTypeArray: + if arg == ArgIndefinite { + for { + majorType, arg, value, err := readMajorType(in) + if err != nil { + return err + } + + if majorType == MajorTypeSimpleFloat && arg == SimpleBreak { + break + } + + if err = readOver(in, majorType, arg, value); err != nil { + return err + } + } + return nil + } else { + for i := uint64(0); i < value; i++ { + if err := ReadOver(in); err != nil { + return err + } + } + return nil + } + + case MajorTypeMap: + if arg == ArgIndefinite { + for { + majorType, arg, value, err := readMajorType(in) + if err != nil { + return err + } + + if majorType == MajorTypeSimpleFloat && arg == SimpleBreak { + break + } + + if err = readOver(in, majorType, arg, value); err != nil { + return err + } + if err = ReadOver(in); err != nil { + return err + } + } + return nil + } else { + for i := uint64(0); i < value; i++ { + if err := ReadOver(in); err != nil { + return err + } + if err := ReadOver(in); err != nil { + return err + } + } + return nil + } + + case MajorTypeTagged: + return ReadOver(in) + + default: + return ErrUnsupportedMajorType + } +} diff --git a/read_raw.go b/read_raw.go new file mode 100644 index 0000000..8b42e26 --- /dev/null +++ b/read_raw.go @@ -0,0 +1,134 @@ +package cbor + +import ( + "io" +) + +func ReadRaw( + in io.Reader, + out io.Writer, +) error { + n, err := in.Read(sharedBuffer[:1]) + if n != 1 { + return err + } + + majorType, arg := decodePrefix(sharedBuffer[0]) + arg, l, v, err := decodeArg(arg) + if err != nil { + return err + } + + ve := 1 + l + if l > 0 { + n, err = in.Read(sharedBuffer[1:ve]) + if n != int(l) { + return err + } + v = shiftBytesInto[uint64](sharedBuffer[1:ve]) + } + + n, err = out.Write(sharedBuffer[0:ve]) + if n != int(ve) { + return err + } + + switch majorType { + case MajorTypeUInt, MajorTypeNInt, MajorTypeSimpleFloat: + return nil + + case MajorTypeBstr, MajorTypeTstr: + if arg == ArgIndefinite { + pin := &peekReader{r: in} + for { + b, err := pin.PeekByte() + if err != nil { + return err + } + + if b == valueBreak { + out.Write([]byte{b}) + break + } + + err = ReadRaw(pin, out) + if err != nil { + return err + } + } + return nil + } else { + return readBytes(in, majorType, arg, v, + func(indefinite bool, length uint64) error { return nil }, + out, + ) + } + + case MajorTypeArray: + if arg == ArgIndefinite { + pin := &peekReader{r: in} + for { + b, err := pin.PeekByte() + if err != nil { + return err + } + + if b == valueBreak { + out.Write([]byte{b}) + break + } + + err = ReadRaw(pin, out) + if err != nil { + return err + } + } + return nil + } else { + for range v { + err = ReadRaw(in, out) + if err != nil { + return err + } + } + return nil + } + + case MajorTypeMap: + if arg == ArgIndefinite { + pin := &peekReader{r: in} + for { + b, err := pin.PeekByte() + if err != nil { + return err + } + + if b == valueBreak { + out.Write([]byte{b}) + break + } + + for range 2 { + err = ReadRaw(pin, out) + if err != nil { + return err + } + } + } + return nil + } else { + for range v { + for range 2 { + err = ReadRaw(in, out) + if err != nil { + return err + } + } + } + return nil + } + + default: // MajorTypeTagged + return ReadRaw(in, out) + } +} diff --git a/read_test.go b/read_test.go new file mode 100644 index 0000000..b4b9c08 --- /dev/null +++ b/read_test.go @@ -0,0 +1,774 @@ +package cbor + +import ( + "bytes" + "encoding/hex" + "io" + "math" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/x448/float16" +) + +func Test_ReadRaw(t *testing.T) { + for _, tt := range tests_ExampleEncoded { + t.Run(tt.encoded, func(t *testing.T) { + encoded := decodeHex(t, tt.encoded) + + in := bytes.NewReader(encoded) + out := bytes.NewBuffer(make([]byte, 0, 128)) + + err := ReadRaw(in, out) + if err != nil { + t.Fatal(err) + } + + if diff := cmp.Diff(encoded, out.Bytes()); diff != "" { + t.Fatal(diff) + } + + n := in.Len() + if n != 0 { + rem := make([]byte, n) + _, _ = in.Read(rem) + t.Fatalf("trailing data - %s", hex.EncodeToString(rem)) + } + }) + } +} + +func Benchmark_ReadRaw(b *testing.B) { + for _, tt := range tests_ExampleEncoded { + encoded := decodeHex(b, tt.encoded) + + in := bytes.NewReader(encoded) + out := bytes.NewBuffer(make([]byte, 0, 128)) + + b.Run(tt.encoded, func(b *testing.B) { + for range b.N { + _, _ = in.Seek(io.SeekStart, 0) + out.Reset() + err := ReadRaw(in, out) + if err != nil { + b.Fatal(err) + } + } + }) + } +} + +func Test_ReadOver(t *testing.T) { + for _, tt := range tests_ExampleEncoded { + t.Run(tt.encoded, func(t *testing.T) { + encoded := decodeHex(t, tt.encoded) + + in := bytes.NewReader(encoded) + + err := ReadOver(in) + if err != nil { + t.Fatal(err) + } + + n := in.Len() + if n != 0 { + rem := make([]byte, n) + _, _ = in.Read(rem) + t.Fatalf("trailing data - %s", hex.EncodeToString(rem)) + } + }) + } +} + +func Benchmark_ReadOver(b *testing.B) { + for _, tt := range tests_ExampleEncoded { + encoded := decodeHex(b, tt.encoded) + + in := bytes.NewReader(encoded) + + b.Run(tt.encoded, func(b *testing.B) { + for range b.N { + _, _ = in.Seek(0, io.SeekStart) + _ = ReadOver(in) + } + }) + } +} + +func Test_ReadNumbers(t *testing.T) { + tests := []struct { + encoded string + + wantInt8 int8 + wantInt8Error error + wantInt16 int16 + wantInt16Error error + wantInt32 int32 + wantInt32Error error + wantInt64 int64 + wantInt64Error error + + wantUint8 uint8 + wantUint8Error error + wantUint16 uint16 + wantUint16Error error + wantUint32 uint32 + wantUint32Error error + wantUint64 uint64 + wantUint64Error error + + wantFloat32 float32 + wantFloat32Error error + wantFloat64 float64 + wantFloat64Error error + }{ + { + encoded: "00", + wantInt8: 0x00, + wantInt16: 0x00, + wantInt32: 0x00, + wantInt64: 0x00, + wantUint8: 0x00, + wantUint16: 0x00, + wantUint32: 0x00, + wantUint64: 0x00, + wantFloat32: 0x00, + wantFloat64: 0x00, + }, + { + encoded: "01", + wantInt8: 0x01, + wantInt16: 0x01, + wantInt32: 0x01, + wantInt64: 0x01, + wantUint8: 0x01, + wantUint16: 0x01, + wantUint32: 0x01, + wantUint64: 0x01, + wantFloat32: 0x01, + wantFloat64: 0x01, + }, + { + encoded: "17", + wantInt8: 0x17, + wantInt16: 0x17, + wantInt32: 0x17, + wantInt64: 0x17, + wantUint8: 0x17, + wantUint16: 0x17, + wantUint32: 0x17, + wantUint64: 0x17, + wantFloat32: 0x17, + wantFloat64: 0x17, + }, + { + encoded: "1818", + wantInt8: 0x18, + wantInt16: 0x18, + wantInt32: 0x18, + wantInt64: 0x18, + wantUint8: 0x18, + wantUint16: 0x18, + wantUint32: 0x18, + wantUint64: 0x18, + wantFloat32: 0x18, + wantFloat64: 0x18, + }, + { + encoded: "18FF", + wantInt8Error: ErrOverflow, + wantInt16: 0xFF, + wantInt32: 0xFF, + wantInt64: 0xFF, + wantUint8: 0xFF, + wantUint16: 0xFF, + wantUint32: 0xFF, + wantUint64: 0xFF, + wantFloat32: 0xFF, + wantFloat64: 0xFF, + }, + { + encoded: "1901FF", + wantInt8Error: ErrOverflow, + wantInt16: 0x01FF, + wantInt32: 0x01FF, + wantInt64: 0x01FF, + wantUint8Error: ErrOverflow, + wantUint16: 0x01FF, + wantUint32: 0x01FF, + wantUint64: 0x01FF, + wantFloat32: 0x01FF, + wantFloat64: 0x01FF, + }, + { + encoded: "19FFFF", + wantInt8Error: ErrOverflow, + wantInt16Error: ErrOverflow, + wantInt32: 0xFFFF, + wantInt64: 0xFFFF, + wantUint8Error: ErrOverflow, + wantUint16: 0xFFFF, + wantUint32: 0xFFFF, + wantUint64: 0xFFFF, + wantFloat32: 0xFFFF, + wantFloat64: 0xFFFF, + }, + { + encoded: "1A0001FFFF", + wantInt8Error: ErrOverflow, + wantInt16Error: ErrOverflow, + wantInt32: 0x01FFFF, + wantInt64: 0x01FFFF, + wantUint8Error: ErrOverflow, + wantUint16Error: ErrOverflow, + wantUint32: 0x01FFFF, + wantUint64: 0x01FFFF, + wantFloat32: 0x01FFFF, + wantFloat64: 0x01FFFF, + }, + { + encoded: "1AFFFFFFFF", + wantInt8Error: ErrOverflow, + wantInt16Error: ErrOverflow, + wantInt32Error: ErrOverflow, + wantInt64: 0xFFFFFFFF, + wantUint8Error: ErrOverflow, + wantUint16Error: ErrOverflow, + wantUint32: 0xFFFFFFFF, + wantUint64: 0xFFFFFFFF, + wantFloat32: 0xFFFFFFFF, + wantFloat64: 0xFFFFFFFF, + }, + { + encoded: "1B00000001FFFFFFFF", + wantInt8Error: ErrOverflow, + wantInt16Error: ErrOverflow, + wantInt32Error: ErrOverflow, + wantInt64: 0x01FFFFFFFF, + wantUint8Error: ErrOverflow, + wantUint16Error: ErrOverflow, + wantUint32Error: ErrOverflow, + wantUint64: 0x01FFFFFFFF, + wantFloat32: 0x01FFFFFFFF, + wantFloat64: 0x01FFFFFFFF, + }, + { + encoded: "1BFFFFFFFFFFFFFFFF", + wantInt8Error: ErrOverflow, + wantInt16Error: ErrOverflow, + wantInt32Error: ErrOverflow, + wantInt64Error: ErrOverflow, + wantUint8Error: ErrOverflow, + wantUint16Error: ErrOverflow, + wantUint32Error: ErrOverflow, + wantUint64: 0xFFFFFFFFFFFFFFFF, + wantFloat32: 0xFFFFFFFFFFFFFFFF, + wantFloat64: 0xFFFFFFFFFFFFFFFF, + }, + // TODO negatives + { + encoded: "F800", + wantInt8: 0x00, + wantInt16: 0x00, + wantInt32: 0x00, + wantInt64: 0x00, + wantUint8: 0x00, + wantUint16: 0x00, + wantUint32: 0x00, + wantUint64: 0x00, + wantFloat32: 0x00, + wantFloat64: 0x00, + }, + { + encoded: "F8FF", + wantInt8Error: ErrOverflow, + wantInt16: 0xFF, + wantInt32: 0xFF, + wantInt64: 0xFF, + wantUint8: 0xFF, + wantUint16: 0xFF, + wantUint32: 0xFF, + wantUint64: 0xFF, + wantFloat32: 0xFF, + wantFloat64: 0xFF, + }, + { + encoded: "F90000", + wantInt8Error: ErrUnsupportedMajorType, + wantInt16Error: ErrUnsupportedMajorType, + wantInt32Error: ErrUnsupportedMajorType, + wantInt64Error: ErrUnsupportedMajorType, + wantUint8Error: ErrUnsupportedMajorType, + wantUint16Error: ErrUnsupportedMajorType, + wantUint32Error: ErrUnsupportedMajorType, + wantUint64Error: ErrUnsupportedMajorType, + wantFloat32: 0, + wantFloat64: 0, + }, + { + encoded: "F97BFF", // max + wantInt8Error: ErrUnsupportedMajorType, + wantInt16Error: ErrUnsupportedMajorType, + wantInt32Error: ErrUnsupportedMajorType, + wantInt64Error: ErrUnsupportedMajorType, + wantUint8Error: ErrUnsupportedMajorType, + wantUint16Error: ErrUnsupportedMajorType, + wantUint32Error: ErrUnsupportedMajorType, + wantUint64Error: ErrUnsupportedMajorType, + wantFloat32: float16.Frombits(0x7bFF).Float32(), + wantFloat64: float64(float16.Frombits(0x7bFF).Float32()), + }, + { + encoded: "F97C00", // +ve inf + wantInt8Error: ErrUnsupportedMajorType, + wantInt16Error: ErrUnsupportedMajorType, + wantInt32Error: ErrUnsupportedMajorType, + wantInt64Error: ErrUnsupportedMajorType, + wantUint8Error: ErrUnsupportedMajorType, + wantUint16Error: ErrUnsupportedMajorType, + wantUint32Error: ErrUnsupportedMajorType, + wantUint64Error: ErrUnsupportedMajorType, + wantFloat32: float32(math.Inf(1)), + wantFloat64: math.Inf(1), + }, + { + encoded: "F9FC00", // -ve inf + wantInt8Error: ErrUnsupportedMajorType, + wantInt16Error: ErrUnsupportedMajorType, + wantInt32Error: ErrUnsupportedMajorType, + wantInt64Error: ErrUnsupportedMajorType, + wantUint8Error: ErrUnsupportedMajorType, + wantUint16Error: ErrUnsupportedMajorType, + wantUint32Error: ErrUnsupportedMajorType, + wantUint64Error: ErrUnsupportedMajorType, + wantFloat32: float32(math.Inf(-1)), + wantFloat64: math.Inf(-1), + }, + { + encoded: "F90000", // 0 + wantInt8Error: ErrUnsupportedMajorType, + wantInt16Error: ErrUnsupportedMajorType, + wantInt32Error: ErrUnsupportedMajorType, + wantInt64Error: ErrUnsupportedMajorType, + wantUint8Error: ErrUnsupportedMajorType, + wantUint16Error: ErrUnsupportedMajorType, + wantUint32Error: ErrUnsupportedMajorType, + wantUint64Error: ErrUnsupportedMajorType, + wantFloat32: 0, + wantFloat64: 0, + }, + { + encoded: "F98000", // -0 + wantInt8Error: ErrUnsupportedMajorType, + wantInt16Error: ErrUnsupportedMajorType, + wantInt32Error: ErrUnsupportedMajorType, + wantInt64Error: ErrUnsupportedMajorType, + wantUint8Error: ErrUnsupportedMajorType, + wantUint16Error: ErrUnsupportedMajorType, + wantUint32Error: ErrUnsupportedMajorType, + wantUint64Error: ErrUnsupportedMajorType, + wantFloat32: -0, + wantFloat64: -0, + }, + { + encoded: "FA7F7FFFFF", // max + wantInt8Error: ErrUnsupportedMajorType, + wantInt16Error: ErrUnsupportedMajorType, + wantInt32Error: ErrUnsupportedMajorType, + wantInt64Error: ErrUnsupportedMajorType, + wantUint8Error: ErrUnsupportedMajorType, + wantUint16Error: ErrUnsupportedMajorType, + wantUint32Error: ErrUnsupportedMajorType, + wantUint64Error: ErrUnsupportedMajorType, + wantFloat32: math.Float32frombits(0x7F7FFFFF), + wantFloat64: float64(math.Float32frombits(0x7F7FFFFF)), + }, + { + encoded: "FA7F800000", // +ve inf + wantInt8Error: ErrUnsupportedMajorType, + wantInt16Error: ErrUnsupportedMajorType, + wantInt32Error: ErrUnsupportedMajorType, + wantInt64Error: ErrUnsupportedMajorType, + wantUint8Error: ErrUnsupportedMajorType, + wantUint16Error: ErrUnsupportedMajorType, + wantUint32Error: ErrUnsupportedMajorType, + wantUint64Error: ErrUnsupportedMajorType, + wantFloat32: float32(math.Inf(1)), + wantFloat64: math.Inf(1), + }, + { + encoded: "FAFF800000", // -ve inf + wantInt8Error: ErrUnsupportedMajorType, + wantInt16Error: ErrUnsupportedMajorType, + wantInt32Error: ErrUnsupportedMajorType, + wantInt64Error: ErrUnsupportedMajorType, + wantUint8Error: ErrUnsupportedMajorType, + wantUint16Error: ErrUnsupportedMajorType, + wantUint32Error: ErrUnsupportedMajorType, + wantUint64Error: ErrUnsupportedMajorType, + wantFloat32: float32(math.Inf(-1)), + wantFloat64: math.Inf(-1), + }, + { + encoded: "FA00000000", // 0 + wantInt8Error: ErrUnsupportedMajorType, + wantInt16Error: ErrUnsupportedMajorType, + wantInt32Error: ErrUnsupportedMajorType, + wantInt64Error: ErrUnsupportedMajorType, + wantUint8Error: ErrUnsupportedMajorType, + wantUint16Error: ErrUnsupportedMajorType, + wantUint32Error: ErrUnsupportedMajorType, + wantUint64Error: ErrUnsupportedMajorType, + wantFloat32: 0, + wantFloat64: 0, + }, + { + encoded: "FA80000000", // -0 + wantInt8Error: ErrUnsupportedMajorType, + wantInt16Error: ErrUnsupportedMajorType, + wantInt32Error: ErrUnsupportedMajorType, + wantInt64Error: ErrUnsupportedMajorType, + wantUint8Error: ErrUnsupportedMajorType, + wantUint16Error: ErrUnsupportedMajorType, + wantUint32Error: ErrUnsupportedMajorType, + wantUint64Error: ErrUnsupportedMajorType, + wantFloat32: -0, + wantFloat64: -0, + }, + { + encoded: "FB7FEFFFFFFFFFFFFF", // max + wantInt8Error: ErrUnsupportedMajorType, + wantInt16Error: ErrUnsupportedMajorType, + wantInt32Error: ErrUnsupportedMajorType, + wantInt64Error: ErrUnsupportedMajorType, + wantUint8Error: ErrUnsupportedMajorType, + wantUint16Error: ErrUnsupportedMajorType, + wantUint32Error: ErrUnsupportedMajorType, + wantUint64Error: ErrUnsupportedMajorType, + wantFloat32Error: ErrOverflow, + wantFloat64: math.Float64frombits(0x7FEFFFFFFFFFFFFF), + }, + { + encoded: "FB7FF0000000000000", // +ve inf + wantInt8Error: ErrUnsupportedMajorType, + wantInt16Error: ErrUnsupportedMajorType, + wantInt32Error: ErrUnsupportedMajorType, + wantInt64Error: ErrUnsupportedMajorType, + wantUint8Error: ErrUnsupportedMajorType, + wantUint16Error: ErrUnsupportedMajorType, + wantUint32Error: ErrUnsupportedMajorType, + wantUint64Error: ErrUnsupportedMajorType, + wantFloat32: float32(math.Inf(1)), + wantFloat64: math.Inf(1), + }, + { + encoded: "FBFFF0000000000000", // -ve inf + wantInt8Error: ErrUnsupportedMajorType, + wantInt16Error: ErrUnsupportedMajorType, + wantInt32Error: ErrUnsupportedMajorType, + wantInt64Error: ErrUnsupportedMajorType, + wantUint8Error: ErrUnsupportedMajorType, + wantUint16Error: ErrUnsupportedMajorType, + wantUint32Error: ErrUnsupportedMajorType, + wantUint64Error: ErrUnsupportedMajorType, + wantFloat32: float32(math.Inf(-1)), + wantFloat64: math.Inf(-1), + }, + { + encoded: "FB0000000000000000", // 0 + wantInt8Error: ErrUnsupportedMajorType, + wantInt16Error: ErrUnsupportedMajorType, + wantInt32Error: ErrUnsupportedMajorType, + wantInt64Error: ErrUnsupportedMajorType, + wantUint8Error: ErrUnsupportedMajorType, + wantUint16Error: ErrUnsupportedMajorType, + wantUint32Error: ErrUnsupportedMajorType, + wantUint64Error: ErrUnsupportedMajorType, + wantFloat32: 0, + wantFloat64: 0, + }, + { + encoded: "FB8000000000000000", // -0 + wantInt8Error: ErrUnsupportedMajorType, + wantInt16Error: ErrUnsupportedMajorType, + wantInt32Error: ErrUnsupportedMajorType, + wantInt64Error: ErrUnsupportedMajorType, + wantUint8Error: ErrUnsupportedMajorType, + wantUint16Error: ErrUnsupportedMajorType, + wantUint32Error: ErrUnsupportedMajorType, + wantUint64Error: ErrUnsupportedMajorType, + wantFloat32: 0, + wantFloat64: -0, + }, + } + + for _, tt := range tests { + t.Run(tt.encoded, func(t *testing.T) { + encoded := decodeHex(t, tt.encoded) + + runTest_ReadSigned(t, "int8", encoded, tt.wantInt8, tt.wantInt8Error) + runTest_ReadSigned(t, "int16", encoded, tt.wantInt16, tt.wantInt16Error) + runTest_ReadSigned(t, "int32", encoded, tt.wantInt32, tt.wantInt32Error) + runTest_ReadSigned(t, "int32", encoded, tt.wantInt64, tt.wantInt64Error) + + runTest_ReadUnsigned(t, "uint8", encoded, tt.wantUint8, tt.wantUint8Error) + runTest_ReadUnsigned(t, "uint16", encoded, tt.wantUint16, tt.wantUint16Error) + runTest_ReadUnsigned(t, "uint32", encoded, tt.wantUint32, tt.wantUint32Error) + runTest_ReadUnsigned(t, "uint64", encoded, tt.wantUint64, tt.wantUint64Error) + + runTest_ReadFloat(t, "float32", encoded, tt.wantFloat32, tt.wantFloat32Error) + runTest_ReadFloat(t, "float64", encoded, tt.wantFloat64, tt.wantFloat64Error) + }) + } +} + +func runTest_ReadSigned[T int8 | int16 | int32 | int64](t *testing.T, name string, encoded []byte, want T, wantErr error) { + t.Run(name, func(t *testing.T) { + got, err := ReadSigned[T](bytes.NewReader(encoded)) + if err != wantErr { + t.Fatalf("want %v, got %v", wantErr, err) + } + if err != nil { + return + } + if diff := cmp.Diff(want, got); diff != "" { + t.Fatal(diff) + } + }) +} + +func runTest_ReadUnsigned[T uint8 | uint16 | uint32 | uint64](t *testing.T, name string, encoded []byte, want T, wantErr error) { + t.Run(name, func(t *testing.T) { + got, err := ReadUnsigned[T](bytes.NewReader(encoded)) + if err != wantErr { + t.Fatalf("want %v, got %v", wantErr, err) + } + if err != nil { + return + } + if diff := cmp.Diff(want, got); diff != "" { + t.Fatal(diff) + } + }) +} + +func runTest_ReadFloat[T float32 | float64](t *testing.T, name string, encoded []byte, want T, wantErr error) { + t.Run(name, func(t *testing.T) { + got, err := ReadFloat[T](bytes.NewReader(encoded)) + if err != wantErr { + t.Fatalf("want %v, got %v", wantErr, err) + } + if err != nil { + return + } + if math.IsNaN(float64(want)) && math.IsNaN(float64(got)) { + // pass + } else if diff := cmp.Diff(want, got); diff != "" { + t.Fatal(diff) + } + }) +} + +func Test_ReadBool(t *testing.T) { + for _, tt := range tests_ExampleEncoded { + t.Run(tt.encoded, func(t *testing.T) { + encoded := decodeHex(t, tt.encoded) + + in := bytes.NewReader(encoded) + + got, err := ReadBool(in) + + var want bool + var wantErr error + switch { + case encoded[0] == 0xf4: + want = false + case encoded[0] == 0xf5: + want = true + case encoded[0]&majorTypeMask == MajorTypeSimpleFloat: + wantErr = ErrUnsupportedValue + default: + wantErr = ErrUnsupportedMajorType + } + + if got != want { + t.Fatalf("want = %t, got = %t", want, got) + } + if err != wantErr { + t.Fatalf("wantErr = %v, err = %v", wantErr, err) + } + }) + } +} + +func Test_ReadArray(t *testing.T) { + tests := []struct { + encoded string + want []int32 + }{ + { + encoded: "80", + want: []int32{}, + }, + { + encoded: "83010203", + want: []int32{1, 2, 3}, + }, + { + encoded: "98190102030405060708090a0b0c0d0e0f101112131415161718181819", + want: []int32{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}, + }, + { + encoded: "9f0102030405060708090a0b0c0d0e0f101112131415161718181819ff", + want: []int32{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}, + }, + } + + for _, tt := range tests { + t.Run(tt.encoded, func(t *testing.T) { + in := bytes.NewBuffer(decodeHex(t, tt.encoded)) + var out []int32 + err := ReadArray(in, + func(indefinite bool, length uint64) error { + out = make([]int32, 0, length) + return nil + }, + func(in io.Reader) error { + v, err := ReadSigned[int32](in) + if err != nil { + return err + } + out = append(out, v) + return nil + }, + ) + if err != nil { + t.Fatal(err) + } + if diff := cmp.Diff(tt.want, out); diff != "" { + t.Fatal(diff) + } + }) + } +} + +func Test_ReadMap(t *testing.T) { + tests := []struct { + encoded string + want map[int32]int32 + }{ + { + encoded: "a0", + want: map[int32]int32{}, + }, + { + encoded: "a201020304", + want: map[int32]int32{1: 2, 3: 4}, + }, + } + + for _, tt := range tests { + t.Run(tt.encoded, func(t *testing.T) { + in := bytes.NewBuffer(decodeHex(t, tt.encoded)) + var out map[int32]int32 + err := ReadMap(in, + func(indefinite bool, length uint64) error { + out = make(map[int32]int32, length) + return nil + }, + func(in io.Reader) error { + k, err := ReadSigned[int32](in) + if err != nil { + return err + } + v, err := ReadSigned[int32](in) + if err != nil { + return err + } + out[k] = v + return nil + }, + ) + if err != nil { + t.Fatal(err) + } + if diff := cmp.Diff(tt.want, out); diff != "" { + t.Fatal(diff) + } + }) + } +} + +func Test_ReadBytes(t *testing.T) { + tests := []struct { + encoded string + want []byte + }{ + { + encoded: "40", + want: []byte{}, + }, + { + encoded: "4401020304", + want: []byte{1, 2, 3, 4}, + }, + } + + for _, tt := range tests { + t.Run(tt.encoded, func(t *testing.T) { + in := bytes.NewBuffer(decodeHex(t, tt.encoded)) + out := bytes.NewBuffer([]byte{}) + err := ReadBytes( + in, + func(indefinite bool, length uint64) error { + out.Grow(int(length)) + return nil + }, + out, + ) + if err != nil { + t.Fatal(err) + } + if diff := cmp.Diff(tt.want, out.Bytes()); diff != "" { + t.Fatal(diff) + } + }) + } +} + +func Test_ReadTag(t *testing.T) { + tests := []struct { + encoded string + want uint64 + }{ + { + encoded: "c0", + want: 0, + }, + { + encoded: "c1", + want: 1, + }, + { + encoded: "d7", + want: 23, + }, + { + encoded: "d818", + want: 24, + }, + } + + for _, tt := range tests { + t.Run(tt.encoded, func(t *testing.T) { + in := bytes.NewBuffer(decodeHex(t, tt.encoded)) + v, err := ReadTag(in) + if err != nil { + t.Fatal(v) + } + if diff := cmp.Diff(tt.want, v); diff != "" { + t.Fatal(diff) + } + }) + } +} diff --git a/util_test.go b/util_test.go new file mode 100644 index 0000000..d5977e8 --- /dev/null +++ b/util_test.go @@ -0,0 +1,17 @@ +package cbor + +import ( + "encoding/hex" + "testing" +) + +func decodeHex(tb testing.TB, encoded string) []byte { + tb.Helper() + + decoded, err := hex.DecodeString(encoded) + if err != nil { + tb.Fatal(err) + } + + return decoded +} diff --git a/write.go b/write.go new file mode 100644 index 0000000..27abd2f --- /dev/null +++ b/write.go @@ -0,0 +1,121 @@ +package cbor + +import ( + "github.com/x448/float16" + "io" + "math" +) + +func writeMajorType(out io.Writer, majorType MajorType, value uint64) (int, error) { + sharedBuffer[0] = byte(majorType) + + if value < uint64(Arg8) { + sharedBuffer[0] |= byte(value) + return out.Write(sharedBuffer[0:1]) + } + + n := 0 + switch { + case value < 0x1_00: + sharedBuffer[0] |= byte(Arg8) + n = 1 + case value < 0x1_00_00: + sharedBuffer[0] |= byte(Arg16) + n = 2 + case value < 0x1_00_00_00_00: + sharedBuffer[0] |= byte(Arg32) + n = 4 + default: + sharedBuffer[0] |= byte(Arg64) + n = 8 + } + + shiftBytesFrom(value, sharedBuffer[1:1+n]) + return out.Write(sharedBuffer[0 : 1+n]) +} + +func WriteUnsigned[T uint8 | uint16 | uint32 | uint64](out io.Writer, value T) (int, error) { + return writeMajorType(out, MajorTypeUInt, uint64(value)) +} + +func WriteSigned[T int8 | int16 | int32 | int64](out io.Writer, value T) (int, error) { + if value >= 0 { + return writeMajorType(out, MajorTypeUInt, uint64(value)) + } + return writeMajorType(out, MajorTypeNInt, uint64(-value-1)) +} + +func WriteFloat[T float16.Float16 | float32 | float64](out io.Writer, value T) (int, error) { + switch v := any(value).(type) { + case float16.Float16: + sharedBuffer[0] = MajorTypeSimpleFloat | SimpleFloat16 + shiftBytesFrom(uint16(v), sharedBuffer[1:3]) + return out.Write(sharedBuffer[0:3]) + + case float32: + if float16.PrecisionFromfloat32(v) == float16.PrecisionExact { + return WriteFloat(out, float16.Fromfloat32(v)) + } + + sharedBuffer[0] = MajorTypeSimpleFloat | SimpleFloat32 + shiftBytesFrom(math.Float32bits(v), sharedBuffer[1:5]) + return out.Write(sharedBuffer[0:5]) + + case float64: + v32 := float32(v) + // TODO NaN, inf... + if v == float64(v32) { + return WriteFloat(out, v32) + } + + sharedBuffer[0] = MajorTypeSimpleFloat | SimpleFloat64 + shiftBytesFrom(math.Float64bits(v), sharedBuffer[1:9]) + return out.Write(sharedBuffer[0:9]) + + default: + panic("unreachable") + } +} + +func WriteBool(out io.Writer, value bool) (int, error) { + if value { + return writeMajorType(out, MajorTypeSimpleFloat, SimpleTrue) + } + return writeMajorType(out, MajorTypeSimpleFloat, uint64(SimpleFalse)) +} + +func WriteTag(out io.Writer, value uint64) (int, error) { + return writeMajorType(out, MajorTypeTagged, value) +} + +func WriteBytes(out io.Writer, value []byte) (int, error) { + tn := 0 + n, err := writeMajorType(out, MajorTypeBstr, uint64(len(value))) + tn += n + if err != nil { + return tn, err + } + n, err = out.Write(value) + tn += n + return tn, err +} + +func WriteString(out io.Writer, value string) (int, error) { + tn := 0 + n, err := writeMajorType(out, MajorTypeTstr, uint64(len(value))) + tn += n + if err != nil { + return tn, err + } + n, err = out.Write(([]byte)(value)) + tn += n + return tn, err +} + +func WriteArrayHeader(out io.Writer, length uint64) (int, error) { + return writeMajorType(out, MajorTypeArray, length) +} + +func WriteMapHeader(out io.Writer, length uint64) (int, error) { + return writeMajorType(out, MajorTypeMap, length) +} diff --git a/write_test.go b/write_test.go new file mode 100644 index 0000000..47b8921 --- /dev/null +++ b/write_test.go @@ -0,0 +1,568 @@ +package cbor + +import ( + "bytes" + "github.com/x448/float16" + "testing" + + "github.com/google/go-cmp/cmp" +) + +func Test_WriteUnsigned(t *testing.T) { + tests := []struct { + withUint8 uint8 + withUint16 uint16 + withUint32 uint32 + withUint64 uint64 + want string + }{ + { + withUint8: 0, + withUint16: 0, + withUint32: 0, + withUint64: 0, + want: "00", + }, + { + withUint8: 1, + withUint16: 1, + withUint32: 1, + withUint64: 1, + want: "01", + }, + { + withUint8: 23, + withUint16: 23, + withUint32: 23, + withUint64: 23, + want: "17", + }, + { + withUint8: 24, + withUint16: 24, + withUint32: 24, + withUint64: 24, + want: "1818", + }, + { + withUint8: 0xff, + withUint16: 0xff, + withUint32: 0xff, + withUint64: 0xff, + want: "18ff", + }, + { + withUint16: 0x100, + withUint32: 0x100, + withUint64: 0x100, + want: "190100", + }, + { + withUint16: 0x0102, + withUint32: 0x0102, + withUint64: 0x0102, + want: "190102", + }, + { + withUint16: 0xffff, + withUint32: 0xffff, + withUint64: 0xffff, + want: "19ffff", + }, + { + withUint32: 0x10000, + withUint64: 0x10000, + want: "1a00010000", + }, + { + withUint32: 0x01020304, + withUint64: 0x01020304, + want: "1a01020304", + }, + { + withUint32: 0xffffffff, + withUint64: 0xffffffff, + want: "1affffffff", + }, + { + withUint64: 0x100000000, + want: "1b0000000100000000", + }, + { + withUint64: 0x0102030405060708, + want: "1b0102030405060708", + }, + { + withUint64: 0xffffffffffffffff, + want: "1bffffffffffffffff", + }, + } + + for _, tt := range tests { + want := decodeHex(t, tt.want) + wantN := len(want) + t.Run(tt.want, func(t *testing.T) { + if wantN <= 2 { + runTest_WriteUnsigned(t, "uint8", tt.withUint8, want, wantN) + } + if wantN <= 3 { + runTest_WriteUnsigned(t, "uint16", tt.withUint16, want, wantN) + } + if wantN <= 5 { + runTest_WriteUnsigned(t, "uint32", tt.withUint32, want, wantN) + } + if wantN <= 9 { + runTest_WriteUnsigned(t, "uint64", tt.withUint64, want, wantN) + } + }) + } +} + +func runTest_WriteUnsigned[N uint8 | uint16 | uint32 | uint64](t *testing.T, name string, with N, want []byte, wantN int) { + t.Run(name, func(t *testing.T) { + out := bytes.NewBuffer(nil) + n, err := WriteUnsigned(out, with) + if diff := cmp.Diff(wantN, n); diff != "" { + t.Fatal(diff) + } + if diff := cmp.Diff(want, out.Bytes()); diff != "" { + t.Fatal(diff) + } + if err != nil { + t.Fatal(err) + } + }) +} + +func Test_WriteSigned(t *testing.T) { + tests := []struct { + with string + withInt8 int8 + withInt16 int16 + withInt32 int32 + withInt64 int64 + want string + }{ + { + withInt8: 0, + withInt16: 0, + withInt32: 0, + withInt64: 0, + want: "00", + }, + { + withInt8: 1, + withInt16: 1, + withInt32: 1, + withInt64: 1, + want: "01", + }, + { + withInt8: 23, + withInt16: 23, + withInt32: 23, + withInt64: 23, + want: "17", + }, + { + withInt8: 24, + withInt16: 24, + withInt32: 24, + withInt64: 24, + want: "1818", + }, + { + withInt8: -1, + withInt16: -1, + withInt32: -1, + withInt64: -1, + want: "20", + }, + { + withInt8: -24, + withInt16: -24, + withInt32: -24, + withInt64: -24, + want: "37", + }, + { + withInt8: -25, + withInt16: -25, + withInt32: -25, + withInt64: -25, + want: "3818", + }, + { + withInt8: 127, + withInt16: 127, + withInt32: 127, + withInt64: 127, + want: "187f", + }, + { + withInt8: -128, + withInt16: -128, + withInt32: -128, + withInt64: -128, + want: "387f", + }, + } + + for _, tt := range tests { + want := decodeHex(t, tt.want) + t.Run(tt.want, func(t *testing.T) { + runTest_WriteSigned(t, "int8", tt.withInt8, want) + runTest_WriteSigned(t, "int16", tt.withInt16, want) + runTest_WriteSigned(t, "int32", tt.withInt32, want) + runTest_WriteSigned(t, "int64", tt.withInt64, want) + }) + } +} + +func runTest_WriteSigned[N int8 | int16 | int32 | int64](t *testing.T, name string, with N, want []byte) { + t.Run(name, func(t *testing.T) { + out := bytes.NewBuffer(nil) + _, err := WriteSigned(out, with) + if diff := cmp.Diff(want, out.Bytes()); diff != "" { + t.Fatal(diff) + } + if err != nil { + t.Fatal(err) + } + }) +} + +func Test_WriteFloat(t *testing.T) { + tests := []struct { + skipFloat16 bool + withFloat16 float16.Float16 + skipFloat32 bool + withFloat32 float32 + withFloat64 float64 + want string + }{ + { + withFloat16: 0, + withFloat32: 0, + withFloat64: 0, + want: "f90000", + }, + { + withFloat16: float16.Fromfloat32(1.0), + withFloat32: 1.0, + withFloat64: 1.0, + want: "f93c00", + }, + { + skipFloat16: true, + skipFloat32: true, + withFloat64: 1.1, + want: "fb3ff199999999999a", + }, + { + withFloat16: float16.Fromfloat32(1.5), + withFloat32: 1.5, + withFloat64: 1.5, + want: "f93e00", + }, + { + withFloat16: float16.Fromfloat32(65504.0), + withFloat32: 65504.0, + withFloat64: 65504.0, + want: "f97bff", + }, + { + skipFloat16: true, + withFloat32: 100000.0, + withFloat64: 100000.0, + want: "fa47c35000", + }, + { + skipFloat16: true, + withFloat32: 3.4028234663852886e+38, + withFloat64: 3.4028234663852886e+38, + want: "fa7f7fffff", + }, + { + skipFloat16: true, + skipFloat32: true, + withFloat64: 1.0e+300, + want: "fb7e37e43c8800759c", + }, + //{ TODO ?? + // withFloat16: float16.Fromfloat32(5.960464477539063e-8), + // withFloat32: 5.960464477539063e-8, + // withFloat64: 5.960464477539063e-8, + // want: "f90001", + //}, + { + withFloat16: float16.Fromfloat32(0.00006103515625), + withFloat32: 0.00006103515625, + withFloat64: 0.00006103515625, + want: "f90400", + }, + { + withFloat16: float16.Fromfloat32(-4.0), + withFloat32: -4.0, + withFloat64: -4.0, + want: "f9c400", + }, + { + skipFloat16: true, + skipFloat32: true, + withFloat64: -4.1, + want: "fbc010666666666666", + }, + } + + for _, tt := range tests { + want := decodeHex(t, tt.want) + + t.Run(tt.want, func(t *testing.T) { + if !tt.skipFloat16 { + runTest_WriteFloat(t, "float16", tt.withFloat16, want) + } + if !tt.skipFloat32 { + runTest_WriteFloat(t, "float32", tt.withFloat32, want) + } + runTest_WriteFloat(t, "float64", tt.withFloat64, want) + }) + } +} + +func runTest_WriteFloat[N float16.Float16 | float32 | float64](t *testing.T, name string, with N, want []byte) { + t.Run(name, func(t *testing.T) { + out := bytes.NewBuffer(nil) + _, err := WriteFloat(out, with) + if diff := cmp.Diff(want, out.Bytes()); diff != "" { + t.Fatal(diff) + } + if err != nil { + t.Fatal(err) + } + }) +} + +func Test_WriteBool(t *testing.T) { + tests := []struct { + with bool + want string + }{ + { + with: false, + want: "f4", + }, + { + with: true, + want: "f5", + }, + } + + for _, tt := range tests { + t.Run(tt.want, func(t *testing.T) { + out := bytes.NewBuffer(nil) + _, err := WriteBool(out, tt.with) + if err != nil { + t.Fatal(err) + } + if diff := cmp.Diff(decodeHex(t, tt.want), out.Bytes()); diff != "" { + t.Fatal(diff) + } + }) + } +} + +func Test_WriteBytes(t *testing.T) { + tests := []struct { + with []byte + want string + }{ + { + with: []byte{}, + want: "40", + }, + { + with: []byte{1, 2, 3, 4}, + want: "4401020304", + }, + } + + for _, tt := range tests { + t.Run(tt.want, func(t *testing.T) { + out := bytes.NewBuffer(nil) + _, err := WriteBytes(out, tt.with) + if diff := cmp.Diff(decodeHex(t, tt.want), out.Bytes()); diff != "" { + t.Fatal(diff) + } + if err != nil { + t.Fatal(err) + } + }) + } +} + +func Test_WriteString(t *testing.T) { + tests := []struct { + with string + want string + }{ + { + with: "", + want: "60", + }, + { + with: "a", + want: "6161", + }, + { + with: "IETF", + want: "6449455446", + }, + { + with: "\"\\", + want: "62225c", + }, + { + with: "\u00fc", + want: "62c3bc", + }, + { + with: "\u6c34", + want: "63e6b0b4", + }, + { + with: "\xf0\x90\x85\x91", // "\ud800\udd51", + want: "64f0908591", + }, + } + + for _, tt := range tests { + t.Run(tt.want, func(t *testing.T) { + out := bytes.NewBuffer(nil) + _, err := WriteString(out, tt.with) + if diff := cmp.Diff(decodeHex(t, tt.want), out.Bytes()); diff != "" { + t.Fatal(diff) + } + if err != nil { + t.Fatal(err) + } + }) + } +} + +func Test_WriteArray(t *testing.T) { + tests := []struct { + with []int + want string + }{ + { + with: []int{}, + want: "80", + }, + { + with: []int{1, 2, 3}, + want: "83010203", + }, + } + + for _, tt := range tests { + t.Run(tt.want, func(t *testing.T) { + out := bytes.NewBuffer(nil) + _, err := WriteArrayHeader(out, uint64(len(tt.with))) + if err != nil { + t.Fatal(err) + } + for _, i := range tt.with { + _, err = WriteSigned(out, int8(i)) + if err != nil { + t.Fatal(err) + } + } + if diff := cmp.Diff(decodeHex(t, tt.want), out.Bytes()); diff != "" { + t.Fatal(diff) + } + if err != nil { + t.Fatal(err) + } + }) + } +} + +func Test_WriteMap(t *testing.T) { + tests := []struct { + with map[int]int + want string + }{ + { + with: map[int]int{}, + want: "a0", + }, + { + with: map[int]int{1: 2, 3: 4}, + want: "a201020304", + }, + } + + for _, tt := range tests { + t.Run(tt.want, func(t *testing.T) { + out := bytes.NewBuffer(nil) + _, err := WriteMapHeader(out, uint64(len(tt.with))) + if err != nil { + t.Fatal(err) + } + for k, v := range tt.with { + _, err = WriteSigned(out, int8(k)) + if err != nil { + t.Fatal(err) + } + _, err = WriteSigned(out, int8(v)) + if err != nil { + t.Fatal(err) + } + } + if diff := cmp.Diff(decodeHex(t, tt.want), out.Bytes()); diff != "" { + t.Fatal(diff) + } + if err != nil { + t.Fatal(err) + } + }) + } +} + +func Test_WriteTag(t *testing.T) { + tests := []struct { + with uint64 + want string + }{ + { + with: 0, + want: "c0", + }, + { + with: 1, + want: "c1", + }, + { + with: 23, + want: "d7", + }, + { + with: 24, + want: "d818", + }, + } + + for _, tt := range tests { + t.Run(tt.want, func(t *testing.T) { + out := bytes.NewBuffer(nil) + _, err := WriteTag(out, tt.with) + if err != nil { + t.Fatal(err) + } + if diff := cmp.Diff(decodeHex(t, tt.want), out.Bytes()); diff != "" { + t.Fatal(diff) + } + }) + } +}