Repository: shamaton/msgpack Branch: main Commit: 014715d0f53e Files: 245 Total size: 619.5 KB Directory structure: gitextract_aynht_4q/ ├── .github/ │ ├── release.yml │ └── workflows/ │ ├── stale.yml │ ├── tagpr.yml │ └── test.yml ├── .gitignore ├── .tagpr ├── CHANGELOG.md ├── LICENSE ├── README.md ├── codecov.yml ├── crash_test.go ├── decode.go ├── def/ │ ├── def.go │ └── error.go ├── encode.go ├── errors.go ├── ext/ │ ├── decode.go │ ├── decoder_stream.go │ ├── encode.go │ └── encode_stream.go ├── go.mod ├── internal/ │ ├── common/ │ │ ├── buffer.go │ │ ├── common.go │ │ ├── common_test.go │ │ └── testutil/ │ │ ├── reader.go │ │ ├── struct.go │ │ └── testutil.go │ ├── decoding/ │ │ ├── bin.go │ │ ├── bin_test.go │ │ ├── bool.go │ │ ├── bool_test.go │ │ ├── complex.go │ │ ├── complex_test.go │ │ ├── decoding.go │ │ ├── decoding_test.go │ │ ├── ext.go │ │ ├── ext_test.go │ │ ├── float.go │ │ ├── float_test.go │ │ ├── int.go │ │ ├── int_test.go │ │ ├── interface.go │ │ ├── interface_test.go │ │ ├── map.go │ │ ├── map_test.go │ │ ├── nil.go │ │ ├── read.go │ │ ├── slice.go │ │ ├── slice_test.go │ │ ├── string.go │ │ ├── string_test.go │ │ ├── struct.go │ │ ├── struct_test.go │ │ ├── uint.go │ │ └── uint_test.go │ ├── encoding/ │ │ ├── bool.go │ │ ├── byte.go │ │ ├── byte_test.go │ │ ├── complex.go │ │ ├── encoding.go │ │ ├── encoding_test.go │ │ ├── ext.go │ │ ├── ext_test.go │ │ ├── float.go │ │ ├── int.go │ │ ├── map.go │ │ ├── nil.go │ │ ├── set.go │ │ ├── slice.go │ │ ├── slice_test.go │ │ ├── string.go │ │ ├── struct.go │ │ ├── struct_test.go │ │ └── uint.go │ └── stream/ │ ├── decoding/ │ │ ├── bin.go │ │ ├── bin_test.go │ │ ├── bool.go │ │ ├── bool_test.go │ │ ├── complex.go │ │ ├── complex_test.go │ │ ├── decoding.go │ │ ├── decoding_test.go │ │ ├── ext.go │ │ ├── ext_test.go │ │ ├── float.go │ │ ├── float_test.go │ │ ├── int.go │ │ ├── int_test.go │ │ ├── interface.go │ │ ├── interface_test.go │ │ ├── map.go │ │ ├── map_test.go │ │ ├── nil.go │ │ ├── read.go │ │ ├── slice.go │ │ ├── slice_test.go │ │ ├── string.go │ │ ├── string_test.go │ │ ├── struct.go │ │ ├── struct_test.go │ │ ├── uint.go │ │ └── uint_test.go │ └── encoding/ │ ├── bool.go │ ├── bool_test.go │ ├── byte.go │ ├── byte_test.go │ ├── complex.go │ ├── complex_test.go │ ├── encoding.go │ ├── encoding_test.go │ ├── ext.go │ ├── ext_test.go │ ├── float.go │ ├── float_test.go │ ├── int.go │ ├── int_test.go │ ├── map.go │ ├── map_test.go │ ├── nil.go │ ├── set.go │ ├── slice.go │ ├── slice_test.go │ ├── string.go │ ├── string_test.go │ ├── struct.go │ ├── struct_test.go │ ├── uint.go │ └── uint_test.go ├── msgpack.go ├── msgpack_example_test.go ├── msgpack_test.go ├── testdata/ │ └── crashers/ │ ├── 007424b0caabdaf653d7efeb3ce6c1c127f628c6 │ ├── 01d1844ec3cdec0e8d60d81fcded73f920270e9c │ ├── 049b2af75fc4ee7160c7ea9bad41a5167b501d32 │ ├── 051d081de15ae4680b4156046787aae72ae0adcc │ ├── 063fea9d12105a80ee56a482aac428543fcf8d49 │ ├── 080c57e02ab7e704e2a2e93aad7674a574dc9d69 │ ├── 0d45579ce9ef9c1ca6e945d3f21ad31097f0c3c0 │ ├── 0f7994b18fb75394c81005c925dd1ae153ad57ad │ ├── 121a9af889bd4ca2266be5a4f680d3bead8d02d6 │ ├── 19eb9972c2a406f15bbae850ac33b42ebd88f7a7 │ ├── 1a3492dbf0b80d08f12ce49c620239e48b74c08c │ ├── 1ae8bba51072f9ff2bdf587596cfb0edf8a5cd12 │ ├── 1b274070ce90f8ccdf0fc33a4afd4e1455766f8b │ ├── 1d49f1635e901393bf65a263810508bea7ec9d51 │ ├── 1e2c5c5ac7754f3e3fd858ce644b6621da2f4d03 │ ├── 236cfc19fdc31facdfe306e479a52bb3c08cb540 │ ├── 2478b7ed4fe72f5030a22146ecf365adba0dc780 │ ├── 24b5d7868ba6df6da0e26365e10fc15bcf72cca6 │ ├── 255b827f8c0a593c5ee7610cdbd31c656f166a37 │ ├── 2a8cbfc34aa4aefa008e9e18276e32fa5e8f56d8 │ ├── 2ba0c664eff181eeb2b101f60c5ca66397f4c386 │ ├── 2c34cfd27e783430e24c2af05361af3540b4d6ad │ ├── 2c4dd5af849b5cda2b7769dc52706e5fa746de73 │ ├── 2cdb9f5adf53f1054ce8e6ceba007396ce6b734b │ ├── 30140397fe38ee61f01eff44b5cfa48285e47889 │ ├── 321500ed9a1796dca3d559d8f129ed31a8790035 │ ├── 3219e4d65ac8671a452900514bc3c7aea2e71be3 │ ├── 36a5cc93968072de9d120643033589df4f316997 │ ├── 3abe9a29f2b95f34da65d88b969e58f4b46b6309 │ ├── 3e5a9e8b50fcdcbe08f8138de6b95b5cd5b69bf2 │ ├── 3f0062c38d031b015f1a8bb82bd370b65d5ca35a │ ├── 4670a8e0d21a624c0d42fd6dc76ef408d2b4e195 │ ├── 47393c214b8f5a3d3c098e3985d24bd1e8d82a85 │ ├── 48947b6f943721ce84ef81e887a16bde2759f7ab │ ├── 49989f6a8875777519c822a92c821cbb95bfc360 │ ├── 4aaffe149efbd0ab6f122a4dc6e9e9aa0a602799 │ ├── 4b65bf83d9b292f65bb5d1b056d68a4811732ef3 │ ├── 4c156cb396c967d81bf3615cbd5ba74221a9c622 │ ├── 4fb8cfeaaac80a1c829b22a43089ef470bcfe5b8 │ ├── 502804b3665c5eb935d6f66e7677f75242f516bb │ ├── 56ff31c3b76d37929e45071a36ca5aa0bc386e09 │ ├── 5bc56c0f00870625b5016a8d242ebe5c2a58802b │ ├── 5e8c489081abe671d760e1840b526f43bc7e6aa3 │ ├── 5ec02b6349403b523d33576c7e2de27fb345edf0 │ ├── 6351ed5a9ac9cbb709ad15b4193edb52b8d37e84 │ ├── 673480b070ea0508c3510627c81a4b519fb7d2f5 │ ├── 683f06bff1a62b73193352f06a7194f0bf60ffb8 │ ├── 68a5e75e9b42928454b320fe8f64ea107e61f60f │ ├── 68be1e92e40e77926e1d9a5c2d800da023f6a4e9 │ ├── 6b7395dabfc4d5a76e276f222c64c4bf7771ffb7 │ ├── 707235f5352edef615d64d00d83fcf92c9dffa57 │ ├── 71819c849b41ee2f4a5b08321b9fc8bf21326ddf │ ├── 730377e1be92f426661e434d4f1cf20a10e16991 │ ├── 73728d128f628772a64990a72340d9fd7987e29b │ ├── 73cc8fe38acd489c2b1fed0a66222aac4e04cc98 │ ├── 74fe2a4034b08b5ecb014b2596e7f21ab55ea729 │ ├── 79aed363731b28a60abf16190df3d60fd1d88e54 │ ├── 7a1d04a0253fbb956c8119c40dc917cb460dabb0 │ ├── 7a96d469f87b8c0fa00a944669469db9c8246714 │ ├── 7b21df3bb234142e7b2dfc81f84eb621f9c7a617 │ ├── 7cd196c0e83037dd1c7aa060e3919f530d5eaa13 │ ├── 8611c05469f8e623e1e035e578d1d50ff2a54e11 │ ├── 861648845b817281bffa42d8ca1c46443cef5cd8 │ ├── 86f9eb6ebcae33f4d3663f90adf430f6d7b5e57c │ ├── 8a90278d26f66fd78e4e38da237c201707bf7ab3 │ ├── 8de4d4284d8227a753986639b1d6e698bc683d8a │ ├── 8e4cb24fea75d067b0d9a5598fdeb66490777000 │ ├── 900843bfb3730b31165e6d480bb212854380de72 │ ├── 964ac50bf26ecb3d0253e5e288484776bbf0abdf │ ├── 98d2aa10f9c5e6d9b1a6fb3dcc6583aa5631882a │ ├── 9ae8dabd9621f44592757414f83d26df04e55bca │ ├── 9b982d96821f364683a7a9cc2495cccc7d5bb705 │ ├── 9ded617601151e2a3dde370fe42f206f6dd38f42 │ ├── 9e2487eee9a3b9f415c36aed893ed2da3d3bace6 │ ├── 9f733d3115030339e63922d6e35920f9dba1207e │ ├── a0bb3eef10ad343fdf00adc2c7a2fab0e4dd679f │ ├── a611449daf97af318de6495f22c907ecb1f83076 │ ├── a8c1d81ff50e57abc043f8bbcc87ce0e123285a7 │ ├── aa1a1bd5b7953186af6c9f2e8f757247fdffa9ce │ ├── b7471e724dfba20b71265eb8f8315ff5add6ccad │ ├── b9df95e660f5bc5d66f2ad7c49339cb3d62d2a99 │ ├── bc22b685617fec260cb94cb1ab9822e393924a35 │ ├── c1a1ed4a1a749c9e38da920c66a4c425e169566b │ ├── c41c1a47577c69284cae60d785ddb240dc3739db │ ├── c4ee5a4632d4f9a51ba57a70cfa2cae832460857 │ ├── c9388db618e924f41e419ccddc068b443657ffa4 │ ├── ce7c29332788aa93186591aceaad307ffbdf9735 │ ├── da39a3ee5e6b4b0d3255bfef95601890afd80709 │ ├── df0c1e8999119939ea4f07c2daeb57b868bb0719 │ ├── e2d6b6d4d9b8346067d2252bfbcefff99a24ed21 │ ├── e62ebb1ab82b8cf630f2b393d89c367dd0b25924 │ ├── e906f0d49a1b91f0b93d64c36bf5a764924ce45f │ ├── ead2a4908cb1750ae6f619656ebf74b153edab25 │ ├── eae5f62c3fc844275194f17d530d26ceafcf941b │ ├── eb9a0e5a353364b221b1e1e3115d34d7948e9ae9 │ ├── edb2dfda8124a5c5935dd1d449f493e868d9f9c0 │ ├── f023f3307ef4fe6b9d8ea2c0ea4fb3bc954f2888 │ ├── f1ec5f959e75412b01270a861f1f1ecdfda1cb9c │ ├── f4aaa2a22e1038e4abda4636592d330fc9fc693b │ ├── f7cb64ffc60ab1f9e0aecbd9c560af1d743fe314 │ ├── f86f8b65ecd59bddb8df4850b538b863021ea14a │ ├── f87f13e299cb66c824237b662a5623647d3af86e │ ├── fa85f6319c6d39864527629c9b25c4b086f398b7 │ └── fc7077d7ef5f47af404835e9ea6401f258b27f83 ├── time/ │ ├── decode.go │ ├── decode_stream.go │ ├── decode_stream_test.go │ ├── decode_test.go │ ├── encode.go │ ├── encode_stream.go │ ├── encode_stream_test.go │ ├── encode_test.go │ └── time.go └── unmarshal_ext_test.go ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/release.yml ================================================ changelog: exclude: labels: - tagpr ================================================ FILE: .github/workflows/stale.yml ================================================ name: Mark stale issues and pull requests # on: # schedule: # - cron: '30 11 * * *' jobs: stale: runs-on: ubuntu-latest permissions: issues: write pull-requests: write steps: - uses: actions/stale@v3 with: days-before-issue-stale: 30 days-before-issue-close: 14 stale-issue-label: "stale" stale-issue-message: "This issue is stale because it has been open for 30 days with no activity." close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale." days-before-pr-stale: -1 days-before-pr-close: -1 repo-token: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .github/workflows/tagpr.yml ================================================ name: tagpr on: push: branches: ["main"] jobs: tagpr: runs-on: ubuntu-latest permissions: contents: write pull-requests: write issues: read steps: - uses: actions/checkout@v6 with: persist-credentials: false - uses: Songmu/tagpr@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .github/workflows/test.yml ================================================ name: test on: [push, pull_request] env: GOPATH: ${{ github.workspace }} WORKSPACE: ${{ github.workspace }}/src/github.com/${{ github.repository }} jobs: test: defaults: run: working-directory: ${{ env.WORKSPACE }} strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] go: ["1.23", "1.24", "1.25", "1.26"] name: ${{ matrix.os }} @ Go ${{ matrix.go }} runs-on: ${{ matrix.os }} steps: - name: Checkout uses: actions/checkout@v4 with: path: ${{ env.WORKSPACE }} - name: Set up Go ${{ matrix.go }} uses: actions/setup-go@v5 with: go-version: ${{ matrix.go }} - name: Cache uses: actions/cache@v4 with: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- - name: Build run: go build -v ./... - name: Test run: go test -v --coverpkg=github.com/shamaton/msgpack/... --coverprofile=coverage.coverprofile.tmp --covermode=atomic ./... - name: Remove testutil from coverage shell: bash run: | cat coverage.coverprofile.tmp | grep -v testutil > coverage.coverprofile rm coverage.coverprofile.tmp - name: Upload coverage to Codecov if: success() && matrix.go == '1.25' && matrix.os == 'ubuntu-latest' uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: false working-directory: ${{ env.WORKSPACE }} lint: needs: test runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 with: path: ${{ env.WORKSPACE }} - name: golangci-lint uses: reviewdog/action-golangci-lint@v2 with: workdir: ${{ env.WORKSPACE }} level: warning reporter: github-pr-review ================================================ FILE: .gitignore ================================================ # Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # Test binary, build with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # coverage *.coverprofile .DS_Store .idea .vscode ================================================ FILE: .tagpr ================================================ [tagpr] releaseBranch = main versionFile = - vPrefix = true changelog = true release = true fixedMajorVersion = 3 ================================================ FILE: CHANGELOG.md ================================================ # Changelog ## [v3.1.1](https://github.com/shamaton/msgpack/compare/v3.1.0...v3.1.1) - 2026-05-12 - chore: isolate formatting cleanup from #60 by @shamaton in https://github.com/shamaton/msgpack/pull/61 - Add tagpr configuration by @shamaton in https://github.com/shamaton/msgpack/pull/63 - fix: validate ext frame bounds before byte-slice decode by @hyp3rd in https://github.com/shamaton/msgpack/pull/60 ## [v3.1.0](https://github.com/shamaton/msgpack/commits/v3.1.0) - 2026-02-06 - Add embedded struct support with optimized fast path by @shamaton in https://github.com/shamaton/msgpack/pull/58 ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2025 Masayuki Shamoto Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # MessagePack for Golang [![Go Reference](https://pkg.go.dev/badge/github.com/shamaton/msgpack.svg)](https://pkg.go.dev/github.com/shamaton/msgpack) ![test](https://github.com/shamaton/msgpack/workflows/test/badge.svg) [![Go Report Card](https://goreportcard.com/badge/github.com/shamaton/msgpack)](https://goreportcard.com/report/github.com/shamaton/msgpack) [![codecov](https://codecov.io/gh/shamaton/msgpack/branch/master/graph/badge.svg?token=9PD2JUK5V3)](https://codecov.io/gh/shamaton/msgpack) [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fshamaton%2Fmsgpack.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fshamaton%2Fmsgpack?ref=badge_shield) ## 📣 Announcement: `time.Time` decoding defaults to **UTC** in v3 Starting with **v3.0.0**, when decoding MessagePack **Timestamp** into Go’s `time.Time`, the default `Location` will be **UTC** (previously `Local`). The instant is unchanged. To keep the old behavior, use `SetDecodedTimeAsLocal()`. ## Features * Supported types : primitive / array / slice / struct / map / interface{} and time.Time * Renaming fields via `msgpack:"field_name"` * Omitting fields via `msgpack:"-"` * Omitting empty fields via `msgpack:"field_name,omitempty"` * Supports extend encoder / decoder [(example)](./msgpack_example_test.go) * Can also Encoding / Decoding struct as array ## Installation Current version is **msgpack/v3**. ```sh go get -u github.com/shamaton/msgpack/v3 ``` ### Upgrading from v2 If you are upgrading from v2, please note the `time.Time` decoding change mentioned in the announcement above. To keep the v2 behavior, use `msgpack.SetDecodedTimeAsLocal()` after upgrading. ## Quick Start ```go package main import ( "github.com/shamaton/msgpack/v3" "net/http" ) type Struct struct { String string } // simple func main() { v := Struct{String: "msgpack"} d, err := msgpack.Marshal(v) if err != nil { panic(err) } r := Struct{} if err = msgpack.Unmarshal(d, &r); err != nil { panic(err) } } // streaming func handle(w http.ResponseWriter, r *http.Request) { var body Struct if err := msgpack.UnmarshalRead(r, &body); err != nil { panic(err) } if err := msgpack.MarshalWrite(w, body); err != nil { panic(err) } } ``` ## v3: `time.Time` decoding defaults to **UTC** **TL;DR:** Starting with **v3.0.0**, when decoding MessagePack **Timestamp** into Go’s `time.Time`, the default `Location` will be **UTC** (previously `Local`). The **instant** is unchanged—only the display/location changes. This avoids host-dependent differences and aligns with common distributed systems practice. ### What is changing? * **Before (v2.x):** Decoded `time.Time` defaults to `Local`. * **After (v3.0.0):** Decoded `time.Time` defaults to **UTC**. MessagePack’s Timestamp encodes an **instant** (epoch seconds + nanoseconds) and does **not** carry timezone info. Your data’s point in time is the same; only `time.Time.Location()` differs. ### Why? * Eliminate environment-dependent behavior, such as different hosts showing different local zones. * Make “UTC by default” the safe, predictable baseline for logs, APIs, and distributed apps. ### Who is affected? * Apps that **display local time** without explicitly converting from UTC. * If your code already normalizes to UTC or explicitly sets a location, you’re likely unaffected. ### Keep the old behavior (Local) If you want the v2 behavior on v3: ```go msgpack.SetDecodedTimeAsLocal() ``` Or convert after the fact: ```go var t time.Time _ = msgpack.Unmarshal(data, &t) t = t.In(time.Local) ``` ### Preview the new behavior on v2 (optional) You can opt into UTC today on v2.x: ```go msgpack.SetDecodedTimeAsUTC() ``` ## Benchmark This result made from [shamaton/msgpack_bench](https://github.com/shamaton/msgpack_bench) ![msgpack_bench](https://github.com/user-attachments/assets/ed5bc4c5-a149-4083-98b8-ee6820c00eae) ## License This library is under the MIT License. ================================================ FILE: codecov.yml ================================================ coverage: status: project: off patch: off ================================================ FILE: crash_test.go ================================================ package msgpack_test import ( "io" "os" "path/filepath" "runtime" "sync" "testing" "github.com/shamaton/msgpack/v3" ) var crashDir = filepath.Join("testdata", "crashers") func TestCrashBinary(t *testing.T) { entries, err := os.ReadDir(crashDir) if err != nil { t.Fatalf("os.ReadDir error. err: %+v", err) } ch := make(chan string, len(entries)) // worker wg := sync.WaitGroup{} for i := 0; i < runtime.NumCPU(); i++ { wg.Add(1) go check(t, &wg, ch) } for _, entry := range entries { ch <- filepath.Join(crashDir, entry.Name()) } close(ch) wg.Wait() } func check(t *testing.T, wg *sync.WaitGroup, ch <-chan string) { var ( path string ok bool data []byte ) t.Helper() defer wg.Done() defer func() { if e := recover(); e != nil { t.Logf("panic occurred.\nfile: %s\nlen: %d\nbin: % x\nerr: %+v", path, len(data), data, e, ) } }() for { path, ok = <-ch // closeされると ok が false になる if !ok { return } file, err := os.Open(path) if err != nil { t.Logf("%s file open error. err: %+v", path, err) t.Fail() return } data, err = io.ReadAll(file) if err != nil { t.Logf("%s io.ReadAll error. err: %+v", path, err) t.Fail() return } var r interface{} err = msgpack.Unmarshal(data, &r) if err == nil { t.Logf("err should be occurred.\nname: %s\nlen: %d\nbin: % x", file.Name(), len(data), data, ) t.Fail() return } path = "" data = nil } } ================================================ FILE: decode.go ================================================ package msgpack import ( "io" "github.com/shamaton/msgpack/v3/internal/decoding" streamdecoding "github.com/shamaton/msgpack/v3/internal/stream/decoding" ) // UnmarshalAsMap decodes data that is encoded as map format. // This is the same thing that StructAsArray sets false. func UnmarshalAsMap(data []byte, v interface{}) error { return decoding.Decode(data, v, false) } // UnmarshalAsArray decodes data that is encoded as array format. // This is the same thing that StructAsArray sets true. func UnmarshalAsArray(data []byte, v interface{}) error { return decoding.Decode(data, v, true) } // UnmarshalReadAsMap decodes from stream. stream data expects map format. // This is the same thing that StructAsArray sets false. func UnmarshalReadAsMap(r io.Reader, v interface{}) error { return streamdecoding.Decode(r, v, false) } // UnmarshalReadAsArray decodes from stream. stream data expects array format. // This is the same thing that StructAsArray sets true. func UnmarshalReadAsArray(r io.Reader, v interface{}) error { return streamdecoding.Decode(r, v, true) } ================================================ FILE: def/def.go ================================================ package def // IntSize : 32 or 64 const IntSize = 32 << (^uint(0) >> 63) var IsIntSize32 = IntSize == 32 // message pack format const ( PositiveFixIntMin = 0x00 PositiveFixIntMax = 0x7f FixMap = 0x80 FixArray = 0x90 FixStr = 0xa0 Nil = 0xc0 False = 0xc2 True = 0xc3 Bin8 = 0xc4 Bin16 = 0xc5 Bin32 = 0xc6 Ext8 = 0xc7 Ext16 = 0xc8 Ext32 = 0xc9 Float32 = 0xca Float64 = 0xcb Uint8 = 0xcc Uint16 = 0xcd Uint32 = 0xce Uint64 = 0xcf Int8 = 0xd0 Int16 = 0xd1 Int32 = 0xd2 Int64 = 0xd3 Fixext1 = 0xd4 Fixext2 = 0xd5 Fixext4 = 0xd6 Fixext8 = 0xd7 Fixext16 = 0xd8 Str8 = 0xd9 Str16 = 0xda Str32 = 0xdb Array16 = 0xdc Array32 = 0xdd Map16 = 0xde Map32 = 0xdf NegativeFixintMin = -32 // 0xe0 NegativeFixintMax = -1 // 0xff ) // byte const ( Byte1 = 1 << iota Byte2 Byte4 Byte8 Byte16 Byte32 ) // ext type const ( TimeStamp = -1 ) // ext type complex var complexTypeCode = int8(-128) // ComplexTypeCode gets complexTypeCode func ComplexTypeCode() int8 { return complexTypeCode } // SetComplexTypeCode sets complexTypeCode func SetComplexTypeCode(code int8) { complexTypeCode = code } ================================================ FILE: def/error.go ================================================ package def import ( "errors" "fmt" ) var ( // base errors ErrMsgpack = errors.New("") // decoding errors ErrNoData = fmt.Errorf("%wno data", ErrMsgpack) ErrHasLeftOver = fmt.Errorf("%wdata has left over", ErrMsgpack) ErrReceiverNotPointer = fmt.Errorf("%wreceiver not pointer", ErrMsgpack) ErrNotMatchArrayElement = fmt.Errorf("%wnot match array element", ErrMsgpack) ErrCanNotDecode = fmt.Errorf("%winvalid code", ErrMsgpack) ErrCanNotSetSliceAsMapKey = fmt.Errorf("%wcan not set slice as map key", ErrMsgpack) ErrCanNotSetMapAsMapKey = fmt.Errorf("%wcan not set map as map key", ErrMsgpack) // encoding errors ErrTooShortBytes = fmt.Errorf("%wtoo short bytes", ErrMsgpack) ErrLackDataLengthToSlice = fmt.Errorf("%wdata length lacks to create slice", ErrMsgpack) ErrLackDataLengthToMap = fmt.Errorf("%wdata length lacks to create map", ErrMsgpack) ErrUnsupportedType = fmt.Errorf("%wunsupported type", ErrMsgpack) ErrUnsupportedLength = fmt.Errorf("%wunsupported length", ErrMsgpack) ErrNotMatchLastIndex = fmt.Errorf("%wnot match last index", ErrMsgpack) ) ================================================ FILE: encode.go ================================================ package msgpack import ( "io" "github.com/shamaton/msgpack/v3/internal/encoding" streamencoding "github.com/shamaton/msgpack/v3/internal/stream/encoding" ) // MarshalAsMap encodes data as map format. // This is the same thing that StructAsArray sets false. func MarshalAsMap(v interface{}) ([]byte, error) { return encoding.Encode(v, false) } // MarshalAsArray encodes data as array format. // This is the same thing that StructAsArray sets true. func MarshalAsArray(v interface{}) ([]byte, error) { return encoding.Encode(v, true) } // MarshalWriteAsMap writes map format encoded data to writer. // This is the same thing that StructAsArray sets false. func MarshalWriteAsMap(w io.Writer, v interface{}) error { return streamencoding.Encode(w, v, false) } // MarshalWriteAsArray writes array format encoded data to writer. // This is the same thing that StructAsArray sets true. func MarshalWriteAsArray(w io.Writer, v interface{}) error { return streamencoding.Encode(w, v, true) } ================================================ FILE: errors.go ================================================ package msgpack import ( "github.com/shamaton/msgpack/v3/def" ) // Error is used in all msgpack error as the based error. var Error = def.ErrMsgpack ================================================ FILE: ext/decode.go ================================================ package ext import ( "reflect" "github.com/shamaton/msgpack/v3/def" ) // Decoder defines an interface for decoding values from bytes. // It provides methods to get the decoder type, check if the data matches the type, // and convert the data into a Go value. type Decoder interface { // Code returns the unique code representing the decoder type. Code() int8 // IsType checks if the data at the given offset matches the expected type. // Returns true if the type matches, false otherwise. IsType(offset int, d *[]byte) bool // AsValue decodes the data at the given offset into a Go value of the specified kind. // Returns the decoded value, the new offset, and an error if decoding fails. AsValue(offset int, k reflect.Kind, d *[]byte) (interface{}, int, error) } // DecoderCommon provides common utility methods for decoding data from bytes. type DecoderCommon struct{} // ReadSize1 reads a single byte from the given index in the byte slice. // Returns the byte and the new index after reading. func (cd *DecoderCommon) ReadSize1(index int, d *[]byte) (byte, int) { rb := def.Byte1 return (*d)[index], index + rb } // ReadSize2 reads two bytes from the given index in the byte slice. // Returns the bytes as a slice and the new index after reading. func (cd *DecoderCommon) ReadSize2(index int, d *[]byte) ([]byte, int) { rb := def.Byte2 return (*d)[index : index+rb], index + rb } // ReadSize4 reads four bytes from the given index in the byte slice. // Returns the bytes as a slice and the new index after reading. func (cd *DecoderCommon) ReadSize4(index int, d *[]byte) ([]byte, int) { rb := def.Byte4 return (*d)[index : index+rb], index + rb } // ReadSize8 reads eight bytes from the given index in the byte slice. // Returns the bytes as a slice and the new index after reading. func (cd *DecoderCommon) ReadSize8(index int, d *[]byte) ([]byte, int) { rb := def.Byte8 return (*d)[index : index+rb], index + rb } // ReadSizeN reads a specified number of bytes (n) from the given index in the byte slice. // Returns the bytes as a slice and the new index after reading. func (cd *DecoderCommon) ReadSizeN(index, n int, d *[]byte) ([]byte, int) { return (*d)[index : index+n], index + n } ================================================ FILE: ext/decoder_stream.go ================================================ package ext import ( "reflect" ) // StreamDecoder defines an interface for decoding streams of data. // It provides methods to retrieve the decoder's code, check type compatibility, // and convert raw data into a Go value of a specified kind. type StreamDecoder interface { // Code returns the unique identifier for the decoder. Code() int8 // IsType checks if the provided code, inner type, and data length match the expected type. // Returns true if the type matches, otherwise false. IsType(code byte, innerType int8, dataLength int) bool // ToValue converts the raw data into a Go value of the specified kind. // Takes the code, raw data, and the target kind as input. // Returns the decoded value or an error if the conversion fails. ToValue(code byte, data []byte, k reflect.Kind) (any, error) } ================================================ FILE: ext/encode.go ================================================ package ext import ( "reflect" ) // Encoder defines an interface for encoding values into bytes. // It provides methods to get the encoding type, calculate the byte size of a value, // and write the encoded value into a byte slice. type Encoder interface { // Code returns the unique code representing the encoder type. Code() int8 // Type returns the reflect.Type of the value that the encoder handles. Type() reflect.Type // CalcByteSize calculates the number of bytes required to encode the given value. // Returns the size and an error if the calculation fails. CalcByteSize(value reflect.Value) (int, error) // WriteToBytes encodes the given value into a byte slice starting at the specified offset. // Returns the new offset after writing the bytes. WriteToBytes(value reflect.Value, offset int, bytes *[]byte) int } // EncoderCommon provides utility methods for encoding various types of values into bytes. // It includes methods to encode integers and unsigned integers of different sizes, // as well as methods to write raw byte slices into a target byte slice. type EncoderCommon struct{} // SetByte1Int64 encodes a single byte from the given int64 value into the byte slice at the specified offset. // Returns the new offset after writing the byte. func (c *EncoderCommon) SetByte1Int64(value int64, offset int, d *[]byte) int { (*d)[offset] = byte(value) return offset + 1 } // SetByte2Int64 encodes the lower two bytes of the given int64 value into the byte slice at the specified offset. // Returns the new offset after writing the bytes. func (c *EncoderCommon) SetByte2Int64(value int64, offset int, d *[]byte) int { (*d)[offset+0] = byte(value >> 8) (*d)[offset+1] = byte(value) return offset + 2 } // SetByte4Int64 encodes the lower four bytes of the given int64 value into the byte slice at the specified offset. // Returns the new offset after writing the bytes. func (c *EncoderCommon) SetByte4Int64(value int64, offset int, d *[]byte) int { (*d)[offset+0] = byte(value >> 24) (*d)[offset+1] = byte(value >> 16) (*d)[offset+2] = byte(value >> 8) (*d)[offset+3] = byte(value) return offset + 4 } // SetByte8Int64 encodes all eight bytes of the given int64 value into the byte slice at the specified offset. // Returns the new offset after writing the bytes. func (c *EncoderCommon) SetByte8Int64(value int64, offset int, d *[]byte) int { (*d)[offset] = byte(value >> 56) (*d)[offset+1] = byte(value >> 48) (*d)[offset+2] = byte(value >> 40) (*d)[offset+3] = byte(value >> 32) (*d)[offset+4] = byte(value >> 24) (*d)[offset+5] = byte(value >> 16) (*d)[offset+6] = byte(value >> 8) (*d)[offset+7] = byte(value) return offset + 8 } // SetByte1Uint64 encodes a single byte from the given uint64 value into the byte slice at the specified offset. // Returns the new offset after writing the byte. func (c *EncoderCommon) SetByte1Uint64(value uint64, offset int, d *[]byte) int { (*d)[offset] = byte(value) return offset + 1 } // SetByte2Uint64 encodes the lower two bytes of the given uint64 value into the byte slice at the specified offset. // Returns the new offset after writing the bytes. func (c *EncoderCommon) SetByte2Uint64(value uint64, offset int, d *[]byte) int { (*d)[offset] = byte(value >> 8) (*d)[offset+1] = byte(value) return offset + 2 } // SetByte4Uint64 encodes the lower four bytes of the given uint64 value into the byte slice at the specified offset. // Returns the new offset after writing the bytes. func (c *EncoderCommon) SetByte4Uint64(value uint64, offset int, d *[]byte) int { (*d)[offset] = byte(value >> 24) (*d)[offset+1] = byte(value >> 16) (*d)[offset+2] = byte(value >> 8) (*d)[offset+3] = byte(value) return offset + 4 } // SetByte8Uint64 encodes all eight bytes of the given uint64 value into the byte slice at the specified offset. // Returns the new offset after writing the bytes. func (c *EncoderCommon) SetByte8Uint64(value uint64, offset int, d *[]byte) int { (*d)[offset] = byte(value >> 56) (*d)[offset+1] = byte(value >> 48) (*d)[offset+2] = byte(value >> 40) (*d)[offset+3] = byte(value >> 32) (*d)[offset+4] = byte(value >> 24) (*d)[offset+5] = byte(value >> 16) (*d)[offset+6] = byte(value >> 8) (*d)[offset+7] = byte(value) return offset + 8 } // SetByte1Int encodes a single byte from the given int value into the byte slice at the specified offset. // Returns the new offset after writing the byte. func (c *EncoderCommon) SetByte1Int(code, offset int, d *[]byte) int { (*d)[offset] = byte(code) return offset + 1 } // SetByte2Int encodes the lower two bytes of the given int value into the byte slice at the specified offset. // Returns the new offset after writing the bytes. func (c *EncoderCommon) SetByte2Int(value int, offset int, d *[]byte) int { (*d)[offset] = byte(value >> 8) (*d)[offset+1] = byte(value) return offset + 2 } // SetByte4Int encodes the lower four bytes of the given int value into the byte slice at the specified offset. // Returns the new offset after writing the bytes. func (c *EncoderCommon) SetByte4Int(value int, offset int, d *[]byte) int { (*d)[offset] = byte(value >> 24) (*d)[offset+1] = byte(value >> 16) (*d)[offset+2] = byte(value >> 8) (*d)[offset+3] = byte(value) return offset + 4 } // SetByte4Uint32 encodes the lower four bytes of the given uint32 value into the byte slice at the specified offset. // Returns the new offset after writing the bytes. func (c *EncoderCommon) SetByte4Uint32(value uint32, offset int, d *[]byte) int { (*d)[offset] = byte(value >> 24) (*d)[offset+1] = byte(value >> 16) (*d)[offset+2] = byte(value >> 8) (*d)[offset+3] = byte(value) return offset + 4 } // SetBytes writes the given byte slice `bs` into the target byte slice at the specified offset. // Returns the new offset after writing the bytes. func (c *EncoderCommon) SetBytes(bs []byte, offset int, d *[]byte) int { for i := range bs { (*d)[offset+i] = bs[i] } return offset + len(bs) } ================================================ FILE: ext/encode_stream.go ================================================ package ext import ( "io" "reflect" "github.com/shamaton/msgpack/v3/internal/common" ) // StreamEncoder is an interface that extended encoders should implement. // It defines methods for encoding data into a stream. type StreamEncoder interface { // Code returns the unique code for the encoder. Code() int8 // Type returns the reflect.Type of the value being encoded. Type() reflect.Type // Write encodes the given value and writes it to the provided StreamWriter. Write(w StreamWriter, value reflect.Value) error } // StreamWriter provides methods for writing data in extended formats. // It wraps an io.Writer and a buffer for efficient writing. type StreamWriter struct { w io.Writer // The underlying writer to write data to. buf *common.Buffer // A buffer used for temporary storage during writing. } // CreateStreamWriter creates and returns a new StreamWriter instance. func CreateStreamWriter(w io.Writer, buf *common.Buffer) StreamWriter { return StreamWriter{w, buf} } // WriteByte1Int64 writes a single byte representation of an int64 value. func (w *StreamWriter) WriteByte1Int64(value int64) error { return w.buf.Write(w.w, byte(value), ) } // WriteByte2Int64 writes a two-byte representation of an int64 value. func (w *StreamWriter) WriteByte2Int64(value int64) error { return w.buf.Write(w.w, byte(value>>8), byte(value), ) } // WriteByte4Int64 writes a four-byte representation of an int64 value. func (w *StreamWriter) WriteByte4Int64(value int64) error { return w.buf.Write(w.w, byte(value>>24), byte(value>>16), byte(value>>8), byte(value), ) } // WriteByte8Int64 writes an eight-byte representation of an int64 value. func (w *StreamWriter) WriteByte8Int64(value int64) error { return w.buf.Write(w.w, byte(value>>56), byte(value>>48), byte(value>>40), byte(value>>32), byte(value>>24), byte(value>>16), byte(value>>8), byte(value), ) } // WriteByte1Uint64 writes a single byte representation of a uint64 value. func (w *StreamWriter) WriteByte1Uint64(value uint64) error { return w.buf.Write(w.w, byte(value), ) } // WriteByte2Uint64 writes a two-byte representation of a uint64 value. func (w *StreamWriter) WriteByte2Uint64(value uint64) error { return w.buf.Write(w.w, byte(value>>8), byte(value), ) } // WriteByte4Uint64 writes a four-byte representation of a uint64 value. func (w *StreamWriter) WriteByte4Uint64(value uint64) error { return w.buf.Write(w.w, byte(value>>24), byte(value>>16), byte(value>>8), byte(value), ) } // WriteByte8Uint64 writes an eight-byte representation of a uint64 value. func (w *StreamWriter) WriteByte8Uint64(value uint64) error { return w.buf.Write(w.w, byte(value>>56), byte(value>>48), byte(value>>40), byte(value>>32), byte(value>>24), byte(value>>16), byte(value>>8), byte(value), ) } // WriteByte1Int writes a single byte representation of an int value. func (w *StreamWriter) WriteByte1Int(value int) error { return w.buf.Write(w.w, byte(value), ) } // WriteByte2Int writes a two-byte representation of an int value. func (w *StreamWriter) WriteByte2Int(value int) error { return w.buf.Write(w.w, byte(value>>8), byte(value), ) } // WriteByte4Int writes a four-byte representation of an int value. func (w *StreamWriter) WriteByte4Int(value int) error { return w.buf.Write(w.w, byte(value>>24), byte(value>>16), byte(value>>8), byte(value), ) } // WriteByte4Uint32 writes a four-byte representation of a uint32 value. func (w *StreamWriter) WriteByte4Uint32(value uint32) error { return w.buf.Write(w.w, byte(value>>24), byte(value>>16), byte(value>>8), byte(value), ) } // WriteBytes writes a slice of bytes to the underlying writer. func (w *StreamWriter) WriteBytes(bs []byte) error { return w.buf.Write(w.w, bs...) } ================================================ FILE: go.mod ================================================ module github.com/shamaton/msgpack/v3 go 1.23 ================================================ FILE: internal/common/buffer.go ================================================ package common import ( "io" "sync" ) type Buffer struct { Data []byte B1 []byte B2 []byte B4 []byte B8 []byte B16 []byte offset int } func (b *Buffer) Write(w io.Writer, vs ...byte) error { if len(b.Data) < b.offset+len(vs) { _, err := w.Write(b.Data[:b.offset]) b.offset = 0 if err != nil { return err } if len(b.Data) < len(vs) { b.Data = append(b.Data, make([]byte, len(vs)-len(b.Data))...) } } for i := range vs { b.Data[b.offset+i] = vs[i] } b.offset += len(vs) return nil } func (b *Buffer) Flush(w io.Writer) error { _, err := w.Write(b.Data[:b.offset]) return err } var bufPool = sync.Pool{ New: func() interface{} { data := make([]byte, 64) return &Buffer{ Data: data, B1: data[:1], B2: data[:2], B4: data[:4], B8: data[:8], B16: data[:16], } }, } func GetBuffer() *Buffer { buf := bufPool.Get().(*Buffer) buf.offset = 0 return buf } func PutBuffer(buf *Buffer) { bufPool.Put(buf) } ================================================ FILE: internal/common/common.go ================================================ package common import ( "reflect" "strings" ) // Common is used encoding/decoding type Common struct{} // FieldInfo holds information about a struct field including its path for embedded structs type FieldInfo struct { Path []int // path to reach this field (indices for embedded structs) Name string // field name or tag Omit bool // omitempty flag Tagged bool // tag name explicitly set OmitPaths [][]int // paths to embedded fields with omitempty } // CollectFields collects all fields from a struct, expanding embedded structs // following the same rules as encoding/json func (c *Common) CollectFields(t reflect.Type, path []int) []FieldInfo { return c.collectFields(t, path, nil) } func (c *Common) collectFields(t reflect.Type, path []int, omitPaths [][]int) []FieldInfo { var fields []FieldInfo var embedded []FieldInfo // embedded fields to process later (lower priority) for i := 0; i < t.NumField(); i++ { field := t.Field(i) // Check field visibility and get omitempty flag public, omit, name := c.CheckField(field) if !public { continue } // Get tag to check if embedded tag := field.Tag.Get("msgpack") // Extract just the name part (before comma if any) tagName := tag for j, ch := range tag { if ch == ',' { tagName = tag[:j] break } } // Check if this is an embedded struct isEmbedded := field.Anonymous && (tag == "" || tagName == "") tagged := tagName != "" if isEmbedded { // Get the actual type (dereference pointer if needed) fieldType := field.Type if fieldType.Kind() == reflect.Ptr { fieldType = fieldType.Elem() } // If it's a struct, expand its fields if fieldType.Kind() == reflect.Struct { newPath := append(append([]int{}, path...), i) nextOmitPaths := omitPaths if omit { nextOmitPaths = appendOmitPath(omitPaths, newPath) } embeddedFields := c.collectFields(fieldType, newPath, nextOmitPaths) embedded = append(embedded, embeddedFields...) continue } } // Regular field or embedded non-struct newPath := append(append([]int{}, path...), i) fields = append(fields, FieldInfo{ Path: newPath, Name: name, Omit: omit, Tagged: tagged, OmitPaths: omitPaths, }) } // Add embedded fields after regular fields (they have lower priority) fields = append(fields, embedded...) // Remove duplicates and handle ambiguous fields return c.deduplicateFields(fields) } func appendOmitPath(paths [][]int, path []int) [][]int { if len(paths) == 0 { return [][]int{path} } newPaths := make([][]int, len(paths)+1) copy(newPaths, paths) newPaths[len(paths)] = path return newPaths } // deduplicateFields removes duplicate fields and handles ambiguous fields // following encoding/json behavior func (c *Common) deduplicateFields(fields []FieldInfo) []FieldInfo { // Group fields by name and depth, preserving order type fieldAtDepth struct { field FieldInfo depth int } fieldsByName := make(map[string][]fieldAtDepth) var seenNames []string // To preserve order for _, f := range fields { if _, seen := fieldsByName[f.Name]; !seen { seenNames = append(seenNames, f.Name) } fieldsByName[f.Name] = append(fieldsByName[f.Name], fieldAtDepth{ field: f, depth: len(f.Path), }) } var result []FieldInfo for _, name := range seenNames { fieldsWithDepth := fieldsByName[name] // Find minimum depth minDepth := fieldsWithDepth[0].depth for _, fd := range fieldsWithDepth { if fd.depth < minDepth { minDepth = fd.depth } } // Count fields at minimum depth var fieldsAtMinDepth []FieldInfo for _, fd := range fieldsWithDepth { if fd.depth == minDepth { fieldsAtMinDepth = append(fieldsAtMinDepth, fd.field) } } // If there's exactly one field at minimum depth, use it if len(fieldsAtMinDepth) == 1 { result = append(result, fieldsAtMinDepth[0]) continue } // Prefer the tagged field if exactly one is tagged at minimum depth var taggedFields []FieldInfo for _, f := range fieldsAtMinDepth { if f.Tagged { taggedFields = append(taggedFields, f) } } if len(taggedFields) == 1 { result = append(result, taggedFields[0]) } // else: ambiguous field, skip it (following encoding/json behavior) } return result } // CheckField returns flag whether should encode/decode or not and field name func (c *Common) CheckField(field reflect.StructField) (public, omit bool, name string) { // A to Z if !c.isPublic(field.Name) { return false, false, "" } tag := field.Tag.Get("msgpack") if tag == "" { return true, false, field.Name } parts := strings.Split(tag, ",") // check ignore if parts[0] == "-" { return false, false, "" } // check omitempty for _, part := range parts[1:] { if part == "omitempty" { omit = true } } // check name name = field.Name if parts[0] != "" { name = parts[0] } return true, omit, name } func (c *Common) isPublic(name string) bool { return 0x41 <= name[0] && name[0] <= 0x5a } ================================================ FILE: internal/common/common_test.go ================================================ package common import ( "reflect" "testing" tu "github.com/shamaton/msgpack/v3/internal/common/testutil" ) func TestCommon_CheckField(t *testing.T) { common := Common{} t.Run("tag:-", func(t *testing.T) { field := reflect.StructField{ Name: "A", Tag: `msgpack:"-"`, } public, omit, v := common.CheckField(field) tu.Equal(t, public, false) tu.Equal(t, omit, false) tu.Equal(t, v, "") }) t.Run("tag:,omitempty", func(t *testing.T) { field := reflect.StructField{ Name: "A", Tag: `msgpack:",omitempty"`, } public, omit, v := common.CheckField(field) tu.Equal(t, public, true) tu.Equal(t, omit, true) tu.Equal(t, v, "A") }) t.Run("tag:-,omitempty", func(t *testing.T) { field := reflect.StructField{ Name: "A", Tag: `msgpack:"-,omitempty"`, } public, omit, v := common.CheckField(field) tu.Equal(t, public, false) tu.Equal(t, omit, false) tu.Equal(t, v, "") }) t.Run("tag:B", func(t *testing.T) { field := reflect.StructField{ Name: "A", Tag: `msgpack:"B"`, } public, omit, v := common.CheckField(field) tu.Equal(t, public, true) tu.Equal(t, omit, false) tu.Equal(t, v, "B") }) t.Run("tag:B,omitempty", func(t *testing.T) { field := reflect.StructField{ Name: "A", Tag: `msgpack:"B,omitempty"`, } public, omit, v := common.CheckField(field) tu.Equal(t, public, true) tu.Equal(t, omit, true) tu.Equal(t, v, "B") }) t.Run("name:A", func(t *testing.T) { field := reflect.StructField{ Name: "A", Tag: `msgpack:""`, } public, omit, v := common.CheckField(field) tu.Equal(t, public, true) tu.Equal(t, omit, false) tu.Equal(t, v, "A") }) t.Run("private", func(t *testing.T) { field := reflect.StructField{ Name: "a", } public, omit, v := common.CheckField(field) tu.Equal(t, public, false) tu.Equal(t, omit, false) tu.Equal(t, v, "") }) } ================================================ FILE: internal/common/testutil/reader.go ================================================ package testutil import ( "errors" "io" ) var ErrReaderErr = errors.New("reader error") type ErrReader struct{} func NewErrReader() *ErrReader { return &ErrReader{} } func (ErrReader) Read(_ []byte) (int, error) { return 0, ErrReaderErr } type TestReader struct { s []byte i int64 // current reading index count int } func NewTestReader(b []byte) *TestReader { return &TestReader{s: b, i: 0, count: 0} } func (r *TestReader) Read(b []byte) (n int, err error) { if r.i >= int64(len(r.s)) { return 0, io.EOF } n = copy(b, r.s[r.i:]) r.i += int64(n) r.count++ return } func (r *TestReader) Count() int { return r.count } ================================================ FILE: internal/common/testutil/struct.go ================================================ package testutil import ( "math" "reflect" "strconv" "github.com/shamaton/msgpack/v3/def" ) // CreateStruct returns a struct that is made dynamically and encoded bytes. func CreateStruct(fieldNum int) (v any, asMapBytes []byte, asArrayBytes []byte) { if fieldNum < 0 { panic("negative field number") } fields := make([]reflect.StructField, 0, fieldNum) asMapBytes = make([]byte, 0, fieldNum*2) asArrayBytes = make([]byte, 0, fieldNum) for i := 0; i < fieldNum; i++ { // create struct field name := "A" + strconv.Itoa(i) typ := reflect.TypeOf(1) field := reflect.StructField{ Name: name, Type: typ, Tag: `json:"B"`, } fields = append(fields, field) // set encoded bytes if len(name) < 32 { asMapBytes = append(asMapBytes, def.FixStr+byte(len(name))) } else if len(name) < math.MaxUint8 { asMapBytes = append(asMapBytes, def.Str8) asMapBytes = append(asMapBytes, byte(len(name))) } for _, c := range name { asMapBytes = append(asMapBytes, byte(c)) } value := byte(i % 0x7f) asMapBytes = append(asMapBytes, value) asArrayBytes = append(asArrayBytes, value) } t := reflect.StructOf(fields) // set field values v = reflect.New(t).Interface() rv := reflect.ValueOf(v) for i := 0; i < rv.Elem().NumField(); i++ { field := rv.Elem().Field(i) field.SetInt(int64(i % 0x7f)) } return v, asMapBytes, asArrayBytes } ================================================ FILE: internal/common/testutil/testutil.go ================================================ package testutil import ( "errors" "reflect" "strings" "testing" ) func NoError(t *testing.T, err error) { t.Helper() if err != nil { t.Fatalf("error is not nil: %v", err) } } func Error(t *testing.T, err error) { t.Helper() if err == nil { t.Fatal(err) } } func IsError(t *testing.T, actual, expected error) { t.Helper() if !errors.Is(actual, expected) { t.Fatalf("not equal. actual: %v, expected: %v", actual, expected) } } func ErrorContains(t *testing.T, err error, errStr string) { t.Helper() if err == nil { t.Fatal("error should occur") } if !strings.Contains(err.Error(), errStr) { t.Fatalf("error does not contain '%s'. err: %v", errStr, err) } } func Equal[T any](t *testing.T, actual, expected T) { t.Helper() if !reflect.DeepEqual(actual, expected) { t.Fatalf("not equal. actual: %v, expected: %v", actual, expected) } } func EqualSlice[T comparable](t *testing.T, actual, expected []T) { t.Helper() if len(actual) != len(expected) { switch a := any(actual).(type) { case []byte: e := any(expected).([]byte) t.Fatalf("diffrent length. actual: [% 02x], expected: [% 02x]", a, e) default: t.Fatalf("diffrent length. actual: %v, expected: %v", actual, expected) } } for i := range actual { if !reflect.DeepEqual(actual[i], expected[i]) { switch a := any(actual).(type) { case []byte: e := any(expected).([]byte) t.Fatalf("not equal. actual: [% 02x], expected: [% 02x]", a, e) default: t.Fatalf("not equal. actual: %v, expected: %v", actual, expected) } } } } func EqualMap[K comparable, V comparable](t *testing.T, actual, expected map[K]V) { t.Helper() if len(actual) != len(expected) { t.Fatalf("diffrent length. actual: %v, expected: %v", actual, expected) } for k, v1 := range actual { if v2, ok := expected[k]; !ok || v1 != v2 { t.Fatalf("not equal. actual: %v, expected: %v", actual, expected) } } } type Equaler[T any] interface { Equal(other T) bool } func EqualEqualer[T Equaler[T]](t *testing.T, actual, expected T) { t.Helper() if !actual.Equal(expected) { t.Fatalf("not equal. actual: %v, expected: %v", actual, expected) } } ================================================ FILE: internal/decoding/bin.go ================================================ package decoding import ( "encoding/binary" "reflect" "unsafe" "github.com/shamaton/msgpack/v3/def" ) func (d *decoder) isCodeBin(v byte) bool { switch v { case def.Bin8, def.Bin16, def.Bin32: return true } return false } func (d *decoder) asBin(offset int, k reflect.Kind) ([]byte, int, error) { code, offset, err := d.readSize1(offset) if err != nil { return emptyBytes, 0, err } switch code { case def.Bin8: l, offset, err := d.readSize1(offset) if err != nil { return emptyBytes, 0, err } v, offset, err := d.readSizeN(offset, int(uint8(l))) if err != nil { return emptyBytes, 0, err } return v, offset, nil case def.Bin16: bs, offset, err := d.readSize2(offset) if err != nil { return emptyBytes, 0, err } v, offset, err := d.readSizeN(offset, int(binary.BigEndian.Uint16(bs))) if err != nil { return emptyBytes, 0, err } return v, offset, nil case def.Bin32: bs, offset, err := d.readSize4(offset) if err != nil { return emptyBytes, 0, err } v, offset, err := d.readSizeN(offset, int(binary.BigEndian.Uint32(bs))) if err != nil { return emptyBytes, 0, err } return v, offset, nil } return emptyBytes, 0, d.errorTemplate(code, k) } func (d *decoder) asBinString(offset int, k reflect.Kind) (string, int, error) { bs, offset, err := d.asBin(offset, k) return *(*string)(unsafe.Pointer(&bs)), offset, err } ================================================ FILE: internal/decoding/bin_test.go ================================================ package decoding import ( "reflect" "testing" "github.com/shamaton/msgpack/v3/def" tu "github.com/shamaton/msgpack/v3/internal/common/testutil" ) func Test_isCodeBin(t *testing.T) { d := decoder{} for i := 0x00; i <= 0xff; i++ { v := byte(i) isBin := v == def.Bin8 || v == def.Bin16 || v == def.Bin32 tu.Equal(t, d.isCodeBin(v), isBin) } } func Test_asBin(t *testing.T) { method := func(d *decoder) func(int, reflect.Kind) ([]byte, int, error) { return d.asBin } testcases := AsXXXTestCases[[]byte]{ { Name: "error.code", Data: []byte{}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Bin8.error.size", Data: []byte{def.Bin8}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Bin8.error.data", Data: []byte{def.Bin8, 1}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Bin8.ok", Data: []byte{def.Bin8, 1, 'a'}, Expected: []byte{'a'}, MethodAs: method, }, { Name: "Bin16.error.size", Data: []byte{def.Bin16}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Bin16.error.data", Data: []byte{def.Bin16, 0, 1}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Bin16.ok", Data: []byte{def.Bin16, 0, 1, 'b'}, Expected: []byte{'b'}, MethodAs: method, }, { Name: "Bin32.error.size", Data: []byte{def.Bin32}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Bin32.error.data", Data: []byte{def.Bin32, 0, 0, 0, 1}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Bin32.ok", Data: []byte{def.Bin32, 0, 0, 0, 1, 'c'}, Expected: []byte{'c'}, MethodAs: method, }, { Name: "Unexpected", Data: []byte{def.Nil}, Error: def.ErrCanNotDecode, MethodAs: method, }, } testcases.Run(t) } ================================================ FILE: internal/decoding/bool.go ================================================ package decoding import ( "reflect" "github.com/shamaton/msgpack/v3/def" ) func (d *decoder) asBool(offset int, k reflect.Kind) (bool, int, error) { code, offset, err := d.readSize1(offset) if err != nil { return false, 0, err } switch code { case def.True: return true, offset, nil case def.False: return false, offset, nil } return false, 0, d.errorTemplate(code, k) } ================================================ FILE: internal/decoding/bool_test.go ================================================ package decoding import ( "reflect" "testing" "github.com/shamaton/msgpack/v3/def" ) func Test_asBool(t *testing.T) { method := func(d *decoder) func(int, reflect.Kind) (bool, int, error) { return d.asBool } testcases := AsXXXTestCases[bool]{ { Name: "error.code", Data: []byte{}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Bool.false", Data: []byte{def.False}, Expected: false, MethodAs: method, }, { Name: "Bool.true", Data: []byte{def.True}, Expected: true, MethodAs: method, }, { Name: "Unexpected", Data: []byte{def.Nil}, Error: def.ErrCanNotDecode, MethodAs: method, }, } testcases.Run(t) } ================================================ FILE: internal/decoding/complex.go ================================================ package decoding import ( "encoding/binary" "fmt" "math" "reflect" "github.com/shamaton/msgpack/v3/def" ) func (d *decoder) asComplex64(offset int, k reflect.Kind) (complex64, int, error) { code, offset, err := d.readSize1(offset) if err != nil { return complex(0, 0), 0, err } switch code { case def.Fixext8: t, offset, err := d.readSize1(offset) if err != nil { return complex(0, 0), 0, err } if int8(t) != def.ComplexTypeCode() { return complex(0, 0), 0, fmt.Errorf("fixext8. complex type is diffrent %d, %d", t, def.ComplexTypeCode()) } rb, offset, err := d.readSize4(offset) if err != nil { return complex(0, 0), 0, err } ib, offset, err := d.readSize4(offset) if err != nil { return complex(0, 0), 0, err } r := math.Float32frombits(binary.BigEndian.Uint32(rb)) i := math.Float32frombits(binary.BigEndian.Uint32(ib)) return complex(r, i), offset, nil case def.Fixext16: t, offset, err := d.readSize1(offset) if err != nil { return complex(0, 0), 0, err } if int8(t) != def.ComplexTypeCode() { return complex(0, 0), 0, fmt.Errorf("fixext16. complex type is diffrent %d, %d", t, def.ComplexTypeCode()) } rb, offset, err := d.readSize8(offset) if err != nil { return complex(0, 0), 0, err } ib, offset, err := d.readSize8(offset) if err != nil { return complex(0, 0), 0, err } r := math.Float64frombits(binary.BigEndian.Uint64(rb)) i := math.Float64frombits(binary.BigEndian.Uint64(ib)) return complex64(complex(r, i)), offset, nil } return complex(0, 0), 0, d.errorTemplate(code, k) } func (d *decoder) asComplex128(offset int, k reflect.Kind) (complex128, int, error) { code, offset, err := d.readSize1(offset) if err != nil { return complex(0, 0), 0, err } switch code { case def.Fixext8: t, offset, err := d.readSize1(offset) if err != nil { return complex(0, 0), 0, err } if int8(t) != def.ComplexTypeCode() { return complex(0, 0), 0, fmt.Errorf("fixext8. complex type is diffrent %d, %d", t, def.ComplexTypeCode()) } rb, offset, err := d.readSize4(offset) if err != nil { return complex(0, 0), 0, err } ib, offset, err := d.readSize4(offset) if err != nil { return complex(0, 0), 0, err } r := math.Float32frombits(binary.BigEndian.Uint32(rb)) i := math.Float32frombits(binary.BigEndian.Uint32(ib)) return complex128(complex(r, i)), offset, nil case def.Fixext16: t, offset, err := d.readSize1(offset) if err != nil { return complex(0, 0), 0, err } if int8(t) != def.ComplexTypeCode() { return complex(0, 0), 0, fmt.Errorf("fixext16. complex type is diffrent %d, %d", t, def.ComplexTypeCode()) } rb, offset, err := d.readSize8(offset) if err != nil { return complex(0, 0), 0, err } ib, offset, err := d.readSize8(offset) if err != nil { return complex(0, 0), 0, err } r := math.Float64frombits(binary.BigEndian.Uint64(rb)) i := math.Float64frombits(binary.BigEndian.Uint64(ib)) return complex(r, i), offset, nil } return complex(0, 0), 0, d.errorTemplate(code, k) } ================================================ FILE: internal/decoding/complex_test.go ================================================ package decoding import ( "reflect" "testing" "github.com/shamaton/msgpack/v3/def" ) func Test_asComplex64(t *testing.T) { method := func(d *decoder) func(int, reflect.Kind) (complex64, int, error) { return d.asComplex64 } testcases := AsXXXTestCases[complex64]{ { Name: "error.code", Data: []byte{}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Fixext8.error.type", Data: []byte{def.Fixext8}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Fixext8.error.r", Data: []byte{def.Fixext8, byte(def.ComplexTypeCode())}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Fixext8.error.i", Data: []byte{def.Fixext8, byte(def.ComplexTypeCode()), 0, 0, 0, 1}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Fixext8.ok", Data: []byte{def.Fixext8, byte(def.ComplexTypeCode()), 63, 128, 0, 0, 63, 128, 0, 0}, Expected: complex(1, 1), MethodAs: method, }, { Name: "Fixext16.error.type", Data: []byte{def.Fixext16}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Fixext16.error.r", Data: []byte{def.Fixext16, byte(def.ComplexTypeCode())}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Fixext16.error.i", Data: []byte{def.Fixext16, byte(def.ComplexTypeCode()), 0, 0, 0, 0, 0, 0, 0, 1}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Fixext16.ok", Data: []byte{ def.Fixext16, byte(def.ComplexTypeCode()), 63, 240, 0, 0, 0, 0, 0, 0, 63, 240, 0, 0, 0, 0, 0, 0, }, Expected: complex(1, 1), MethodAs: method, }, { Name: "Unexpected", Data: []byte{def.Nil}, Error: def.ErrCanNotDecode, MethodAs: method, }, } testcases.Run(t) } func Test_asComplex128(t *testing.T) { method := func(d *decoder) func(int, reflect.Kind) (complex128, int, error) { return d.asComplex128 } testcases := AsXXXTestCases[complex128]{ { Name: "error.code", Data: []byte{}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Fixext8.error.type", Data: []byte{def.Fixext8}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Fixext8.error.r", Data: []byte{def.Fixext8, byte(def.ComplexTypeCode())}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Fixext8.error.i", Data: []byte{def.Fixext8, byte(def.ComplexTypeCode()), 0, 0, 0, 1}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Fixext8.ok", Data: []byte{def.Fixext8, byte(def.ComplexTypeCode()), 63, 128, 0, 0, 63, 128, 0, 0}, Expected: complex(1, 1), MethodAs: method, }, { Name: "Fixext16.error.type", Data: []byte{def.Fixext16}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Fixext16.error.r", Data: []byte{def.Fixext16, byte(def.ComplexTypeCode())}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Fixext16.error.i", Data: []byte{def.Fixext16, byte(def.ComplexTypeCode()), 0, 0, 0, 0, 0, 0, 0, 1}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Fixext16.ok", Data: []byte{ def.Fixext16, byte(def.ComplexTypeCode()), 63, 240, 0, 0, 0, 0, 0, 0, 63, 240, 0, 0, 0, 0, 0, 0, }, Expected: complex(1, 1), MethodAs: method, }, { Name: "Unexpected", Data: []byte{def.Nil}, Error: def.ErrCanNotDecode, MethodAs: method, }, } testcases.Run(t) } ================================================ FILE: internal/decoding/decoding.go ================================================ package decoding import ( "fmt" "reflect" "github.com/shamaton/msgpack/v3/def" "github.com/shamaton/msgpack/v3/internal/common" ) type decoder struct { data []byte asArray bool common.Common } // Decode analyzes the MessagePack-encoded data and stores // the result into the pointer of v. func Decode(data []byte, v interface{}, asArray bool) error { d := decoder{data: data, asArray: asArray} if len(d.data) < 1 { return def.ErrNoData } rv := reflect.ValueOf(v) if rv.Kind() != reflect.Ptr { return fmt.Errorf("%w. v.(type): %T", def.ErrReceiverNotPointer, v) } rv = rv.Elem() last, err := d.decode(rv, 0) if err != nil { return err } if len(data) != last { return fmt.Errorf("%w size=%d, last=%d", def.ErrHasLeftOver, len(data), last) } return err } func (d *decoder) decode(rv reflect.Value, offset int) (int, error) { k := rv.Kind() switch k { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: v, o, err := d.asInt(offset, k) if err != nil { return 0, err } rv.SetInt(v) offset = o case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: v, o, err := d.asUint(offset, k) if err != nil { return 0, err } rv.SetUint(v) offset = o case reflect.Float32: v, o, err := d.asFloat32(offset, k) if err != nil { return 0, err } rv.SetFloat(float64(v)) offset = o case reflect.Float64: v, o, err := d.asFloat64(offset, k) if err != nil { return 0, err } rv.SetFloat(v) offset = o case reflect.String: // byte slice if d.isCodeBin(d.data[offset]) { v, offset, err := d.asBinString(offset, k) if err != nil { return 0, err } rv.SetString(v) return offset, nil } v, o, err := d.asString(offset, k) if err != nil { return 0, err } rv.SetString(v) offset = o case reflect.Bool: v, o, err := d.asBool(offset, k) if err != nil { return 0, err } rv.SetBool(v) offset = o case reflect.Slice: // nil if d.isCodeNil(d.data[offset]) { offset++ return offset, nil } // byte slice if d.isCodeBin(d.data[offset]) { bs, offset, err := d.asBin(offset, k) if err != nil { return 0, err } rv.SetBytes(bs) return offset, nil } // string to bytes if d.isCodeString(d.data[offset]) { l, offset, err := d.stringByteLength(offset, k) if err != nil { return 0, err } bs, offset, err := d.asStringByteByLength(offset, l, k) if err != nil { return 0, err } rv.SetBytes(bs) return offset, nil } // get slice length l, o, err := d.sliceLength(offset, k) if err != nil { return 0, err } if err = d.hasRequiredLeastSliceSize(o, l); err != nil { return 0, err } // check fixed type fixedOffset, found, err := d.asFixedSlice(rv, o, l) if err != nil { return 0, err } if found { return fixedOffset, nil } // create slice dynamically tmpSlice := reflect.MakeSlice(rv.Type(), l, l) for i := 0; i < l; i++ { v := tmpSlice.Index(i) if v.Kind() == reflect.Struct { o, err = d.setStruct(v, o, k) } else { o, err = d.decode(v, o) } if err != nil { return 0, err } } rv.Set(tmpSlice) offset = o case reflect.Complex64: v, o, err := d.asComplex64(offset, k) if err != nil { return 0, err } rv.SetComplex(complex128(v)) offset = o case reflect.Complex128: v, o, err := d.asComplex128(offset, k) if err != nil { return 0, err } rv.SetComplex(v) offset = o case reflect.Array: // nil if d.isCodeNil(d.data[offset]) { offset++ return offset, nil } // byte slice if d.isCodeBin(d.data[offset]) { bs, offset, err := d.asBin(offset, k) if err != nil { return 0, err } if len(bs) > rv.Len() { return 0, fmt.Errorf("%v len is %d, but msgpack has %d elements, %w", rv.Type(), rv.Len(), len(bs), def.ErrNotMatchArrayElement) } for i, b := range bs { rv.Index(i).SetUint(uint64(b)) } return offset, nil } // string to bytes if d.isCodeString(d.data[offset]) { l, offset, err := d.stringByteLength(offset, k) if err != nil { return 0, err } if l > rv.Len() { return 0, fmt.Errorf("%v len is %d, but msgpack has %d elements, %w", rv.Type(), rv.Len(), l, def.ErrNotMatchArrayElement) } bs, offset, err := d.asStringByteByLength(offset, l, k) if err != nil { return 0, err } for i, b := range bs { rv.Index(i).SetUint(uint64(b)) } return offset, nil } // get slice length l, o, err := d.sliceLength(offset, k) if err != nil { return 0, err } if l > rv.Len() { return 0, fmt.Errorf("%v len is %d, but msgpack has %d elements, %w", rv.Type(), rv.Len(), l, def.ErrNotMatchArrayElement) } if err = d.hasRequiredLeastSliceSize(o, l); err != nil { return 0, err } // create array dynamically for i := 0; i < l; i++ { o, err = d.decode(rv.Index(i), o) if err != nil { return 0, err } } offset = o case reflect.Map: // nil if d.isCodeNil(d.data[offset]) { offset++ return offset, nil } // get map length l, o, err := d.mapLength(offset, k) if err != nil { return 0, err } if err = d.hasRequiredLeastMapSize(o, l); err != nil { return 0, err } // check fixed type fixedOffset, found, err := d.asFixedMap(rv, o, l) if err != nil { return 0, err } if found { return fixedOffset, nil } // create dynamically key := rv.Type().Key() value := rv.Type().Elem() if rv.IsNil() { rv.Set(reflect.MakeMapWithSize(rv.Type(), l)) } for i := 0; i < l; i++ { k := reflect.New(key).Elem() v := reflect.New(value).Elem() o, err = d.decode(k, o) if err != nil { return 0, err } o, err = d.decode(v, o) if err != nil { return 0, err } rv.SetMapIndex(k, v) } offset = o case reflect.Struct: o, err := d.setStruct(rv, offset, k) if err != nil { return 0, err } offset = o case reflect.Ptr: // nil if d.isCodeNil(d.data[offset]) { offset++ return offset, nil } if rv.Elem().Kind() == reflect.Invalid { n := reflect.New(rv.Type().Elem()) rv.Set(n) } o, err := d.decode(rv.Elem(), offset) if err != nil { return 0, err } offset = o case reflect.Interface: if rv.Elem().Kind() == reflect.Ptr { o, err := d.decode(rv.Elem(), offset) if err != nil { return 0, err } offset = o } else { v, o, err := d.asInterface(offset, k) if err != nil { return 0, err } if v != nil { rv.Set(reflect.ValueOf(v)) } offset = o } default: return 0, fmt.Errorf("%v is %w type", rv.Kind(), def.ErrUnsupportedType) } return offset, nil } func (d *decoder) errorTemplate(code byte, k reflect.Kind) error { return fmt.Errorf("%w %x decoding as %v", def.ErrCanNotDecode, code, k) } ================================================ FILE: internal/decoding/decoding_test.go ================================================ package decoding import ( "reflect" "testing" "github.com/shamaton/msgpack/v3/def" tu "github.com/shamaton/msgpack/v3/internal/common/testutil" ) type AsXXXTestCase[T any] struct { Name string Data []byte Expected T Error error MethodAs func(d *decoder) func(int, reflect.Kind) (T, int, error) MethodAsCustom func(d *decoder) (int, T, error) } type AsXXXTestCases[T any] []AsXXXTestCase[T] func (tcs AsXXXTestCases[T]) Run(t *testing.T) { for _, tc := range tcs { tc.Run(t) } } func (tc *AsXXXTestCase[T]) Run(t *testing.T) { const kind = reflect.String t.Helper() if tc.MethodAs == nil && tc.MethodAsCustom == nil { t.Fatal("must set either method or MethodAsCustom") } methodAs := func(d *decoder) (T, int, error) { if tc.MethodAs != nil { return tc.MethodAs(d)(0, kind) } if tc.MethodAsCustom != nil { v, o, err := tc.MethodAsCustom(d) return o, v, err } panic("unreachable") } t.Run(tc.Name, func(t *testing.T) { d := decoder{ data: tc.Data, } v, offset, err := methodAs(&d) if tc.Error != nil { tu.IsError(t, err, tc.Error) return } tu.NoError(t, err) tu.Equal(t, v, tc.Expected) tu.Equal(t, offset, len(tc.Data)) }) } func TestDecoding(t *testing.T) { t.Run("empty data", func(t *testing.T) { v := new(int) err := Decode(nil, v, false) tu.IsError(t, err, def.ErrNoData) }) t.Run("not pointer", func(t *testing.T) { v := 0 err := Decode([]byte{def.PositiveFixIntMax}, v, false) tu.IsError(t, err, def.ErrReceiverNotPointer) }) t.Run("left data", func(t *testing.T) { v := new(int) err := Decode([]byte{def.PositiveFixIntMin, 0}, v, false) tu.IsError(t, err, def.ErrHasLeftOver) }) } func Test_decodeWithCode(t *testing.T) { var target any method := func(d *decoder) func(offset int, _ reflect.Kind) (bool, int, error) { return func(offset int, _ reflect.Kind) (bool, int, error) { rv := reflect.ValueOf(target) o, err := d.decode(rv.Elem(), offset) return true, o, err } } t.Run("Int", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error", Data: []byte{def.Int8}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "ok", Data: []byte{def.Int8, 5}, Expected: true, MethodAs: method, }, } v := new(int) target = v testcases.Run(t) tu.Equal(t, *v, 5) }) t.Run("Uint", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error", Data: []byte{def.Uint8}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "ok", Data: []byte{def.Uint8, 5}, Expected: true, MethodAs: method, }, } v := new(uint) target = v testcases.Run(t) tu.Equal(t, *v, 5) }) t.Run("Float32", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error", Data: []byte{def.Float32}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "ok", Data: []byte{def.Float32, 63, 128, 0, 0}, Expected: true, MethodAs: method, }, } v := new(float32) target = v testcases.Run(t) tu.Equal(t, *v, 1) }) t.Run("Float64", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error", Data: []byte{def.Float64}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "ok", Data: []byte{def.Float64, 63, 240, 0, 0, 0, 0, 0, 0}, Expected: true, MethodAs: method, }, } v := new(float64) target = v testcases.Run(t) tu.Equal(t, *v, 1) }) t.Run("BinString", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error", Data: []byte{def.Bin8}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "ok", Data: []byte{def.Bin8, 1, 'a'}, Expected: true, MethodAs: method, }, } v := new(string) target = v testcases.Run(t) tu.Equal(t, *v, "a") }) t.Run("String", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error", Data: []byte{def.Str8}, Expected: false, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "ok", Data: []byte{def.Str8, 1, 'b'}, Expected: true, MethodAs: method, }, } v := new(string) target = v testcases.Run(t) tu.Equal(t, *v, "b") }) t.Run("Bool", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error", Data: []byte{def.Int8}, Error: def.ErrCanNotDecode, MethodAs: method, }, { Name: "ok", Data: []byte{def.True}, Expected: true, MethodAs: method, }, } v := new(bool) target = v testcases.Run(t) tu.Equal(t, *v, true) }) t.Run("Slice.nil", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "ok", Data: []byte{def.Nil}, Expected: true, MethodAs: method, }, } v := new([]int) target = v testcases.Run(t) tu.Equal(t, *v, nil) }) t.Run("Slice.bin", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error", Data: []byte{def.Bin8}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "ok", Data: []byte{def.Bin8, 1, 2}, Expected: true, MethodAs: method, }, } v := new([]byte) target = v testcases.Run(t) tu.Equal(t, *v, []byte{2}) }) t.Run("Slice.string", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error.strlen", Data: []byte{def.Str8}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "error.bytelen", Data: []byte{def.Str8, 1}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "ok", Data: []byte{def.Str8, 1, 'c'}, Expected: true, MethodAs: method, }, } v := new([]byte) target = v testcases.Run(t) tu.Equal(t, *v, []byte{'c'}) }) t.Run("Slice.fixed", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error.strlen", Data: []byte{def.Array16}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "error.require", Data: []byte{def.Array16, 0, 1}, Error: def.ErrLackDataLengthToSlice, MethodAs: method, }, { Name: "error.slice", Data: []byte{def.Array16, 0, 1, def.Int8}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "ok", Data: []byte{def.Array16, 0, 1, def.PositiveFixIntMin + 3}, Expected: true, MethodAs: method, }, } v := new([]int) target = v testcases.Run(t) tu.Equal(t, *v, []int{3}) }) t.Run("Slice.struct", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error.strlen", Data: []byte{def.Array16}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "error.require", Data: []byte{def.Array16, 0, 1}, Error: def.ErrLackDataLengthToSlice, MethodAs: method, }, { Name: "error.slice", Data: []byte{def.Array16, 0, 1, def.Map16}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "error.struct", Data: []byte{def.Array16, 0, 1, def.FixMap + 1, def.FixStr + 1, 'v'}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "ok", Data: []byte{def.Array16, 0, 1, def.FixMap + 1, def.FixStr + 1, 'v', def.PositiveFixIntMin + 3}, Expected: true, MethodAs: method, }, } type st struct { V int `msgpack:"v"` } v := new([]st) target = v testcases.Run(t) tu.Equal(t, *v, []st{{V: 3}}) }) t.Run("Slice.map", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error.strlen", Data: []byte{def.Array16}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "error.require", Data: []byte{def.Array16, 0, 1}, Error: def.ErrLackDataLengthToSlice, MethodAs: method, }, { Name: "error.slice", Data: []byte{def.Array16, 0, 1, def.Map16}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "error.map", Data: []byte{def.Array16, 0, 1, def.FixMap + 1, def.FixStr + 1, 'v'}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "ok", Data: []byte{def.Array16, 0, 1, def.FixMap + 1, def.FixStr + 1, 'v', def.PositiveFixIntMin + 3}, Expected: true, MethodAs: method, }, } v := new([]map[string]int) target = v testcases.Run(t) tu.Equal(t, *v, []map[string]int{{"v": 3}}) }) t.Run("Complex64", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error", Data: []byte{def.Fixext8}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "ok", Data: []byte{def.Fixext8, byte(def.ComplexTypeCode()), 63, 128, 0, 0, 63, 128, 0, 0}, Expected: true, MethodAs: method, }, } v := new(complex64) target = v testcases.Run(t) tu.Equal(t, *v, complex(1, 1)) }) t.Run("Complex128", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error", Data: []byte{def.Fixext8}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "ok", Data: []byte{def.Fixext8, byte(def.ComplexTypeCode()), 63, 128, 0, 0, 63, 128, 0, 0}, Expected: true, MethodAs: method, }, } v := new(complex128) target = v testcases.Run(t) tu.Equal(t, *v, complex(1, 1)) }) t.Run("Array.nil", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "ok", Data: []byte{def.Nil}, Expected: true, MethodAs: method, }, } v := new([1]int) target = v testcases.Run(t) tu.Equal(t, *v, [1]int{}) }) t.Run("Array.bin", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error.bin", Data: []byte{def.Bin8}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "error.len", Data: []byte{def.Bin8, 2, 1, 2}, Error: def.ErrNotMatchArrayElement, MethodAs: method, }, { Name: "ok", Data: []byte{def.Bin8, 1, 2}, Expected: true, MethodAs: method, }, } v := new([1]byte) target = v testcases.Run(t) tu.Equal(t, *v, [1]byte{2}) }) t.Run("Array.string", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error.strlen", Data: []byte{def.Str8}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "error.compare", Data: []byte{def.Str8, 2}, Error: def.ErrNotMatchArrayElement, MethodAs: method, }, { Name: "error.bytelen", Data: []byte{def.Str8, 1}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "ok", Data: []byte{def.Str8, 1, 'c'}, Expected: true, MethodAs: method, }, } v := new([1]byte) target = v testcases.Run(t) tu.Equal(t, *v, [1]byte{'c'}) }) t.Run("Array.struct", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error.strlen", Data: []byte{def.Array16}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "error.len.match", Data: []byte{def.Array16, 0, 2}, Error: def.ErrNotMatchArrayElement, MethodAs: method, }, { Name: "error.slice", Data: []byte{def.Array16, 0, 1}, Error: def.ErrLackDataLengthToSlice, MethodAs: method, }, { Name: "error.struct", Data: []byte{def.Array16, 0, 1, def.FixMap + 1, def.FixStr + 1, 'v'}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "ok", Data: []byte{def.Array16, 0, 1, def.FixMap + 1, def.FixStr + 1, 'v', def.PositiveFixIntMin + 3}, Expected: true, MethodAs: method, }, } type st struct { V int `msgpack:"v"` } v := new([1]st) target = v testcases.Run(t) tu.Equal(t, *v, [1]st{{V: 3}}) }) t.Run("Map.nil", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "ok", Data: []byte{def.Nil}, Expected: true, MethodAs: method, }, } v := new(map[string]int) target = v testcases.Run(t) tu.Equal(t, *v, nil) }) t.Run("Map.fixed", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error.strlen", Data: []byte{def.Map16}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "error.require", Data: []byte{def.Map16, 0, 1}, Error: def.ErrLackDataLengthToMap, MethodAs: method, }, { Name: "error.map", Data: []byte{def.Map16, 0, 1, def.Str16, 0}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "ok", Data: []byte{def.Map16, 0, 1, def.FixStr + 1, 'a', def.PositiveFixIntMin + 3}, Expected: true, MethodAs: method, }, } v := new(map[string]int) target = v testcases.Run(t) tu.Equal(t, *v, map[string]int{"a": 3}) }) t.Run("Map.struct", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error.strlen", Data: []byte{def.Map16}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "error.require", Data: []byte{def.Map16, 0, 1}, Error: def.ErrLackDataLengthToMap, MethodAs: method, }, { Name: "error.key", Data: []byte{def.Map16, 0, 1, def.Str16, 0}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "error.value", Data: []byte{def.Map16, 0, 1, def.FixStr + 1, 'a', def.FixMap + 1, def.FixStr + 1, 'v'}, Expected: true, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "ok", Data: []byte{def.Map16, 0, 1, def.FixStr + 1, 'a', def.FixMap + 1, def.FixStr + 1, 'v', def.PositiveFixIntMin + 3}, Expected: true, MethodAs: method, }, } type st struct { V int `msgpack:"v"` } v := new(map[string]st) target = v testcases.Run(t) tu.Equal(t, *v, map[string]st{"a": {V: 3}}) }) t.Run("Struct", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error", Data: []byte{def.Map16}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "ok", Data: []byte{def.Map16, 0, 1, def.FixStr + 1, 'v', def.PositiveFixIntMin + 3}, Expected: true, MethodAs: method, }, } type st struct { V int `msgpack:"v"` } v := new(st) target = v testcases.Run(t) tu.Equal(t, *v, st{V: 3}) }) t.Run("Ptr.nil", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "ok", Data: []byte{def.Nil}, Expected: true, MethodAs: method, }, } v := new(int) target = &v testcases.Run(t) tu.Equal(t, *v, 0) }) t.Run("Ptr", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error", Data: []byte{def.Int8}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "ok", Data: []byte{def.Int8, 3}, Expected: true, MethodAs: method, }, } v := new(int) target = &v testcases.Run(t) tu.Equal(t, *v, 3) }) t.Run("Interface.ptr", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error", Data: []byte{def.Int8}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "ok", Data: []byte{def.Int8, 3}, Expected: true, MethodAs: method, }, } v := (any)(new(int)) target = &v testcases.Run(t) vv := v.(*int) tu.Equal(t, *vv, 3) }) t.Run("Interface", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error", Data: []byte{def.Map16, 0, 1, def.FixStr + 1, 'v', def.Int8}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "ok", Data: []byte{def.Map16, 0, 1, def.FixStr + 1, 'v', def.Int8, 3}, Expected: true, MethodAs: method, }, } type st struct { V any `msgpack:"v"` } v := new(st) target = v testcases.Run(t) var vv any = int8(3) tu.Equal(t, v.V, vv) }) } ================================================ FILE: internal/decoding/ext.go ================================================ package decoding import ( "encoding/binary" "github.com/shamaton/msgpack/v3/def" "github.com/shamaton/msgpack/v3/ext" "github.com/shamaton/msgpack/v3/time" ) var ( extCoderMap = map[int8]ext.Decoder{time.Decoder.Code(): time.Decoder} extCoders = []ext.Decoder{time.Decoder} ) // AddExtDecoder adds decoders for extension types. func AddExtDecoder(f ext.Decoder) { // ignore time if f.Code() == time.Decoder.Code() { return } _, ok := extCoderMap[f.Code()] if !ok { extCoderMap[f.Code()] = f updateExtCoders() } } // RemoveExtDecoder removes decoders for extension types. func RemoveExtDecoder(f ext.Decoder) { // ignore time if f.Code() == time.Decoder.Code() { return } _, ok := extCoderMap[f.Code()] if ok { delete(extCoderMap, f.Code()) updateExtCoders() } } func updateExtCoders() { extCoders = make([]ext.Decoder, len(extCoderMap)) i := 0 for k := range extCoderMap { extCoders[i] = extCoderMap[k] i++ } } func (d *decoder) extEndOffset(offset int) (bool, int, error) { code, offset, err := d.readSize1(offset) if err != nil { return false, 0, err } return d.extEndOffsetWithCode(code, offset) } func (d *decoder) extEndOffsetWithCode(code byte, offset int) (bool, int, error) { switch code { case def.Fixext1: _, offset, err := d.readSizeN(offset, def.Byte1+def.Byte1) return true, offset, err case def.Fixext2: _, offset, err := d.readSizeN(offset, def.Byte1+def.Byte2) return true, offset, err case def.Fixext4: _, offset, err := d.readSizeN(offset, def.Byte1+def.Byte4) return true, offset, err case def.Fixext8: _, offset, err := d.readSizeN(offset, def.Byte1+def.Byte8) return true, offset, err case def.Fixext16: _, offset, err := d.readSizeN(offset, def.Byte1+def.Byte16) return true, offset, err case def.Ext8: size, offset, err := d.readSize1(offset) if err != nil { return true, 0, err } _, offset, err = d.readSizeN(offset, def.Byte1+int(size)) return true, offset, err case def.Ext16: sizeBytes, offset, err := d.readSize2(offset) if err != nil { return true, 0, err } _, offset, err = d.readSizeN(offset, def.Byte1+int(binary.BigEndian.Uint16(sizeBytes))) return true, offset, err case def.Ext32: sizeBytes, offset, err := d.readSize4(offset) if err != nil { return true, 0, err } _, offset, err = d.readSizeN(offset, def.Byte1+int(binary.BigEndian.Uint32(sizeBytes))) return true, offset, err default: return false, 0, nil } } /* var zero = time.Unix(0,0) func (d *decoder) isDateTime(offset int) bool { code, offset := d.readSize1(offset) if code == def.Fixext4 { t, _ := d.readSize1(offset) return int8(t) == def.TimeStamp } else if code == def.Fixext8 { t, _ := d.readSize1(offset) return int8(t) == def.TimeStamp } else if code == def.Ext8 { l, offset := d.readSize1(offset) t, _ := d.readSize1(offset) return l == 12 && int8(t) == def.TimeStamp } return false } func (d *decoder) asDateTime(offset int, k reflect.Kind) (time.Time, int, error) { code, offset := d.readSize1(offset) switch code { case def.Fixext4: _, offset = d.readSize1(offset) bs, offset := d.readSize4(offset) return time.Unix(int64(binary.BigEndian.Uint32(bs)), 0), offset, nil case def.Fixext8: _, offset = d.readSize1(offset) bs, offset := d.readSize8(offset) data64 := binary.BigEndian.Uint64(bs) nano := int64(data64 >> 34) if nano > 999999999 { return zero, 0, fmt.Errorf("In timestamp 64 formats, nanoseconds must not be larger than 999999999 : %d", nano) } return time.Unix(int64(data64&0x00000003ffffffff), nano), offset, nil case def.Ext8: _, offset = d.readSize1(offset) _, offset = d.readSize1(offset) nanobs, offset := d.readSize4(offset) secbs, offset := d.readSize8(offset) nano := binary.BigEndian.Uint32(nanobs) if nano > 999999999 { return zero, 0, fmt.Errorf("In timestamp 96 formats, nanoseconds must not be larger than 999999999 : %d", nano) } sec := binary.BigEndian.Uint64(secbs) return time.Unix(int64(sec), int64(nano)), offset, nil } return zero, 0, d.errorTemplate(code, k) } */ ================================================ FILE: internal/decoding/ext_test.go ================================================ package decoding import ( "reflect" "testing" "github.com/shamaton/msgpack/v3/def" tu "github.com/shamaton/msgpack/v3/internal/common/testutil" "github.com/shamaton/msgpack/v3/time" ) func Test_AddExtDecoder(t *testing.T) { t.Run("ignore", func(t *testing.T) { AddExtDecoder(time.Decoder) tu.Equal(t, len(extCoders), 1) }) } func Test_RemoveExtDecoder(t *testing.T) { t.Run("ignore", func(t *testing.T) { RemoveExtDecoder(time.Decoder) tu.Equal(t, len(extCoders), 1) }) } type trackingExtDecoder struct { isTypeCalls int asValueCalls int } func (td *trackingExtDecoder) Code() int8 { return 42 } func (td *trackingExtDecoder) IsType(_ int, _ *[]byte) bool { td.isTypeCalls++ return false } func (td *trackingExtDecoder) AsValue(_ int, _ reflect.Kind, _ *[]byte) (interface{}, int, error) { td.asValueCalls++ return nil, 0, ErrTestExtDecoder } func TestExtValidationRejectsTruncatedBytesBeforeCustomDecoders(t *testing.T) { dec := &trackingExtDecoder{} AddExtDecoder(dec) defer RemoveExtDecoder(dec) testcases := []struct { name string data []byte }{ {name: "fixext1", data: []byte{def.Fixext1, 42}}, {name: "fixext2", data: []byte{def.Fixext2, 42, 0}}, {name: "fixext4", data: []byte{def.Fixext4, 42, 0, 0, 0}}, {name: "fixext8", data: []byte{def.Fixext8, 42, 0, 0, 0, 0, 0, 0, 0}}, {name: "fixext16", data: []byte{def.Fixext16, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, {name: "ext8", data: []byte{def.Ext8, 1, 42}}, {name: "ext16", data: []byte{def.Ext16, 0, 1, 42}}, {name: "ext32", data: []byte{def.Ext32, 0, 0, 0, 1, 42}}, } for _, tc := range testcases { t.Run("interface/"+tc.name, func(t *testing.T) { dec.isTypeCalls = 0 dec.asValueCalls = 0 d := decoder{data: tc.data} _, _, err := d.asInterface(0, reflect.Interface) tu.IsError(t, err, def.ErrTooShortBytes) tu.Equal(t, dec.isTypeCalls, 0) tu.Equal(t, dec.asValueCalls, 0) }) t.Run("struct/"+tc.name, func(t *testing.T) { dec.isTypeCalls = 0 dec.asValueCalls = 0 d := decoder{data: tc.data} var v struct{} _, err := d.setStruct(reflect.ValueOf(&v).Elem(), 0, reflect.Struct) tu.IsError(t, err, def.ErrTooShortBytes) tu.Equal(t, dec.isTypeCalls, 0) tu.Equal(t, dec.asValueCalls, 0) }) } } ================================================ FILE: internal/decoding/float.go ================================================ package decoding import ( "encoding/binary" "math" "reflect" "github.com/shamaton/msgpack/v3/def" ) func (d *decoder) asFloat32(offset int, k reflect.Kind) (float32, int, error) { code, _, err := d.readSize1(offset) if err != nil { return 0, 0, err } switch { case code == def.Float32: offset++ bs, offset, err := d.readSize4(offset) if err != nil { return 0, 0, err } v := math.Float32frombits(binary.BigEndian.Uint32(bs)) return v, offset, nil case d.isPositiveFixNum(code), code == def.Uint8, code == def.Uint16, code == def.Uint32, code == def.Uint64: v, offset, err := d.asUint(offset, k) if err != nil { return 0, 0, err } return float32(v), offset, nil case d.isNegativeFixNum(code), code == def.Int8, code == def.Int16, code == def.Int32, code == def.Int64: v, offset, err := d.asInt(offset, k) if err != nil { return 0, 0, err } return float32(v), offset, nil case code == def.Nil: offset++ return 0, offset, nil } return 0, 0, d.errorTemplate(code, k) } func (d *decoder) asFloat64(offset int, k reflect.Kind) (float64, int, error) { code, _, err := d.readSize1(offset) if err != nil { return 0, 0, err } switch { case code == def.Float64: offset++ bs, offset, err := d.readSize8(offset) if err != nil { return 0, 0, err } v := math.Float64frombits(binary.BigEndian.Uint64(bs)) return v, offset, nil case code == def.Float32: offset++ bs, offset, err := d.readSize4(offset) if err != nil { return 0, 0, err } v := math.Float32frombits(binary.BigEndian.Uint32(bs)) return float64(v), offset, nil case d.isPositiveFixNum(code), code == def.Uint8, code == def.Uint16, code == def.Uint32, code == def.Uint64: v, offset, err := d.asUint(offset, k) if err != nil { return 0, 0, err } return float64(v), offset, nil case d.isNegativeFixNum(code), code == def.Int8, code == def.Int16, code == def.Int32, code == def.Int64: v, offset, err := d.asInt(offset, k) if err != nil { return 0, 0, err } return float64(v), offset, nil case code == def.Nil: offset++ return 0, offset, nil } return 0, 0, d.errorTemplate(code, k) } ================================================ FILE: internal/decoding/float_test.go ================================================ package decoding import ( "reflect" "testing" "github.com/shamaton/msgpack/v3/def" ) func Test_asFloat32(t *testing.T) { method := func(d *decoder) func(int, reflect.Kind) (float32, int, error) { return d.asFloat32 } testcases := AsXXXTestCases[float32]{ { Name: "error.code", Data: []byte{}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Float32.error", Data: []byte{def.Float32}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Float32.ok", Data: []byte{def.Float32, 63, 128, 0, 0}, Expected: float32(1), MethodAs: method, }, { Name: "PositiveFixNum.ok", Data: []byte{def.PositiveFixIntMin + 1}, Expected: float32(1), MethodAs: method, }, { Name: "Uint8.error", Data: []byte{def.Uint8}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Uint8.ok", Data: []byte{def.Uint8, 1}, Expected: float32(1), MethodAs: method, }, { Name: "Uint16.error", Data: []byte{def.Uint16}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Uint16.ok", Data: []byte{def.Uint16, 0, 1}, Expected: float32(1), MethodAs: method, }, { Name: "Uint32.error", Data: []byte{def.Uint32}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Uint32.ok", Data: []byte{def.Uint32, 0, 0, 0, 1}, Expected: float32(1), MethodAs: method, }, { Name: "Uint64.error", Data: []byte{def.Uint64}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Uint64.ok", Data: []byte{def.Uint64, 0, 0, 0, 0, 0, 0, 0, 1}, Expected: float32(1), MethodAs: method, }, { Name: "NegativeFixNum.ok", Data: []byte{0xff}, Expected: float32(-1), MethodAs: method, }, { Name: "Int8.error", Data: []byte{def.Int8}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Int8.ok", Data: []byte{def.Int8, 0xff}, Expected: float32(-1), MethodAs: method, }, { Name: "Int16.error", Data: []byte{def.Int16}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Int16.ok", Data: []byte{def.Int16, 0xff, 0xff}, Expected: float32(-1), MethodAs: method, }, { Name: "Int32.error", Data: []byte{def.Int32}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Int32.ok", Data: []byte{def.Int32, 0xff, 0xff, 0xff, 0xff}, Expected: float32(-1), MethodAs: method, }, { Name: "Int64.error", Data: []byte{def.Int64}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Int64.ok", Data: []byte{def.Int64, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, Expected: float32(-1), MethodAs: method, }, { Name: "Nil.ok", Data: []byte{def.Nil}, Expected: float32(0), MethodAs: method, }, { Name: "Unexpected", Data: []byte{def.Str8}, Error: def.ErrCanNotDecode, MethodAs: method, }, } testcases.Run(t) } func Test_asFloat64(t *testing.T) { method := func(d *decoder) func(int, reflect.Kind) (float64, int, error) { return d.asFloat64 } testcases := AsXXXTestCases[float64]{ { Name: "error.code", Data: []byte{}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Float64.error", Data: []byte{def.Float64}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Float64.ok", Data: []byte{def.Float64, 63, 240, 0, 0, 0, 0, 0, 0}, Expected: float64(1), MethodAs: method, }, { Name: "Float32.error", Data: []byte{def.Float32}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Float32.ok", Data: []byte{def.Float32, 63, 128, 0, 0}, Expected: float64(1), MethodAs: method, }, { Name: "PositiveFixNum.ok", Data: []byte{def.PositiveFixIntMin + 1}, Expected: float64(1), MethodAs: method, }, { Name: "Uint8.error", Data: []byte{def.Uint8}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Uint8.ok", Data: []byte{def.Uint8, 1}, Expected: float64(1), MethodAs: method, }, { Name: "Uint16.error", Data: []byte{def.Uint16}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Uint16.ok", Data: []byte{def.Uint16, 0, 1}, Expected: float64(1), MethodAs: method, }, { Name: "Uint32.error", Data: []byte{def.Uint32}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Uint32.ok", Data: []byte{def.Uint32, 0, 0, 0, 1}, Expected: float64(1), MethodAs: method, }, { Name: "Uint64.error", Data: []byte{def.Uint64}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Uint64.ok", Data: []byte{def.Uint64, 0, 0, 0, 0, 0, 0, 0, 1}, Expected: float64(1), MethodAs: method, }, { Name: "NegativeFixNum.ok", Data: []byte{0xff}, Expected: float64(-1), MethodAs: method, }, { Name: "Int8.error", Data: []byte{def.Int8}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Int8.ok", Data: []byte{def.Int8, 0xff}, Expected: float64(-1), MethodAs: method, }, { Name: "Int16.error", Data: []byte{def.Int16}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Int16.ok", Data: []byte{def.Int16, 0xff, 0xff}, Expected: float64(-1), MethodAs: method, }, { Name: "Int32.error", Data: []byte{def.Int32}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Int32.ok", Data: []byte{def.Int32, 0xff, 0xff, 0xff, 0xff}, Expected: float64(-1), MethodAs: method, }, { Name: "Int64.error", Data: []byte{def.Int64}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Int64.ok", Data: []byte{def.Int64, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, Expected: float64(-1), MethodAs: method, }, { Name: "Nil.ok", Data: []byte{def.Nil}, Expected: float64(0), MethodAs: method, }, { Name: "Unexpected", Data: []byte{def.Str8}, Error: def.ErrCanNotDecode, MethodAs: method, }, } testcases.Run(t) } ================================================ FILE: internal/decoding/int.go ================================================ package decoding import ( "encoding/binary" "reflect" "github.com/shamaton/msgpack/v3/def" ) func (d *decoder) isPositiveFixNum(v byte) bool { return def.PositiveFixIntMin <= v && v <= def.PositiveFixIntMax } func (d *decoder) isNegativeFixNum(v byte) bool { return def.NegativeFixintMin <= int8(v) && int8(v) <= def.NegativeFixintMax } func (d *decoder) asInt(offset int, k reflect.Kind) (int64, int, error) { code, _, err := d.readSize1(offset) if err != nil { return 0, 0, err } switch { case d.isPositiveFixNum(code): b, offset, err := d.readSize1(offset) if err != nil { return 0, 0, err } return int64(b), offset, nil case d.isNegativeFixNum(code): b, offset, err := d.readSize1(offset) if err != nil { return 0, 0, err } return int64(int8(b)), offset, nil case code == def.Uint8: offset++ b, offset, err := d.readSize1(offset) if err != nil { return 0, 0, err } return int64(uint8(b)), offset, nil case code == def.Int8: offset++ b, offset, err := d.readSize1(offset) if err != nil { return 0, 0, err } return int64(int8(b)), offset, nil case code == def.Uint16: offset++ bs, offset, err := d.readSize2(offset) if err != nil { return 0, 0, err } v := binary.BigEndian.Uint16(bs) return int64(v), offset, nil case code == def.Int16: offset++ bs, offset, err := d.readSize2(offset) if err != nil { return 0, 0, err } v := int16(binary.BigEndian.Uint16(bs)) return int64(v), offset, nil case code == def.Uint32: offset++ bs, offset, err := d.readSize4(offset) if err != nil { return 0, 0, err } v := binary.BigEndian.Uint32(bs) return int64(v), offset, nil case code == def.Int32: offset++ bs, offset, err := d.readSize4(offset) if err != nil { return 0, 0, err } v := int32(binary.BigEndian.Uint32(bs)) return int64(v), offset, nil case code == def.Uint64: offset++ bs, offset, err := d.readSize8(offset) if err != nil { return 0, 0, err } return int64(binary.BigEndian.Uint64(bs)), offset, nil case code == def.Int64: offset++ bs, offset, err := d.readSize8(offset) if err != nil { return 0, 0, err } return int64(binary.BigEndian.Uint64(bs)), offset, nil case code == def.Float32: v, offset, err := d.asFloat32(offset, k) if err != nil { return 0, 0, err } return int64(v), offset, nil case code == def.Float64: v, offset, err := d.asFloat64(offset, k) if err != nil { return 0, 0, err } return int64(v), offset, nil case code == def.Nil: offset++ return 0, offset, nil } return 0, 0, d.errorTemplate(code, k) } ================================================ FILE: internal/decoding/int_test.go ================================================ package decoding import ( "reflect" "testing" "github.com/shamaton/msgpack/v3/def" ) func Test_asInt(t *testing.T) { method := func(d *decoder) func(int, reflect.Kind) (int64, int, error) { return d.asInt } testcases := AsXXXTestCases[int64]{ { Name: "error.code", Data: []byte{}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "PositiveFixNum.ok", Data: []byte{def.PositiveFixIntMin + 1}, Expected: int64(1), MethodAs: method, }, { Name: "Uint8.error", Data: []byte{def.Uint8}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Uint8.ok", Data: []byte{def.Uint8, 1}, Expected: int64(1), MethodAs: method, }, { Name: "Uint16.error", Data: []byte{def.Uint16}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Uint16.ok", Data: []byte{def.Uint16, 0, 1}, Expected: int64(1), MethodAs: method, }, { Name: "Uint32.error", Data: []byte{def.Uint32}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Uint32.ok", Data: []byte{def.Uint32, 0, 0, 0, 1}, Expected: int64(1), MethodAs: method, }, { Name: "Uint64.error", Data: []byte{def.Uint64}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Uint64.ok", Data: []byte{def.Uint64, 0, 0, 0, 0, 0, 0, 0, 1}, Expected: int64(1), MethodAs: method, }, { Name: "NegativeFixNum.ok", Data: []byte{0xff}, Expected: int64(-1), MethodAs: method, }, { Name: "Int8.error", Data: []byte{def.Int8}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Int8.ok", Data: []byte{def.Int8, 0xff}, Expected: int64(-1), MethodAs: method, }, { Name: "Int16.error", Data: []byte{def.Int16}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Int16.ok", Data: []byte{def.Int16, 0xff, 0xff}, Expected: int64(-1), MethodAs: method, }, { Name: "Int32.error", Data: []byte{def.Int32}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Int32.ok", Data: []byte{def.Int32, 0xff, 0xff, 0xff, 0xff}, Expected: int64(-1), MethodAs: method, }, { Name: "Int64.error", Data: []byte{def.Int64}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Int64.ok", Data: []byte{def.Int64, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, Expected: int64(-1), MethodAs: method, }, { Name: "Float32.error", Data: []byte{def.Float32}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Float32.ok", Data: []byte{def.Float32, 63, 128, 0, 0}, Expected: int64(1), MethodAs: method, }, { Name: "Float64.error", Data: []byte{def.Float64}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Float64.ok", Data: []byte{def.Float64, 63, 240, 0, 0, 0, 0, 0, 0}, Expected: int64(1), MethodAs: method, }, { Name: "Nil.ok", Data: []byte{def.Nil}, Expected: int64(0), MethodAs: method, }, { Name: "Unexpected", Data: []byte{def.Str8}, Error: def.ErrCanNotDecode, MethodAs: method, }, } testcases.Run(t) } ================================================ FILE: internal/decoding/interface.go ================================================ package decoding import ( "fmt" "reflect" "github.com/shamaton/msgpack/v3/def" ) func (d *decoder) asInterface(offset int, k reflect.Kind) (interface{}, int, error) { code, _, err := d.readSize1(offset) if err != nil { return 0, 0, err } switch { case code == def.Nil: offset++ return nil, offset, nil case code == def.True, code == def.False: v, offset, err := d.asBool(offset, k) if err != nil { return nil, 0, err } return v, offset, nil case d.isPositiveFixNum(code), code == def.Uint8: v, offset, err := d.asUint(offset, k) if err != nil { return nil, 0, err } return uint8(v), offset, err case code == def.Uint16: v, offset, err := d.asUint(offset, k) if err != nil { return nil, 0, err } return uint16(v), offset, err case code == def.Uint32: v, offset, err := d.asUint(offset, k) if err != nil { return nil, 0, err } return uint32(v), offset, err case code == def.Uint64: v, offset, err := d.asUint(offset, k) if err != nil { return nil, 0, err } return v, offset, err case d.isNegativeFixNum(code), code == def.Int8: v, offset, err := d.asInt(offset, k) if err != nil { return nil, 0, err } return int8(v), offset, err case code == def.Int16: v, offset, err := d.asInt(offset, k) if err != nil { return nil, 0, err } return int16(v), offset, err case code == def.Int32: v, offset, err := d.asInt(offset, k) if err != nil { return nil, 0, err } return int32(v), offset, err case code == def.Int64: v, offset, err := d.asInt(offset, k) if err != nil { return nil, 0, err } return v, offset, err case code == def.Float32: v, offset, err := d.asFloat32(offset, k) if err != nil { return nil, 0, err } return v, offset, err case code == def.Float64: v, offset, err := d.asFloat64(offset, k) if err != nil { return nil, 0, err } return v, offset, err case d.isFixString(code), code == def.Str8, code == def.Str16, code == def.Str32: v, offset, err := d.asString(offset, k) if err != nil { return nil, 0, err } return v, offset, err case code == def.Bin8, code == def.Bin16, code == def.Bin32: v, offset, err := d.asBin(offset, k) if err != nil { return nil, 0, err } return v, offset, err case d.isFixSlice(code), code == def.Array16, code == def.Array32: l, o, err := d.sliceLength(offset, k) if err != nil { return nil, 0, err } if err = d.hasRequiredLeastSliceSize(o, l); err != nil { return nil, 0, err } v := make([]interface{}, l) for i := 0; i < l; i++ { vv, o2, err := d.asInterface(o, k) if err != nil { return nil, 0, err } v[i] = vv o = o2 } offset = o return v, offset, nil case d.isFixMap(code), code == def.Map16, code == def.Map32: l, o, err := d.mapLength(offset, k) if err != nil { return nil, 0, err } if err = d.hasRequiredLeastMapSize(o, l); err != nil { return nil, 0, err } v := make(map[interface{}]interface{}, l) for i := 0; i < l; i++ { if err := d.canSetAsMapKey(o); err != nil { return nil, 0, err } key, o2, err := d.asInterface(o, k) if err != nil { return nil, 0, err } value, o2, err := d.asInterface(o2, k) if err != nil { return nil, 0, err } v[key] = value o = o2 } offset = o return v, offset, nil } /* use ext if d.isDateTime(offset) { v, offset, err := d.asDateTime(offset, k) if err != nil { return nil, 0, err } return v, offset, nil } */ // ext isExt, _, err := d.extEndOffset(offset) if err != nil { return nil, 0, err } if isExt { for i := range extCoders { if extCoders[i].IsType(offset, &d.data) { v, offset, err := extCoders[i].AsValue(offset, k, &d.data) if err != nil { return nil, 0, err } return v, offset, nil } } } return nil, 0, d.errorTemplate(code, k) } func (d *decoder) canSetAsMapKey(index int) error { code, _, err := d.readSize1(index) if err != nil { return err } switch { case d.isFixSlice(code), code == def.Array16, code == def.Array32: return fmt.Errorf("%w. code: %x", def.ErrCanNotSetSliceAsMapKey, code) case d.isFixMap(code), code == def.Map16, code == def.Map32: return fmt.Errorf("%w. code: %x", def.ErrCanNotSetMapAsMapKey, code) } return nil } ================================================ FILE: internal/decoding/interface_test.go ================================================ package decoding import ( "fmt" "reflect" "testing" "github.com/shamaton/msgpack/v3/def" "github.com/shamaton/msgpack/v3/ext" ) func Test_asInterfaceWithCode(t *testing.T) { dec := testExt2Decoder{} AddExtDecoder(&dec) defer RemoveExtDecoder(&dec) method := func(d *decoder) func(int, reflect.Kind) (any, int, error) { return d.asInterface } testcases := AsXXXTestCases[any]{ { Name: "error.code", Data: []byte{}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Uint8.error", Data: []byte{def.Uint8}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Uint16.error", Data: []byte{def.Uint16}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Uint32.error", Data: []byte{def.Uint32}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Uint64.error", Data: []byte{def.Uint64}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Int8.error", Data: []byte{def.Int8}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Int16.error", Data: []byte{def.Int16}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Int32.error", Data: []byte{def.Int32}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Int64.error", Data: []byte{def.Int64}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Float32.error", Data: []byte{def.Float32}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Float64.error", Data: []byte{def.Float64}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Str.error", Data: []byte{def.Str8}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Bin.error", Data: []byte{def.Bin8}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Array.error.length", Data: []byte{def.Array16}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Array.error.required", Data: []byte{def.Array16, 0, 1}, Error: def.ErrLackDataLengthToSlice, MethodAs: method, }, { Name: "Array.error.set", Data: []byte{def.Array16, 0, 1, def.Int8}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Map.error.length", Data: []byte{def.Map16}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Map.error.required", Data: []byte{def.Map16, 0, 1}, Error: def.ErrLackDataLengthToMap, MethodAs: method, }, { Name: "Map.error.set.can.slice", Data: []byte{def.Map16, 0, 1, def.Array16, 0}, Error: def.ErrCanNotSetSliceAsMapKey, MethodAs: method, }, { Name: "Map.error.set.can.map", Data: []byte{def.Map16, 0, 1, def.Map16, 0}, Error: def.ErrCanNotSetMapAsMapKey, MethodAs: method, }, { Name: "Map.error.set.key", Data: []byte{def.Map16, 0, 1, def.Str8, 1}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Map.error.set.value", Data: []byte{def.Map16, 0, 1, def.FixStr + 1, 'a'}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "ExtCoder.error.truncated", Data: []byte{def.Fixext1, 3}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "ExtCoder.error", Data: []byte{def.Fixext1, 3, 0}, Error: ErrTestExtDecoder, MethodAs: method, }, } for _, tc := range testcases { tc.Run(t) } } // TODO: to testutil type testExt2Decoder struct { ext.DecoderCommon } var _ ext.Decoder = (*testExt2Decoder)(nil) func (td *testExt2Decoder) Code() int8 { return 3 } func (td *testExt2Decoder) IsType(o int, d *[]byte) bool { // todo : lack of error handling code, _ := td.ReadSize1(o, d) if code == def.Fixext1 { extCode, _ := td.ReadSize1(o+1, d) return int8(extCode) == td.Code() } return false } var ErrTestExtDecoder = fmt.Errorf("testExtDecoder") func (td *testExt2Decoder) AsValue(_ int, _ reflect.Kind, _ *[]byte) (any, int, error) { return nil, 0, ErrTestExtDecoder } ================================================ FILE: internal/decoding/map.go ================================================ package decoding import ( "encoding/binary" "reflect" "github.com/shamaton/msgpack/v3/def" ) var ( typeMapStringInt = reflect.TypeOf(map[string]int{}) typeMapStringInt8 = reflect.TypeOf(map[string]int8{}) typeMapStringInt16 = reflect.TypeOf(map[string]int16{}) typeMapStringInt32 = reflect.TypeOf(map[string]int32{}) typeMapStringInt64 = reflect.TypeOf(map[string]int64{}) typeMapStringUint = reflect.TypeOf(map[string]uint{}) typeMapStringUint8 = reflect.TypeOf(map[string]uint8{}) typeMapStringUint16 = reflect.TypeOf(map[string]uint16{}) typeMapStringUint32 = reflect.TypeOf(map[string]uint32{}) typeMapStringUint64 = reflect.TypeOf(map[string]uint64{}) typeMapStringFloat32 = reflect.TypeOf(map[string]float32{}) typeMapStringFloat64 = reflect.TypeOf(map[string]float64{}) typeMapStringBool = reflect.TypeOf(map[string]bool{}) typeMapStringString = reflect.TypeOf(map[string]string{}) typeMapIntString = reflect.TypeOf(map[int]string{}) typeMapInt8String = reflect.TypeOf(map[int8]string{}) typeMapInt16String = reflect.TypeOf(map[int16]string{}) typeMapInt32String = reflect.TypeOf(map[int32]string{}) typeMapInt64String = reflect.TypeOf(map[int64]string{}) typeMapIntBool = reflect.TypeOf(map[int]bool{}) typeMapInt8Bool = reflect.TypeOf(map[int8]bool{}) typeMapInt16Bool = reflect.TypeOf(map[int16]bool{}) typeMapInt32Bool = reflect.TypeOf(map[int32]bool{}) typeMapInt64Bool = reflect.TypeOf(map[int64]bool{}) typeMapUintString = reflect.TypeOf(map[uint]string{}) typeMapUint8String = reflect.TypeOf(map[uint8]string{}) typeMapUint16String = reflect.TypeOf(map[uint16]string{}) typeMapUint32String = reflect.TypeOf(map[uint32]string{}) typeMapUint64String = reflect.TypeOf(map[uint64]string{}) typeMapUintBool = reflect.TypeOf(map[uint]bool{}) typeMapUint8Bool = reflect.TypeOf(map[uint8]bool{}) typeMapUint16Bool = reflect.TypeOf(map[uint16]bool{}) typeMapUint32Bool = reflect.TypeOf(map[uint32]bool{}) typeMapUint64Bool = reflect.TypeOf(map[uint64]bool{}) typeMapFloat32String = reflect.TypeOf(map[float32]string{}) typeMapFloat64String = reflect.TypeOf(map[float64]string{}) typeMapFloat32Bool = reflect.TypeOf(map[float32]bool{}) typeMapFloat64Bool = reflect.TypeOf(map[float64]bool{}) ) func (d *decoder) isFixMap(v byte) bool { return def.FixMap <= v && v <= def.FixMap+0x0f } func (d *decoder) mapLength(offset int, k reflect.Kind) (int, int, error) { code, offset, err := d.readSize1(offset) if err != nil { return 0, 0, err } switch { case d.isFixMap(code): return int(code - def.FixMap), offset, nil case code == def.Map16: bs, offset, err := d.readSize2(offset) if err != nil { return 0, 0, err } return int(binary.BigEndian.Uint16(bs)), offset, nil case code == def.Map32: bs, offset, err := d.readSize4(offset) if err != nil { return 0, 0, err } return int(binary.BigEndian.Uint32(bs)), offset, nil } return 0, 0, d.errorTemplate(code, k) } func (d *decoder) hasRequiredLeastMapSize(offset, length int) error { // minimum check (byte length) if len(d.data[offset:]) < length*2 { return def.ErrLackDataLengthToMap } return nil } func (d *decoder) asFixedMap(rv reflect.Value, offset int, l int) (int, bool, error) { t := rv.Type() keyKind := t.Key().Kind() valueKind := t.Elem().Kind() switch t { case typeMapStringInt: m := make(map[string]int, l) for i := 0; i < l; i++ { k, o, err := d.asString(offset, keyKind) if err != nil { return 0, false, err } v, o, err := d.asInt(o, valueKind) if err != nil { return 0, false, err } m[k] = int(v) offset = o } rv.Set(reflect.ValueOf(m)) return offset, true, nil case typeMapStringUint: m := make(map[string]uint, l) for i := 0; i < l; i++ { k, o, err := d.asString(offset, keyKind) if err != nil { return 0, false, err } v, o, err := d.asUint(o, valueKind) if err != nil { return 0, false, err } m[k] = uint(v) offset = o } rv.Set(reflect.ValueOf(m)) return offset, true, nil case typeMapStringFloat32: m := make(map[string]float32, l) for i := 0; i < l; i++ { k, o, err := d.asString(offset, keyKind) if err != nil { return 0, false, err } v, o, err := d.asFloat32(o, valueKind) if err != nil { return 0, false, err } m[k] = v offset = o } rv.Set(reflect.ValueOf(m)) return offset, true, nil case typeMapStringFloat64: m := make(map[string]float64, l) for i := 0; i < l; i++ { k, o, err := d.asString(offset, keyKind) if err != nil { return 0, false, err } v, o, err := d.asFloat64(o, valueKind) if err != nil { return 0, false, err } m[k] = v offset = o } rv.Set(reflect.ValueOf(m)) return offset, true, nil case typeMapStringBool: m := make(map[string]bool, l) for i := 0; i < l; i++ { k, o, err := d.asString(offset, keyKind) if err != nil { return 0, false, err } v, o, err := d.asBool(o, valueKind) if err != nil { return 0, false, err } m[k] = v offset = o } rv.Set(reflect.ValueOf(m)) return offset, true, nil case typeMapStringString: m := make(map[string]string, l) for i := 0; i < l; i++ { k, o, err := d.asString(offset, keyKind) if err != nil { return 0, false, err } v, o, err := d.asString(o, valueKind) if err != nil { return 0, false, err } m[k] = v offset = o } rv.Set(reflect.ValueOf(m)) return offset, true, nil case typeMapStringInt8: m := make(map[string]int8, l) for i := 0; i < l; i++ { k, o, err := d.asString(offset, keyKind) if err != nil { return 0, false, err } v, o, err := d.asInt(o, valueKind) if err != nil { return 0, false, err } m[k] = int8(v) offset = o } rv.Set(reflect.ValueOf(m)) return offset, true, nil case typeMapStringInt16: m := make(map[string]int16, l) for i := 0; i < l; i++ { k, o, err := d.asString(offset, keyKind) if err != nil { return 0, false, err } v, o, err := d.asInt(o, valueKind) if err != nil { return 0, false, err } m[k] = int16(v) offset = o } rv.Set(reflect.ValueOf(m)) return offset, true, nil case typeMapStringInt32: m := make(map[string]int32, l) for i := 0; i < l; i++ { k, o, err := d.asString(offset, keyKind) if err != nil { return 0, false, err } v, o, err := d.asInt(o, valueKind) if err != nil { return 0, false, err } m[k] = int32(v) offset = o } rv.Set(reflect.ValueOf(m)) return offset, true, nil case typeMapStringInt64: m := make(map[string]int64, l) for i := 0; i < l; i++ { k, o, err := d.asString(offset, keyKind) if err != nil { return 0, false, err } v, o, err := d.asInt(o, valueKind) if err != nil { return 0, false, err } m[k] = v offset = o } rv.Set(reflect.ValueOf(m)) return offset, true, nil case typeMapStringUint8: m := make(map[string]uint8, l) for i := 0; i < l; i++ { k, o, err := d.asString(offset, keyKind) if err != nil { return 0, false, err } v, o, err := d.asUint(o, valueKind) if err != nil { return 0, false, err } m[k] = uint8(v) offset = o } rv.Set(reflect.ValueOf(m)) return offset, true, nil case typeMapStringUint16: m := make(map[string]uint16, l) for i := 0; i < l; i++ { k, o, err := d.asString(offset, keyKind) if err != nil { return 0, false, err } v, o, err := d.asUint(o, valueKind) if err != nil { return 0, false, err } m[k] = uint16(v) offset = o } rv.Set(reflect.ValueOf(m)) return offset, true, nil case typeMapStringUint32: m := make(map[string]uint32, l) for i := 0; i < l; i++ { k, o, err := d.asString(offset, keyKind) if err != nil { return 0, false, err } v, o, err := d.asUint(o, valueKind) if err != nil { return 0, false, err } m[k] = uint32(v) offset = o } rv.Set(reflect.ValueOf(m)) return offset, true, nil case typeMapStringUint64: m := make(map[string]uint64, l) for i := 0; i < l; i++ { k, o, err := d.asString(offset, keyKind) if err != nil { return 0, false, err } v, o, err := d.asUint(o, valueKind) if err != nil { return 0, false, err } m[k] = v offset = o } rv.Set(reflect.ValueOf(m)) return offset, true, nil case typeMapIntString: m := make(map[int]string, l) for i := 0; i < l; i++ { k, o, err := d.asInt(offset, keyKind) if err != nil { return 0, false, err } v, o, err := d.asString(o, valueKind) if err != nil { return 0, false, err } m[int(k)] = v offset = o } rv.Set(reflect.ValueOf(m)) return offset, true, nil case typeMapInt8String: m := make(map[int8]string, l) for i := 0; i < l; i++ { k, o, err := d.asInt(offset, keyKind) if err != nil { return 0, false, err } v, o, err := d.asString(o, valueKind) if err != nil { return 0, false, err } m[int8(k)] = v offset = o } rv.Set(reflect.ValueOf(m)) return offset, true, nil case typeMapInt16String: m := make(map[int16]string, l) for i := 0; i < l; i++ { k, o, err := d.asInt(offset, keyKind) if err != nil { return 0, false, err } v, o, err := d.asString(o, valueKind) if err != nil { return 0, false, err } m[int16(k)] = v offset = o } rv.Set(reflect.ValueOf(m)) return offset, true, nil case typeMapInt32String: m := make(map[int32]string, l) for i := 0; i < l; i++ { k, o, err := d.asInt(offset, keyKind) if err != nil { return 0, false, err } v, o, err := d.asString(o, valueKind) if err != nil { return 0, false, err } m[int32(k)] = v offset = o } rv.Set(reflect.ValueOf(m)) return offset, true, nil case typeMapInt64String: m := make(map[int64]string, l) for i := 0; i < l; i++ { k, o, err := d.asInt(offset, keyKind) if err != nil { return 0, false, err } v, o, err := d.asString(o, valueKind) if err != nil { return 0, false, err } m[k] = v offset = o } rv.Set(reflect.ValueOf(m)) return offset, true, nil case typeMapIntBool: m := make(map[int]bool, l) for i := 0; i < l; i++ { k, o, err := d.asInt(offset, keyKind) if err != nil { return 0, false, err } v, o, err := d.asBool(o, valueKind) if err != nil { return 0, false, err } m[int(k)] = v offset = o } rv.Set(reflect.ValueOf(m)) return offset, true, nil case typeMapInt8Bool: m := make(map[int8]bool, l) for i := 0; i < l; i++ { k, o, err := d.asInt(offset, keyKind) if err != nil { return 0, false, err } v, o, err := d.asBool(o, valueKind) if err != nil { return 0, false, err } m[int8(k)] = v offset = o } rv.Set(reflect.ValueOf(m)) return offset, true, nil case typeMapInt16Bool: m := make(map[int16]bool, l) for i := 0; i < l; i++ { k, o, err := d.asInt(offset, keyKind) if err != nil { return 0, false, err } v, o, err := d.asBool(o, valueKind) if err != nil { return 0, false, err } m[int16(k)] = v offset = o } rv.Set(reflect.ValueOf(m)) return offset, true, nil case typeMapInt32Bool: m := make(map[int32]bool, l) for i := 0; i < l; i++ { k, o, err := d.asInt(offset, keyKind) if err != nil { return 0, false, err } v, o, err := d.asBool(o, valueKind) if err != nil { return 0, false, err } m[int32(k)] = v offset = o } rv.Set(reflect.ValueOf(m)) return offset, true, nil case typeMapInt64Bool: m := make(map[int64]bool, l) for i := 0; i < l; i++ { k, o, err := d.asInt(offset, keyKind) if err != nil { return 0, false, err } v, o, err := d.asBool(o, valueKind) if err != nil { return 0, false, err } m[k] = v offset = o } rv.Set(reflect.ValueOf(m)) return offset, true, nil case typeMapUintString: m := make(map[uint]string, l) for i := 0; i < l; i++ { k, o, err := d.asUint(offset, keyKind) if err != nil { return 0, false, err } v, o, err := d.asString(o, valueKind) if err != nil { return 0, false, err } m[uint(k)] = v offset = o } rv.Set(reflect.ValueOf(m)) return offset, true, nil case typeMapUint8String: m := make(map[uint8]string, l) for i := 0; i < l; i++ { k, o, err := d.asUint(offset, keyKind) if err != nil { return 0, false, err } v, o, err := d.asString(o, valueKind) if err != nil { return 0, false, err } m[uint8(k)] = v offset = o } rv.Set(reflect.ValueOf(m)) return offset, true, nil case typeMapUint16String: m := make(map[uint16]string, l) for i := 0; i < l; i++ { k, o, err := d.asUint(offset, keyKind) if err != nil { return 0, false, err } v, o, err := d.asString(o, valueKind) if err != nil { return 0, false, err } m[uint16(k)] = v offset = o } rv.Set(reflect.ValueOf(m)) return offset, true, nil case typeMapUint32String: m := make(map[uint32]string, l) for i := 0; i < l; i++ { k, o, err := d.asUint(offset, keyKind) if err != nil { return 0, false, err } v, o, err := d.asString(o, valueKind) if err != nil { return 0, false, err } m[uint32(k)] = v offset = o } rv.Set(reflect.ValueOf(m)) return offset, true, nil case typeMapUint64String: m := make(map[uint64]string, l) for i := 0; i < l; i++ { k, o, err := d.asUint(offset, keyKind) if err != nil { return 0, false, err } v, o, err := d.asString(o, valueKind) if err != nil { return 0, false, err } m[k] = v offset = o } rv.Set(reflect.ValueOf(m)) return offset, true, nil case typeMapUintBool: m := make(map[uint]bool, l) for i := 0; i < l; i++ { k, o, err := d.asUint(offset, keyKind) if err != nil { return 0, false, err } v, o, err := d.asBool(o, valueKind) if err != nil { return 0, false, err } m[uint(k)] = v offset = o } rv.Set(reflect.ValueOf(m)) return offset, true, nil case typeMapUint8Bool: m := make(map[uint8]bool, l) for i := 0; i < l; i++ { k, o, err := d.asUint(offset, keyKind) if err != nil { return 0, false, err } v, o, err := d.asBool(o, valueKind) if err != nil { return 0, false, err } m[uint8(k)] = v offset = o } rv.Set(reflect.ValueOf(m)) return offset, true, nil case typeMapUint16Bool: m := make(map[uint16]bool, l) for i := 0; i < l; i++ { k, o, err := d.asUint(offset, keyKind) if err != nil { return 0, false, err } v, o, err := d.asBool(o, valueKind) if err != nil { return 0, false, err } m[uint16(k)] = v offset = o } rv.Set(reflect.ValueOf(m)) return offset, true, nil case typeMapUint32Bool: m := make(map[uint32]bool, l) for i := 0; i < l; i++ { k, o, err := d.asUint(offset, keyKind) if err != nil { return 0, false, err } v, o, err := d.asBool(o, valueKind) if err != nil { return 0, false, err } m[uint32(k)] = v offset = o } rv.Set(reflect.ValueOf(m)) return offset, true, nil case typeMapUint64Bool: m := make(map[uint64]bool, l) for i := 0; i < l; i++ { k, o, err := d.asUint(offset, keyKind) if err != nil { return 0, false, err } v, o, err := d.asBool(o, valueKind) if err != nil { return 0, false, err } m[k] = v offset = o } rv.Set(reflect.ValueOf(m)) return offset, true, nil case typeMapFloat32String: m := make(map[float32]string, l) for i := 0; i < l; i++ { k, o, err := d.asFloat32(offset, keyKind) if err != nil { return 0, false, err } v, o, err := d.asString(o, valueKind) if err != nil { return 0, false, err } m[k] = v offset = o } rv.Set(reflect.ValueOf(m)) return offset, true, nil case typeMapFloat64String: m := make(map[float64]string, l) for i := 0; i < l; i++ { k, o, err := d.asFloat64(offset, keyKind) if err != nil { return 0, false, err } v, o, err := d.asString(o, valueKind) if err != nil { return 0, false, err } m[k] = v offset = o } rv.Set(reflect.ValueOf(m)) return offset, true, nil case typeMapFloat32Bool: m := make(map[float32]bool, l) for i := 0; i < l; i++ { k, o, err := d.asFloat32(offset, keyKind) if err != nil { return 0, false, err } v, o, err := d.asBool(o, valueKind) if err != nil { return 0, false, err } m[k] = v offset = o } rv.Set(reflect.ValueOf(m)) return offset, true, nil case typeMapFloat64Bool: m := make(map[float64]bool, l) for i := 0; i < l; i++ { k, o, err := d.asFloat64(offset, keyKind) if err != nil { return 0, false, err } v, o, err := d.asBool(o, valueKind) if err != nil { return 0, false, err } m[k] = v offset = o } rv.Set(reflect.ValueOf(m)) return offset, true, nil } return offset, false, nil } ================================================ FILE: internal/decoding/map_test.go ================================================ package decoding import ( "fmt" "math" "reflect" "testing" "github.com/shamaton/msgpack/v3/def" tu "github.com/shamaton/msgpack/v3/internal/common/testutil" ) func Test_mapLength(t *testing.T) { method := func(d *decoder) func(int, reflect.Kind) (int, int, error) { return d.mapLength } testcases := AsXXXTestCases[int]{ { Name: "error.code", Data: []byte{}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "FixMap", Data: []byte{def.FixMap + 3}, Expected: 3, MethodAs: method, }, { Name: "Map16.error", Data: []byte{def.Map16}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Map16.ok", Data: []byte{def.Map16, 0xff, 0xff}, Expected: math.MaxUint16, MethodAs: method, }, { Name: "Map32.error", Data: []byte{def.Map32}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Map32.ok", Data: []byte{def.Map32, 0xff, 0xff, 0xff, 0xff}, Expected: math.MaxUint32, MethodAs: method, }, { Name: "Unexpected", Data: []byte{def.Nil}, Error: def.ErrCanNotDecode, MethodAs: method, }, } testcases.Run(t) } func Test_asFixedMap_StringInt(t *testing.T) { run := func(t *testing.T, v any, dv byte) { method := func(d *decoder) (int, bool, error) { rv := reflect.ValueOf(v) return d.asFixedMap(rv.Elem(), 0, 1) } name := fmt.Sprintf("%T", v) testcases := AsXXXTestCases[bool]{ { Name: name + ".error.asString", Data: []byte{def.Int32}, Expected: false, Error: def.ErrCanNotDecode, MethodAsCustom: method, }, { Name: name + ".error.asInt", Data: []byte{def.FixStr + 1, 'a', def.Str8}, Expected: false, Error: def.ErrCanNotDecode, MethodAsCustom: method, }, { Name: name + ".ok", Data: []byte{def.FixStr + 1, 'a', def.PositiveFixIntMin + dv}, Expected: true, MethodAsCustom: method, }, } testcases.Run(t) } v1 := new(map[string]int) run(t, v1, 1) tu.EqualMap(t, *v1, map[string]int{"a": 1}) v2 := new(map[string]int8) run(t, v2, 2) tu.EqualMap(t, *v2, map[string]int8{"a": 2}) v3 := new(map[string]int16) run(t, v3, 3) tu.EqualMap(t, *v3, map[string]int16{"a": 3}) v4 := new(map[string]int32) run(t, v4, 4) tu.EqualMap(t, *v4, map[string]int32{"a": 4}) v5 := new(map[string]int64) run(t, v5, 5) tu.EqualMap(t, *v5, map[string]int64{"a": 5}) } func Test_asFixedMap_StringUint(t *testing.T) { run := func(t *testing.T, v any, dv byte) { method := func(d *decoder) (int, bool, error) { rv := reflect.ValueOf(v) return d.asFixedMap(rv.Elem(), 0, 1) } name := fmt.Sprintf("%T", v) testcases := AsXXXTestCases[bool]{ { Name: name + ".error.asString", Data: []byte{def.Int32}, Expected: false, Error: def.ErrCanNotDecode, MethodAsCustom: method, }, { Name: name + ".error.asUint", Data: []byte{def.FixStr + 1, 'a', def.Str8}, Expected: false, Error: def.ErrCanNotDecode, MethodAsCustom: method, }, { Name: name + ".ok", Data: []byte{def.FixStr + 1, 'a', def.Uint8, dv}, Expected: true, MethodAsCustom: method, }, } testcases.Run(t) } v1 := new(map[string]uint) run(t, v1, 1) tu.EqualMap(t, *v1, map[string]uint{"a": 1}) v2 := new(map[string]uint8) run(t, v2, 2) tu.EqualMap(t, *v2, map[string]uint8{"a": 2}) v3 := new(map[string]uint16) run(t, v3, 3) tu.EqualMap(t, *v3, map[string]uint16{"a": 3}) v4 := new(map[string]uint32) run(t, v4, 4) tu.EqualMap(t, *v4, map[string]uint32{"a": 4}) v5 := new(map[string]uint64) run(t, v5, 5) tu.EqualMap(t, *v5, map[string]uint64{"a": 5}) } func Test_asFixedMap_StringFloat(t *testing.T) { run := func(t *testing.T, v any, dv byte) { method := func(d *decoder) (int, bool, error) { rv := reflect.ValueOf(v) return d.asFixedMap(rv.Elem(), 0, 1) } name := fmt.Sprintf("%T", v) testcases := AsXXXTestCases[bool]{ { Name: name + ".error.asString", Data: []byte{def.Int32}, Expected: false, Error: def.ErrCanNotDecode, MethodAsCustom: method, }, { Name: name + ".error.asFloat", Data: []byte{def.FixStr + 1, 'a', def.Str8}, Expected: false, Error: def.ErrCanNotDecode, MethodAsCustom: method, }, { Name: name + ".ok", Data: []byte{def.FixStr + 1, 'a', def.Int16, 0, dv}, Expected: true, MethodAsCustom: method, }, } testcases.Run(t) } v1 := new(map[string]float32) run(t, v1, 1) tu.EqualMap(t, *v1, map[string]float32{"a": 1}) v2 := new(map[string]float64) run(t, v2, 2) tu.EqualMap(t, *v2, map[string]float64{"a": 2}) } func Test_asFixedMap_StringBool(t *testing.T) { run := func(t *testing.T, v any, dv byte) { method := func(d *decoder) (int, bool, error) { rv := reflect.ValueOf(v) return d.asFixedMap(rv.Elem(), 0, 1) } name := fmt.Sprintf("%T", v) testcases := AsXXXTestCases[bool]{ { Name: name + ".error.asString", Data: []byte{def.Int32}, Expected: false, Error: def.ErrCanNotDecode, MethodAsCustom: method, }, { Name: name + ".error.asBool", Data: []byte{def.FixStr + 1, 'a', def.Str8}, Expected: false, Error: def.ErrCanNotDecode, MethodAsCustom: method, }, { Name: name + ".ok", Data: []byte{def.FixStr + 1, 'a', dv}, Expected: true, MethodAsCustom: method, }, } testcases.Run(t) } v1 := new(map[string]bool) run(t, v1, def.True) tu.EqualMap(t, *v1, map[string]bool{"a": true}) } func Test_asFixedMap_StringString(t *testing.T) { run := func(t *testing.T, v any, dv byte) { method := func(d *decoder) (int, bool, error) { rv := reflect.ValueOf(v) return d.asFixedMap(rv.Elem(), 0, 1) } name := fmt.Sprintf("%T", v) testcases := AsXXXTestCases[bool]{ { Name: name + ".error.asString", Data: []byte{def.Int32}, Expected: false, Error: def.ErrCanNotDecode, MethodAsCustom: method, }, { Name: name + ".error.asString", Data: []byte{def.FixStr + 1, 'a', def.Int32}, Expected: false, Error: def.ErrCanNotDecode, MethodAsCustom: method, }, { Name: name + ".ok", Data: []byte{def.FixStr + 1, 'a', def.FixStr + 1, dv}, Expected: true, MethodAsCustom: method, }, } testcases.Run(t) } v1 := new(map[string]string) run(t, v1, 'b') tu.EqualMap(t, *v1, map[string]string{"a": "b"}) } func Test_asFixedMap_IntString(t *testing.T) { run := func(t *testing.T, v any, dv byte) { method := func(d *decoder) (int, bool, error) { rv := reflect.ValueOf(v) return d.asFixedMap(rv.Elem(), 0, 1) } name := fmt.Sprintf("%T", v) testcases := AsXXXTestCases[bool]{ { Name: name + ".error.asInt", Data: []byte{def.Str8}, Expected: false, Error: def.ErrCanNotDecode, MethodAsCustom: method, }, { Name: name + ".error.asString", Data: []byte{def.Int8, dv, def.Int32}, Expected: false, Error: def.ErrCanNotDecode, MethodAsCustom: method, }, { Name: name + ".ok", Data: []byte{def.Int8, dv, def.FixStr + 1, 'b'}, Expected: true, MethodAsCustom: method, }, } testcases.Run(t) } v1 := new(map[int]string) run(t, v1, 1) tu.EqualMap(t, *v1, map[int]string{1: "b"}) v2 := new(map[int8]string) run(t, v2, 2) tu.EqualMap(t, *v2, map[int8]string{int8(2): "b"}) v3 := new(map[int16]string) run(t, v3, 3) tu.EqualMap(t, *v3, map[int16]string{int16(3): "b"}) v4 := new(map[int32]string) run(t, v4, 4) tu.EqualMap(t, *v4, map[int32]string{int32(4): "b"}) v5 := new(map[int64]string) run(t, v5, 5) tu.EqualMap(t, *v5, map[int64]string{int64(5): "b"}) } func Test_asFixedMap_IntBool(t *testing.T) { run := func(t *testing.T, v any, dv byte) { method := func(d *decoder) (int, bool, error) { rv := reflect.ValueOf(v) return d.asFixedMap(rv.Elem(), 0, 1) } name := fmt.Sprintf("%T", v) testcases := AsXXXTestCases[bool]{ { Name: name + ".error.asInt", Data: []byte{def.Str8}, Expected: false, Error: def.ErrCanNotDecode, MethodAsCustom: method, }, { Name: name + ".error.asBool", Data: []byte{def.Int8, dv, def.Int32}, Expected: false, Error: def.ErrCanNotDecode, MethodAsCustom: method, }, { Name: name + ".ok", Data: []byte{def.Int8, dv, def.True}, Expected: true, MethodAsCustom: method, }, } testcases.Run(t) } v1 := new(map[int]bool) run(t, v1, 1) tu.EqualMap(t, *v1, map[int]bool{1: true}) v2 := new(map[int8]bool) run(t, v2, 2) tu.EqualMap(t, *v2, map[int8]bool{int8(2): true}) v3 := new(map[int16]bool) run(t, v3, 3) tu.EqualMap(t, *v3, map[int16]bool{int16(3): true}) v4 := new(map[int32]bool) run(t, v4, 4) tu.EqualMap(t, *v4, map[int32]bool{int32(4): true}) v5 := new(map[int64]bool) run(t, v5, 5) tu.EqualMap(t, *v5, map[int64]bool{int64(5): true}) } func Test_asFixedMap_UintString(t *testing.T) { run := func(t *testing.T, v any, dv byte) { method := func(d *decoder) (int, bool, error) { rv := reflect.ValueOf(v) return d.asFixedMap(rv.Elem(), 0, 1) } name := fmt.Sprintf("%T", v) testcases := AsXXXTestCases[bool]{ { Name: name + ".error.asUint", Data: []byte{def.Str8}, Expected: false, Error: def.ErrCanNotDecode, MethodAsCustom: method, }, { Name: name + ".error.asString", Data: []byte{def.Uint8, dv, def.Int32}, Expected: false, Error: def.ErrCanNotDecode, MethodAsCustom: method, }, { Name: name + ".ok", Data: []byte{def.Uint8, dv, def.FixStr + 1, 'b'}, Expected: true, MethodAsCustom: method, }, } testcases.Run(t) } v1 := new(map[uint]string) run(t, v1, 1) tu.EqualMap(t, *v1, map[uint]string{1: "b"}) v2 := new(map[uint8]string) run(t, v2, 2) tu.EqualMap(t, *v2, map[uint8]string{uint8(2): "b"}) v3 := new(map[uint16]string) run(t, v3, 3) tu.EqualMap(t, *v3, map[uint16]string{uint16(3): "b"}) v4 := new(map[uint32]string) run(t, v4, 4) tu.EqualMap(t, *v4, map[uint32]string{uint32(4): "b"}) v5 := new(map[uint64]string) run(t, v5, 5) tu.EqualMap(t, *v5, map[uint64]string{uint64(5): "b"}) } func Test_asFixedMap_UintBool(t *testing.T) { run := func(t *testing.T, v any, dv byte) { method := func(d *decoder) (int, bool, error) { rv := reflect.ValueOf(v) return d.asFixedMap(rv.Elem(), 0, 1) } name := fmt.Sprintf("%T", v) testcases := AsXXXTestCases[bool]{ { Name: name + ".error.asUint", Data: []byte{def.Str8}, Expected: false, Error: def.ErrCanNotDecode, MethodAsCustom: method, }, { Name: name + ".error.asBool", Data: []byte{def.Uint8, dv, def.Int32}, Expected: false, Error: def.ErrCanNotDecode, MethodAsCustom: method, }, { Name: name + ".ok", Data: []byte{def.Uint8, dv, def.True}, Expected: true, MethodAsCustom: method, }, } testcases.Run(t) } v1 := new(map[uint]bool) run(t, v1, 1) tu.EqualMap(t, *v1, map[uint]bool{1: true}) v2 := new(map[uint8]bool) run(t, v2, 2) tu.EqualMap(t, *v2, map[uint8]bool{uint8(2): true}) v3 := new(map[uint16]bool) run(t, v3, 3) tu.EqualMap(t, *v3, map[uint16]bool{uint16(3): true}) v4 := new(map[uint32]bool) run(t, v4, 4) tu.EqualMap(t, *v4, map[uint32]bool{uint32(4): true}) v5 := new(map[uint64]bool) run(t, v5, 5) tu.EqualMap(t, *v5, map[uint64]bool{uint64(5): true}) } func Test_asFixedMap_FloatString(t *testing.T) { run := func(t *testing.T, v any, dv byte) { method := func(d *decoder) (int, bool, error) { rv := reflect.ValueOf(v) return d.asFixedMap(rv.Elem(), 0, 1) } name := fmt.Sprintf("%T", v) testcases := AsXXXTestCases[bool]{ { Name: name + ".error.asFloat", Data: []byte{def.Str8}, Expected: false, Error: def.ErrCanNotDecode, MethodAsCustom: method, }, { Name: name + ".error.asString", Data: []byte{def.Uint8, dv, def.Int32}, Expected: false, Error: def.ErrCanNotDecode, MethodAsCustom: method, }, { Name: name + ".ok", Data: []byte{def.Uint8, dv, def.FixStr + 1, 'b'}, Expected: true, MethodAsCustom: method, }, } testcases.Run(t) } v1 := new(map[float32]string) run(t, v1, 1) tu.EqualMap(t, *v1, map[float32]string{1: "b"}) v2 := new(map[float64]string) run(t, v2, 2) tu.EqualMap(t, *v2, map[float64]string{2: "b"}) } func Test_asFixedMap_FloatBool(t *testing.T) { run := func(t *testing.T, v any, dv byte) { method := func(d *decoder) (int, bool, error) { rv := reflect.ValueOf(v) return d.asFixedMap(rv.Elem(), 0, 1) } name := fmt.Sprintf("%T", v) testcases := AsXXXTestCases[bool]{ { Name: name + ".error.asFloat", Data: []byte{def.Str8}, Expected: false, Error: def.ErrCanNotDecode, MethodAsCustom: method, }, { Name: name + ".error.asBool", Data: []byte{def.Uint8, dv, def.Int32}, Expected: false, Error: def.ErrCanNotDecode, MethodAsCustom: method, }, { Name: name + ".ok", Data: []byte{def.Uint8, dv, def.True}, Expected: true, MethodAsCustom: method, }, } testcases.Run(t) } v1 := new(map[float32]bool) run(t, v1, 1) tu.EqualMap(t, *v1, map[float32]bool{1: true}) v2 := new(map[float64]bool) run(t, v2, 2) tu.EqualMap(t, *v2, map[float64]bool{2: true}) } ================================================ FILE: internal/decoding/nil.go ================================================ package decoding import "github.com/shamaton/msgpack/v3/def" func (d *decoder) isCodeNil(v byte) bool { return def.Nil == v } ================================================ FILE: internal/decoding/read.go ================================================ package decoding import ( "github.com/shamaton/msgpack/v3/def" ) func (d *decoder) readSize1(index int) (byte, int, error) { rb := def.Byte1 if len(d.data) < index+rb { return 0, 0, def.ErrTooShortBytes } return d.data[index], index + rb, nil } func (d *decoder) readSize2(index int) ([]byte, int, error) { return d.readSizeN(index, def.Byte2) } func (d *decoder) readSize4(index int) ([]byte, int, error) { return d.readSizeN(index, def.Byte4) } func (d *decoder) readSize8(index int) ([]byte, int, error) { return d.readSizeN(index, def.Byte8) } func (d *decoder) readSizeN(index, n int) ([]byte, int, error) { if len(d.data) < index+n { return emptyBytes, 0, def.ErrTooShortBytes } return d.data[index : index+n], index + n, nil } ================================================ FILE: internal/decoding/slice.go ================================================ package decoding import ( "encoding/binary" "reflect" "github.com/shamaton/msgpack/v3/def" ) var ( typeIntSlice = reflect.TypeOf([]int{}) typeInt8Slice = reflect.TypeOf([]int8{}) typeInt16Slice = reflect.TypeOf([]int16{}) typeInt32Slice = reflect.TypeOf([]int32{}) typeInt64Slice = reflect.TypeOf([]int64{}) typeUintSlice = reflect.TypeOf([]uint{}) typeUint8Slice = reflect.TypeOf([]uint8{}) typeUint16Slice = reflect.TypeOf([]uint16{}) typeUint32Slice = reflect.TypeOf([]uint32{}) typeUint64Slice = reflect.TypeOf([]uint64{}) typeFloat32Slice = reflect.TypeOf([]float32{}) typeFloat64Slice = reflect.TypeOf([]float64{}) typeStringSlice = reflect.TypeOf([]string{}) typeBoolSlice = reflect.TypeOf([]bool{}) ) func (d *decoder) isFixSlice(v byte) bool { return def.FixArray <= v && v <= def.FixArray+0x0f } func (d *decoder) sliceLength(offset int, k reflect.Kind) (int, int, error) { code, offset, err := d.readSize1(offset) if err != nil { return 0, 0, err } switch { case d.isFixSlice(code): return int(code - def.FixArray), offset, nil case code == def.Array16: bs, offset, err := d.readSize2(offset) if err != nil { return 0, 0, err } return int(binary.BigEndian.Uint16(bs)), offset, nil case code == def.Array32: bs, offset, err := d.readSize4(offset) if err != nil { return 0, 0, err } return int(binary.BigEndian.Uint32(bs)), offset, nil } return 0, 0, d.errorTemplate(code, k) } func (d *decoder) hasRequiredLeastSliceSize(offset, length int) error { // minimum check (byte length) if len(d.data[offset:]) < length { return def.ErrLackDataLengthToSlice } return nil } func (d *decoder) asFixedSlice(rv reflect.Value, offset int, l int) (int, bool, error) { t := rv.Type() k := t.Elem().Kind() switch t { case typeIntSlice: sli := make([]int, l) for i := range sli { v, o, err := d.asInt(offset, k) if err != nil { return 0, false, err } sli[i] = int(v) offset = o } rv.Set(reflect.ValueOf(sli)) return offset, true, nil case typeUintSlice: sli := make([]uint, l) for i := range sli { v, o, err := d.asUint(offset, k) if err != nil { return 0, false, err } sli[i] = uint(v) offset = o } rv.Set(reflect.ValueOf(sli)) return offset, true, nil case typeStringSlice: sli := make([]string, l) for i := range sli { v, o, err := d.asString(offset, k) if err != nil { return 0, false, err } sli[i] = v offset = o } rv.Set(reflect.ValueOf(sli)) return offset, true, nil case typeBoolSlice: sli := make([]bool, l) for i := range sli { v, o, err := d.asBool(offset, k) if err != nil { return 0, false, err } sli[i] = v offset = o } rv.Set(reflect.ValueOf(sli)) return offset, true, nil case typeFloat32Slice: sli := make([]float32, l) for i := range sli { v, o, err := d.asFloat32(offset, k) if err != nil { return 0, false, err } sli[i] = v offset = o } rv.Set(reflect.ValueOf(sli)) return offset, true, nil case typeFloat64Slice: sli := make([]float64, l) for i := range sli { v, o, err := d.asFloat64(offset, k) if err != nil { return 0, false, err } sli[i] = v offset = o } rv.Set(reflect.ValueOf(sli)) return offset, true, nil case typeInt8Slice: sli := make([]int8, l) for i := range sli { v, o, err := d.asInt(offset, k) if err != nil { return 0, false, err } sli[i] = int8(v) offset = o } rv.Set(reflect.ValueOf(sli)) return offset, true, nil case typeInt16Slice: sli := make([]int16, l) for i := range sli { v, o, err := d.asInt(offset, k) if err != nil { return 0, false, err } sli[i] = int16(v) offset = o } rv.Set(reflect.ValueOf(sli)) return offset, true, nil case typeInt32Slice: sli := make([]int32, l) for i := range sli { v, o, err := d.asInt(offset, k) if err != nil { return 0, false, err } sli[i] = int32(v) offset = o } rv.Set(reflect.ValueOf(sli)) return offset, true, nil case typeInt64Slice: sli := make([]int64, l) for i := range sli { v, o, err := d.asInt(offset, k) if err != nil { return 0, false, err } sli[i] = v offset = o } rv.Set(reflect.ValueOf(sli)) return offset, true, nil case typeUint8Slice: sli := make([]uint8, l) for i := range sli { v, o, err := d.asUint(offset, k) if err != nil { return 0, false, err } sli[i] = uint8(v) offset = o } rv.Set(reflect.ValueOf(sli)) return offset, true, nil case typeUint16Slice: sli := make([]uint16, l) for i := range sli { v, o, err := d.asUint(offset, k) if err != nil { return 0, false, err } sli[i] = uint16(v) offset = o } rv.Set(reflect.ValueOf(sli)) return offset, true, nil case typeUint32Slice: sli := make([]uint32, l) for i := range sli { v, o, err := d.asUint(offset, k) if err != nil { return 0, false, err } sli[i] = uint32(v) offset = o } rv.Set(reflect.ValueOf(sli)) return offset, true, nil case typeUint64Slice: sli := make([]uint64, l) for i := range sli { v, o, err := d.asUint(offset, k) if err != nil { return 0, false, err } sli[i] = v offset = o } rv.Set(reflect.ValueOf(sli)) return offset, true, nil } return offset, false, nil } ================================================ FILE: internal/decoding/slice_test.go ================================================ package decoding import ( "math" "reflect" "testing" "github.com/shamaton/msgpack/v3/def" tu "github.com/shamaton/msgpack/v3/internal/common/testutil" ) func Test_sliceLength(t *testing.T) { method := func(d *decoder) func(int, reflect.Kind) (int, int, error) { return d.sliceLength } testcases := AsXXXTestCases[int]{ { Name: "error.code", Data: []byte{}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "FixArray", Data: []byte{def.FixArray + 3}, Expected: 3, MethodAs: method, }, { Name: "Array16.error", Data: []byte{def.Array16}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Array16.ok", Data: []byte{def.Array16, 0xff, 0xff}, Expected: math.MaxUint16, MethodAs: method, }, { Name: "Array32.error", Data: []byte{def.Array32}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Array32.ok", Data: []byte{def.Array32, 0xff, 0xff, 0xff, 0xff}, Expected: math.MaxUint32, MethodAs: method, }, { Name: "Unexpected", Data: []byte{def.Nil}, Error: def.ErrCanNotDecode, MethodAs: method, }, } testcases.Run(t) } func Test_asFixedSlice_Int(t *testing.T) { run := func(t *testing.T, v any) { method := func(d *decoder) (int, bool, error) { rv := reflect.ValueOf(v) return d.asFixedSlice(rv.Elem(), 0, 1) } testcases := AsXXXTestCases[bool]{ { Name: "error", Data: []byte{}, Error: def.ErrTooShortBytes, MethodAsCustom: method, }, { Name: "ok", Data: []byte{def.PositiveFixIntMin + 3}, Expected: true, MethodAsCustom: method, }, } testcases.Run(t) } v1 := new([]int) run(t, v1) tu.EqualSlice(t, *v1, []int{3}) } func Test_asFixedSlice_Int8(t *testing.T) { run := func(t *testing.T, v any) { method := func(d *decoder) (int, bool, error) { rv := reflect.ValueOf(v) return d.asFixedSlice(rv.Elem(), 0, 1) } testcases := AsXXXTestCases[bool]{ { Name: "error", Data: []byte{}, Error: def.ErrTooShortBytes, MethodAsCustom: method, }, { Name: "ok", Data: []byte{def.PositiveFixIntMin + 4}, Expected: true, MethodAsCustom: method, }, } for _, tc := range testcases { tc.Run(t) } } v1 := new([]int8) run(t, v1) tu.EqualSlice(t, *v1, []int8{4}) } func Test_asFixedSlice_Int16(t *testing.T) { run := func(t *testing.T, v any) { method := func(d *decoder) (int, bool, error) { rv := reflect.ValueOf(v) return d.asFixedSlice(rv.Elem(), 0, 1) } testcases := AsXXXTestCases[bool]{ { Name: "error", Data: []byte{}, Error: def.ErrTooShortBytes, MethodAsCustom: method, }, { Name: "ok", Data: []byte{def.PositiveFixIntMin + 5}, Expected: true, MethodAsCustom: method, }, } testcases.Run(t) } v1 := new([]int16) run(t, v1) tu.EqualSlice(t, *v1, []int16{5}) } func Test_asFixedSlice_Int32(t *testing.T) { run := func(t *testing.T, v any) { method := func(d *decoder) (int, bool, error) { rv := reflect.ValueOf(v) return d.asFixedSlice(rv.Elem(), 0, 1) } testcases := AsXXXTestCases[bool]{ { Name: "error", Data: []byte{}, Error: def.ErrTooShortBytes, MethodAsCustom: method, }, { Name: "ok", Data: []byte{def.PositiveFixIntMin + 6}, Expected: true, MethodAsCustom: method, }, } testcases.Run(t) } v1 := new([]int32) run(t, v1) tu.EqualSlice(t, *v1, []int32{6}) } func Test_asFixedSlice_Int64(t *testing.T) { run := func(t *testing.T, v any) { method := func(d *decoder) (int, bool, error) { rv := reflect.ValueOf(v) return d.asFixedSlice(rv.Elem(), 0, 1) } testcases := AsXXXTestCases[bool]{ { Name: "error", Data: []byte{}, Error: def.ErrTooShortBytes, MethodAsCustom: method, }, { Name: "ok", Data: []byte{def.PositiveFixIntMin + 7}, Expected: true, MethodAsCustom: method, }, } testcases.Run(t) } v1 := new([]int64) run(t, v1) tu.EqualSlice(t, *v1, []int64{7}) } func Test_asFixedSlice_Uint(t *testing.T) { run := func(t *testing.T, v any) { method := func(d *decoder) (int, bool, error) { rv := reflect.ValueOf(v) return d.asFixedSlice(rv.Elem(), 0, 1) } testcases := AsXXXTestCases[bool]{ { Name: "error", Data: []byte{}, Error: def.ErrTooShortBytes, MethodAsCustom: method, }, { Name: "ok", Data: []byte{def.PositiveFixIntMin + 5}, Expected: true, MethodAsCustom: method, }, } testcases.Run(t) } v1 := new([]uint) run(t, v1) tu.EqualSlice(t, *v1, []uint{5}) } func Test_asFixedSlice_Uint8(t *testing.T) { run := func(t *testing.T, v any) { method := func(d *decoder) (int, bool, error) { rv := reflect.ValueOf(v) return d.asFixedSlice(rv.Elem(), 0, 1) } testcases := AsXXXTestCases[bool]{ { Name: "error", Data: []byte{}, Error: def.ErrTooShortBytes, MethodAsCustom: method, }, { Name: "ok", Data: []byte{def.PositiveFixIntMin + 6}, Expected: true, MethodAsCustom: method, }, } testcases.Run(t) } v1 := new([]uint8) run(t, v1) tu.EqualSlice(t, *v1, []uint8{6}) } func Test_asFixedSlice_Uint16(t *testing.T) { run := func(t *testing.T, v any) { method := func(d *decoder) (int, bool, error) { rv := reflect.ValueOf(v) return d.asFixedSlice(rv.Elem(), 0, 1) } testcases := AsXXXTestCases[bool]{ { Name: "error", Data: []byte{}, Error: def.ErrTooShortBytes, MethodAsCustom: method, }, { Name: "ok", Data: []byte{def.PositiveFixIntMin + 7}, Expected: true, MethodAsCustom: method, }, } testcases.Run(t) } v1 := new([]uint16) run(t, v1) tu.EqualSlice(t, *v1, []uint16{7}) } func Test_asFixedSlice_Uint32(t *testing.T) { run := func(t *testing.T, v any) { method := func(d *decoder) (int, bool, error) { rv := reflect.ValueOf(v) return d.asFixedSlice(rv.Elem(), 0, 1) } testcases := AsXXXTestCases[bool]{ { Name: "error", Data: []byte{}, Error: def.ErrTooShortBytes, MethodAsCustom: method, }, { Name: "ok", Data: []byte{def.PositiveFixIntMin + 8}, Expected: true, MethodAsCustom: method, }, } testcases.Run(t) } v1 := new([]uint32) run(t, v1) tu.EqualSlice(t, *v1, []uint32{8}) } func Test_asFixedSlice_Uint64(t *testing.T) { run := func(t *testing.T, v any) { method := func(d *decoder) (int, bool, error) { rv := reflect.ValueOf(v) return d.asFixedSlice(rv.Elem(), 0, 1) } testcases := AsXXXTestCases[bool]{ { Name: "error", Data: []byte{}, Error: def.ErrTooShortBytes, MethodAsCustom: method, }, { Name: "ok", Data: []byte{def.PositiveFixIntMin + 9}, Expected: true, MethodAsCustom: method, }, } testcases.Run(t) } v1 := new([]uint64) run(t, v1) tu.EqualSlice(t, *v1, []uint64{9}) } func Test_asFixedSlice_Float32(t *testing.T) { run := func(t *testing.T, v any) { method := func(d *decoder) (int, bool, error) { rv := reflect.ValueOf(v) return d.asFixedSlice(rv.Elem(), 0, 1) } testcases := AsXXXTestCases[bool]{ { Name: "error", Data: []byte{}, Error: def.ErrTooShortBytes, MethodAsCustom: method, }, { Name: "ok", Data: []byte{def.Float32, 63, 128, 0, 0}, Expected: true, MethodAsCustom: method, }, } testcases.Run(t) } v1 := new([]float32) run(t, v1) tu.EqualSlice(t, *v1, []float32{1}) } func Test_asFixedSlice_Float64(t *testing.T) { run := func(t *testing.T, v any) { method := func(d *decoder) (int, bool, error) { rv := reflect.ValueOf(v) return d.asFixedSlice(rv.Elem(), 0, 1) } testcases := AsXXXTestCases[bool]{ { Name: "error", Data: []byte{}, Error: def.ErrTooShortBytes, MethodAsCustom: method, }, { Name: "ok", Data: []byte{def.Float64, 63, 240, 0, 0, 0, 0, 0, 0}, Expected: true, MethodAsCustom: method, }, } testcases.Run(t) } v1 := new([]float64) run(t, v1) tu.EqualSlice(t, *v1, []float64{1}) } func Test_asFixedSlice_String(t *testing.T) { run := func(t *testing.T, v any) { method := func(d *decoder) (int, bool, error) { rv := reflect.ValueOf(v) return d.asFixedSlice(rv.Elem(), 0, 2) } testcases := AsXXXTestCases[bool]{ { Name: "error", Data: []byte{}, Error: def.ErrTooShortBytes, MethodAsCustom: method, }, { Name: "ok", Data: []byte{def.FixStr + 1, 'a', def.FixStr + 1, 'b'}, Expected: true, MethodAsCustom: method, }, } testcases.Run(t) } v1 := new([]string) run(t, v1) tu.EqualSlice(t, *v1, []string{"a", "b"}) } func Test_asFixedSlice_Bool(t *testing.T) { run := func(t *testing.T, v any) { method := func(d *decoder) (int, bool, error) { rv := reflect.ValueOf(v) return d.asFixedSlice(rv.Elem(), 0, 1) } testcases := AsXXXTestCases[bool]{ { Name: "error", Data: []byte{}, Error: def.ErrTooShortBytes, MethodAsCustom: method, }, { Name: "ok", Data: []byte{def.True}, Expected: true, MethodAsCustom: method, }, } testcases.Run(t) } v1 := new([]bool) run(t, v1) tu.EqualSlice(t, *v1, []bool{true}) } ================================================ FILE: internal/decoding/string.go ================================================ package decoding import ( "encoding/binary" "reflect" "github.com/shamaton/msgpack/v3/def" ) var ( emptyString = "" emptyBytes = []byte{} ) func (d *decoder) isCodeString(code byte) bool { return d.isFixString(code) || code == def.Str8 || code == def.Str16 || code == def.Str32 } func (d *decoder) isFixString(v byte) bool { return def.FixStr <= v && v <= def.FixStr+0x1f } func (d *decoder) stringByteLength(offset int, k reflect.Kind) (int, int, error) { code, offset, err := d.readSize1(offset) if err != nil { return 0, 0, err } if def.FixStr <= code && code <= def.FixStr+0x1f { l := int(code - def.FixStr) return l, offset, nil } else if code == def.Str8 { b, offset, err := d.readSize1(offset) if err != nil { return 0, 0, err } return int(b), offset, nil } else if code == def.Str16 { b, offset, err := d.readSize2(offset) if err != nil { return 0, 0, err } return int(binary.BigEndian.Uint16(b)), offset, nil } else if code == def.Str32 { b, offset, err := d.readSize4(offset) if err != nil { return 0, 0, err } return int(binary.BigEndian.Uint32(b)), offset, nil } else if code == def.Nil { return 0, offset, nil } return 0, 0, d.errorTemplate(code, k) } func (d *decoder) asString(offset int, k reflect.Kind) (string, int, error) { bs, offset, err := d.asStringByte(offset, k) if err != nil { return emptyString, 0, err } return string(bs), offset, nil } func (d *decoder) asStringByte(offset int, k reflect.Kind) ([]byte, int, error) { l, offset, err := d.stringByteLength(offset, k) if err != nil { return emptyBytes, 0, err } return d.asStringByteByLength(offset, l, k) } func (d *decoder) asStringByteByLength(offset int, l int, k reflect.Kind) ([]byte, int, error) { if l < 1 { return emptyBytes, offset, nil } return d.readSizeN(offset, l) } ================================================ FILE: internal/decoding/string_test.go ================================================ package decoding import ( "math" "reflect" "testing" "github.com/shamaton/msgpack/v3/def" ) func Test_stringByteLength(t *testing.T) { method := func(d *decoder) func(int, reflect.Kind) (int, int, error) { return d.stringByteLength } testcases := AsXXXTestCases[int]{ { Name: "error.code", Data: []byte{}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "FixStr.ok", Data: []byte{def.FixStr + 1}, Expected: 1, MethodAs: method, }, { Name: "Str8.error", Data: []byte{def.Str8}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Str8.ok", Data: []byte{def.Str8, 0xff}, Expected: math.MaxUint8, MethodAs: method, }, { Name: "Str16.error", Data: []byte{def.Str16}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Str16.ok", Data: []byte{def.Str16, 0xff, 0xff}, Expected: math.MaxUint16, MethodAs: method, }, { Name: "Str32.error", Data: []byte{def.Str32}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Str32.ok", Data: []byte{def.Str32, 0xff, 0xff, 0xff, 0xff}, Expected: math.MaxUint32, MethodAs: method, }, { Name: "Nil", Data: []byte{def.Nil}, Expected: 0, MethodAs: method, }, { Name: "Unexpected", Data: []byte{def.Array16}, Error: def.ErrCanNotDecode, MethodAs: method, }, } testcases.Run(t) } func Test_asString(t *testing.T) { method := func(d *decoder) func(int, reflect.Kind) (string, int, error) { return d.asString } testcases := AsXXXTestCases[string]{ { Name: "error.string", Data: []byte{def.FixStr + 1}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "ok", Data: []byte{def.FixStr + 1, 'a'}, Expected: "a", MethodAs: method, }, } testcases.Run(t) } func Test_asStringByte(t *testing.T) { method := func(d *decoder) func(int, reflect.Kind) ([]byte, int, error) { return d.asStringByte } testcases := AsXXXTestCases[[]byte]{ { Name: "error", Data: []byte{def.FixStr + 1}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "ok", Data: []byte{def.FixStr + 1, 'a'}, Expected: []byte{'a'}, MethodAs: method, }, } testcases.Run(t) } ================================================ FILE: internal/decoding/struct.go ================================================ package decoding import ( "encoding/binary" "reflect" "sync" "github.com/shamaton/msgpack/v3/def" ) type structCacheTypeMap struct { keys [][]byte // fast path detection hasEmbedded bool // fast path (hasEmbedded == false): direct field access simpleIndexes []int // embedded path (hasEmbedded == true): path-based access indexes [][]int // field path (support for embedded structs) } type structCacheTypeArray struct { // fast path detection hasEmbedded bool // fast path (hasEmbedded == false): direct field access simpleIndexes []int // embedded path (hasEmbedded == true): path-based access indexes [][]int // field path (support for embedded structs) } // struct cache map var ( mapSCTM = sync.Map{} mapSCTA = sync.Map{} ) // getFieldByPath returns the field value by following the path of indices. // The bool indicates whether the path was reachable (no nil pointer in the path). func getFieldByPath(rv reflect.Value, path []int, allowAlloc bool) (reflect.Value, bool) { for _, idx := range path { // Handle pointer indirection if needed if rv.Kind() == reflect.Ptr { if rv.IsNil() { if !allowAlloc { return reflect.Value{}, false } // Allocate new value if pointer is nil rv.Set(reflect.New(rv.Type().Elem())) } rv = rv.Elem() } rv = rv.Field(idx) } return rv, true } func (d *decoder) setStruct(rv reflect.Value, offset int, k reflect.Kind) (int, error) { /* if d.isDateTime(offset) { dt, offset, err := d.asDateTime(offset, k) if err != nil { return 0, err } rv.Set(reflect.ValueOf(dt)) return offset, nil } */ isExt, _, err := d.extEndOffset(offset) if err != nil { return 0, err } if isExt { for i := range extCoders { if extCoders[i].IsType(offset, &d.data) { v, offset, err := extCoders[i].AsValue(offset, k, &d.data) if err != nil { return 0, err } // Validate that the receptacle is of the right value type. if rv.Type() == reflect.TypeOf(v) { rv.Set(reflect.ValueOf(v)) return offset, nil } } } } if d.asArray { return d.setStructFromArray(rv, offset, k) } return d.setStructFromMap(rv, offset, k) } func (d *decoder) setStructFromArray(rv reflect.Value, offset int, k reflect.Kind) (int, error) { // get length l, o, err := d.sliceLength(offset, k) if err != nil { return 0, err } if err = d.hasRequiredLeastSliceSize(o, l); err != nil { return 0, err } // find or create reference var scta *structCacheTypeArray cache, findCache := mapSCTA.Load(rv.Type()) if !findCache { scta = &structCacheTypeArray{} fields := d.CollectFields(rv.Type(), nil) // detect embedded fields hasEmbedded := false for _, f := range fields { if len(f.Path) > 1 || len(f.OmitPaths) > 0 { hasEmbedded = true break } } scta.hasEmbedded = hasEmbedded for _, field := range fields { if hasEmbedded { scta.indexes = append(scta.indexes, field.Path) } else { scta.simpleIndexes = append(scta.simpleIndexes, field.Path[0]) } } mapSCTA.Store(rv.Type(), scta) } else { scta = cache.(*structCacheTypeArray) } // set value if scta.hasEmbedded { for i := 0; i < l; i++ { if i < len(scta.indexes) { allowAlloc := !d.isCodeNil(d.data[o]) fieldValue, ok := getFieldByPath(rv, scta.indexes[i], allowAlloc) if ok { o, err = d.decode(fieldValue, o) if err != nil { return 0, err } } else { o, err = d.jumpOffset(o) if err != nil { return 0, err } } } else { o, err = d.jumpOffset(o) if err != nil { return 0, err } } } } else { for i := 0; i < l; i++ { if i < len(scta.simpleIndexes) { o, err = d.decode(rv.Field(scta.simpleIndexes[i]), o) if err != nil { return 0, err } } else { o, err = d.jumpOffset(o) if err != nil { return 0, err } } } } return o, nil } func (d *decoder) setStructFromMap(rv reflect.Value, offset int, k reflect.Kind) (int, error) { // get length l, o, err := d.mapLength(offset, k) if err != nil { return 0, err } if err = d.hasRequiredLeastMapSize(o, l); err != nil { return 0, err } var sctm *structCacheTypeMap cache, cacheFind := mapSCTM.Load(rv.Type()) if !cacheFind { sctm = &structCacheTypeMap{} fields := d.CollectFields(rv.Type(), nil) // detect embedded fields hasEmbedded := false for _, f := range fields { if len(f.Path) > 1 || len(f.OmitPaths) > 0 { hasEmbedded = true break } } sctm.hasEmbedded = hasEmbedded for _, field := range fields { sctm.keys = append(sctm.keys, []byte(field.Name)) if hasEmbedded { sctm.indexes = append(sctm.indexes, field.Path) } else { sctm.simpleIndexes = append(sctm.simpleIndexes, field.Path[0]) } } mapSCTM.Store(rv.Type(), sctm) } else { sctm = cache.(*structCacheTypeMap) } if sctm.hasEmbedded { for i := 0; i < l; i++ { dataKey, o2, err := d.asStringByte(o, k) if err != nil { return 0, err } fieldPath := []int(nil) for keyIndex, keyBytes := range sctm.keys { if len(keyBytes) != len(dataKey) { continue } found := true for dataIndex := range dataKey { if dataKey[dataIndex] != keyBytes[dataIndex] { found = false break } } if found { fieldPath = sctm.indexes[keyIndex] break } } if fieldPath != nil { allowAlloc := !d.isCodeNil(d.data[o2]) fieldValue, ok := getFieldByPath(rv, fieldPath, allowAlloc) if ok { o2, err = d.decode(fieldValue, o2) if err != nil { return 0, err } } else { o2, err = d.jumpOffset(o2) if err != nil { return 0, err } } } else { o2, err = d.jumpOffset(o2) if err != nil { return 0, err } } o = o2 } } else { for i := 0; i < l; i++ { dataKey, o2, err := d.asStringByte(o, k) if err != nil { return 0, err } fieldIndex := -1 for keyIndex, keyBytes := range sctm.keys { if len(keyBytes) != len(dataKey) { continue } found := true for dataIndex := range dataKey { if dataKey[dataIndex] != keyBytes[dataIndex] { found = false break } } if found { fieldIndex = sctm.simpleIndexes[keyIndex] break } } if fieldIndex >= 0 { o2, err = d.decode(rv.Field(fieldIndex), o2) if err != nil { return 0, err } } else { o2, err = d.jumpOffset(o2) if err != nil { return 0, err } } o = o2 } } return o, nil } func (d *decoder) jumpOffset(offset int) (int, error) { code, offset, err := d.readSize1(offset) if err != nil { return 0, err } switch { case code == def.True, code == def.False, code == def.Nil: // do nothing case d.isPositiveFixNum(code) || d.isNegativeFixNum(code): // do nothing case code == def.Uint8, code == def.Int8: offset += def.Byte1 case code == def.Uint16, code == def.Int16: offset += def.Byte2 case code == def.Uint32, code == def.Int32, code == def.Float32: offset += def.Byte4 case code == def.Uint64, code == def.Int64, code == def.Float64: offset += def.Byte8 case d.isFixString(code): offset += int(code - def.FixStr) case code == def.Str8, code == def.Bin8: b, o, err := d.readSize1(offset) if err != nil { return 0, err } o += int(b) offset = o case code == def.Str16, code == def.Bin16: bs, o, err := d.readSize2(offset) if err != nil { return 0, err } o += int(binary.BigEndian.Uint16(bs)) offset = o case code == def.Str32, code == def.Bin32: bs, o, err := d.readSize4(offset) if err != nil { return 0, err } o += int(binary.BigEndian.Uint32(bs)) offset = o case d.isFixSlice(code): l := int(code - def.FixArray) for i := 0; i < l; i++ { offset, err = d.jumpOffset(offset) if err != nil { return 0, err } } case code == def.Array16: bs, o, err := d.readSize2(offset) if err != nil { return 0, err } l := int(binary.BigEndian.Uint16(bs)) for i := 0; i < l; i++ { o, err = d.jumpOffset(o) if err != nil { return 0, err } } offset = o case code == def.Array32: bs, o, err := d.readSize4(offset) if err != nil { return 0, err } l := int(binary.BigEndian.Uint32(bs)) for i := 0; i < l; i++ { o, err = d.jumpOffset(o) if err != nil { return 0, err } } offset = o case d.isFixMap(code): l := int(code - def.FixMap) for i := 0; i < l*2; i++ { offset, err = d.jumpOffset(offset) if err != nil { return 0, err } } case code == def.Map16: bs, o, err := d.readSize2(offset) if err != nil { return 0, err } l := int(binary.BigEndian.Uint16(bs)) for i := 0; i < l*2; i++ { o, err = d.jumpOffset(o) if err != nil { return 0, err } } offset = o case code == def.Map32: bs, o, err := d.readSize4(offset) if err != nil { return 0, err } l := int(binary.BigEndian.Uint32(bs)) for i := 0; i < l*2; i++ { o, err = d.jumpOffset(o) if err != nil { return 0, err } } offset = o default: isExt, o, err := d.extEndOffsetWithCode(code, offset) if err != nil { return 0, err } if isExt { offset = o } } return offset, nil } ================================================ FILE: internal/decoding/struct_test.go ================================================ package decoding import ( "reflect" "testing" "time" "github.com/shamaton/msgpack/v3/def" tu "github.com/shamaton/msgpack/v3/internal/common/testutil" ) func Test_setStruct_ext(t *testing.T) { run := func(t *testing.T, rv reflect.Value) { method := func(d *decoder) func(int, reflect.Kind) (any, int, error) { return func(offset int, k reflect.Kind) (any, int, error) { o, err := d.setStruct(rv, offset, k) return nil, o, err } } testcases := AsXXXTestCases[any]{ { Name: "ExtCoder.error", Data: []byte{def.Fixext1, 3, 0}, Error: ErrTestExtDecoder, MethodAs: method, }, { Name: "ExtCoder.ok", Data: []byte{def.Fixext4, 255, 0, 0, 0, 0}, MethodAs: method, }, } testcases.Run(t) } ngDec := testExt2Decoder{} AddExtDecoder(&ngDec) defer RemoveExtDecoder(&ngDec) v1 := new(time.Time) run(t, reflect.ValueOf(v1).Elem()) tu.EqualEqualer(t, *v1, time.Unix(0, 0)) } func Test_setStructFromMap(t *testing.T) { run := func(t *testing.T, rv reflect.Value) { method := func(d *decoder) func(int, reflect.Kind) (any, int, error) { return func(offset int, k reflect.Kind) (any, int, error) { o, err := d.setStructFromMap(rv, offset, k) return nil, o, err } } testcases := AsXXXTestCases[any]{ { Name: "error.length", Data: []byte{}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "error.required", Data: []byte{def.Map16, 0, 1}, Error: def.ErrLackDataLengthToMap, MethodAs: method, }, { Name: "error.key", Data: []byte{def.Map16, 0, 1, def.Str16, 0}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "error.decode", Data: []byte{def.Map16, 0, 1, def.FixStr + 1, 'v', def.Array16}, Error: def.ErrCanNotDecode, MethodAs: method, }, { Name: "error.jump", Data: []byte{def.Map16, 0, 2, def.FixStr + 1, 'v', 0, def.FixStr + 1, 'b'}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "ok", Data: []byte{def.Map16, 0, 1, def.FixStr + 1, 'v', def.PositiveFixIntMin + 7}, MethodAs: method, }, } testcases.Run(t) } type st struct { V int `msgpack:"v"` } v1 := new(st) run(t, reflect.ValueOf(v1).Elem()) tu.Equal(t, v1.V, 7) } func Test_setStructFromArray(t *testing.T) { run := func(t *testing.T, rv reflect.Value) { method := func(d *decoder) func(int, reflect.Kind) (any, int, error) { return func(offset int, k reflect.Kind) (any, int, error) { o, err := d.setStructFromArray(rv, offset, k) return nil, o, err } } testcases := AsXXXTestCases[any]{ { Name: "error.length", Data: []byte{}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "error.required", Data: []byte{def.Array16, 0, 1}, Error: def.ErrLackDataLengthToSlice, MethodAs: method, }, { Name: "error.decode", Data: []byte{def.Array16, 0, 1, def.Array16}, Error: def.ErrCanNotDecode, MethodAs: method, }, { Name: "error.jump", Data: []byte{def.Array16, 0, 2, 0, def.Array16}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "ok", Data: []byte{def.Array16, 0, 1, def.PositiveFixIntMin + 8}, MethodAs: method, }, } testcases.Run(t) } type st struct { V int `msgpack:"v"` } v1 := new(st) run(t, reflect.ValueOf(v1).Elem()) tu.Equal(t, v1.V, 8) } func Test_jumpOffset(t *testing.T) { method := func(d *decoder) func(int, reflect.Kind) (any, int, error) { return func(offset int, _ reflect.Kind) (any, int, error) { o, err := d.jumpOffset(offset) return nil, o, err } } testcases := AsXXXTestCases[any]{ { Name: "error.read.code", Data: []byte{}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "True.ok", Data: []byte{def.True}, MethodAs: method, }, { Name: "False.ok", Data: []byte{def.False}, MethodAs: method, }, { Name: "PositiveFixNum.ok", Data: []byte{def.PositiveFixIntMin + 1}, MethodAs: method, }, { Name: "NegativeFixNum.ok", Data: []byte{0xf0}, MethodAs: method, }, { Name: "Uint8.ok", Data: []byte{def.Uint8, 1}, MethodAs: method, }, { Name: "Int8.ok", Data: []byte{def.Int8, 1}, MethodAs: method, }, { Name: "Uint16.ok", Data: []byte{def.Uint16, 0, 1}, MethodAs: method, }, { Name: "Int16.ok", Data: []byte{def.Int16, 0, 1}, MethodAs: method, }, { Name: "Uint32.ok", Data: []byte{def.Uint32, 0, 0, 0, 0}, MethodAs: method, }, { Name: "Int32.ok", Data: []byte{def.Int32, 0, 0, 0, 0}, MethodAs: method, }, { Name: "Float32.ok", Data: []byte{def.Float32, 0, 0, 0, 0}, MethodAs: method, }, { Name: "Uint64.ok", Data: []byte{def.Uint64, 0, 0, 0, 0, 0, 0, 0, 0}, MethodAs: method, }, { Name: "Int64.ok", Data: []byte{def.Int64, 0, 0, 0, 0, 0, 0, 0, 0}, MethodAs: method, }, { Name: "Float64.ok", Data: []byte{def.Float64, 0, 0, 0, 0, 0, 0, 0, 0}, MethodAs: method, }, { Name: "FixStr.ok", Data: []byte{def.FixStr + 1, 0}, MethodAs: method, }, { Name: "Str8.ng.length", Data: []byte{def.Str8}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Str8.ok", Data: []byte{def.Str8, 1, 'a'}, MethodAs: method, }, { Name: "Bin8.ng.length", Data: []byte{def.Bin8}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Bin8.ok", Data: []byte{def.Bin8, 1, 'a'}, MethodAs: method, }, { Name: "Str16.ng.length", Data: []byte{def.Str16}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Str16.ok", Data: []byte{def.Str16, 0, 1, 'a'}, MethodAs: method, }, { Name: "Bin16.ng.length", Data: []byte{def.Bin16}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Bin16.ok", Data: []byte{def.Bin16, 0, 1, 'a'}, MethodAs: method, }, { Name: "Str32.ng.length", Data: []byte{def.Str32}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Str32.ok", Data: []byte{def.Str32, 0, 0, 0, 1, 'a'}, MethodAs: method, }, { Name: "Bin32.ng.length", Data: []byte{def.Bin32}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Bin32.ok", Data: []byte{def.Bin32, 0, 0, 0, 1, 'a'}, MethodAs: method, }, { Name: "FixSlice.ng", Data: []byte{def.FixArray + 1}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "FixSlice.ok", Data: []byte{def.FixArray + 1, 0xc1}, MethodAs: method, }, { Name: "Array16.ng.len", Data: []byte{def.Array16}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Array16.ng.jump", Data: []byte{def.Array16, 0, 1}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Array16.ok", Data: []byte{def.Array16, 0, 1, 0xc1}, MethodAs: method, }, { Name: "Array32.ng.len", Data: []byte{def.Array32}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Array32.ng.jump", Data: []byte{def.Array32, 0, 0, 0, 1}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Array32.ok", Data: []byte{def.Array32, 0, 0, 0, 1, 0xc1}, MethodAs: method, }, { Name: "FixMap.ng", Data: []byte{def.FixMap + 1}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "FixMap.ok", Data: []byte{def.FixMap + 1, 0xc1, 0xc1}, MethodAs: method, }, { Name: "Map16.ng.len", Data: []byte{def.Map16}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Map16.ng.jump", Data: []byte{def.Map16, 0, 1}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Map16.ok", Data: []byte{def.Map16, 0, 1, 0xc1, 0xc1}, MethodAs: method, }, { Name: "Map32.ng.len", Data: []byte{def.Map32}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Map32.ng.jump", Data: []byte{def.Map32, 0, 0, 0, 1}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Map32.ok", Data: []byte{def.Map32, 0, 0, 0, 1, 0xc1, 0xc1}, MethodAs: method, }, { Name: "Fixext1.ng", Data: []byte{def.Fixext1}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Fixext1.ok", Data: []byte{def.Fixext1, 0, 0}, MethodAs: method, }, { Name: "Fixext2.ng", Data: []byte{def.Fixext2}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Fixext2.ok", Data: []byte{def.Fixext2, 0, 0, 0}, MethodAs: method, }, { Name: "Fixext4.ng", Data: []byte{def.Fixext4}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Fixext4.ok", Data: []byte{def.Fixext4, 0, 0, 0, 0, 0}, MethodAs: method, }, { Name: "Fixext8.ng", Data: []byte{def.Fixext8}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Fixext8.ok", Data: []byte{def.Fixext8, 0, 0, 0, 0, 0, 0, 0, 0, 0}, MethodAs: method, }, { Name: "Fixext16.ng", Data: []byte{def.Fixext16}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Fixext16.ok", Data: []byte{def.Fixext16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, MethodAs: method, }, { Name: "Ext8.ng.size", Data: []byte{def.Ext8}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Ext8.ok", Data: []byte{def.Ext8, 1, 0, 0}, MethodAs: method, }, { Name: "Ext16.ng.size", Data: []byte{def.Ext16}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Ext16.ok", Data: []byte{def.Ext16, 0, 1, 0, 0}, MethodAs: method, }, { Name: "Ext32.ng.size", Data: []byte{def.Ext32}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Ext32.ok", Data: []byte{def.Ext32, 0, 0, 0, 1, 0, 0}, MethodAs: method, }, { Name: "Unexpected", Data: []byte{0xc1}, MethodAs: method, }, } testcases.Run(t) } ================================================ FILE: internal/decoding/uint.go ================================================ package decoding import ( "encoding/binary" "reflect" "github.com/shamaton/msgpack/v3/def" ) func (d *decoder) asUint(offset int, k reflect.Kind) (uint64, int, error) { code, _, err := d.readSize1(offset) if err != nil { return 0, 0, err } switch { case d.isPositiveFixNum(code): b, offset, err := d.readSize1(offset) if err != nil { return 0, 0, err } return uint64(b), offset, nil case d.isNegativeFixNum(code): b, offset, err := d.readSize1(offset) if err != nil { return 0, 0, err } return uint64(int8(b)), offset, nil case code == def.Uint8: offset++ b, offset, err := d.readSize1(offset) if err != nil { return 0, 0, err } return uint64(uint8(b)), offset, nil case code == def.Int8: offset++ b, offset, err := d.readSize1(offset) if err != nil { return 0, 0, err } return uint64(int8(b)), offset, nil case code == def.Uint16: offset++ bs, offset, err := d.readSize2(offset) if err != nil { return 0, 0, err } v := binary.BigEndian.Uint16(bs) return uint64(v), offset, nil case code == def.Int16: offset++ bs, offset, err := d.readSize2(offset) if err != nil { return 0, 0, err } v := int16(binary.BigEndian.Uint16(bs)) return uint64(v), offset, nil case code == def.Uint32: offset++ bs, offset, err := d.readSize4(offset) if err != nil { return 0, 0, err } v := binary.BigEndian.Uint32(bs) return uint64(v), offset, nil case code == def.Int32: offset++ bs, offset, err := d.readSize4(offset) if err != nil { return 0, 0, err } v := int32(binary.BigEndian.Uint32(bs)) return uint64(v), offset, nil case code == def.Uint64: offset++ bs, offset, err := d.readSize8(offset) if err != nil { return 0, 0, err } return binary.BigEndian.Uint64(bs), offset, nil case code == def.Int64: offset++ bs, offset, err := d.readSize8(offset) if err != nil { return 0, 0, err } return binary.BigEndian.Uint64(bs), offset, nil case code == def.Nil: offset++ return 0, offset, nil } return 0, 0, d.errorTemplate(code, k) } ================================================ FILE: internal/decoding/uint_test.go ================================================ package decoding import ( "math" "reflect" "testing" "github.com/shamaton/msgpack/v3/def" ) func Test_asUint(t *testing.T) { method := func(d *decoder) func(int, reflect.Kind) (uint64, int, error) { return d.asUint } testcases := AsXXXTestCases[uint64]{ { Name: "error.code", Data: []byte{}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "PositiveFixNum.ok", Data: []byte{def.PositiveFixIntMin + 1}, Expected: uint64(1), MethodAs: method, }, { Name: "Uint8.error", Data: []byte{def.Uint8}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Uint8.ok", Data: []byte{def.Uint8, 1}, Expected: uint64(1), MethodAs: method, }, { Name: "Uint16.error", Data: []byte{def.Uint16}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Uint16.ok", Data: []byte{def.Uint16, 0, 1}, Expected: uint64(1), MethodAs: method, }, { Name: "Uint32.error", Data: []byte{def.Uint32}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Uint32.ok", Data: []byte{def.Uint32, 0, 0, 0, 1}, Expected: uint64(1), MethodAs: method, }, { Name: "Uint64.error", Data: []byte{def.Uint64}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Uint64.ok", Data: []byte{def.Uint64, 0, 0, 0, 0, 0, 0, 0, 1}, Expected: uint64(1), MethodAs: method, }, { Name: "NegativeFixNum.ok", Data: []byte{0xff}, Expected: uint64(math.MaxUint64), MethodAs: method, }, { Name: "Int8.error", Data: []byte{def.Int8}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Int8.ok", Data: []byte{def.Int8, 0xff}, Expected: uint64(math.MaxUint64), MethodAs: method, }, { Name: "Int16.error", Data: []byte{def.Int16}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Int16.ok", Data: []byte{def.Int16, 0xff, 0xff}, Expected: uint64(math.MaxUint64), MethodAs: method, }, { Name: "Int32.error", Data: []byte{def.Int32}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Int32.ok", Data: []byte{def.Int32, 0xff, 0xff, 0xff, 0xff}, Expected: uint64(math.MaxUint64), MethodAs: method, }, { Name: "Int64.error", Data: []byte{def.Int64}, Error: def.ErrTooShortBytes, MethodAs: method, }, { Name: "Int64.ok", Data: []byte{def.Int64, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, Expected: uint64(math.MaxUint64), MethodAs: method, }, { Name: "Nil.ok", Data: []byte{def.Nil}, Expected: uint64(0), MethodAs: method, }, { Name: "Unexpected", Data: []byte{def.Str8}, Error: def.ErrCanNotDecode, MethodAs: method, }, } testcases.Run(t) } ================================================ FILE: internal/encoding/bool.go ================================================ package encoding import "github.com/shamaton/msgpack/v3/def" //func (e *encoder) calcBool() int { // return 0 //} func (e *encoder) writeBool(v bool, offset int) int { if v { offset = e.setByte1Int(def.True, offset) } else { offset = e.setByte1Int(def.False, offset) } return offset } ================================================ FILE: internal/encoding/byte.go ================================================ package encoding import ( "fmt" "math" "reflect" "github.com/shamaton/msgpack/v3/def" ) var typeByte = reflect.TypeOf(byte(0)) func (e *encoder) isByteSlice(rv reflect.Value) bool { return rv.Type().Elem() == typeByte } func (e *encoder) calcByteSlice(l int) (int, error) { if l <= math.MaxUint8 { return def.Byte1 + def.Byte1 + l, nil } else if l <= math.MaxUint16 { return def.Byte1 + def.Byte2 + l, nil } else if uint(l) <= math.MaxUint32 { return def.Byte1 + def.Byte4 + l, nil } // not supported error return 0, fmt.Errorf("%w slice length : %d", def.ErrUnsupportedType, l) } func (e *encoder) writeByteSliceLength(l int, offset int) int { if l <= math.MaxUint8 { offset = e.setByte1Int(def.Bin8, offset) offset = e.setByte1Int(l, offset) } else if l <= math.MaxUint16 { offset = e.setByte1Int(def.Bin16, offset) offset = e.setByte2Int(l, offset) } else if uint(l) <= math.MaxUint32 { offset = e.setByte1Int(def.Bin32, offset) offset = e.setByte4Int(l, offset) } return offset } ================================================ FILE: internal/encoding/byte_test.go ================================================ package encoding import ( "math" "testing" "github.com/shamaton/msgpack/v3/def" tu "github.com/shamaton/msgpack/v3/internal/common/testutil" ) func Test_calcByteSlice(t *testing.T) { testcases := []struct { name string value int result int error error }{ { name: "u8", value: math.MaxUint8, result: def.Byte1 + def.Byte1 + math.MaxUint8, }, { name: "u16", value: math.MaxUint16, result: def.Byte1 + def.Byte2 + math.MaxUint16, }, { name: "u32", value: math.MaxUint32, result: def.Byte1 + def.Byte4 + math.MaxUint32, }, { name: "u32over", value: math.MaxUint32 + 1, error: def.ErrUnsupportedType, }, } for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { e := encoder{} result, err := e.calcByteSlice(tc.value) tu.IsError(t, err, tc.error) tu.Equal(t, result, tc.result) }) } } ================================================ FILE: internal/encoding/complex.go ================================================ package encoding import ( "math" "github.com/shamaton/msgpack/v3/def" ) func (e *encoder) calcComplex64() int { return def.Byte1 + def.Byte1 + def.Byte8 } func (e *encoder) calcComplex128() int { return def.Byte1 + def.Byte1 + def.Byte16 } func (e *encoder) writeComplex64(v complex64, offset int) int { offset = e.setByte1Int(def.Fixext8, offset) offset = e.setByte1Int(int(def.ComplexTypeCode()), offset) offset = e.setByte4Uint64(uint64(math.Float32bits(real(v))), offset) offset = e.setByte4Uint64(uint64(math.Float32bits(imag(v))), offset) return offset } func (e *encoder) writeComplex128(v complex128, offset int) int { offset = e.setByte1Int(def.Fixext16, offset) offset = e.setByte1Int(int(def.ComplexTypeCode()), offset) offset = e.setByte8Uint64(math.Float64bits(real(v)), offset) offset = e.setByte8Uint64(math.Float64bits(imag(v)), offset) return offset } ================================================ FILE: internal/encoding/encoding.go ================================================ package encoding import ( "fmt" "math" "reflect" "github.com/shamaton/msgpack/v3/def" "github.com/shamaton/msgpack/v3/internal/common" ) type encoder struct { d []byte asArray bool common.Common mk map[uintptr][]reflect.Value mv map[uintptr][]reflect.Value } // Encode returns the MessagePack-encoded byte array of v. func Encode(v interface{}, asArray bool) (b []byte, err error) { e := encoder{asArray: asArray} /* defer func() { e := recover() if e != nil { b = nil err = fmt.Errorf("unexpected error!! \n%s", stackTrace()) } }() */ rv := reflect.ValueOf(v) if rv.Kind() == reflect.Ptr { rv = rv.Elem() if rv.Kind() == reflect.Ptr { rv = rv.Elem() } } size, err := e.calcSize(rv) if err != nil { return nil, err } e.d = make([]byte, size) last := e.create(rv, 0) if size != last { return nil, fmt.Errorf("%w size=%d, lastIdx=%d", def.ErrNotMatchLastIndex, size, last) } return e.d, err } //func stackTrace() string { // msg := "" // for depth := 0; ; depth++ { // _, file, line, ok := runtime.Caller(depth) // if !ok { // break // } // msg += fmt.Sprintln(depth, ": ", file, ":", line) // } // return msg //} func (e *encoder) calcSize(rv reflect.Value) (int, error) { switch rv.Kind() { case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: v := rv.Uint() return e.calcUint(v), nil case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: v := rv.Int() return e.calcInt(int64(v)), nil case reflect.Float32: return e.calcFloat32(0), nil case reflect.Float64: return e.calcFloat64(0), nil case reflect.String: return e.calcString(rv.String()), nil case reflect.Bool: return def.Byte1, nil case reflect.Complex64: return e.calcComplex64(), nil case reflect.Complex128: return e.calcComplex128(), nil case reflect.Slice: if rv.IsNil() { return def.Byte1, nil } // bin format if e.isByteSlice(rv) { size, err := e.calcByteSlice(rv.Len()) if err != nil { return 0, err } return size, nil } if size, find := e.calcFixedSlice(rv); find { return size, nil } // func elem := rv.Type().Elem() var f structCalcFunc if elem.Kind() == reflect.Struct { f = e.getStructCalc(elem) } else { f = e.calcSize } l := rv.Len() size, err := e.calcLength(l) if err != nil { return 0, err } // objects size for i := 0; i < l; i++ { s, err := f(rv.Index(i)) if err != nil { return 0, err } size += s } return size, nil case reflect.Array: // bin format if e.isByteSlice(rv) { size, err := e.calcByteSlice(rv.Len()) if err != nil { return 0, err } return size, nil } // func elem := rv.Type().Elem() var f structCalcFunc if elem.Kind() == reflect.Struct { f = e.getStructCalc(elem) } else { f = e.calcSize } l := rv.Len() size, err := e.calcLength(l) if err != nil { return 0, err } // objects size for i := 0; i < l; i++ { s, err := f(rv.Index(i)) if err != nil { return 0, err } size += s } return size, nil case reflect.Map: if rv.IsNil() { return def.Byte1, nil } if size, find := e.calcFixedMap(rv); find { return size, nil } if e.mk == nil { e.mk = map[uintptr][]reflect.Value{} e.mv = map[uintptr][]reflect.Value{} } keys := rv.MapKeys() size, err := e.calcLength(len(keys)) if err != nil { return 0, err } // key-value mv := make([]reflect.Value, len(keys)) i := 0 for _, k := range keys { keySize, err := e.calcSize(k) if err != nil { return 0, err } value := rv.MapIndex(k) valueSize, err := e.calcSize(value) if err != nil { return 0, err } size += keySize + valueSize mv[i] = value i++ } e.mk[rv.Pointer()], e.mv[rv.Pointer()] = keys, mv return size, nil case reflect.Struct: size, err := e.calcStruct(rv) if err != nil { return 0, err } return size, nil case reflect.Ptr: if rv.IsNil() { return def.Byte1, nil } size, err := e.calcSize(rv.Elem()) if err != nil { return 0, err } return size, nil case reflect.Interface: size, err := e.calcSize(rv.Elem()) if err != nil { return 0, err } return size, nil case reflect.Invalid: // do nothing (return nil) return def.Byte1, nil default: return 0, fmt.Errorf("%v is %w type", rv.Kind(), def.ErrUnsupportedType) } } func (e *encoder) calcLength(l int) (int, error) { if l <= 0x0f { return def.Byte1, nil } else if l <= math.MaxUint16 { return def.Byte1 + def.Byte2, nil } else if uint(l) <= math.MaxUint32 { return def.Byte1 + def.Byte4, nil } // not supported error return 0, fmt.Errorf("array length %d is %w", l, def.ErrUnsupportedLength) } func (e *encoder) create(rv reflect.Value, offset int) int { switch rv.Kind() { case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: v := rv.Uint() offset = e.writeUint(v, offset) case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: v := rv.Int() offset = e.writeInt(v, offset) case reflect.Float32: offset = e.writeFloat32(rv.Float(), offset) case reflect.Float64: offset = e.writeFloat64(rv.Float(), offset) case reflect.Bool: offset = e.writeBool(rv.Bool(), offset) case reflect.String: offset = e.writeString(rv.String(), offset) case reflect.Complex64: offset = e.writeComplex64(complex64(rv.Complex()), offset) case reflect.Complex128: offset = e.writeComplex128(rv.Complex(), offset) case reflect.Slice: if rv.IsNil() { return e.writeNil(offset) } // bin format if e.isByteSlice(rv) { offset = e.writeByteSliceLength(rv.Len(), offset) offset = e.setBytes(rv.Bytes(), offset) return offset } if offset, find := e.writeFixedSlice(rv, offset); find { return offset } // func elem := rv.Type().Elem() var f structWriteFunc if elem.Kind() == reflect.Struct { f = e.getStructWriter(elem) } else { f = e.create } // objects l := rv.Len() offset = e.writeSliceLength(l, offset) for i := 0; i < l; i++ { offset = f(rv.Index(i), offset) } case reflect.Array: l := rv.Len() // bin format if e.isByteSlice(rv) { offset = e.writeByteSliceLength(l, offset) // objects for i := 0; i < l; i++ { offset = e.setByte1Uint64(rv.Index(i).Uint(), offset) } return offset } // format offset = e.writeSliceLength(l, offset) // func elem := rv.Type().Elem() var f structWriteFunc if elem.Kind() == reflect.Struct { f = e.getStructWriter(elem) } else { f = e.create } // objects for i := 0; i < l; i++ { offset = f(rv.Index(i), offset) } case reflect.Map: if rv.IsNil() { return e.writeNil(offset) } l := rv.Len() offset = e.writeMapLength(l, offset) if offset, find := e.writeFixedMap(rv, offset); find { return offset } // key-value p := rv.Pointer() for i := range e.mk[p] { offset = e.create(e.mk[p][i], offset) offset = e.create(e.mv[p][i], offset) } case reflect.Struct: offset = e.writeStruct(rv, offset) case reflect.Ptr: if rv.IsNil() { return e.writeNil(offset) } offset = e.create(rv.Elem(), offset) case reflect.Interface: offset = e.create(rv.Elem(), offset) case reflect.Invalid: return e.writeNil(offset) } return offset } ================================================ FILE: internal/encoding/encoding_test.go ================================================ package encoding import ( "math" "reflect" "strconv" "testing" "github.com/shamaton/msgpack/v3/def" tu "github.com/shamaton/msgpack/v3/internal/common/testutil" ) func TestEncode(t *testing.T) { v := 1 vv := &v b, err := Encode(&vv, false) tu.NoError(t, err) tu.EqualSlice(t, b, []byte{def.PositiveFixIntMin + 1}) } func Test_encode(t *testing.T) { type st struct { V int } type testcase struct { value any code byte error error } f := func(tcs []testcase, t *testing.T) { for _, tc := range tcs { rv := reflect.ValueOf(tc.value) t.Run(rv.Type().String(), func(t *testing.T) { e := encoder{} size, err := e.calcSize(rv) tu.IsError(t, err, tc.error) if err != nil { return } e.d = make([]byte, size) result := e.create(rv, 0) tu.Equal(t, result, size) tu.Equal(t, e.d[0], tc.code) }) } } var testcases []testcase // slice tests testcases = []testcase{ { value: ([]byte)(nil), code: def.Nil, }, { value: make([]byte, math.MaxUint32+1), error: def.ErrUnsupportedType, }, { value: make([]int, 1), code: def.FixArray + 1, }, { value: make([]int, math.MaxUint16), code: def.Array16, }, // too heavy //{ // value: make([]int, math.MaxUint32), // code: def.Array32, //}, //{ // value: make([]int, math.MaxUint32+1), // error: def.ErrUnsupportedType, //}, { value: []st{{1}}, code: def.FixArray + 1, }, { value: []chan int{make(chan int)}, error: def.ErrUnsupportedType, }, } t.Run("slice", func(t *testing.T) { f(testcases, t) }) // array tests testcases = []testcase{ // stack frame too large (compile error) //{ // value: [math.MaxUint32 + 1]byte{}, // error: def.ErrUnsupportedType, //}, { value: [1]int{}, code: def.FixArray + 1, }, { value: [math.MaxUint16]int{}, code: def.Array16, }, // stack frame too large (compile error) //{ // value: [math.MaxUint32]int{}, // code: def.Array32, //}, //{ // value: [math.MaxUint32 + 1]int{}, // error: def.ErrUnsupportedType, //}, { value: [1]st{{1}}, code: def.FixArray + 1, }, { value: [1]chan int{make(chan int)}, error: def.ErrUnsupportedType, }, } t.Run("array", func(t *testing.T) { f(testcases, t) }) // map tests createMap := func(l int) map[string]int { m := map[string]int{} for i := 0; i < l; i++ { m[strconv.Itoa(i)] = i } return m } testcases = []testcase{ { value: (map[string]int)(nil), code: def.Nil, }, { value: createMap(1), code: def.FixMap + 1, }, { value: createMap(math.MaxUint16), code: def.Map16, }, // too heavy //{ // value: createMap(math.MaxUint32), // code: def.Map32, //}, //{ // value: createMap(math.MaxUint32 + 1), // error: def.ErrUnsupportedType, //}, { value: map[chan int]int{make(chan int): 1}, error: def.ErrUnsupportedType, }, { value: map[string]chan int{"a": make(chan int)}, error: def.ErrUnsupportedType, }, } t.Run("map", func(t *testing.T) { f(testcases, t) }) type unsupport struct { Chan chan int } testcases = []testcase{ { value: unsupport{make(chan int)}, error: def.ErrUnsupportedType, }, } t.Run("struct", func(t *testing.T) { f(testcases, t) }) ch := make(chan int) testcases = []testcase{ { value: (*int)(nil), code: def.Nil, }, { value: &ch, error: def.ErrUnsupportedType, }, { value: new(int), code: 0, }, } t.Run("ptr", func(t *testing.T) { f(testcases, t) }) type inter struct { V any } testcases = []testcase{ { value: inter{V: make(chan int)}, error: def.ErrUnsupportedType, }, { value: inter{V: 1}, code: def.FixMap + 1, }, } t.Run("interface", func(t *testing.T) { f(testcases, t) }) } func Test_calcLength(t *testing.T) { e := encoder{} testcases := []struct { name string length int size int err error }{ { name: "0x0f", length: 0x0f, size: def.Byte1, err: nil, }, { name: "MaxUint16", length: math.MaxUint16, size: def.Byte1 + def.Byte2, err: nil, }, { name: "MaxUint32", length: math.MaxUint32, size: def.Byte1 + def.Byte4, err: nil, }, { name: "error", length: math.MaxUint32 + 1, size: 0, err: def.ErrUnsupportedLength, }, } for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { size, err := e.calcLength(tc.length) tu.IsError(t, err, tc.err) tu.Equal(t, size, tc.size) }) } } ================================================ FILE: internal/encoding/ext.go ================================================ package encoding import ( "reflect" "github.com/shamaton/msgpack/v3/ext" "github.com/shamaton/msgpack/v3/time" ) var ( extCoderMap = map[reflect.Type]ext.Encoder{time.Encoder.Type(): time.Encoder} extCoders = []ext.Encoder{time.Encoder} ) // AddExtEncoder adds encoders for extension types. func AddExtEncoder(f ext.Encoder) { // ignore time if f.Type() == time.Encoder.Type() { return } _, ok := extCoderMap[f.Type()] if !ok { extCoderMap[f.Type()] = f updateExtCoders() } } // RemoveExtEncoder removes encoders for extension types. func RemoveExtEncoder(f ext.Encoder) { // ignore time if f.Type() == time.Encoder.Type() { return } _, ok := extCoderMap[f.Type()] if ok { delete(extCoderMap, f.Type()) updateExtCoders() } } func updateExtCoders() { extCoders = make([]ext.Encoder, len(extCoderMap)) i := 0 for k := range extCoderMap { extCoders[i] = extCoderMap[k] i++ } } /* func (e *encoder) isDateTime(value reflect.Value) (bool, time.Time) { i := value.Interface() switch t := i.(type) { case time.Time: return true, t } return false, now } func (e *encoder) calcTime(t time.Time) int { secs := uint64(t.Unix()) if secs>>34 == 0 { data := uint64(t.Nanosecond())<<34 | secs if data&0xffffffff00000000 == 0 { return def.Byte1 + def.Byte4 } return def.Byte1 + def.Byte8 } return def.Byte1 + def.Byte1 + def.Byte4 + def.Byte8 } func (e *encoder) writeTime(t time.Time, offset int) int { secs := uint64(t.Unix()) if secs>>34 == 0 { data := uint64(t.Nanosecond())<<34 | secs if data&0xffffffff00000000 == 0 { offset = e.setByte1Int(def.Fixext4, offset) offset = e.setByte1Int(def.TimeStamp, offset) offset = e.setByte4Uint64(data, offset) return offset } offset = e.setByte1Int(def.Fixext8, offset) offset = e.setByte1Int(def.TimeStamp, offset) offset = e.setByte8Uint64(data, offset) return offset } offset = e.setByte1Int(def.Ext8, offset) offset = e.setByte1Int(12, offset) offset = e.setByte1Int(def.TimeStamp, offset) offset = e.setByte4Int(t.Nanosecond(), offset) offset = e.setByte8Uint64(secs, offset) return offset } */ ================================================ FILE: internal/encoding/ext_test.go ================================================ package encoding import ( "testing" tu "github.com/shamaton/msgpack/v3/internal/common/testutil" "github.com/shamaton/msgpack/v3/time" ) func Test_AddExtEncoder(t *testing.T) { t.Run("ignore", func(t *testing.T) { AddExtEncoder(time.Encoder) tu.Equal(t, len(extCoders), 1) }) } func Test_RemoveExtEncoder(t *testing.T) { t.Run("ignore", func(t *testing.T) { RemoveExtEncoder(time.Encoder) tu.Equal(t, len(extCoders), 1) }) } ================================================ FILE: internal/encoding/float.go ================================================ package encoding import ( "math" "github.com/shamaton/msgpack/v3/def" ) func (e *encoder) calcFloat32(_ float64) int { return def.Byte1 + def.Byte4 } func (e *encoder) calcFloat64(_ float64) int { return def.Byte1 + def.Byte8 } func (e *encoder) writeFloat32(v float64, offset int) int { offset = e.setByte1Int(def.Float32, offset) offset = e.setByte4Uint64(uint64(math.Float32bits(float32(v))), offset) return offset } func (e *encoder) writeFloat64(v float64, offset int) int { offset = e.setByte1Int(def.Float64, offset) offset = e.setByte8Uint64(math.Float64bits(v), offset) return offset } ================================================ FILE: internal/encoding/int.go ================================================ package encoding import ( "math" "github.com/shamaton/msgpack/v3/def" ) func (e *encoder) isNegativeFixInt64(v int64) bool { return def.NegativeFixintMin <= v && v <= def.NegativeFixintMax } func (e *encoder) calcInt(v int64) int { if v >= 0 { return e.calcUint(uint64(v)) } else if e.isNegativeFixInt64(v) { // format code only return def.Byte1 } else if v >= math.MinInt8 { return def.Byte1 + def.Byte1 } else if v >= math.MinInt16 { return def.Byte1 + def.Byte2 } else if v >= math.MinInt32 { return def.Byte1 + def.Byte4 } return def.Byte1 + def.Byte8 } func (e *encoder) writeInt(v int64, offset int) int { if v >= 0 { offset = e.writeUint(uint64(v), offset) } else if e.isNegativeFixInt64(v) { offset = e.setByte1Int64(v, offset) } else if v >= math.MinInt8 { offset = e.setByte1Int(def.Int8, offset) offset = e.setByte1Int64(v, offset) } else if v >= math.MinInt16 { offset = e.setByte1Int(def.Int16, offset) offset = e.setByte2Int64(v, offset) } else if v >= math.MinInt32 { offset = e.setByte1Int(def.Int32, offset) offset = e.setByte4Int64(v, offset) } else { offset = e.setByte1Int(def.Int64, offset) offset = e.setByte8Int64(v, offset) } return offset } ================================================ FILE: internal/encoding/map.go ================================================ package encoding import ( "math" "reflect" "github.com/shamaton/msgpack/v3/def" ) func (e *encoder) calcFixedMap(rv reflect.Value) (int, bool) { // calcLength formally returns (int, error), but for map lengths in Go // the error case is unreachable. The error value is always nil and is // intentionally ignored with `_`. switch m := rv.Interface().(type) { case map[string]int: size, _ := e.calcLength(len(m)) for k, v := range m { size += e.calcString(k) size += e.calcInt(int64(v)) } return size, true case map[string]uint: size, _ := e.calcLength(len(m)) for k, v := range m { size += e.calcString(k) size += e.calcUint(uint64(v)) } return size, true case map[string]string: size, _ := e.calcLength(len(m)) for k, v := range m { size += e.calcString(k) size += e.calcString(v) } return size, true case map[string]float32: size, _ := e.calcLength(len(m)) for k := range m { size += e.calcString(k) size += e.calcFloat32(0) } return size, true case map[string]float64: size, _ := e.calcLength(len(m)) for k := range m { size += e.calcString(k) size += e.calcFloat64(0) } return size, true case map[string]bool: size, _ := e.calcLength(len(m)) for k := range m { size += e.calcString(k) size += def.Byte1 /*+ e.calcBool()*/ } return size, true case map[string]int8: size, _ := e.calcLength(len(m)) for k, v := range m { size += e.calcString(k) size += e.calcInt(int64(v)) } return size, true case map[string]int16: size, _ := e.calcLength(len(m)) for k, v := range m { size += e.calcString(k) size += e.calcInt(int64(v)) } return size, true case map[string]int32: size, _ := e.calcLength(len(m)) for k, v := range m { size += e.calcString(k) size += e.calcInt(int64(v)) } return size, true case map[string]int64: size, _ := e.calcLength(len(m)) for k, v := range m { size += e.calcString(k) size += e.calcInt(v) } return size, true case map[string]uint8: size, _ := e.calcLength(len(m)) for k, v := range m { size += e.calcString(k) size += e.calcUint(uint64(v)) } return size, true case map[string]uint16: size, _ := e.calcLength(len(m)) for k, v := range m { size += e.calcString(k) size += e.calcUint(uint64(v)) } return size, true case map[string]uint32: size, _ := e.calcLength(len(m)) for k, v := range m { size += e.calcString(k) size += e.calcUint(uint64(v)) } return size, true case map[string]uint64: size, _ := e.calcLength(len(m)) for k, v := range m { size += e.calcString(k) size += e.calcUint(v) } return size, true case map[int]string: size, _ := e.calcLength(len(m)) for k, v := range m { size += e.calcInt(int64(k)) size += e.calcString(v) } return size, true case map[int]bool: size, _ := e.calcLength(len(m)) for k := range m { size += e.calcInt(int64(k)) size += def.Byte1 /* + e.calcBool()*/ } return size, true case map[uint]string: size, _ := e.calcLength(len(m)) for k, v := range m { size += e.calcUint(uint64(k)) size += e.calcString(v) } return size, true case map[uint]bool: size, _ := e.calcLength(len(m)) for k := range m { size += e.calcUint(uint64(k)) size += def.Byte1 /* + e.calcBool()*/ } return size, true case map[float32]string: size, _ := e.calcLength(len(m)) for k, v := range m { size += e.calcFloat32(float64(k)) size += e.calcString(v) } return size, true case map[float32]bool: size, _ := e.calcLength(len(m)) for k := range m { size += e.calcFloat32(float64(k)) size += def.Byte1 /* + e.calcBool()*/ } return size, true case map[float64]string: size, _ := e.calcLength(len(m)) for k, v := range m { size += e.calcFloat64(k) size += e.calcString(v) } return size, true case map[float64]bool: size, _ := e.calcLength(len(m)) for k := range m { size += e.calcFloat64(k) size += def.Byte1 /* + e.calcBool()*/ } return size, true case map[int8]string: size, _ := e.calcLength(len(m)) for k, v := range m { size += e.calcInt(int64(k)) size += e.calcString(v) } return size, true case map[int8]bool: size, _ := e.calcLength(len(m)) for k := range m { size += e.calcInt(int64(k)) size += def.Byte1 /* + e.calcBool()*/ } return size, true case map[int16]string: size, _ := e.calcLength(len(m)) for k, v := range m { size += e.calcInt(int64(k)) size += e.calcString(v) } return size, true case map[int16]bool: size, _ := e.calcLength(len(m)) for k := range m { size += e.calcInt(int64(k)) size += def.Byte1 /* + e.calcBool()*/ } return size, true case map[int32]string: size, _ := e.calcLength(len(m)) for k, v := range m { size += e.calcInt(int64(k)) size += e.calcString(v) } return size, true case map[int32]bool: size, _ := e.calcLength(len(m)) for k := range m { size += e.calcInt(int64(k)) size += def.Byte1 /* + e.calcBool()*/ } return size, true case map[int64]string: size, _ := e.calcLength(len(m)) for k, v := range m { size += e.calcInt(k) size += e.calcString(v) } return size, true case map[int64]bool: size, _ := e.calcLength(len(m)) for k := range m { size += e.calcInt(k) size += def.Byte1 /* + e.calcBool()*/ } return size, true case map[uint8]string: size, _ := e.calcLength(len(m)) for k, v := range m { size += e.calcUint(uint64(k)) size += e.calcString(v) } return size, true case map[uint8]bool: size, _ := e.calcLength(len(m)) for k := range m { size += e.calcUint(uint64(k)) size += def.Byte1 /* + e.calcBool()*/ } return size, true case map[uint16]string: size, _ := e.calcLength(len(m)) for k, v := range m { size += e.calcUint(uint64(k)) size += e.calcString(v) } return size, true case map[uint16]bool: size, _ := e.calcLength(len(m)) for k := range m { size += e.calcUint(uint64(k)) size += def.Byte1 /* + e.calcBool()*/ } return size, true case map[uint32]string: size, _ := e.calcLength(len(m)) for k, v := range m { size += e.calcUint(uint64(k)) size += e.calcString(v) } return size, true case map[uint32]bool: size, _ := e.calcLength(len(m)) for k := range m { size += e.calcUint(uint64(k)) size += def.Byte1 /* + e.calcBool()*/ } return size, true case map[uint64]string: size, _ := e.calcLength(len(m)) for k, v := range m { size += e.calcUint(k) size += e.calcString(v) } return size, true case map[uint64]bool: size, _ := e.calcLength(len(m)) for k := range m { size += e.calcUint(k) size += def.Byte1 /* + e.calcBool()*/ } return size, true } return 0, false } func (e *encoder) writeMapLength(l int, offset int) int { // format if l <= 0x0f { offset = e.setByte1Int(def.FixMap+l, offset) } else if l <= math.MaxUint16 { offset = e.setByte1Int(def.Map16, offset) offset = e.setByte2Int(l, offset) } else if uint(l) <= math.MaxUint32 { offset = e.setByte1Int(def.Map32, offset) offset = e.setByte4Int(l, offset) } return offset } func (e *encoder) writeFixedMap(rv reflect.Value, offset int) (int, bool) { switch m := rv.Interface().(type) { case map[string]int: for k, v := range m { offset = e.writeString(k, offset) offset = e.writeInt(int64(v), offset) } return offset, true case map[string]uint: for k, v := range m { offset = e.writeString(k, offset) offset = e.writeUint(uint64(v), offset) } return offset, true case map[string]float32: for k, v := range m { offset = e.writeString(k, offset) offset = e.writeFloat32(float64(v), offset) } return offset, true case map[string]float64: for k, v := range m { offset = e.writeString(k, offset) offset = e.writeFloat64(v, offset) } return offset, true case map[string]bool: for k, v := range m { offset = e.writeString(k, offset) offset = e.writeBool(v, offset) } return offset, true case map[string]string: for k, v := range m { offset = e.writeString(k, offset) offset = e.writeString(v, offset) } return offset, true case map[string]int8: for k, v := range m { offset = e.writeString(k, offset) offset = e.writeInt(int64(v), offset) } return offset, true case map[string]int16: for k, v := range m { offset = e.writeString(k, offset) offset = e.writeInt(int64(v), offset) } return offset, true case map[string]int32: for k, v := range m { offset = e.writeString(k, offset) offset = e.writeInt(int64(v), offset) } return offset, true case map[string]int64: for k, v := range m { offset = e.writeString(k, offset) offset = e.writeInt(int64(v), offset) } return offset, true case map[string]uint8: for k, v := range m { offset = e.writeString(k, offset) offset = e.writeUint(uint64(v), offset) } return offset, true case map[string]uint16: for k, v := range m { offset = e.writeString(k, offset) offset = e.writeUint(uint64(v), offset) } return offset, true case map[string]uint32: for k, v := range m { offset = e.writeString(k, offset) offset = e.writeUint(uint64(v), offset) } return offset, true case map[string]uint64: for k, v := range m { offset = e.writeString(k, offset) offset = e.writeUint(uint64(v), offset) } return offset, true case map[int]string: for k, v := range m { offset = e.writeInt(int64(k), offset) offset = e.writeString(v, offset) } return offset, true case map[int]bool: for k, v := range m { offset = e.writeInt(int64(k), offset) offset = e.writeBool(v, offset) } return offset, true case map[uint]string: for k, v := range m { offset = e.writeUint(uint64(k), offset) offset = e.writeString(v, offset) } return offset, true case map[uint]bool: for k, v := range m { offset = e.writeUint(uint64(k), offset) offset = e.writeBool(v, offset) } return offset, true case map[float32]string: for k, v := range m { offset = e.writeFloat32(float64(k), offset) offset = e.writeString(v, offset) } return offset, true case map[float32]bool: for k, v := range m { offset = e.writeFloat32(float64(k), offset) offset = e.writeBool(v, offset) } return offset, true case map[float64]string: for k, v := range m { offset = e.writeFloat64(k, offset) offset = e.writeString(v, offset) } return offset, true case map[float64]bool: for k, v := range m { offset = e.writeFloat64(k, offset) offset = e.writeBool(v, offset) } return offset, true case map[int8]string: for k, v := range m { offset = e.writeInt(int64(k), offset) offset = e.writeString(v, offset) } return offset, true case map[int8]bool: for k, v := range m { offset = e.writeInt(int64(k), offset) offset = e.writeBool(v, offset) } return offset, true case map[int16]string: for k, v := range m { offset = e.writeInt(int64(k), offset) offset = e.writeString(v, offset) } return offset, true case map[int16]bool: for k, v := range m { offset = e.writeInt(int64(k), offset) offset = e.writeBool(v, offset) } return offset, true case map[int32]string: for k, v := range m { offset = e.writeInt(int64(k), offset) offset = e.writeString(v, offset) } return offset, true case map[int32]bool: for k, v := range m { offset = e.writeInt(int64(k), offset) offset = e.writeBool(v, offset) } return offset, true case map[int64]string: for k, v := range m { offset = e.writeInt(k, offset) offset = e.writeString(v, offset) } return offset, true case map[int64]bool: for k, v := range m { offset = e.writeInt(k, offset) offset = e.writeBool(v, offset) } return offset, true case map[uint8]string: for k, v := range m { offset = e.writeUint(uint64(k), offset) offset = e.writeString(v, offset) } return offset, true case map[uint8]bool: for k, v := range m { offset = e.writeUint(uint64(k), offset) offset = e.writeBool(v, offset) } return offset, true case map[uint16]string: for k, v := range m { offset = e.writeUint(uint64(k), offset) offset = e.writeString(v, offset) } return offset, true case map[uint16]bool: for k, v := range m { offset = e.writeUint(uint64(k), offset) offset = e.writeBool(v, offset) } return offset, true case map[uint32]string: for k, v := range m { offset = e.writeUint(uint64(k), offset) offset = e.writeString(v, offset) } return offset, true case map[uint32]bool: for k, v := range m { offset = e.writeUint(uint64(k), offset) offset = e.writeBool(v, offset) } return offset, true case map[uint64]string: for k, v := range m { offset = e.writeUint(k, offset) offset = e.writeString(v, offset) } return offset, true case map[uint64]bool: for k, v := range m { offset = e.writeUint(k, offset) offset = e.writeBool(v, offset) } return offset, true } return offset, false } ================================================ FILE: internal/encoding/nil.go ================================================ package encoding import "github.com/shamaton/msgpack/v3/def" func (e *encoder) writeNil(offset int) int { offset = e.setByte1Int(def.Nil, offset) return offset } ================================================ FILE: internal/encoding/set.go ================================================ package encoding func (e *encoder) setByte1Int64(value int64, offset int) int { e.d[offset] = byte(value) return offset + 1 } func (e *encoder) setByte2Int64(value int64, offset int) int { e.d[offset+0] = byte(value >> 8) e.d[offset+1] = byte(value) return offset + 2 } func (e *encoder) setByte4Int64(value int64, offset int) int { e.d[offset+0] = byte(value >> 24) e.d[offset+1] = byte(value >> 16) e.d[offset+2] = byte(value >> 8) e.d[offset+3] = byte(value) return offset + 4 } func (e *encoder) setByte8Int64(value int64, offset int) int { e.d[offset] = byte(value >> 56) e.d[offset+1] = byte(value >> 48) e.d[offset+2] = byte(value >> 40) e.d[offset+3] = byte(value >> 32) e.d[offset+4] = byte(value >> 24) e.d[offset+5] = byte(value >> 16) e.d[offset+6] = byte(value >> 8) e.d[offset+7] = byte(value) return offset + 8 } func (e *encoder) setByte1Uint64(value uint64, offset int) int { e.d[offset] = byte(value) return offset + 1 } func (e *encoder) setByte2Uint64(value uint64, offset int) int { e.d[offset] = byte(value >> 8) e.d[offset+1] = byte(value) return offset + 2 } func (e *encoder) setByte4Uint64(value uint64, offset int) int { e.d[offset] = byte(value >> 24) e.d[offset+1] = byte(value >> 16) e.d[offset+2] = byte(value >> 8) e.d[offset+3] = byte(value) return offset + 4 } func (e *encoder) setByte8Uint64(value uint64, offset int) int { e.d[offset] = byte(value >> 56) e.d[offset+1] = byte(value >> 48) e.d[offset+2] = byte(value >> 40) e.d[offset+3] = byte(value >> 32) e.d[offset+4] = byte(value >> 24) e.d[offset+5] = byte(value >> 16) e.d[offset+6] = byte(value >> 8) e.d[offset+7] = byte(value) return offset + 8 } func (e *encoder) setByte1Int(code, offset int) int { e.d[offset] = byte(code) return offset + 1 } func (e *encoder) setByte2Int(value int, offset int) int { e.d[offset] = byte(value >> 8) e.d[offset+1] = byte(value) return offset + 2 } func (e *encoder) setByte4Int(value int, offset int) int { e.d[offset] = byte(value >> 24) e.d[offset+1] = byte(value >> 16) e.d[offset+2] = byte(value >> 8) e.d[offset+3] = byte(value) return offset + 4 } func (e *encoder) setBytes(bs []byte, offset int) int { for i := range bs { e.d[offset+i] = bs[i] } return offset + len(bs) } ================================================ FILE: internal/encoding/slice.go ================================================ package encoding import ( "math" "reflect" "github.com/shamaton/msgpack/v3/def" ) func (e *encoder) calcFixedSlice(rv reflect.Value) (int, bool) { // calcLength formally returns (int, error), but for map lengths in Go // the error case is unreachable. The error value is always nil and is // intentionally ignored with `_`. switch sli := rv.Interface().(type) { case []int: size, _ := e.calcLength(len(sli)) for _, v := range sli { size += e.calcInt(int64(v)) } return size, true case []uint: size, _ := e.calcLength(len(sli)) for _, v := range sli { size += e.calcUint(uint64(v)) } return size, true case []string: size, _ := e.calcLength(len(sli)) for _, v := range sli { size += e.calcString(v) } return size, true case []float32: size, _ := e.calcLength(len(sli)) for _, v := range sli { size += e.calcFloat32(float64(v)) } return size, true case []float64: size, _ := e.calcLength(len(sli)) for _, v := range sli { size += e.calcFloat64(v) } return size, true case []bool: size, _ := e.calcLength(len(sli)) size += def.Byte1 * len(sli) return size, true case []int8: size, _ := e.calcLength(len(sli)) for _, v := range sli { size += e.calcInt(int64(v)) } return size, true case []int16: size, _ := e.calcLength(len(sli)) for _, v := range sli { size += e.calcInt(int64(v)) } return size, true case []int32: size, _ := e.calcLength(len(sli)) for _, v := range sli { size += e.calcInt(int64(v)) } return size, true case []int64: size, _ := e.calcLength(len(sli)) for _, v := range sli { size += e.calcInt(v) } return size, true case []uint8: size, _ := e.calcLength(len(sli)) for _, v := range sli { size += e.calcUint(uint64(v)) } return size, true case []uint16: size, _ := e.calcLength(len(sli)) for _, v := range sli { size += e.calcUint(uint64(v)) } return size, true case []uint32: size, _ := e.calcLength(len(sli)) for _, v := range sli { size += e.calcUint(uint64(v)) } return size, true case []uint64: size, _ := e.calcLength(len(sli)) for _, v := range sli { size += e.calcUint(v) } return size, true } return 0, false } func (e *encoder) writeSliceLength(l int, offset int) int { // format size if l <= 0x0f { offset = e.setByte1Int(def.FixArray+l, offset) } else if l <= math.MaxUint16 { offset = e.setByte1Int(def.Array16, offset) offset = e.setByte2Int(l, offset) } else if uint(l) <= math.MaxUint32 { offset = e.setByte1Int(def.Array32, offset) offset = e.setByte4Int(l, offset) } return offset } func (e *encoder) writeFixedSlice(rv reflect.Value, offset int) (int, bool) { switch sli := rv.Interface().(type) { case []int: offset = e.writeSliceLength(len(sli), offset) for _, v := range sli { offset = e.writeInt(int64(v), offset) } return offset, true case []uint: offset = e.writeSliceLength(len(sli), offset) for _, v := range sli { offset = e.writeUint(uint64(v), offset) } return offset, true case []string: offset = e.writeSliceLength(len(sli), offset) for _, v := range sli { offset = e.writeString(v, offset) } return offset, true case []float32: offset = e.writeSliceLength(len(sli), offset) for _, v := range sli { offset = e.writeFloat32(float64(v), offset) } return offset, true case []float64: offset = e.writeSliceLength(len(sli), offset) for _, v := range sli { offset = e.writeFloat64(float64(v), offset) } return offset, true case []bool: offset = e.writeSliceLength(len(sli), offset) for _, v := range sli { offset = e.writeBool(v, offset) } return offset, true case []int8: offset = e.writeSliceLength(len(sli), offset) for _, v := range sli { offset = e.writeInt(int64(v), offset) } return offset, true case []int16: offset = e.writeSliceLength(len(sli), offset) for _, v := range sli { offset = e.writeInt(int64(v), offset) } return offset, true case []int32: offset = e.writeSliceLength(len(sli), offset) for _, v := range sli { offset = e.writeInt(int64(v), offset) } return offset, true case []int64: offset = e.writeSliceLength(len(sli), offset) for _, v := range sli { offset = e.writeInt(v, offset) } return offset, true case []uint8: offset = e.writeSliceLength(len(sli), offset) for _, v := range sli { offset = e.writeUint(uint64(v), offset) } return offset, true case []uint16: offset = e.writeSliceLength(len(sli), offset) for _, v := range sli { offset = e.writeUint(uint64(v), offset) } return offset, true case []uint32: offset = e.writeSliceLength(len(sli), offset) for _, v := range sli { offset = e.writeUint(uint64(v), offset) } return offset, true case []uint64: offset = e.writeSliceLength(len(sli), offset) for _, v := range sli { offset = e.writeUint(v, offset) } return offset, true } return offset, false } ================================================ FILE: internal/encoding/slice_test.go ================================================ package encoding import ( "reflect" "testing" tu "github.com/shamaton/msgpack/v3/internal/common/testutil" ) func Test_FixedSlice(t *testing.T) { testcases := []struct { value any size int }{ { value: []int{-1}, size: 2, }, { value: []uint{1}, size: 2, }, { value: []string{"a"}, size: 3, }, { value: []float32{1.23}, size: 6, }, { value: []float64{1.23}, size: 10, }, { value: []bool{true}, size: 2, }, { value: []int8{1}, size: 2, }, { value: []int16{1}, size: 2, }, { value: []int32{1}, size: 2, }, { value: []int64{1}, size: 2, }, { value: []uint8{1}, size: 2, }, { value: []uint16{1}, size: 2, }, { value: []uint32{1}, size: 2, }, { value: []uint64{1}, size: 2, }, } for _, tc := range testcases { rv := reflect.ValueOf(tc.value) t.Run(rv.Type().String(), func(t *testing.T) { e := encoder{} size, b := e.calcFixedSlice(rv) tu.Equal(t, b, true) tu.Equal(t, size, tc.size) e.d = make([]byte, size) result, b := e.writeFixedSlice(rv, 0) tu.Equal(t, b, true) tu.Equal(t, result, size) }) } } ================================================ FILE: internal/encoding/string.go ================================================ package encoding import ( "math" "unsafe" "github.com/shamaton/msgpack/v3/def" ) func (e *encoder) calcString(v string) int { // NOTE : unsafe strBytes := *(*[]byte)(unsafe.Pointer(&v)) l := len(strBytes) if l < 32 { return def.Byte1 + l } else if l <= math.MaxUint8 { return def.Byte1 + def.Byte1 + l } else if l <= math.MaxUint16 { return def.Byte1 + def.Byte2 + l } return def.Byte1 + def.Byte4 + l // NOTE : length over uint32 } func (e *encoder) writeString(str string, offset int) int { // NOTE : unsafe strBytes := *(*[]byte)(unsafe.Pointer(&str)) l := len(strBytes) if l < 32 { offset = e.setByte1Int(def.FixStr+l, offset) } else if l <= math.MaxUint8 { offset = e.setByte1Int(def.Str8, offset) offset = e.setByte1Int(l, offset) } else if l <= math.MaxUint16 { offset = e.setByte1Int(def.Str16, offset) offset = e.setByte2Int(l, offset) } else { offset = e.setByte1Int(def.Str32, offset) offset = e.setByte4Int(l, offset) } offset += copy(e.d[offset:], str) return offset } ================================================ FILE: internal/encoding/struct.go ================================================ package encoding import ( "math" "reflect" "sync" "github.com/shamaton/msgpack/v3/def" "github.com/shamaton/msgpack/v3/internal/common" ) type structCache struct { // common fields names []string omits []bool noOmit bool // fast path detection hasEmbedded bool // fast path (hasEmbedded == false): direct field access simpleIndexes []int // embedded path (hasEmbedded == true): path-based access indexes [][]int // field path (support for embedded structs) omitPaths [][][]int // embedded omitempty parent paths common.Common } var cachemap = sync.Map{} type ( structCalcFunc func(rv reflect.Value) (int, error) structWriteFunc func(rv reflect.Value, offset int) int ) // getFieldByPath returns the field value by following the path of indices. // The bool indicates whether the path was reachable (no nil pointer in the path). func getFieldByPath(rv reflect.Value, path []int) (reflect.Value, bool) { for _, idx := range path { // Handle pointer indirection if needed if rv.Kind() == reflect.Ptr { if rv.IsNil() { // Return invalid value if pointer is nil return reflect.Value{}, false } rv = rv.Elem() } rv = rv.Field(idx) } return rv, true } func shouldOmitByParent(rv reflect.Value, omitPaths [][]int) bool { for _, path := range omitPaths { parentValue, ok := getFieldByPath(rv, path) if !ok || parentValue.IsZero() { return true } } return false } func (e *encoder) getStructCalc(typ reflect.Type) structCalcFunc { for j := range extCoders { if extCoders[j].Type() == typ { return extCoders[j].CalcByteSize } } if e.asArray { return e.calcStructArray } return e.calcStructMap } func (e *encoder) calcStruct(rv reflect.Value) (int, error) { //if isTime, tm := e.isDateTime(rv); isTime { // size := e.calcTime(tm) // return size, nil //} for i := range extCoders { if extCoders[i].Type() == rv.Type() { return extCoders[i].CalcByteSize(rv) } } if e.asArray { return e.calcStructArray(rv) } return e.calcStructMap(rv) } func (e *encoder) calcStructArray(rv reflect.Value) (int, error) { ret := 0 t := rv.Type() cache, find := cachemap.Load(t) var c *structCache if !find { c = &structCache{} fields := e.CollectFields(t, nil) // detect embedded fields hasEmbedded := false for _, f := range fields { if len(f.Path) > 1 || len(f.OmitPaths) > 0 { hasEmbedded = true break } } c.hasEmbedded = hasEmbedded omitCount := 0 for _, field := range fields { c.names = append(c.names, field.Name) c.omits = append(c.omits, field.Omit) if hasEmbedded { c.indexes = append(c.indexes, field.Path) c.omitPaths = append(c.omitPaths, field.OmitPaths) } else { c.simpleIndexes = append(c.simpleIndexes, field.Path[0]) } if field.Omit { omitCount++ } } c.noOmit = omitCount == 0 cachemap.Store(t, c) } else { c = cache.(*structCache) } // calculate size based on path type var numFields int if c.hasEmbedded { numFields = len(c.indexes) for i := 0; i < numFields; i++ { fieldValue, ok := getFieldByPath(rv, c.indexes[i]) if shouldOmitByParent(rv, c.omitPaths[i]) || !ok { fieldValue = reflect.Value{} } size, err := e.calcSize(fieldValue) if err != nil { return 0, err } ret += size } } else { numFields = len(c.simpleIndexes) for i := 0; i < numFields; i++ { size, err := e.calcSize(rv.Field(c.simpleIndexes[i])) if err != nil { return 0, err } ret += size } } // format size size, err := e.calcLength(numFields) if err != nil { return 0, err } ret += size return ret, nil } func (e *encoder) calcStructMap(rv reflect.Value) (int, error) { ret := 0 t := rv.Type() cache, find := cachemap.Load(t) var c *structCache if !find { c = &structCache{} fields := e.CollectFields(t, nil) // detect embedded fields hasEmbedded := false for _, f := range fields { if len(f.Path) > 1 || len(f.OmitPaths) > 0 { hasEmbedded = true break } } c.hasEmbedded = hasEmbedded omitCount := 0 for _, field := range fields { c.names = append(c.names, field.Name) c.omits = append(c.omits, field.Omit) if hasEmbedded { c.indexes = append(c.indexes, field.Path) c.omitPaths = append(c.omitPaths, field.OmitPaths) } else { c.simpleIndexes = append(c.simpleIndexes, field.Path[0]) } if field.Omit { omitCount++ } } c.noOmit = omitCount == 0 cachemap.Store(t, c) } else { c = cache.(*structCache) } l := 0 if c.hasEmbedded { for i := 0; i < len(c.indexes); i++ { fieldValue, ok := getFieldByPath(rv, c.indexes[i]) if shouldOmitByParent(rv, c.omitPaths[i]) || !ok { continue } size, err := e.calcSizeWithOmitEmpty(fieldValue, c.names[i], c.omits[i]) if err != nil { return 0, err } ret += size if size > 0 { l++ } } } else { for i := 0; i < len(c.simpleIndexes); i++ { size, err := e.calcSizeWithOmitEmpty(rv.Field(c.simpleIndexes[i]), c.names[i], c.omits[i]) if err != nil { return 0, err } ret += size if size > 0 { l++ } } } // format size size, err := e.calcLength(l) if err != nil { return 0, err } ret += size return ret, nil } func (e *encoder) calcSizeWithOmitEmpty(rv reflect.Value, name string, omit bool) (int, error) { keySize := 0 valueSize := 0 if !omit || !rv.IsZero() { keySize = e.calcString(name) vSize, err := e.calcSize(rv) if err != nil { return 0, err } valueSize = vSize } return keySize + valueSize, nil } func (e *encoder) getStructWriter(typ reflect.Type) structWriteFunc { for i := range extCoders { if extCoders[i].Type() == typ { return func(rv reflect.Value, offset int) int { return extCoders[i].WriteToBytes(rv, offset, &e.d) } } } if e.asArray { return e.writeStructArray } return e.writeStructMap } func (e *encoder) writeStruct(rv reflect.Value, offset int) int { /* if isTime, tm := e.isDateTime(rv); isTime { return e.writeTime(tm, offset) } */ for i := range extCoders { if extCoders[i].Type() == rv.Type() { return extCoders[i].WriteToBytes(rv, offset, &e.d) } } if e.asArray { return e.writeStructArray(rv, offset) } return e.writeStructMap(rv, offset) } func (e *encoder) writeStructArray(rv reflect.Value, offset int) int { cache, _ := cachemap.Load(rv.Type()) c := cache.(*structCache) // write format var num int if c.hasEmbedded { num = len(c.indexes) } else { num = len(c.simpleIndexes) } if num <= 0x0f { offset = e.setByte1Int(def.FixArray+num, offset) } else if num <= math.MaxUint16 { offset = e.setByte1Int(def.Array16, offset) offset = e.setByte2Int(num, offset) } else if uint(num) <= math.MaxUint32 { offset = e.setByte1Int(def.Array32, offset) offset = e.setByte4Int(num, offset) } if c.hasEmbedded { for i := 0; i < num; i++ { fieldValue, ok := getFieldByPath(rv, c.indexes[i]) if shouldOmitByParent(rv, c.omitPaths[i]) || !ok { fieldValue = reflect.Value{} } offset = e.create(fieldValue, offset) } } else { for i := 0; i < num; i++ { offset = e.create(rv.Field(c.simpleIndexes[i]), offset) } } return offset } func (e *encoder) writeStructMap(rv reflect.Value, offset int) int { cache, _ := cachemap.Load(rv.Type()) c := cache.(*structCache) // format size l := 0 if c.hasEmbedded { num := len(c.indexes) for i := 0; i < num; i++ { fieldValue, ok := getFieldByPath(rv, c.indexes[i]) if shouldOmitByParent(rv, c.omitPaths[i]) || !ok { continue } if c.noOmit || !c.omits[i] || !fieldValue.IsZero() { l++ } } } else { num := len(c.simpleIndexes) for i := 0; i < num; i++ { if c.noOmit || !c.omits[i] || !rv.Field(c.simpleIndexes[i]).IsZero() { l++ } } } if l <= 0x0f { offset = e.setByte1Int(def.FixMap+l, offset) } else if l <= math.MaxUint16 { offset = e.setByte1Int(def.Map16, offset) offset = e.setByte2Int(l, offset) } else if uint(l) <= math.MaxUint32 { offset = e.setByte1Int(def.Map32, offset) offset = e.setByte4Int(l, offset) } if c.hasEmbedded { num := len(c.indexes) for i := 0; i < num; i++ { fieldValue, ok := getFieldByPath(rv, c.indexes[i]) if shouldOmitByParent(rv, c.omitPaths[i]) || !ok { continue } if c.noOmit || !c.omits[i] || !fieldValue.IsZero() { offset = e.writeString(c.names[i], offset) offset = e.create(fieldValue, offset) } } } else { num := len(c.simpleIndexes) for i := 0; i < num; i++ { fieldValue := rv.Field(c.simpleIndexes[i]) if c.noOmit || !c.omits[i] || !fieldValue.IsZero() { offset = e.writeString(c.names[i], offset) offset = e.create(fieldValue, offset) } } } return offset } ================================================ FILE: internal/encoding/struct_test.go ================================================ package encoding import ( "math" "reflect" "testing" "github.com/shamaton/msgpack/v3/def" tu "github.com/shamaton/msgpack/v3/internal/common/testutil" ) func Test_calcStructArray(t *testing.T) { type b struct { B []byte } t.Run("non-cache", func(t *testing.T) { value := b{B: make([]byte, math.MaxUint32+1)} e := encoder{} rv := reflect.ValueOf(value) _, err := e.calcStructArray(rv) tu.IsError(t, err, def.ErrUnsupportedType) }) t.Run("cache", func(t *testing.T) { value := b{B: make([]byte, 1)} e := encoder{} rv := reflect.ValueOf(value) _, err := e.calcStructArray(rv) tu.NoError(t, err) value = b{B: make([]byte, math.MaxUint32+1)} rv = reflect.ValueOf(value) _, err = e.calcStructArray(rv) tu.IsError(t, err, def.ErrUnsupportedType) }) testcases := []struct { name string value int result int error error }{ { name: "0x0f", value: 0x0f, result: def.Byte1, }, { name: "u16", value: math.MaxUint16, result: def.Byte1 + def.Byte2, }, { name: "u32", value: math.MaxUint16 + 1, result: def.Byte1 + def.Byte4, }, // can not run by out of memory //{ // name: "u32over", // value: math.MaxUint32 + 1, // result: 0, //}, } for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { e := encoder{} v, _, bs := tu.CreateStruct(tc.value) rv := reflect.ValueOf(v).Elem() result, err := e.calcStructArray(rv) tu.IsError(t, err, tc.error) tu.Equal(t, result, tc.result+len(bs)) }) } } func Test_calcStructMap(t *testing.T) { type b struct { B []byte } t.Run("non-cache", func(t *testing.T) { value := b{B: make([]byte, math.MaxUint32+1)} e := encoder{} rv := reflect.ValueOf(value) _, err := e.calcStructMap(rv) tu.IsError(t, err, def.ErrUnsupportedType) }) t.Run("cache", func(t *testing.T) { value := b{B: make([]byte, 1)} e := encoder{} rv := reflect.ValueOf(value) _, err := e.calcStructMap(rv) tu.NoError(t, err) value = b{B: make([]byte, math.MaxUint32+1)} rv = reflect.ValueOf(value) _, err = e.calcStructMap(rv) tu.IsError(t, err, def.ErrUnsupportedType) }) testcases := []struct { name string value int result int error error }{ { name: "0x0f", value: 0x0f, result: def.Byte1, }, { name: "u16", value: math.MaxUint16, result: def.Byte1 + def.Byte2, }, { name: "u32", value: math.MaxUint16 + 1, result: def.Byte1 + def.Byte4, }, // can not run by out of memory //{ // name: "u32over", // value: math.MaxUint32 + 1, // result: 0, //}, } for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { e := encoder{} v, bs, _ := tu.CreateStruct(tc.value) rv := reflect.ValueOf(v).Elem() result, err := e.calcStructMap(rv) tu.IsError(t, err, tc.error) tu.Equal(t, result, tc.result+len(bs)) }) } } func Test_writeStructArray(t *testing.T) { testcases := []struct { name string value int code byte }{ { name: "0x0f", value: 0x0f, code: def.FixArray + 0x0f, }, { name: "u16", value: math.MaxUint16, code: def.Array16, }, { name: "u32", value: math.MaxUint16 + 1, code: def.Array32, }, } for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { e := encoder{} v, _, _ := tu.CreateStruct(tc.value) rv := reflect.ValueOf(v).Elem() size, err := e.calcStructArray(rv) tu.NoError(t, err) e.d = make([]byte, size) result := e.writeStructArray(rv, 0) tu.Equal(t, len(e.d), result) tu.Equal(t, e.d[0], tc.code) }) } } func Test_writeStructMap(t *testing.T) { testcases := []struct { name string value int code byte }{ { name: "0x0f", value: 0x0f, code: def.FixMap + 0x0f, }, { name: "u16", value: math.MaxUint16, code: def.Map16, }, { name: "u32", value: math.MaxUint16 + 1, code: def.Map32, }, } for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { e := encoder{} v, _, _ := tu.CreateStruct(tc.value) rv := reflect.ValueOf(v).Elem() size, err := e.calcStructMap(rv) tu.NoError(t, err) e.d = make([]byte, size) result := e.writeStructMap(rv, 0) tu.Equal(t, len(e.d), result) tu.Equal(t, e.d[0], tc.code) }) } } func Test_calcSizeWithOmitEmpty(t *testing.T) { e := encoder{} var v any v = func() {} _, err := e.calcSizeWithOmitEmpty(reflect.ValueOf(v), "a", false) tu.Error(t, err) v = 1 _, err = e.calcSizeWithOmitEmpty(reflect.ValueOf(v), "a", false) tu.NoError(t, err) } func Test_structCache(t *testing.T) { type forMap struct { Int int Uint uint `msgpack:",omitempty"` Str string } type forMapNoOmit struct { Int int Uint uint Str string } type forArray struct { Int int `msgpack:",omitempty"` Uint uint Str string `msgpack:",omitempty"` } type forArrayNoOmit struct { Int int Uint uint Str string } e := encoder{} testcases := []struct { v any omitCount int f func(rv reflect.Value) (int, error) }{ { v: forMap{}, omitCount: 1, f: e.calcStructMap, }, { v: forMapNoOmit{}, omitCount: 0, f: e.calcStructMap, }, { v: forArray{}, omitCount: 2, f: e.calcStructArray, }, { v: forArrayNoOmit{}, omitCount: 0, f: e.calcStructArray, }, } for _, c := range testcases { rv := reflect.ValueOf(c.v) t.Run(rv.String(), func(t *testing.T) { _, found := cachemap.Load(rv.Type()) tu.Equal(t, found, false) _, err := c.f(rv) tu.NoError(t, err) cache, _ := cachemap.Load(rv.Type()) ca, ok := cache.(*structCache) tu.Equal(t, ok, true) tu.Equal(t, len(ca.omits), rv.NumField()) count := 0 for _, b := range ca.omits { if b { count++ } } tu.Equal(t, count, c.omitCount) }) } } ================================================ FILE: internal/encoding/uint.go ================================================ package encoding import ( "math" "github.com/shamaton/msgpack/v3/def" ) func (e *encoder) calcUint(v uint64) int { if v <= math.MaxInt8 { // format code only return def.Byte1 } else if v <= math.MaxUint8 { return def.Byte1 + def.Byte1 } else if v <= math.MaxUint16 { return def.Byte1 + def.Byte2 } else if v <= math.MaxUint32 { return def.Byte1 + def.Byte4 } return def.Byte1 + def.Byte8 } func (e *encoder) writeUint(v uint64, offset int) int { if v <= math.MaxInt8 { offset = e.setByte1Uint64(v, offset) } else if v <= math.MaxUint8 { offset = e.setByte1Int(def.Uint8, offset) offset = e.setByte1Uint64(v, offset) } else if v <= math.MaxUint16 { offset = e.setByte1Int(def.Uint16, offset) offset = e.setByte2Uint64(v, offset) } else if v <= math.MaxUint32 { offset = e.setByte1Int(def.Uint32, offset) offset = e.setByte4Uint64(v, offset) } else { offset = e.setByte1Int(def.Uint64, offset) offset = e.setByte8Uint64(v, offset) } return offset } ================================================ FILE: internal/stream/decoding/bin.go ================================================ package decoding import ( "encoding/binary" "reflect" "unsafe" "github.com/shamaton/msgpack/v3/def" ) func (d *decoder) isCodeBin(v byte) bool { switch v { case def.Bin8, def.Bin16, def.Bin32: return true } return false } func (d *decoder) asBinWithCode(code byte, k reflect.Kind) ([]byte, error) { switch code { case def.Bin8: l, err := d.readSize1() if err != nil { return emptyBytes, err } // avoid common buffer reference return d.copySizeN(int(l)) case def.Bin16: bs, err := d.readSize2() if err != nil { return emptyBytes, err } // avoid common buffer reference return d.copySizeN(int(binary.BigEndian.Uint16(bs))) case def.Bin32: bs, err := d.readSize4() if err != nil { return emptyBytes, err } // avoid common buffer reference return d.copySizeN(int(binary.BigEndian.Uint32(bs))) } return emptyBytes, d.errorTemplate(code, k) } func (d *decoder) asBinStringWithCode(code byte, k reflect.Kind) (string, error) { bs, err := d.asBinWithCode(code, k) return *(*string)(unsafe.Pointer(&bs)), err } func (d *decoder) copySizeN(n int) ([]byte, error) { bs, err := d.readSizeN(n) if err != nil { return emptyBytes, err } v := make([]byte, n) copy(v, bs) return v, nil } ================================================ FILE: internal/stream/decoding/bin_test.go ================================================ package decoding import ( "io" "reflect" "testing" "github.com/shamaton/msgpack/v3/def" tu "github.com/shamaton/msgpack/v3/internal/common/testutil" ) func Test_isCodeBin(t *testing.T) { d := decoder{} for i := 0x00; i <= 0xff; i++ { v := byte(i) isBin := v == def.Bin8 || v == def.Bin16 || v == def.Bin32 tu.Equal(t, d.isCodeBin(v), isBin) } } func Test_asBinWithCode(t *testing.T) { method := func(d *decoder) func(byte, reflect.Kind) ([]byte, error) { return d.asBinWithCode } testcases := AsXXXTestCases[[]byte]{ { Name: "Bin8.error.size", Code: def.Bin8, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Bin8.error.data", Code: def.Bin8, Data: []byte{1}, ReadCount: 1, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Bin8.ok", Code: def.Bin8, Data: []byte{1, 'a'}, Expected: []byte{'a'}, ReadCount: 2, MethodAsWithCode: method, }, { Name: "Bin16.error", Code: def.Bin16, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Bin16.error.data", Code: def.Bin16, Data: []byte{0, 1}, ReadCount: 1, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Bin16.ok", Code: def.Bin16, Data: []byte{0, 1, 'b'}, Expected: []byte{'b'}, ReadCount: 2, MethodAsWithCode: method, }, { Name: "Bin32.error", Code: def.Bin32, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Bin32.error.data", Code: def.Bin32, Data: []byte{0, 0, 0, 1}, ReadCount: 1, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Bin32.ok", Code: def.Bin32, Data: []byte{0, 0, 0, 1, 'c'}, Expected: []byte{'c'}, ReadCount: 2, MethodAsWithCode: method, }, { Name: "Unexpected", Code: def.Nil, Error: def.ErrCanNotDecode, MethodAsWithCode: method, }, } for _, tc := range testcases { tc.Run(t) } } ================================================ FILE: internal/stream/decoding/bool.go ================================================ package decoding import ( "reflect" "github.com/shamaton/msgpack/v3/def" ) func (d *decoder) asBool(k reflect.Kind) (bool, error) { code, err := d.readSize1() if err != nil { return false, err } return d.asBoolWithCode(code, k) } func (d *decoder) asBoolWithCode(code byte, k reflect.Kind) (bool, error) { switch code { case def.True: return true, nil case def.False: return false, nil } return false, d.errorTemplate(code, k) } ================================================ FILE: internal/stream/decoding/bool_test.go ================================================ package decoding import ( "io" "reflect" "testing" "github.com/shamaton/msgpack/v3/def" ) func Test_asBool(t *testing.T) { method := func(d *decoder) func(reflect.Kind) (bool, error) { return d.asBool } testcases := AsXXXTestCases[bool]{ { Name: "error", ReadCount: 0, Error: io.EOF, MethodAs: method, }, { Name: "ok", Data: []byte{def.True}, Expected: true, ReadCount: 1, MethodAs: method, }, } for _, tc := range testcases { tc.Run(t) } } func Test_asBoolWithCode(t *testing.T) { method := func(d *decoder) func(byte, reflect.Kind) (bool, error) { return d.asBoolWithCode } testcases := AsXXXTestCases[bool]{ { Name: "True", Code: def.True, Expected: true, MethodAsWithCode: method, }, { Name: "False", Code: def.False, Expected: false, MethodAsWithCode: method, }, { Name: "Unexpected", Code: def.Nil, Error: def.ErrCanNotDecode, MethodAsWithCode: method, }, } for _, tc := range testcases { tc.Run(t) } } ================================================ FILE: internal/stream/decoding/complex.go ================================================ package decoding import ( "encoding/binary" "fmt" "math" "reflect" "github.com/shamaton/msgpack/v3/def" ) func (d *decoder) asComplex64(code byte, k reflect.Kind) (complex64, error) { switch code { case def.Fixext8: t, err := d.readSize1() if err != nil { return complex(0, 0), err } if int8(t) != def.ComplexTypeCode() { return complex(0, 0), fmt.Errorf("fixext8. complex type is diffrent %d, %d", t, def.ComplexTypeCode()) } rb, err := d.readSize4() if err != nil { return complex(0, 0), err } r := math.Float32frombits(binary.BigEndian.Uint32(rb)) ib, err := d.readSize4() if err != nil { return complex(0, 0), err } i := math.Float32frombits(binary.BigEndian.Uint32(ib)) return complex(r, i), nil case def.Fixext16: t, err := d.readSize1() if err != nil { return complex(0, 0), err } if int8(t) != def.ComplexTypeCode() { return complex(0, 0), fmt.Errorf("fixext16. complex type is diffrent %d, %d", t, def.ComplexTypeCode()) } rb, err := d.readSize8() if err != nil { return complex(0, 0), err } r := math.Float64frombits(binary.BigEndian.Uint64(rb)) ib, err := d.readSize8() if err != nil { return complex(0, 0), err } i := math.Float64frombits(binary.BigEndian.Uint64(ib)) return complex64(complex(r, i)), nil } return complex(0, 0), d.errorTemplate(code, k) } func (d *decoder) asComplex128(code byte, k reflect.Kind) (complex128, error) { switch code { case def.Fixext8: t, err := d.readSize1() if err != nil { return complex(0, 0), err } if int8(t) != def.ComplexTypeCode() { return complex(0, 0), fmt.Errorf("fixext8. complex type is diffrent %d, %d", t, def.ComplexTypeCode()) } rb, err := d.readSize4() if err != nil { return complex(0, 0), err } r := math.Float32frombits(binary.BigEndian.Uint32(rb)) ib, err := d.readSize4() if err != nil { return complex(0, 0), err } i := math.Float32frombits(binary.BigEndian.Uint32(ib)) return complex128(complex(r, i)), nil case def.Fixext16: t, err := d.readSize1() if err != nil { return complex(0, 0), err } if int8(t) != def.ComplexTypeCode() { return complex(0, 0), fmt.Errorf("fixext16. complex type is diffrent %d, %d", t, def.ComplexTypeCode()) } rb, err := d.readSize8() if err != nil { return complex(0, 0), err } r := math.Float64frombits(binary.BigEndian.Uint64(rb)) ib, err := d.readSize8() if err != nil { return complex(0, 0), err } i := math.Float64frombits(binary.BigEndian.Uint64(ib)) return complex(r, i), nil } return complex(0, 0), d.errorTemplate(code, k) } ================================================ FILE: internal/stream/decoding/complex_test.go ================================================ package decoding import ( "io" "reflect" "testing" "github.com/shamaton/msgpack/v3/def" ) func Test_asComplex64(t *testing.T) { method := func(d *decoder) func(byte, reflect.Kind) (complex64, error) { return d.asComplex64 } testcases := AsXXXTestCases[complex64]{ { Name: "Fixext8.error.type", Code: def.Fixext8, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Fixext8.error.r", Code: def.Fixext8, Data: []byte{byte(def.ComplexTypeCode())}, Error: io.EOF, ReadCount: 1, MethodAsWithCode: method, }, { Name: "Fixext8.error.i", Code: def.Fixext8, Data: []byte{byte(def.ComplexTypeCode()), 0, 0, 0, 1}, Error: io.EOF, ReadCount: 2, MethodAsWithCode: method, }, { Name: "Fixext8.ok", Code: def.Fixext8, Data: []byte{byte(def.ComplexTypeCode()), 63, 128, 0, 0, 63, 128, 0, 0}, Expected: complex(1, 1), ReadCount: 3, MethodAsWithCode: method, }, { Name: "Fixext16.error.type", Code: def.Fixext16, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Fixext16.error.r", Code: def.Fixext16, Data: []byte{byte(def.ComplexTypeCode())}, Error: io.EOF, ReadCount: 1, MethodAsWithCode: method, }, { Name: "Fixext16.error.i", Code: def.Fixext16, Data: []byte{byte(def.ComplexTypeCode()), 0, 0, 0, 1}, Error: io.EOF, ReadCount: 2, MethodAsWithCode: method, }, { Name: "Fixext16.ok", Code: def.Fixext16, Data: []byte{ byte(def.ComplexTypeCode()), 63, 240, 0, 0, 0, 0, 0, 0, 63, 240, 0, 0, 0, 0, 0, 0, }, Expected: complex(1, 1), ReadCount: 3, MethodAsWithCode: method, }, { Name: "Unexpected", Code: def.Nil, Error: def.ErrCanNotDecode, MethodAsWithCode: method, }, } for _, tc := range testcases { tc.Run(t) } } func Test_asComplex128(t *testing.T) { method := func(d *decoder) func(byte, reflect.Kind) (complex128, error) { return d.asComplex128 } testcases := AsXXXTestCases[complex128]{ { Name: "Fixext8.error.type", Code: def.Fixext8, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Fixext8.error.r", Code: def.Fixext8, Data: []byte{byte(def.ComplexTypeCode())}, Error: io.EOF, ReadCount: 1, MethodAsWithCode: method, }, { Name: "Fixext8.error.i", Code: def.Fixext8, Data: []byte{byte(def.ComplexTypeCode()), 0, 0, 0, 1}, Error: io.EOF, ReadCount: 2, MethodAsWithCode: method, }, { Name: "Fixext8.ok", Code: def.Fixext8, Data: []byte{byte(def.ComplexTypeCode()), 63, 128, 0, 0, 63, 128, 0, 0}, Expected: complex(1, 1), ReadCount: 3, MethodAsWithCode: method, }, { Name: "Fixext16.error.type", Code: def.Fixext16, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Fixext16.error.r", Code: def.Fixext16, Data: []byte{byte(def.ComplexTypeCode())}, Error: io.EOF, ReadCount: 1, MethodAsWithCode: method, }, { Name: "Fixext16.error.i", Code: def.Fixext16, Data: []byte{byte(def.ComplexTypeCode()), 0, 0, 0, 1}, Error: io.EOF, ReadCount: 2, MethodAsWithCode: method, }, { Name: "Fixext16.ok", Code: def.Fixext16, Data: []byte{ byte(def.ComplexTypeCode()), 63, 240, 0, 0, 0, 0, 0, 0, 63, 240, 0, 0, 0, 0, 0, 0, }, Expected: complex(1, 1), ReadCount: 3, MethodAsWithCode: method, }, { Name: "Unexpected", Code: def.Nil, Error: def.ErrCanNotDecode, MethodAsWithCode: method, }, } for _, tc := range testcases { tc.Run(t) } } ================================================ FILE: internal/stream/decoding/decoding.go ================================================ package decoding import ( "fmt" "io" "reflect" "github.com/shamaton/msgpack/v3/def" "github.com/shamaton/msgpack/v3/internal/common" ) type decoder struct { r io.Reader asArray bool buf *common.Buffer common.Common } // Decode analyzes the MessagePack-encoded data and stores // the result into the pointer of v. func Decode(r io.Reader, v interface{}, asArray bool) error { if r == nil { return def.ErrNoData } rv := reflect.ValueOf(v) if rv.Kind() != reflect.Ptr { return fmt.Errorf("%w. v.(type): %T", def.ErrReceiverNotPointer, v) } rv = rv.Elem() d := decoder{ r: r, buf: common.GetBuffer(), asArray: asArray, } err := d.decode(rv) common.PutBuffer(d.buf) return err } func (d *decoder) decode(rv reflect.Value) error { code, err := d.readSize1() if err != nil { return err } return d.decodeWithCode(code, rv) } func (d *decoder) decodeWithCode(code byte, rv reflect.Value) error { k := rv.Kind() switch k { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: v, err := d.asIntWithCode(code, k) if err != nil { return err } rv.SetInt(v) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: v, err := d.asUintWithCode(code, k) if err != nil { return err } rv.SetUint(v) case reflect.Float32: v, err := d.asFloat32WithCode(code, k) if err != nil { return err } rv.SetFloat(float64(v)) case reflect.Float64: v, err := d.asFloat64WithCode(code, k) if err != nil { return err } rv.SetFloat(v) case reflect.String: // byte slice if d.isCodeBin(code) { v, err := d.asBinStringWithCode(code, k) if err != nil { return err } rv.SetString(v) return nil } v, err := d.asStringWithCode(code, k) if err != nil { return err } rv.SetString(v) case reflect.Bool: v, err := d.asBoolWithCode(code, k) if err != nil { return err } rv.SetBool(v) case reflect.Slice: // nil if d.isCodeNil(code) { return nil } // byte slice if d.isCodeBin(code) { bs, err := d.asBinWithCode(code, k) if err != nil { return err } rv.SetBytes(bs) return nil } // string to bytes if d.isCodeString(code) { l, err := d.stringByteLength(code, k) if err != nil { return err } bs, err := d.asStringByteByLength(l, k) if err != nil { return err } rv.SetBytes(bs) return nil } // get slice length l, err := d.sliceLength(code, k) if err != nil { return err } // check fixed type found, err := d.asFixedSlice(rv, l) if err != nil { return err } if found { return nil } // create slice dynamically tmpSlice := reflect.MakeSlice(rv.Type(), l, l) for i := 0; i < l; i++ { v := tmpSlice.Index(i) if v.Kind() == reflect.Struct { structCode, err := d.readSize1() if err != nil { return err } if err = d.setStruct(structCode, v, k); err != nil { return err } } else { if err = d.decode(v); err != nil { return err } } } rv.Set(tmpSlice) case reflect.Complex64: v, err := d.asComplex64(code, k) if err != nil { return err } rv.SetComplex(complex128(v)) case reflect.Complex128: v, err := d.asComplex128(code, k) if err != nil { return err } rv.SetComplex(v) case reflect.Array: // nil if d.isCodeNil(code) { return nil } // byte slice if d.isCodeBin(code) { bs, err := d.asBinWithCode(code, k) if err != nil { return err } if len(bs) > rv.Len() { return fmt.Errorf("%v len is %d, but msgpack has %d elements, %w", rv.Type(), rv.Len(), len(bs), def.ErrNotMatchArrayElement) } for i, b := range bs { rv.Index(i).SetUint(uint64(b)) } return nil } // string to bytes if d.isCodeString(code) { l, err := d.stringByteLength(code, k) if err != nil { return err } if l > rv.Len() { return fmt.Errorf("%v len is %d, but msgpack has %d elements, %w", rv.Type(), rv.Len(), l, def.ErrNotMatchArrayElement) } bs, err := d.asStringByteByLength(l, k) if err != nil { return err } for i, b := range bs { rv.Index(i).SetUint(uint64(b)) } return nil } // get slice length l, err := d.sliceLength(code, k) if err != nil { return err } if l > rv.Len() { return fmt.Errorf("%v len is %d, but msgpack has %d elements, %w", rv.Type(), rv.Len(), l, def.ErrNotMatchArrayElement) } // create array dynamically for i := 0; i < l; i++ { err = d.decode(rv.Index(i)) if err != nil { return err } } case reflect.Map: // nil if d.isCodeNil(code) { return nil } // get map length l, err := d.mapLength(code, k) if err != nil { return err } // check fixed type found, err := d.asFixedMap(rv, l) if err != nil { return err } if found { return nil } // create dynamically key := rv.Type().Key() value := rv.Type().Elem() if rv.IsNil() { rv.Set(reflect.MakeMapWithSize(rv.Type(), l)) } for i := 0; i < l; i++ { k := reflect.New(key).Elem() v := reflect.New(value).Elem() err = d.decode(k) if err != nil { return err } err = d.decode(v) if err != nil { return err } rv.SetMapIndex(k, v) } case reflect.Struct: err := d.setStruct(code, rv, k) if err != nil { return err } case reflect.Ptr: // nil if d.isCodeNil(code) { return nil } if rv.Elem().Kind() == reflect.Invalid { n := reflect.New(rv.Type().Elem()) rv.Set(n) } err := d.decodeWithCode(code, rv.Elem()) if err != nil { return err } case reflect.Interface: if rv.Elem().Kind() == reflect.Ptr { err := d.decode(rv.Elem()) if err != nil { return err } } else { v, err := d.asInterfaceWithCode(code, k) if err != nil { return err } if v != nil { rv.Set(reflect.ValueOf(v)) } } default: return fmt.Errorf("%v is %w type", rv.Kind(), def.ErrUnsupportedType) } return nil } func (d *decoder) errorTemplate(code byte, k reflect.Kind) error { return fmt.Errorf("%w %x decoding as %v", def.ErrCanNotDecode, code, k) } ================================================ FILE: internal/stream/decoding/decoding_test.go ================================================ package decoding import ( "io" "reflect" "testing" "github.com/shamaton/msgpack/v3/def" "github.com/shamaton/msgpack/v3/internal/common" tu "github.com/shamaton/msgpack/v3/internal/common/testutil" ) type AsXXXTestCase[T any] struct { Name string Code byte Data []byte ReadCount int Expected T Error error MethodAs func(d *decoder) func(reflect.Kind) (T, error) MethodAsWithCode func(d *decoder) func(byte, reflect.Kind) (T, error) MethodAsCustom func(d *decoder) (T, error) } type AsXXXTestCases[T any] []AsXXXTestCase[T] func (tcs AsXXXTestCases[T]) Run(t *testing.T) { for _, tc := range tcs { tc.Run(t) } } func (tc *AsXXXTestCase[T]) Run(t *testing.T) { const kind = reflect.String t.Helper() if tc.MethodAs == nil && tc.MethodAsWithCode == nil && tc.MethodAsCustom == nil { t.Fatal("must set either method or methodAsWithCode or MethodAsCustom") } methodAs := func(d *decoder) (T, error) { if tc.MethodAs != nil { return tc.MethodAs(d)(kind) } if tc.MethodAsWithCode != nil { return tc.MethodAsWithCode(d)(tc.Code, kind) } if tc.MethodAsCustom != nil { return tc.MethodAsCustom(d) } panic("unreachable") } t.Run(tc.Name, func(t *testing.T) { r := tu.NewTestReader(tc.Data) d := decoder{ r: r, buf: common.GetBuffer(), } defer common.PutBuffer(d.buf) v, err := methodAs(&d) tu.Equal(t, r.Count(), tc.ReadCount) if tc.Error != nil { tu.IsError(t, err, tc.Error) return } tu.NoError(t, err) tu.Equal(t, v, tc.Expected) p := make([]byte, 1) n, err := d.r.Read(p) tu.IsError(t, err, io.EOF) tu.Equal(t, n, 0) }) } func TestDecoding(t *testing.T) { t.Run("nil reader", func(t *testing.T) { v := new(int) err := Decode(nil, v, false) tu.IsError(t, err, def.ErrNoData) }) t.Run("not pointer", func(t *testing.T) { v := 0 r := tu.NewTestReader([]byte{def.PositiveFixIntMax}) err := Decode(r, v, false) tu.IsError(t, err, def.ErrReceiverNotPointer) }) } func Test_decodeWithCode(t *testing.T) { var target any method := func(d *decoder) func(code byte, _ reflect.Kind) (bool, error) { return func(code byte, _ reflect.Kind) (bool, error) { rv := reflect.ValueOf(target) return true, d.decodeWithCode(code, rv.Elem()) } } t.Run("Int", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error", Code: def.Int8, Data: []byte{}, ReadCount: 0, Error: io.EOF, MethodAsWithCode: method, }, { Name: "ok", Code: def.Int8, Data: []byte{5}, Expected: true, ReadCount: 1, MethodAsWithCode: method, }, } v := new(int) target = v testcases.Run(t) tu.Equal(t, *v, 5) }) t.Run("Uint", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error", Code: def.Uint8, Data: []byte{}, ReadCount: 0, Error: io.EOF, MethodAsWithCode: method, }, { Name: "ok", Code: def.Uint8, Data: []byte{5}, Expected: true, ReadCount: 1, MethodAsWithCode: method, }, } v := new(uint) target = v testcases.Run(t) tu.Equal(t, *v, 5) }) t.Run("Float32", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error", Code: def.Float32, Data: []byte{}, ReadCount: 0, Error: io.EOF, MethodAsWithCode: method, }, { Name: "ok", Code: def.Float32, Data: []byte{63, 128, 0, 0}, Expected: true, ReadCount: 1, MethodAsWithCode: method, }, } v := new(float32) target = v testcases.Run(t) tu.Equal(t, *v, 1) }) t.Run("Float64", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error", Code: def.Float64, Data: []byte{}, ReadCount: 0, Error: io.EOF, MethodAsWithCode: method, }, { Name: "ok", Code: def.Float64, Data: []byte{63, 240, 0, 0, 0, 0, 0, 0}, Expected: true, ReadCount: 1, MethodAsWithCode: method, }, } v := new(float64) target = v testcases.Run(t) tu.Equal(t, *v, 1) }) t.Run("BinString", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error", Code: def.Bin8, Data: []byte{}, ReadCount: 0, Error: io.EOF, MethodAsWithCode: method, }, { Name: "ok", Code: def.Bin8, Data: []byte{1, 'a'}, Expected: true, ReadCount: 2, MethodAsWithCode: method, }, } v := new(string) target = v testcases.Run(t) tu.Equal(t, *v, "a") }) t.Run("String", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error", Code: def.Str8, Data: []byte{}, Expected: false, ReadCount: 0, Error: io.EOF, MethodAsWithCode: method, }, { Name: "ok", Code: def.Str8, Data: []byte{1, 'b'}, Expected: true, ReadCount: 2, MethodAsWithCode: method, }, } v := new(string) target = v testcases.Run(t) tu.Equal(t, *v, "b") }) t.Run("Bool", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error", Code: def.Int8, Data: []byte{}, ReadCount: 0, Error: def.ErrCanNotDecode, MethodAsWithCode: method, }, { Name: "ok", Code: def.True, Data: []byte{}, Expected: true, ReadCount: 0, MethodAsWithCode: method, }, } v := new(bool) target = v testcases.Run(t) tu.Equal(t, *v, true) }) t.Run("Slice.nil", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "ok", Code: def.Nil, Data: []byte{}, Expected: true, ReadCount: 0, MethodAsWithCode: method, }, } v := new([]int) target = v testcases.Run(t) tu.Equal(t, *v, nil) }) t.Run("Slice.bin", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error", Code: def.Bin8, Data: []byte{}, ReadCount: 0, Error: io.EOF, MethodAsWithCode: method, }, { Name: "ok", Code: def.Bin8, Data: []byte{1, 2}, Expected: true, ReadCount: 2, MethodAsWithCode: method, }, } v := new([]byte) target = v testcases.Run(t) tu.Equal(t, *v, []byte{2}) }) t.Run("Slice.string", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error.strlen", Code: def.Str8, Data: []byte{}, ReadCount: 0, Error: io.EOF, MethodAsWithCode: method, }, { Name: "error.bytelen", Code: def.Str8, Data: []byte{1}, ReadCount: 1, Error: io.EOF, MethodAsWithCode: method, }, { Name: "ok", Code: def.Str8, Data: []byte{1, 'c'}, Expected: true, ReadCount: 2, MethodAsWithCode: method, }, } v := new([]byte) target = v testcases.Run(t) tu.Equal(t, *v, []byte{'c'}) }) t.Run("Slice.fixed", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error.strlen", Code: def.Array16, Data: []byte{}, ReadCount: 0, Error: io.EOF, MethodAsWithCode: method, }, { Name: "error.slice", Code: def.Array16, Data: []byte{0, 1}, ReadCount: 1, Error: io.EOF, MethodAsWithCode: method, }, { Name: "ok", Code: def.Array16, Data: []byte{0, 1, def.PositiveFixIntMin + 3}, Expected: true, ReadCount: 2, MethodAsWithCode: method, }, } v := new([]int) target = v testcases.Run(t) tu.Equal(t, *v, []int{3}) }) t.Run("Slice.struct", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error.strlen", Code: def.Array16, Data: []byte{}, ReadCount: 0, Error: io.EOF, MethodAsWithCode: method, }, { Name: "error.slice", Code: def.Array16, Data: []byte{0, 1}, ReadCount: 1, Error: io.EOF, MethodAsWithCode: method, }, { Name: "error.struct", Code: def.Array16, Data: []byte{0, 1, def.FixMap + 1, def.FixStr + 1, 'v'}, ReadCount: 4, Error: io.EOF, MethodAsWithCode: method, }, { Name: "ok", Code: def.Array16, Data: []byte{0, 1, def.FixMap + 1, def.FixStr + 1, 'v', def.PositiveFixIntMin + 3}, Expected: true, ReadCount: 5, MethodAsWithCode: method, }, } type st struct { V int `msgpack:"v"` } v := new([]st) target = v testcases.Run(t) tu.Equal(t, *v, []st{{V: 3}}) }) t.Run("Slice.map", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error.strlen", Code: def.Array16, Data: []byte{}, ReadCount: 0, Error: io.EOF, MethodAsWithCode: method, }, { Name: "error.slice", Code: def.Array16, Data: []byte{0, 1}, ReadCount: 1, Error: io.EOF, MethodAsWithCode: method, }, { Name: "error.map", Code: def.Array16, Data: []byte{0, 1, def.FixMap + 1, def.FixStr + 1, 'v'}, ReadCount: 4, Error: io.EOF, MethodAsWithCode: method, }, { Name: "ok", Code: def.Array16, Data: []byte{0, 1, def.FixMap + 1, def.FixStr + 1, 'v', def.PositiveFixIntMin + 3}, Expected: true, ReadCount: 5, MethodAsWithCode: method, }, } v := new([]map[string]int) target = v testcases.Run(t) tu.Equal(t, *v, []map[string]int{{"v": 3}}) }) t.Run("Complex64", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error", Code: def.Fixext8, Data: []byte{}, ReadCount: 0, Error: io.EOF, MethodAsWithCode: method, }, { Name: "ok", Code: def.Fixext8, Data: []byte{byte(def.ComplexTypeCode()), 63, 128, 0, 0, 63, 128, 0, 0}, Expected: true, ReadCount: 3, MethodAsWithCode: method, }, } v := new(complex64) target = v testcases.Run(t) tu.Equal(t, *v, complex(1, 1)) }) t.Run("Complex128", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error", Code: def.Fixext8, Data: []byte{}, ReadCount: 0, Error: io.EOF, MethodAsWithCode: method, }, { Name: "ok", Code: def.Fixext8, Data: []byte{byte(def.ComplexTypeCode()), 63, 128, 0, 0, 63, 128, 0, 0}, Expected: true, ReadCount: 3, MethodAsWithCode: method, }, } v := new(complex128) target = v testcases.Run(t) tu.Equal(t, *v, complex(1, 1)) }) t.Run("Array.nil", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "ok", Code: def.Nil, Data: []byte{}, Expected: true, ReadCount: 0, MethodAsWithCode: method, }, } v := new([1]int) target = v testcases.Run(t) tu.Equal(t, *v, [1]int{}) }) t.Run("Array.bin", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error.bin", Code: def.Bin8, Data: []byte{}, ReadCount: 0, Error: io.EOF, MethodAsWithCode: method, }, { Name: "error.len", Code: def.Bin8, Data: []byte{2, 1, 2}, ReadCount: 2, Error: def.ErrNotMatchArrayElement, MethodAsWithCode: method, }, { Name: "ok", Code: def.Bin8, Data: []byte{1, 2}, Expected: true, ReadCount: 2, MethodAsWithCode: method, }, } v := new([1]byte) target = v testcases.Run(t) tu.Equal(t, *v, [1]byte{2}) }) t.Run("Array.string", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error.strlen", Code: def.Str8, Data: []byte{}, ReadCount: 0, Error: io.EOF, MethodAsWithCode: method, }, { Name: "error.compare", Code: def.Str8, Data: []byte{2}, ReadCount: 1, Error: def.ErrNotMatchArrayElement, MethodAsWithCode: method, }, { Name: "error.bytelen", Code: def.Str8, Data: []byte{1}, ReadCount: 1, Error: io.EOF, MethodAsWithCode: method, }, { Name: "ok", Code: def.Str8, Data: []byte{1, 'c'}, Expected: true, ReadCount: 2, MethodAsWithCode: method, }, } v := new([1]byte) target = v testcases.Run(t) tu.Equal(t, *v, [1]byte{'c'}) }) t.Run("Array.struct", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error.strlen", Code: def.Array16, Data: []byte{}, ReadCount: 0, Error: io.EOF, MethodAsWithCode: method, }, { Name: "error.strlen", Code: def.Array16, Data: []byte{0, 2}, ReadCount: 1, Error: def.ErrNotMatchArrayElement, MethodAsWithCode: method, }, { Name: "error.slice", Code: def.Array16, Data: []byte{0, 1}, ReadCount: 1, Error: io.EOF, MethodAsWithCode: method, }, { Name: "error.struct", Code: def.Array16, Data: []byte{0, 1, def.FixMap + 1, def.FixStr + 1, 'v'}, ReadCount: 4, Error: io.EOF, MethodAsWithCode: method, }, { Name: "ok", Code: def.Array16, Data: []byte{0, 1, def.FixMap + 1, def.FixStr + 1, 'v', def.PositiveFixIntMin + 3}, Expected: true, ReadCount: 5, MethodAsWithCode: method, }, } type st struct { V int `msgpack:"v"` } v := new([1]st) target = v testcases.Run(t) tu.Equal(t, *v, [1]st{{V: 3}}) }) t.Run("Map.nil", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "ok", Code: def.Nil, Data: []byte{}, Expected: true, ReadCount: 0, MethodAsWithCode: method, }, } v := new(map[string]int) target = v testcases.Run(t) tu.Equal(t, *v, nil) }) t.Run("Map.fixed", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error.strlen", Code: def.Map16, Data: []byte{}, ReadCount: 0, Error: io.EOF, MethodAsWithCode: method, }, { Name: "error.map", Code: def.Map16, Data: []byte{0, 1}, ReadCount: 1, Error: io.EOF, MethodAsWithCode: method, }, { Name: "ok", Code: def.Map16, Data: []byte{0, 1, def.FixStr + 1, 'a', def.PositiveFixIntMin + 3}, Expected: true, ReadCount: 4, MethodAsWithCode: method, }, } v := new(map[string]int) target = v testcases.Run(t) tu.Equal(t, *v, map[string]int{"a": 3}) }) t.Run("Map.struct", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error.strlen", Code: def.Map16, Data: []byte{}, ReadCount: 0, Error: io.EOF, MethodAsWithCode: method, }, { Name: "error.key", Code: def.Map16, Data: []byte{0, 1}, ReadCount: 1, Error: io.EOF, MethodAsWithCode: method, }, { Name: "error.value", Code: def.Map16, Data: []byte{0, 1, def.FixStr + 1, 'a', def.FixMap + 1, def.FixStr + 1, 'v'}, Expected: true, ReadCount: 6, Error: io.EOF, MethodAsWithCode: method, }, { Name: "ok", Code: def.Map16, Data: []byte{0, 1, def.FixStr + 1, 'a', def.FixMap + 1, def.FixStr + 1, 'v', def.PositiveFixIntMin + 3}, Expected: true, ReadCount: 7, MethodAsWithCode: method, }, } type st struct { V int `msgpack:"v"` } v := new(map[string]st) target = v testcases.Run(t) tu.Equal(t, *v, map[string]st{"a": {V: 3}}) }) t.Run("Struct", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error", Code: def.Map16, Data: []byte{}, ReadCount: 0, Error: io.EOF, MethodAsWithCode: method, }, { Name: "ok", Code: def.Map16, Data: []byte{0, 1, def.FixStr + 1, 'v', def.PositiveFixIntMin + 3}, Expected: true, ReadCount: 4, MethodAsWithCode: method, }, } type st struct { V int `msgpack:"v"` } v := new(st) target = v testcases.Run(t) tu.Equal(t, *v, st{V: 3}) }) t.Run("Ptr.nil", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "ok", Code: def.Nil, Data: []byte{}, Expected: true, ReadCount: 0, MethodAsWithCode: method, }, } v := new(int) target = &v testcases.Run(t) tu.Equal(t, *v, 0) }) t.Run("Ptr", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error", Code: def.Int8, Data: []byte{}, ReadCount: 0, Error: io.EOF, MethodAsWithCode: method, }, { Name: "ok", Code: def.Int8, Data: []byte{3}, Expected: true, ReadCount: 1, MethodAsWithCode: method, }, } v := new(int) target = &v testcases.Run(t) tu.Equal(t, *v, 3) }) t.Run("Interface.ptr", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error", Code: def.Int8, Data: []byte{}, ReadCount: 0, Error: io.EOF, MethodAsWithCode: method, }, { Name: "ok", Code: def.Int8, Data: []byte{3}, Expected: true, ReadCount: 1, MethodAsWithCode: method, }, } v := (any)(new(int)) target = &v testcases.Run(t) vv := v.(*int) tu.Equal(t, *vv, 3) }) t.Run("Interface", func(t *testing.T) { testcases := AsXXXTestCases[bool]{ { Name: "error", Code: def.Map16, Data: []byte{0, 1, def.FixStr + 1, 'v', def.Int8}, ReadCount: 4, Error: io.EOF, MethodAsWithCode: method, }, { Name: "ok", Code: def.Map16, Data: []byte{0, 1, def.FixStr + 1, 'v', def.Int8, 3}, Expected: true, ReadCount: 5, MethodAsWithCode: method, }, } type st struct { V any `msgpack:"v"` } v := new(st) target = v testcases.Run(t) var vv any = int8(3) tu.Equal(t, v.V, vv) }) } ================================================ FILE: internal/stream/decoding/ext.go ================================================ package decoding import ( "encoding/binary" "github.com/shamaton/msgpack/v3/def" "github.com/shamaton/msgpack/v3/ext" "github.com/shamaton/msgpack/v3/time" ) var ( extCoderMap = map[int8]ext.StreamDecoder{time.StreamDecoder.Code(): time.StreamDecoder} extCoders = []ext.StreamDecoder{time.StreamDecoder} ) // AddExtDecoder adds decoders for extension types. func AddExtDecoder(f ext.StreamDecoder) { // ignore time if f.Code() == time.Decoder.Code() { return } _, ok := extCoderMap[f.Code()] if !ok { extCoderMap[f.Code()] = f updateExtCoders() } } // RemoveExtDecoder removes decoders for extension types. func RemoveExtDecoder(f ext.StreamDecoder) { // ignore time if f.Code() == time.Decoder.Code() { return } _, ok := extCoderMap[f.Code()] if ok { delete(extCoderMap, f.Code()) updateExtCoders() } } func updateExtCoders() { extCoders = make([]ext.StreamDecoder, len(extCoderMap)) i := 0 for k := range extCoderMap { extCoders[i] = extCoderMap[k] i++ } } func (d *decoder) readIfExtType(code byte) (innerType int8, data []byte, err error) { switch code { case def.Fixext1: typ, err := d.readSize1() if err != nil { return 0, nil, err } v, err := d.readSize1() if err != nil { return 0, nil, err } return int8(typ), []byte{v}, nil case def.Fixext2: typ, err := d.readSize1() if err != nil { return 0, nil, err } data, err = d.readSize2() if err != nil { return 0, nil, err } return int8(typ), data, nil case def.Fixext4: typ, err := d.readSize1() if err != nil { return 0, nil, err } data, err = d.readSize4() if err != nil { return 0, nil, err } return int8(typ), data, nil case def.Fixext8: typ, err := d.readSize1() if err != nil { return 0, nil, err } data, err = d.readSize8() if err != nil { return 0, nil, err } return int8(typ), data, nil case def.Fixext16: typ, err := d.readSize1() if err != nil { return 0, nil, err } data, err = d.readSize16() if err != nil { return 0, nil, err } return int8(typ), data, nil case def.Ext8: bs, err := d.readSize1() if err != nil { return 0, nil, err } size := int(bs) typ, err := d.readSize1() if err != nil { return 0, nil, err } data, err = d.readSizeN(size) if err != nil { return 0, nil, err } return int8(typ), data, nil case def.Ext16: bs, err := d.readSize2() if err != nil { return 0, nil, err } size := int(binary.BigEndian.Uint16(bs)) typ, err := d.readSize1() if err != nil { return 0, nil, err } data, err = d.readSizeN(size) if err != nil { return 0, nil, err } return int8(typ), data, nil case def.Ext32: bs, err := d.readSize4() if err != nil { return 0, nil, err } size := int(binary.BigEndian.Uint32(bs)) typ, err := d.readSize1() if err != nil { return 0, nil, err } data, err = d.readSizeN(size) if err != nil { return 0, nil, err } return int8(typ), data, nil } return 0, nil, nil } ================================================ FILE: internal/stream/decoding/ext_test.go ================================================ package decoding import ( "io" "testing" "github.com/shamaton/msgpack/v3/def" "github.com/shamaton/msgpack/v3/internal/common" tu "github.com/shamaton/msgpack/v3/internal/common/testutil" "github.com/shamaton/msgpack/v3/time" ) func Test_AddExtDecoder(t *testing.T) { t.Run("ignore", func(t *testing.T) { AddExtDecoder(time.StreamDecoder) tu.Equal(t, len(extCoders), 1) }) } func Test_RemoveExtDecoder(t *testing.T) { t.Run("ignore", func(t *testing.T) { RemoveExtDecoder(time.StreamDecoder) tu.Equal(t, len(extCoders), 1) }) } func Test_readIfExtType(t *testing.T) { testcases := []struct { name string code byte r []byte typ int8 data []byte err error count int }{ { name: "Fixext1.error.type", code: def.Fixext1, r: []byte{}, err: io.EOF, count: 0, }, { name: "Fixext1.error.data", code: def.Fixext1, r: []byte{1}, err: io.EOF, count: 1, }, { name: "Fixext1.ok", code: def.Fixext1, r: []byte{1, 2}, typ: 1, data: []byte{2}, count: 2, }, { name: "Fixext2.error.type", code: def.Fixext2, r: []byte{}, err: io.EOF, count: 0, }, { name: "Fixext2.error.data", code: def.Fixext2, r: []byte{2}, err: io.EOF, count: 1, }, { name: "Fixext2.ok", code: def.Fixext2, r: []byte{2, 3, 4}, typ: 2, data: []byte{3, 4}, count: 2, }, { name: "Fixext4.error.type", code: def.Fixext4, r: []byte{}, err: io.EOF, count: 0, }, { name: "Fixext4.error.data", code: def.Fixext4, r: []byte{4}, err: io.EOF, count: 1, }, { name: "Fixext4.ok", code: def.Fixext4, r: []byte{4, 5, 6, 7, 8}, typ: 4, data: []byte{5, 6, 7, 8}, count: 2, }, { name: "Fixext8.error.type", code: def.Fixext8, r: []byte{}, err: io.EOF, count: 0, }, { name: "Fixext8.error.data", code: def.Fixext8, r: []byte{8}, err: io.EOF, count: 1, }, { name: "Fixext8.ok", code: def.Fixext8, r: []byte{8, 1, 2, 3, 4, 5, 6, 7, 8}, typ: 8, data: []byte{1, 2, 3, 4, 5, 6, 7, 8}, count: 2, }, { name: "Fixext16.error.type", code: def.Fixext16, r: []byte{}, err: io.EOF, count: 0, }, { name: "Fixext16.error.data", code: def.Fixext16, r: []byte{16}, err: io.EOF, count: 1, }, { name: "Fixext16.ok", code: def.Fixext16, r: []byte{16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, typ: 16, data: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, count: 2, }, { name: "Ext8.error.size", code: def.Ext8, r: []byte{}, err: io.EOF, count: 0, }, { name: "Ext8.error.type", code: def.Ext8, r: []byte{1}, err: io.EOF, count: 1, }, { name: "Ext8.error.data", code: def.Ext8, r: []byte{1, 18}, err: io.EOF, count: 2, }, { name: "Ext8.ok", code: def.Ext8, r: []byte{1, 18, 2}, typ: 18, data: []byte{2}, count: 3, }, { name: "Ext16.error.size", code: def.Ext16, r: []byte{}, err: io.EOF, count: 0, }, { name: "Ext16.error.type", code: def.Ext16, r: []byte{0, 1}, err: io.EOF, count: 1, }, { name: "Ext16.error.data", code: def.Ext16, r: []byte{0, 1, 24}, err: io.EOF, count: 2, }, { name: "Ext16.ok", code: def.Ext16, r: []byte{0, 1, 24, 3}, typ: 24, data: []byte{3}, count: 3, }, { name: "Ext32.error.size", code: def.Ext32, r: []byte{}, err: io.EOF, count: 0, }, { name: "Ext32.error.type", code: def.Ext32, r: []byte{0, 0, 0, 1}, err: io.EOF, count: 1, }, { name: "Ext32.error.data", code: def.Ext32, r: []byte{0, 0, 0, 1, 32}, err: io.EOF, count: 2, }, { name: "Ext32.ok", code: def.Ext32, r: []byte{0, 0, 0, 1, 32, 4}, typ: 32, data: []byte{4}, count: 3, }, } for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { r := tu.NewTestReader(tc.r) d := decoder{ r: r, buf: common.GetBuffer(), } defer common.PutBuffer(d.buf) typ, data, err := d.readIfExtType(tc.code) tu.IsError(t, err, tc.err) tu.Equal(t, tc.typ, typ) tu.EqualSlice(t, data, tc.data) tu.Equal(t, r.Count(), tc.count) }) } } ================================================ FILE: internal/stream/decoding/float.go ================================================ package decoding import ( "encoding/binary" "math" "reflect" "github.com/shamaton/msgpack/v3/def" ) func (d *decoder) asFloat32(k reflect.Kind) (float32, error) { code, err := d.readSize1() if err != nil { return 0, err } return d.asFloat32WithCode(code, k) } func (d *decoder) asFloat32WithCode(code byte, k reflect.Kind) (float32, error) { switch { case code == def.Float32: bs, err := d.readSize4() if err != nil { return 0, err } v := math.Float32frombits(binary.BigEndian.Uint32(bs)) return v, nil case d.isPositiveFixNum(code), code == def.Uint8, code == def.Uint16, code == def.Uint32, code == def.Uint64: v, err := d.asUintWithCode(code, k) if err != nil { return 0, err } return float32(v), nil case d.isNegativeFixNum(code), code == def.Int8, code == def.Int16, code == def.Int32, code == def.Int64: v, err := d.asIntWithCode(code, k) if err != nil { return 0, err } return float32(v), nil case code == def.Nil: return 0, nil } return 0, d.errorTemplate(code, k) } func (d *decoder) asFloat64(k reflect.Kind) (float64, error) { code, err := d.readSize1() if err != nil { return 0, err } return d.asFloat64WithCode(code, k) } func (d *decoder) asFloat64WithCode(code byte, k reflect.Kind) (float64, error) { switch { case code == def.Float64: bs, err := d.readSize8() if err != nil { return 0, err } v := math.Float64frombits(binary.BigEndian.Uint64(bs)) return v, nil case code == def.Float32: bs, err := d.readSize4() if err != nil { return 0, err } v := math.Float32frombits(binary.BigEndian.Uint32(bs)) return float64(v), nil case d.isPositiveFixNum(code), code == def.Uint8, code == def.Uint16, code == def.Uint32, code == def.Uint64: v, err := d.asUintWithCode(code, k) if err != nil { return 0, err } return float64(v), nil case d.isNegativeFixNum(code), code == def.Int8, code == def.Int16, code == def.Int32, code == def.Int64: v, err := d.asIntWithCode(code, k) if err != nil { return 0, err } return float64(v), nil case code == def.Nil: return 0, nil } return 0, d.errorTemplate(code, k) } ================================================ FILE: internal/stream/decoding/float_test.go ================================================ package decoding import ( "io" "reflect" "testing" "github.com/shamaton/msgpack/v3/def" ) func Test_asFloat32(t *testing.T) { method := func(d *decoder) func(reflect.Kind) (float32, error) { return d.asFloat32 } testcases := AsXXXTestCases[float32]{ { Name: "error", Error: io.EOF, MethodAs: method, }, { Name: "ok", Data: []byte{def.Float32, 63, 128, 0, 0}, Expected: float32(1), ReadCount: 2, MethodAs: method, }, } for _, tc := range testcases { tc.Run(t) } } func Test_asFloat32WithCode(t *testing.T) { method := func(d *decoder) func(byte, reflect.Kind) (float32, error) { return d.asFloat32WithCode } testcases := AsXXXTestCases[float32]{ { Name: "Float32.error", Code: def.Float32, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Float32.ok", Code: def.Float32, Data: []byte{63, 128, 0, 0}, Expected: float32(1), ReadCount: 1, MethodAsWithCode: method, }, { Name: "Uint8.error", Code: def.Uint8, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Uint8.ok", Code: def.Uint8, Data: []byte{1}, Expected: float32(1), ReadCount: 1, MethodAsWithCode: method, }, { Name: "Int8.error", Code: def.Int8, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Int8.ok", Code: def.Int8, Data: []byte{0xff}, Expected: float32(-1), ReadCount: 1, MethodAsWithCode: method, }, { Name: "Nil.ok", Code: def.Nil, Expected: float32(0), ReadCount: 0, MethodAsWithCode: method, }, { Name: "Unexpected", Code: def.Str8, Error: def.ErrCanNotDecode, MethodAsWithCode: method, }, } for _, tc := range testcases { tc.Run(t) } } func Test_asFloat64(t *testing.T) { method := func(d *decoder) func(reflect.Kind) (float64, error) { return d.asFloat64 } testcases := AsXXXTestCases[float64]{ { Name: "error", Error: io.EOF, MethodAs: method, }, { Name: "ok", Data: []byte{def.Float64, 63, 240, 0, 0, 0, 0, 0, 0}, Expected: float64(1), ReadCount: 2, MethodAs: method, }, } for _, tc := range testcases { tc.Run(t) } } func Test_asFloat64WithCode(t *testing.T) { method := func(d *decoder) func(byte, reflect.Kind) (float64, error) { return d.asFloat64WithCode } testcases := AsXXXTestCases[float64]{ { Name: "Float64.error", Code: def.Float64, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Float64.ok", Code: def.Float64, Data: []byte{63, 240, 0, 0, 0, 0, 0, 0}, Expected: float64(1), ReadCount: 1, MethodAsWithCode: method, }, { Name: "Float32.error", Code: def.Float32, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Float32.ok", Code: def.Float32, Data: []byte{63, 128, 0, 0}, Expected: float64(1), ReadCount: 1, MethodAsWithCode: method, }, { Name: "Uint8.error", Code: def.Uint8, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Uint8.ok", Code: def.Uint8, Data: []byte{1}, Expected: float64(1), ReadCount: 1, MethodAsWithCode: method, }, { Name: "Int8.error", Code: def.Int8, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Int8.ok", Code: def.Int8, Data: []byte{0xff}, Expected: float64(-1), ReadCount: 1, MethodAsWithCode: method, }, { Name: "Nil.ok", Code: def.Nil, Expected: float64(0), ReadCount: 0, MethodAsWithCode: method, }, { Name: "Unexpected", Code: def.Str8, Error: def.ErrCanNotDecode, MethodAsWithCode: method, }, } for _, tc := range testcases { tc.Run(t) } } ================================================ FILE: internal/stream/decoding/int.go ================================================ package decoding import ( "encoding/binary" "reflect" "github.com/shamaton/msgpack/v3/def" ) func (d *decoder) isPositiveFixNum(v byte) bool { return def.PositiveFixIntMin <= v && v <= def.PositiveFixIntMax } func (d *decoder) isNegativeFixNum(v byte) bool { return def.NegativeFixintMin <= int8(v) && int8(v) <= def.NegativeFixintMax } func (d *decoder) asInt(k reflect.Kind) (int64, error) { code, err := d.readSize1() if err != nil { return 0, err } return d.asIntWithCode(code, k) } func (d *decoder) asIntWithCode(code byte, k reflect.Kind) (int64, error) { switch { case d.isPositiveFixNum(code): return int64(code), nil case d.isNegativeFixNum(code): return int64(int8(code)), nil case code == def.Uint8: b, err := d.readSize1() if err != nil { return 0, err } return int64(b), nil case code == def.Int8: b, err := d.readSize1() if err != nil { return 0, err } return int64(int8(b)), nil case code == def.Uint16: bs, err := d.readSize2() if err != nil { return 0, err } v := binary.BigEndian.Uint16(bs) return int64(v), nil case code == def.Int16: bs, err := d.readSize2() if err != nil { return 0, err } v := int16(binary.BigEndian.Uint16(bs)) return int64(v), nil case code == def.Uint32: bs, err := d.readSize4() if err != nil { return 0, err } v := binary.BigEndian.Uint32(bs) return int64(v), nil case code == def.Int32: bs, err := d.readSize4() if err != nil { return 0, err } v := int32(binary.BigEndian.Uint32(bs)) return int64(v), nil case code == def.Uint64: bs, err := d.readSize8() if err != nil { return 0, err } return int64(binary.BigEndian.Uint64(bs)), nil case code == def.Int64: bs, err := d.readSize8() if err != nil { return 0, err } return int64(binary.BigEndian.Uint64(bs)), nil case code == def.Float32: v, err := d.asFloat32WithCode(code, k) if err != nil { return 0, err } return int64(v), nil case code == def.Float64: v, err := d.asFloat64WithCode(code, k) if err != nil { return 0, err } return int64(v), nil case code == def.Nil: return 0, nil } return 0, d.errorTemplate(code, k) } ================================================ FILE: internal/stream/decoding/int_test.go ================================================ package decoding import ( "io" "reflect" "testing" "github.com/shamaton/msgpack/v3/def" ) func Test_asInt(t *testing.T) { method := func(d *decoder) func(reflect.Kind) (int64, error) { return d.asInt } testcases := AsXXXTestCases[int64]{ { Name: "error", Data: []byte{}, Error: io.EOF, ReadCount: 0, MethodAs: method, }, { Name: "ok", Data: []byte{def.Int8, 1}, Expected: 1, ReadCount: 2, MethodAs: method, }, } for _, tc := range testcases { tc.Run(t) } } func Test_asIntWithCode(t *testing.T) { method := func(d *decoder) func(byte, reflect.Kind) (int64, error) { return d.asIntWithCode } testcases := AsXXXTestCases[int64]{ { Name: "Uint8.error", Code: def.Uint8, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Uint8.ok", Code: def.Uint8, Data: []byte{1}, Expected: 1, ReadCount: 1, MethodAsWithCode: method, }, { Name: "Int8.error", Code: def.Int8, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Int8.ok", Code: def.Int8, Data: []byte{1}, Expected: 1, ReadCount: 1, MethodAsWithCode: method, }, { Name: "Uint16.error", Code: def.Uint16, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Uint16.ok", Code: def.Uint16, Data: []byte{0, 1}, Expected: 1, ReadCount: 1, MethodAsWithCode: method, }, { Name: "Int16.error", Code: def.Int16, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Int16.ok", Code: def.Int16, Data: []byte{0, 1}, Expected: 1, ReadCount: 1, MethodAsWithCode: method, }, { Name: "Uint32.error", Code: def.Uint32, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Uint32.ok", Code: def.Uint32, Data: []byte{0, 0, 0, 1}, Expected: 1, ReadCount: 1, MethodAsWithCode: method, }, { Name: "Int32.error", Code: def.Int32, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Int32.ok", Code: def.Int32, Data: []byte{0, 0, 0, 1}, Expected: 1, ReadCount: 1, MethodAsWithCode: method, }, { Name: "Uint64.error", Code: def.Uint64, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Uint64.ok", Code: def.Uint64, Data: []byte{0, 0, 0, 0, 0, 0, 0, 1}, Expected: 1, ReadCount: 1, MethodAsWithCode: method, }, { Name: "Int64.error", Code: def.Int64, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Int64.ok", Code: def.Int64, Data: []byte{0, 0, 0, 0, 0, 0, 0, 1}, Expected: 1, ReadCount: 1, MethodAsWithCode: method, }, { Name: "Float32.error", Code: def.Float32, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Float32.ok", Code: def.Float32, Data: []byte{63, 128, 0, 0}, Expected: 1, ReadCount: 1, MethodAsWithCode: method, }, { Name: "Float64.error", Code: def.Float64, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Float64.ok", Code: def.Float64, Data: []byte{63, 240, 0, 0, 0, 0, 0, 0}, Expected: 1, ReadCount: 1, MethodAsWithCode: method, }, { Name: "Nil", Code: def.Nil, Expected: 0, MethodAsWithCode: method, }, { Name: "Unexpected", Code: def.Array16, Error: def.ErrCanNotDecode, MethodAsWithCode: method, }, } for _, tc := range testcases { tc.Run(t) } } ================================================ FILE: internal/stream/decoding/interface.go ================================================ package decoding import ( "fmt" "reflect" "github.com/shamaton/msgpack/v3/def" ) func (d *decoder) asInterface(k reflect.Kind) (interface{}, error) { code, err := d.readSize1() if err != nil { return nil, err } return d.asInterfaceWithCode(code, k) } func (d *decoder) asInterfaceWithCode(code byte, k reflect.Kind) (interface{}, error) { switch { case code == def.Nil: return nil, nil case code == def.True, code == def.False: v, err := d.asBoolWithCode(code, k) if err != nil { return nil, err } return v, nil case d.isPositiveFixNum(code), code == def.Uint8: v, err := d.asUintWithCode(code, k) if err != nil { return nil, err } return uint8(v), err case code == def.Uint16: v, err := d.asUintWithCode(code, k) if err != nil { return nil, err } return uint16(v), err case code == def.Uint32: v, err := d.asUintWithCode(code, k) if err != nil { return nil, err } return uint32(v), err case code == def.Uint64: v, err := d.asUintWithCode(code, k) if err != nil { return nil, err } return v, err case d.isNegativeFixNum(code), code == def.Int8: v, err := d.asIntWithCode(code, k) if err != nil { return nil, err } return int8(v), err case code == def.Int16: v, err := d.asIntWithCode(code, k) if err != nil { return nil, err } return int16(v), err case code == def.Int32: v, err := d.asIntWithCode(code, k) if err != nil { return nil, err } return int32(v), err case code == def.Int64: v, err := d.asIntWithCode(code, k) if err != nil { return nil, err } return v, err case code == def.Float32: v, err := d.asFloat32WithCode(code, k) if err != nil { return nil, err } return v, err case code == def.Float64: v, err := d.asFloat64WithCode(code, k) if err != nil { return nil, err } return v, err case d.isFixString(code), code == def.Str8, code == def.Str16, code == def.Str32: v, err := d.asStringWithCode(code, k) if err != nil { return nil, err } return v, err case code == def.Bin8, code == def.Bin16, code == def.Bin32: v, err := d.asBinWithCode(code, k) if err != nil { return nil, err } return v, err case d.isFixSlice(code), code == def.Array16, code == def.Array32: l, err := d.sliceLength(code, k) if err != nil { return nil, err } v := make([]interface{}, l) for i := 0; i < l; i++ { vv, err := d.asInterface(k) if err != nil { return nil, err } v[i] = vv } return v, nil case d.isFixMap(code), code == def.Map16, code == def.Map32: l, err := d.mapLength(code, k) if err != nil { return nil, err } v := make(map[interface{}]interface{}, l) for i := 0; i < l; i++ { keyCode, err := d.readSize1() if err != nil { return 0, err } if err := d.canSetAsMapKey(keyCode); err != nil { return nil, err } key, err := d.asInterfaceWithCode(keyCode, k) if err != nil { return nil, err } value, err := d.asInterface(k) if err != nil { return nil, err } v[key] = value } return v, nil } // ext extInnerType, extData, err := d.readIfExtType(code) if err != nil { return nil, err } for i := range extCoders { if extCoders[i].IsType(code, extInnerType, len(extData)) { v, err := extCoders[i].ToValue(code, extData, k) if err != nil { return nil, err } return v, nil } } return nil, d.errorTemplate(code, k) } func (d *decoder) canSetAsMapKey(code byte) error { switch { case d.isFixSlice(code), code == def.Array16, code == def.Array32: return fmt.Errorf("%w. code: %x", def.ErrCanNotSetSliceAsMapKey, code) case d.isFixMap(code), code == def.Map16, code == def.Map32: return fmt.Errorf("%w. code: %x", def.ErrCanNotSetMapAsMapKey, code) } return nil } ================================================ FILE: internal/stream/decoding/interface_test.go ================================================ package decoding import ( "fmt" "io" "reflect" "testing" "github.com/shamaton/msgpack/v3/def" "github.com/shamaton/msgpack/v3/ext" ) func Test_asInterface(t *testing.T) { method := func(d *decoder) func(reflect.Kind) (any, error) { return d.asInterface } testcases := AsXXXTestCases[any]{ { Name: "error", Data: []byte{}, Error: io.EOF, ReadCount: 0, MethodAs: method, }, { Name: "ok", Data: []byte{def.Nil}, Expected: nil, ReadCount: 1, MethodAs: method, }, } for _, tc := range testcases { tc.Run(t) } } func Test_asInterfaceWithCode(t *testing.T) { dec := testExt2StreamDecoder{} AddExtDecoder(&dec) defer RemoveExtDecoder(&dec) method := func(d *decoder) func(byte, reflect.Kind) (any, error) { return d.asInterfaceWithCode } testcases := AsXXXTestCases[any]{ { Name: "Uint8.error", Code: def.Uint8, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Uint16.error", Code: def.Uint16, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Uint32.error", Code: def.Uint32, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Uint64.error", Code: def.Uint64, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Int8.error", Code: def.Int8, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Int16.error", Code: def.Int16, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Int32.error", Code: def.Int32, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Int64.error", Code: def.Int64, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Float32.error", Code: def.Float32, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Float32.ok", Code: def.Float32, Data: []byte{63, 128, 0, 0}, Expected: float32(1), ReadCount: 1, MethodAsWithCode: method, }, { Name: "Float64.error", Code: def.Float64, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Str.error", Code: def.Str8, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Bin.error", Code: def.Bin8, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Array.error.length", Code: def.Array16, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Array.error.set", Code: def.Array16, Data: []byte{0, 1}, ReadCount: 1, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Map.error.length", Code: def.Map16, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Map.error.set.key_code", Code: def.Map16, Data: []byte{0, 1}, ReadCount: 1, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Map.error.set.can.slice", Code: def.Map16, Data: []byte{0, 1, def.Array16}, ReadCount: 2, Error: def.ErrCanNotSetSliceAsMapKey, MethodAsWithCode: method, }, { Name: "Map.error.set.can.map", Code: def.Map16, Data: []byte{0, 1, def.Map16}, ReadCount: 2, Error: def.ErrCanNotSetMapAsMapKey, MethodAsWithCode: method, }, { Name: "Map.error.set.key", Code: def.Map16, Data: []byte{0, 1, def.FixStr + 1}, ReadCount: 2, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Map.error.set.value", Code: def.Map16, Data: []byte{0, 1, def.FixStr + 1, 'a'}, ReadCount: 3, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Ext.error", Code: def.Fixext1, Data: []byte{}, ReadCount: 0, Error: io.EOF, MethodAsWithCode: method, }, { Name: "ExtCoder.error", Code: def.Fixext1, Data: []byte{3, 0}, ReadCount: 2, Error: ErrTestExtStreamDecoder, MethodAsWithCode: method, }, { Name: "Unexpected", Code: def.Fixext1, Data: []byte{4, 0}, ReadCount: 2, Error: def.ErrCanNotDecode, MethodAsWithCode: method, }, } for _, tc := range testcases { tc.Run(t) } } // TODO: to testutil type testExt2StreamDecoder struct{} var _ ext.StreamDecoder = (*testExt2StreamDecoder)(nil) func (td *testExt2StreamDecoder) Code() int8 { return 3 } func (td *testExt2StreamDecoder) IsType(_ byte, code int8, _ int) bool { return code == td.Code() } var ErrTestExtStreamDecoder = fmt.Errorf("testExtStreamDecoder") func (td *testExt2StreamDecoder) ToValue(_ byte, _ []byte, k reflect.Kind) (any, error) { return nil, ErrTestExtStreamDecoder } ================================================ FILE: internal/stream/decoding/map.go ================================================ package decoding import ( "encoding/binary" "reflect" "github.com/shamaton/msgpack/v3/def" ) var ( typeMapStringInt = reflect.TypeOf(map[string]int{}) typeMapStringInt8 = reflect.TypeOf(map[string]int8{}) typeMapStringInt16 = reflect.TypeOf(map[string]int16{}) typeMapStringInt32 = reflect.TypeOf(map[string]int32{}) typeMapStringInt64 = reflect.TypeOf(map[string]int64{}) typeMapStringUint = reflect.TypeOf(map[string]uint{}) typeMapStringUint8 = reflect.TypeOf(map[string]uint8{}) typeMapStringUint16 = reflect.TypeOf(map[string]uint16{}) typeMapStringUint32 = reflect.TypeOf(map[string]uint32{}) typeMapStringUint64 = reflect.TypeOf(map[string]uint64{}) typeMapStringFloat32 = reflect.TypeOf(map[string]float32{}) typeMapStringFloat64 = reflect.TypeOf(map[string]float64{}) typeMapStringBool = reflect.TypeOf(map[string]bool{}) typeMapStringString = reflect.TypeOf(map[string]string{}) typeMapIntString = reflect.TypeOf(map[int]string{}) typeMapInt8String = reflect.TypeOf(map[int8]string{}) typeMapInt16String = reflect.TypeOf(map[int16]string{}) typeMapInt32String = reflect.TypeOf(map[int32]string{}) typeMapInt64String = reflect.TypeOf(map[int64]string{}) typeMapIntBool = reflect.TypeOf(map[int]bool{}) typeMapInt8Bool = reflect.TypeOf(map[int8]bool{}) typeMapInt16Bool = reflect.TypeOf(map[int16]bool{}) typeMapInt32Bool = reflect.TypeOf(map[int32]bool{}) typeMapInt64Bool = reflect.TypeOf(map[int64]bool{}) typeMapUintString = reflect.TypeOf(map[uint]string{}) typeMapUint8String = reflect.TypeOf(map[uint8]string{}) typeMapUint16String = reflect.TypeOf(map[uint16]string{}) typeMapUint32String = reflect.TypeOf(map[uint32]string{}) typeMapUint64String = reflect.TypeOf(map[uint64]string{}) typeMapUintBool = reflect.TypeOf(map[uint]bool{}) typeMapUint8Bool = reflect.TypeOf(map[uint8]bool{}) typeMapUint16Bool = reflect.TypeOf(map[uint16]bool{}) typeMapUint32Bool = reflect.TypeOf(map[uint32]bool{}) typeMapUint64Bool = reflect.TypeOf(map[uint64]bool{}) typeMapFloat32String = reflect.TypeOf(map[float32]string{}) typeMapFloat64String = reflect.TypeOf(map[float64]string{}) typeMapFloat32Bool = reflect.TypeOf(map[float32]bool{}) typeMapFloat64Bool = reflect.TypeOf(map[float64]bool{}) ) func (d *decoder) isFixMap(v byte) bool { return def.FixMap <= v && v <= def.FixMap+0x0f } func (d *decoder) mapLength(code byte, k reflect.Kind) (int, error) { switch { case d.isFixMap(code): return int(code - def.FixMap), nil case code == def.Map16: bs, err := d.readSize2() if err != nil { return 0, err } return int(binary.BigEndian.Uint16(bs)), nil case code == def.Map32: bs, err := d.readSize4() if err != nil { return 0, err } return int(binary.BigEndian.Uint32(bs)), nil } return 0, d.errorTemplate(code, k) } func (d *decoder) asFixedMap(rv reflect.Value, l int) (bool, error) { t := rv.Type() keyKind := t.Key().Kind() valueKind := t.Elem().Kind() switch t { case typeMapStringInt: m := make(map[string]int, l) for i := 0; i < l; i++ { k, err := d.asString(keyKind) if err != nil { return false, err } v, err := d.asInt(valueKind) if err != nil { return false, err } m[k] = int(v) } rv.Set(reflect.ValueOf(m)) return true, nil case typeMapStringUint: m := make(map[string]uint, l) for i := 0; i < l; i++ { k, err := d.asString(keyKind) if err != nil { return false, err } v, err := d.asUint(valueKind) if err != nil { return false, err } m[k] = uint(v) } rv.Set(reflect.ValueOf(m)) return true, nil case typeMapStringFloat32: m := make(map[string]float32, l) for i := 0; i < l; i++ { k, err := d.asString(keyKind) if err != nil { return false, err } v, err := d.asFloat32(valueKind) if err != nil { return false, err } m[k] = v } rv.Set(reflect.ValueOf(m)) return true, nil case typeMapStringFloat64: m := make(map[string]float64, l) for i := 0; i < l; i++ { k, err := d.asString(keyKind) if err != nil { return false, err } v, err := d.asFloat64(valueKind) if err != nil { return false, err } m[k] = v } rv.Set(reflect.ValueOf(m)) return true, nil case typeMapStringBool: m := make(map[string]bool, l) for i := 0; i < l; i++ { k, err := d.asString(keyKind) if err != nil { return false, err } v, err := d.asBool(valueKind) if err != nil { return false, err } m[k] = v } rv.Set(reflect.ValueOf(m)) return true, nil case typeMapStringString: m := make(map[string]string, l) for i := 0; i < l; i++ { k, err := d.asString(keyKind) if err != nil { return false, err } v, err := d.asString(valueKind) if err != nil { return false, err } m[k] = v } rv.Set(reflect.ValueOf(m)) return true, nil case typeMapStringInt8: m := make(map[string]int8, l) for i := 0; i < l; i++ { k, err := d.asString(keyKind) if err != nil { return false, err } v, err := d.asInt(valueKind) if err != nil { return false, err } m[k] = int8(v) } rv.Set(reflect.ValueOf(m)) return true, nil case typeMapStringInt16: m := make(map[string]int16, l) for i := 0; i < l; i++ { k, err := d.asString(keyKind) if err != nil { return false, err } v, err := d.asInt(valueKind) if err != nil { return false, err } m[k] = int16(v) } rv.Set(reflect.ValueOf(m)) return true, nil case typeMapStringInt32: m := make(map[string]int32, l) for i := 0; i < l; i++ { k, err := d.asString(keyKind) if err != nil { return false, err } v, err := d.asInt(valueKind) if err != nil { return false, err } m[k] = int32(v) } rv.Set(reflect.ValueOf(m)) return true, nil case typeMapStringInt64: m := make(map[string]int64, l) for i := 0; i < l; i++ { k, err := d.asString(keyKind) if err != nil { return false, err } v, err := d.asInt(valueKind) if err != nil { return false, err } m[k] = v } rv.Set(reflect.ValueOf(m)) return true, nil case typeMapStringUint8: m := make(map[string]uint8, l) for i := 0; i < l; i++ { k, err := d.asString(keyKind) if err != nil { return false, err } v, err := d.asUint(valueKind) if err != nil { return false, err } m[k] = uint8(v) } rv.Set(reflect.ValueOf(m)) return true, nil case typeMapStringUint16: m := make(map[string]uint16, l) for i := 0; i < l; i++ { k, err := d.asString(keyKind) if err != nil { return false, err } v, err := d.asUint(valueKind) if err != nil { return false, err } m[k] = uint16(v) } rv.Set(reflect.ValueOf(m)) return true, nil case typeMapStringUint32: m := make(map[string]uint32, l) for i := 0; i < l; i++ { k, err := d.asString(keyKind) if err != nil { return false, err } v, err := d.asUint(valueKind) if err != nil { return false, err } m[k] = uint32(v) } rv.Set(reflect.ValueOf(m)) return true, nil case typeMapStringUint64: m := make(map[string]uint64, l) for i := 0; i < l; i++ { k, err := d.asString(keyKind) if err != nil { return false, err } v, err := d.asUint(valueKind) if err != nil { return false, err } m[k] = v } rv.Set(reflect.ValueOf(m)) return true, nil case typeMapIntString: m := make(map[int]string, l) for i := 0; i < l; i++ { k, err := d.asInt(keyKind) if err != nil { return false, err } v, err := d.asString(valueKind) if err != nil { return false, err } m[int(k)] = v } rv.Set(reflect.ValueOf(m)) return true, nil case typeMapInt8String: m := make(map[int8]string, l) for i := 0; i < l; i++ { k, err := d.asInt(keyKind) if err != nil { return false, err } v, err := d.asString(valueKind) if err != nil { return false, err } m[int8(k)] = v } rv.Set(reflect.ValueOf(m)) return true, nil case typeMapInt16String: m := make(map[int16]string, l) for i := 0; i < l; i++ { k, err := d.asInt(keyKind) if err != nil { return false, err } v, err := d.asString(valueKind) if err != nil { return false, err } m[int16(k)] = v } rv.Set(reflect.ValueOf(m)) return true, nil case typeMapInt32String: m := make(map[int32]string, l) for i := 0; i < l; i++ { k, err := d.asInt(keyKind) if err != nil { return false, err } v, err := d.asString(valueKind) if err != nil { return false, err } m[int32(k)] = v } rv.Set(reflect.ValueOf(m)) return true, nil case typeMapInt64String: m := make(map[int64]string, l) for i := 0; i < l; i++ { k, err := d.asInt(keyKind) if err != nil { return false, err } v, err := d.asString(valueKind) if err != nil { return false, err } m[k] = v } rv.Set(reflect.ValueOf(m)) return true, nil case typeMapIntBool: m := make(map[int]bool, l) for i := 0; i < l; i++ { k, err := d.asInt(keyKind) if err != nil { return false, err } v, err := d.asBool(valueKind) if err != nil { return false, err } m[int(k)] = v } rv.Set(reflect.ValueOf(m)) return true, nil case typeMapInt8Bool: m := make(map[int8]bool, l) for i := 0; i < l; i++ { k, err := d.asInt(keyKind) if err != nil { return false, err } v, err := d.asBool(valueKind) if err != nil { return false, err } m[int8(k)] = v } rv.Set(reflect.ValueOf(m)) return true, nil case typeMapInt16Bool: m := make(map[int16]bool, l) for i := 0; i < l; i++ { k, err := d.asInt(keyKind) if err != nil { return false, err } v, err := d.asBool(valueKind) if err != nil { return false, err } m[int16(k)] = v } rv.Set(reflect.ValueOf(m)) return true, nil case typeMapInt32Bool: m := make(map[int32]bool, l) for i := 0; i < l; i++ { k, err := d.asInt(keyKind) if err != nil { return false, err } v, err := d.asBool(valueKind) if err != nil { return false, err } m[int32(k)] = v } rv.Set(reflect.ValueOf(m)) return true, nil case typeMapInt64Bool: m := make(map[int64]bool, l) for i := 0; i < l; i++ { k, err := d.asInt(keyKind) if err != nil { return false, err } v, err := d.asBool(valueKind) if err != nil { return false, err } m[k] = v } rv.Set(reflect.ValueOf(m)) return true, nil case typeMapUintString: m := make(map[uint]string, l) for i := 0; i < l; i++ { k, err := d.asUint(keyKind) if err != nil { return false, err } v, err := d.asString(valueKind) if err != nil { return false, err } m[uint(k)] = v } rv.Set(reflect.ValueOf(m)) return true, nil case typeMapUint8String: m := make(map[uint8]string, l) for i := 0; i < l; i++ { k, err := d.asUint(keyKind) if err != nil { return false, err } v, err := d.asString(valueKind) if err != nil { return false, err } m[uint8(k)] = v } rv.Set(reflect.ValueOf(m)) return true, nil case typeMapUint16String: m := make(map[uint16]string, l) for i := 0; i < l; i++ { k, err := d.asUint(keyKind) if err != nil { return false, err } v, err := d.asString(valueKind) if err != nil { return false, err } m[uint16(k)] = v } rv.Set(reflect.ValueOf(m)) return true, nil case typeMapUint32String: m := make(map[uint32]string, l) for i := 0; i < l; i++ { k, err := d.asUint(keyKind) if err != nil { return false, err } v, err := d.asString(valueKind) if err != nil { return false, err } m[uint32(k)] = v } rv.Set(reflect.ValueOf(m)) return true, nil case typeMapUint64String: m := make(map[uint64]string, l) for i := 0; i < l; i++ { k, err := d.asUint(keyKind) if err != nil { return false, err } v, err := d.asString(valueKind) if err != nil { return false, err } m[k] = v } rv.Set(reflect.ValueOf(m)) return true, nil case typeMapUintBool: m := make(map[uint]bool, l) for i := 0; i < l; i++ { k, err := d.asUint(keyKind) if err != nil { return false, err } v, err := d.asBool(valueKind) if err != nil { return false, err } m[uint(k)] = v } rv.Set(reflect.ValueOf(m)) return true, nil case typeMapUint8Bool: m := make(map[uint8]bool, l) for i := 0; i < l; i++ { k, err := d.asUint(keyKind) if err != nil { return false, err } v, err := d.asBool(valueKind) if err != nil { return false, err } m[uint8(k)] = v } rv.Set(reflect.ValueOf(m)) return true, nil case typeMapUint16Bool: m := make(map[uint16]bool, l) for i := 0; i < l; i++ { k, err := d.asUint(keyKind) if err != nil { return false, err } v, err := d.asBool(valueKind) if err != nil { return false, err } m[uint16(k)] = v } rv.Set(reflect.ValueOf(m)) return true, nil case typeMapUint32Bool: m := make(map[uint32]bool, l) for i := 0; i < l; i++ { k, err := d.asUint(keyKind) if err != nil { return false, err } v, err := d.asBool(valueKind) if err != nil { return false, err } m[uint32(k)] = v } rv.Set(reflect.ValueOf(m)) return true, nil case typeMapUint64Bool: m := make(map[uint64]bool, l) for i := 0; i < l; i++ { k, err := d.asUint(keyKind) if err != nil { return false, err } v, err := d.asBool(valueKind) if err != nil { return false, err } m[k] = v } rv.Set(reflect.ValueOf(m)) return true, nil case typeMapFloat32String: m := make(map[float32]string, l) for i := 0; i < l; i++ { k, err := d.asFloat32(keyKind) if err != nil { return false, err } v, err := d.asString(valueKind) if err != nil { return false, err } m[k] = v } rv.Set(reflect.ValueOf(m)) return true, nil case typeMapFloat64String: m := make(map[float64]string, l) for i := 0; i < l; i++ { k, err := d.asFloat64(keyKind) if err != nil { return false, err } v, err := d.asString(valueKind) if err != nil { return false, err } m[k] = v } rv.Set(reflect.ValueOf(m)) return true, nil case typeMapFloat32Bool: m := make(map[float32]bool, l) for i := 0; i < l; i++ { k, err := d.asFloat32(keyKind) if err != nil { return false, err } v, err := d.asBool(valueKind) if err != nil { return false, err } m[k] = v } rv.Set(reflect.ValueOf(m)) return true, nil case typeMapFloat64Bool: m := make(map[float64]bool, l) for i := 0; i < l; i++ { k, err := d.asFloat64(keyKind) if err != nil { return false, err } v, err := d.asBool(valueKind) if err != nil { return false, err } m[k] = v } rv.Set(reflect.ValueOf(m)) return true, nil } return false, nil } ================================================ FILE: internal/stream/decoding/map_test.go ================================================ package decoding import ( "fmt" "io" "math" "reflect" "testing" "github.com/shamaton/msgpack/v3/def" tu "github.com/shamaton/msgpack/v3/internal/common/testutil" ) func Test_mapLength(t *testing.T) { method := func(d *decoder) func(byte, reflect.Kind) (int, error) { return d.mapLength } testcases := AsXXXTestCases[int]{ { Name: "FixMap", Code: def.FixMap + 3, Expected: 3, MethodAsWithCode: method, }, { Name: "Map16.error", Code: def.Map16, Data: []byte{}, ReadCount: 0, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Map16.ok", Code: def.Map16, Data: []byte{0xff, 0xff}, Expected: math.MaxUint16, ReadCount: 1, MethodAsWithCode: method, }, { Name: "Map32.error", Code: def.Map32, Data: []byte{}, ReadCount: 0, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Map32.ok", Code: def.Map32, Data: []byte{0xff, 0xff, 0xff, 0xff}, Expected: math.MaxUint32, ReadCount: 1, MethodAsWithCode: method, }, { Name: "Unexpected", Code: def.Nil, Error: def.ErrCanNotDecode, MethodAsWithCode: method, }, } for _, tc := range testcases { tc.Run(t) } } func Test_asFixedMap_StringInt(t *testing.T) { run := func(t *testing.T, v any, dv byte) { method := func(d *decoder) (bool, error) { rv := reflect.ValueOf(v) return d.asFixedMap(rv.Elem(), 1) } name := fmt.Sprintf("%T", v) testcases := AsXXXTestCases[bool]{ { Name: name + ".error.asString", Code: def.Int32, Data: []byte{def.Int32}, Expected: false, ReadCount: 1, Error: def.ErrCanNotDecode, MethodAsCustom: method, }, { Name: name + ".error.asInt", Code: def.Str8, Data: []byte{def.FixStr + 1, 'a', def.Str8}, Expected: false, ReadCount: 3, Error: def.ErrCanNotDecode, MethodAsCustom: method, }, { Name: name + ".ok", Data: []byte{def.FixStr + 1, 'a', def.PositiveFixIntMin + dv}, Expected: true, ReadCount: 3, MethodAsCustom: method, }, } for _, tc := range testcases { tc.Run(t) } } v1 := new(map[string]int) run(t, v1, 1) tu.EqualMap(t, *v1, map[string]int{"a": 1}) v2 := new(map[string]int8) run(t, v2, 2) tu.EqualMap(t, *v2, map[string]int8{"a": 2}) v3 := new(map[string]int16) run(t, v3, 3) tu.EqualMap(t, *v3, map[string]int16{"a": 3}) v4 := new(map[string]int32) run(t, v4, 4) tu.EqualMap(t, *v4, map[string]int32{"a": 4}) v5 := new(map[string]int64) run(t, v5, 5) tu.EqualMap(t, *v5, map[string]int64{"a": 5}) } func Test_asFixedMap_StringUint(t *testing.T) { run := func(t *testing.T, v any, dv byte) { method := func(d *decoder) (bool, error) { rv := reflect.ValueOf(v) return d.asFixedMap(rv.Elem(), 1) } name := fmt.Sprintf("%T", v) testcases := AsXXXTestCases[bool]{ { Name: name + ".error.asString", Code: def.Int32, Data: []byte{def.Int32}, Expected: false, ReadCount: 1, MethodAsCustom: method, Error: def.ErrCanNotDecode, }, { Name: name + ".error.asUint", Code: def.Str8, Data: []byte{def.FixStr + 1, 'a', def.Str8}, Expected: false, ReadCount: 3, MethodAsCustom: method, Error: def.ErrCanNotDecode, }, { Name: name + ".ok", Data: []byte{def.FixStr + 1, 'a', def.Uint8, dv}, Expected: true, ReadCount: 4, MethodAsCustom: method, }, } for _, tc := range testcases { tc.Run(t) } } v1 := new(map[string]uint) run(t, v1, 1) tu.EqualMap(t, *v1, map[string]uint{"a": 1}) v2 := new(map[string]uint8) run(t, v2, 2) tu.EqualMap(t, *v2, map[string]uint8{"a": 2}) v3 := new(map[string]uint16) run(t, v3, 3) tu.EqualMap(t, *v3, map[string]uint16{"a": 3}) v4 := new(map[string]uint32) run(t, v4, 4) tu.EqualMap(t, *v4, map[string]uint32{"a": 4}) v5 := new(map[string]uint64) run(t, v5, 5) tu.EqualMap(t, *v5, map[string]uint64{"a": 5}) } func Test_asFixedMap_StringFloat(t *testing.T) { run := func(t *testing.T, v any, dv byte) { method := func(d *decoder) (bool, error) { rv := reflect.ValueOf(v) return d.asFixedMap(rv.Elem(), 1) } name := fmt.Sprintf("%T", v) testcases := AsXXXTestCases[bool]{ { Name: name + ".error.asString", Code: def.Int32, Data: []byte{def.Int32}, Expected: false, ReadCount: 1, MethodAsCustom: method, Error: def.ErrCanNotDecode, }, { Name: name + ".error.asFloat", Code: def.Str8, Data: []byte{def.FixStr + 1, 'a', def.Str8}, Expected: false, ReadCount: 3, MethodAsCustom: method, Error: def.ErrCanNotDecode, }, { Name: name + ".ok", Data: []byte{def.FixStr + 1, 'a', def.Int16, 0, dv}, Expected: true, ReadCount: 4, MethodAsCustom: method, }, } for _, tc := range testcases { tc.Run(t) } } v1 := new(map[string]float32) run(t, v1, 1) tu.EqualMap(t, *v1, map[string]float32{"a": 1}) v2 := new(map[string]float64) run(t, v2, 2) tu.EqualMap(t, *v2, map[string]float64{"a": 2}) } func Test_asFixedMap_StringBool(t *testing.T) { run := func(t *testing.T, v any, dv byte) { method := func(d *decoder) (bool, error) { rv := reflect.ValueOf(v) return d.asFixedMap(rv.Elem(), 1) } name := fmt.Sprintf("%T", v) testcases := AsXXXTestCases[bool]{ { Name: name + ".error.asString", Code: def.Int32, Data: []byte{def.Int32}, Expected: false, ReadCount: 1, Error: def.ErrCanNotDecode, MethodAsCustom: method, }, { Name: name + ".error.asBool", Code: def.Str8, Data: []byte{def.FixStr + 1, 'a', def.Str8}, Expected: false, ReadCount: 3, Error: def.ErrCanNotDecode, MethodAsCustom: method, }, { Name: name + ".ok", Data: []byte{def.FixStr + 1, 'a', dv}, Expected: true, ReadCount: 3, MethodAsCustom: method, }, } for _, tc := range testcases { tc.Run(t) } } v1 := new(map[string]bool) run(t, v1, def.True) tu.EqualMap(t, *v1, map[string]bool{"a": true}) } func Test_asFixedMap_StringString(t *testing.T) { run := func(t *testing.T, v any, dv byte) { method := func(d *decoder) (bool, error) { rv := reflect.ValueOf(v) return d.asFixedMap(rv.Elem(), 1) } name := fmt.Sprintf("%T", v) testcases := AsXXXTestCases[bool]{ { Name: name + ".error.asString", Code: def.Int32, Data: []byte{def.Int32}, Expected: false, ReadCount: 1, Error: def.ErrCanNotDecode, MethodAsCustom: method, }, { Name: name + ".error.asString", Code: def.Int32, Data: []byte{def.FixStr + 1, 'a', def.Int32}, Expected: false, ReadCount: 3, Error: def.ErrCanNotDecode, MethodAsCustom: method, }, { Name: name + ".ok", Data: []byte{def.FixStr + 1, 'a', def.FixStr + 1, dv}, Expected: true, ReadCount: 4, MethodAsCustom: method, }, } for _, tc := range testcases { tc.Run(t) } } v1 := new(map[string]string) run(t, v1, 'b') tu.EqualMap(t, *v1, map[string]string{"a": "b"}) } func Test_asFixedMap_IntString(t *testing.T) { run := func(t *testing.T, v any, dv byte) { method := func(d *decoder) (bool, error) { rv := reflect.ValueOf(v) return d.asFixedMap(rv.Elem(), 1) } name := fmt.Sprintf("%T", v) testcases := AsXXXTestCases[bool]{ { Name: name + ".error.asInt", Code: def.Str8, Data: []byte{def.Str8}, Expected: false, ReadCount: 1, Error: def.ErrCanNotDecode, MethodAsCustom: method, }, { Name: name + ".error.asString", Code: def.Int32, Data: []byte{def.Int8, dv, def.Int32}, Expected: false, ReadCount: 3, Error: def.ErrCanNotDecode, MethodAsCustom: method, }, { Name: name + ".ok", Data: []byte{def.Int8, dv, def.FixStr + 1, 'b'}, Expected: true, ReadCount: 4, MethodAsCustom: method, }, } for _, tc := range testcases { tc.Run(t) } } v1 := new(map[int]string) run(t, v1, 1) tu.EqualMap(t, *v1, map[int]string{1: "b"}) v2 := new(map[int8]string) run(t, v2, 2) tu.EqualMap(t, *v2, map[int8]string{int8(2): "b"}) v3 := new(map[int16]string) run(t, v3, 3) tu.EqualMap(t, *v3, map[int16]string{int16(3): "b"}) v4 := new(map[int32]string) run(t, v4, 4) tu.EqualMap(t, *v4, map[int32]string{int32(4): "b"}) v5 := new(map[int64]string) run(t, v5, 5) tu.EqualMap(t, *v5, map[int64]string{int64(5): "b"}) } func Test_asFixedMap_IntBool(t *testing.T) { run := func(t *testing.T, v any, dv byte) { method := func(d *decoder) (bool, error) { rv := reflect.ValueOf(v) return d.asFixedMap(rv.Elem(), 1) } name := fmt.Sprintf("%T", v) testcases := AsXXXTestCases[bool]{ { Name: name + ".error.asInt", Code: def.Str8, Data: []byte{def.Str8}, Expected: false, ReadCount: 1, Error: def.ErrCanNotDecode, MethodAsCustom: method, }, { Name: name + ".error.asBool", Code: def.Int32, Data: []byte{def.Int8, dv, def.Int32}, Expected: false, ReadCount: 3, Error: def.ErrCanNotDecode, MethodAsCustom: method, }, { Name: name + ".ok", Data: []byte{def.Int8, dv, def.True}, Expected: true, ReadCount: 3, MethodAsCustom: method, }, } for _, tc := range testcases { tc.Run(t) } } v1 := new(map[int]bool) run(t, v1, 1) tu.EqualMap(t, *v1, map[int]bool{1: true}) v2 := new(map[int8]bool) run(t, v2, 2) tu.EqualMap(t, *v2, map[int8]bool{int8(2): true}) v3 := new(map[int16]bool) run(t, v3, 3) tu.EqualMap(t, *v3, map[int16]bool{int16(3): true}) v4 := new(map[int32]bool) run(t, v4, 4) tu.EqualMap(t, *v4, map[int32]bool{int32(4): true}) v5 := new(map[int64]bool) run(t, v5, 5) tu.EqualMap(t, *v5, map[int64]bool{int64(5): true}) } func Test_asFixedMap_UintString(t *testing.T) { run := func(t *testing.T, v any, dv byte) { method := func(d *decoder) (bool, error) { rv := reflect.ValueOf(v) return d.asFixedMap(rv.Elem(), 1) } name := fmt.Sprintf("%T", v) testcases := AsXXXTestCases[bool]{ { Name: name + ".error.asUint", Code: def.Str8, Data: []byte{def.Str8}, Expected: false, ReadCount: 1, Error: def.ErrCanNotDecode, MethodAsCustom: method, }, { Name: name + ".error.asString", Code: def.Int32, Data: []byte{def.Uint8, dv, def.Int32}, Expected: false, ReadCount: 3, Error: def.ErrCanNotDecode, MethodAsCustom: method, }, { Name: name + ".ok", Data: []byte{def.Uint8, dv, def.FixStr + 1, 'b'}, Expected: true, ReadCount: 4, MethodAsCustom: method, }, } for _, tc := range testcases { tc.Run(t) } } v1 := new(map[uint]string) run(t, v1, 1) tu.EqualMap(t, *v1, map[uint]string{1: "b"}) v2 := new(map[uint8]string) run(t, v2, 2) tu.EqualMap(t, *v2, map[uint8]string{uint8(2): "b"}) v3 := new(map[uint16]string) run(t, v3, 3) tu.EqualMap(t, *v3, map[uint16]string{uint16(3): "b"}) v4 := new(map[uint32]string) run(t, v4, 4) tu.EqualMap(t, *v4, map[uint32]string{uint32(4): "b"}) v5 := new(map[uint64]string) run(t, v5, 5) tu.EqualMap(t, *v5, map[uint64]string{uint64(5): "b"}) } func Test_asFixedMap_UintBool(t *testing.T) { run := func(t *testing.T, v any, dv byte) { method := func(d *decoder) (bool, error) { rv := reflect.ValueOf(v) return d.asFixedMap(rv.Elem(), 1) } name := fmt.Sprintf("%T", v) testcases := AsXXXTestCases[bool]{ { Name: name + ".error.asUint", Code: def.Str8, Data: []byte{def.Str8}, Expected: false, ReadCount: 1, Error: def.ErrCanNotDecode, MethodAsCustom: method, }, { Name: name + ".error.asBool", Code: def.Int32, Data: []byte{def.Uint8, dv, def.Int32}, Expected: false, ReadCount: 3, Error: def.ErrCanNotDecode, MethodAsCustom: method, }, { Name: name + ".ok", Data: []byte{def.Uint8, dv, def.True}, Expected: true, ReadCount: 3, MethodAsCustom: method, }, } for _, tc := range testcases { tc.Run(t) } } v1 := new(map[uint]bool) run(t, v1, 1) tu.EqualMap(t, *v1, map[uint]bool{1: true}) v2 := new(map[uint8]bool) run(t, v2, 2) tu.EqualMap(t, *v2, map[uint8]bool{uint8(2): true}) v3 := new(map[uint16]bool) run(t, v3, 3) tu.EqualMap(t, *v3, map[uint16]bool{uint16(3): true}) v4 := new(map[uint32]bool) run(t, v4, 4) tu.EqualMap(t, *v4, map[uint32]bool{uint32(4): true}) v5 := new(map[uint64]bool) run(t, v5, 5) tu.EqualMap(t, *v5, map[uint64]bool{uint64(5): true}) } func Test_asFixedMap_FloatString(t *testing.T) { run := func(t *testing.T, v any, dv byte) { method := func(d *decoder) (bool, error) { rv := reflect.ValueOf(v) return d.asFixedMap(rv.Elem(), 1) } name := fmt.Sprintf("%T", v) testcases := AsXXXTestCases[bool]{ { Name: name + ".error.asFloat", Code: def.Str8, Data: []byte{def.Str8}, Expected: false, ReadCount: 1, Error: def.ErrCanNotDecode, MethodAsCustom: method, }, { Name: name + ".error.asString", Code: def.Int32, Data: []byte{def.Uint8, dv, def.Int32}, Expected: false, ReadCount: 3, Error: def.ErrCanNotDecode, MethodAsCustom: method, }, { Name: name + ".ok", Data: []byte{def.Uint8, dv, def.FixStr + 1, 'b'}, Expected: true, ReadCount: 4, MethodAsCustom: method, }, } for _, tc := range testcases { tc.Run(t) } } v1 := new(map[float32]string) run(t, v1, 1) tu.EqualMap(t, *v1, map[float32]string{1: "b"}) v2 := new(map[float64]string) run(t, v2, 2) tu.EqualMap(t, *v2, map[float64]string{2: "b"}) } func Test_asFixedMap_FloatBool(t *testing.T) { run := func(t *testing.T, v any, dv byte) { method := func(d *decoder) (bool, error) { rv := reflect.ValueOf(v) return d.asFixedMap(rv.Elem(), 1) } name := fmt.Sprintf("%T", v) testcases := AsXXXTestCases[bool]{ { Name: name + ".error.asFloat", Code: def.Str8, Data: []byte{def.Str8}, Expected: false, ReadCount: 1, Error: def.ErrCanNotDecode, MethodAsCustom: method, }, { Name: name + ".error.asBool", Code: def.Int32, Data: []byte{def.Uint8, dv, def.Int32}, Expected: false, ReadCount: 3, Error: def.ErrCanNotDecode, MethodAsCustom: method, }, { Name: name + ".ok", Data: []byte{def.Uint8, dv, def.True}, Expected: true, ReadCount: 3, MethodAsCustom: method, }, } for _, tc := range testcases { tc.Run(t) } } v1 := new(map[float32]bool) run(t, v1, 1) tu.EqualMap(t, *v1, map[float32]bool{1: true}) v2 := new(map[float64]bool) run(t, v2, 2) tu.EqualMap(t, *v2, map[float64]bool{2: true}) } ================================================ FILE: internal/stream/decoding/nil.go ================================================ package decoding import "github.com/shamaton/msgpack/v3/def" func (d *decoder) isCodeNil(v byte) bool { return def.Nil == v } ================================================ FILE: internal/stream/decoding/read.go ================================================ package decoding func (d *decoder) readSize1() (byte, error) { if _, err := d.r.Read(d.buf.B1); err != nil { return 0, err } return d.buf.B1[0], nil } func (d *decoder) readSize2() ([]byte, error) { if _, err := d.r.Read(d.buf.B2); err != nil { return emptyBytes, err } return d.buf.B2, nil } func (d *decoder) readSize4() ([]byte, error) { if _, err := d.r.Read(d.buf.B4); err != nil { return emptyBytes, err } return d.buf.B4, nil } func (d *decoder) readSize8() ([]byte, error) { if _, err := d.r.Read(d.buf.B8); err != nil { return emptyBytes, err } return d.buf.B8, nil } func (d *decoder) readSize16() ([]byte, error) { if _, err := d.r.Read(d.buf.B16); err != nil { return emptyBytes, err } return d.buf.B16, nil } func (d *decoder) readSizeN(n int) ([]byte, error) { var b []byte if n <= len(d.buf.Data) { b = d.buf.Data[:n] } else { d.buf.Data = append(d.buf.Data, make([]byte, n-len(d.buf.Data))...) b = d.buf.Data } if _, err := d.r.Read(b); err != nil { return emptyBytes, err } return b, nil } ================================================ FILE: internal/stream/decoding/slice.go ================================================ package decoding import ( "encoding/binary" "reflect" "github.com/shamaton/msgpack/v3/def" ) var ( typeIntSlice = reflect.TypeOf([]int{}) typeInt8Slice = reflect.TypeOf([]int8{}) typeInt16Slice = reflect.TypeOf([]int16{}) typeInt32Slice = reflect.TypeOf([]int32{}) typeInt64Slice = reflect.TypeOf([]int64{}) typeUintSlice = reflect.TypeOf([]uint{}) typeUint8Slice = reflect.TypeOf([]uint8{}) typeUint16Slice = reflect.TypeOf([]uint16{}) typeUint32Slice = reflect.TypeOf([]uint32{}) typeUint64Slice = reflect.TypeOf([]uint64{}) typeFloat32Slice = reflect.TypeOf([]float32{}) typeFloat64Slice = reflect.TypeOf([]float64{}) typeStringSlice = reflect.TypeOf([]string{}) typeBoolSlice = reflect.TypeOf([]bool{}) ) func (d *decoder) isFixSlice(v byte) bool { return def.FixArray <= v && v <= def.FixArray+0x0f } func (d *decoder) sliceLength(code byte, k reflect.Kind) (int, error) { switch { case d.isFixSlice(code): return int(code - def.FixArray), nil case code == def.Array16: bs, err := d.readSize2() if err != nil { return 0, err } return int(binary.BigEndian.Uint16(bs)), nil case code == def.Array32: bs, err := d.readSize4() if err != nil { return 0, err } return int(binary.BigEndian.Uint32(bs)), nil } return 0, d.errorTemplate(code, k) } func (d *decoder) asFixedSlice(rv reflect.Value, l int) (bool, error) { t := rv.Type() k := t.Elem().Kind() switch t { case typeIntSlice: sli := make([]int, l) for i := range sli { v, err := d.asInt(k) if err != nil { return false, err } sli[i] = int(v) } rv.Set(reflect.ValueOf(sli)) return true, nil case typeUintSlice: sli := make([]uint, l) for i := range sli { v, err := d.asUint(k) if err != nil { return false, err } sli[i] = uint(v) } rv.Set(reflect.ValueOf(sli)) return true, nil case typeStringSlice: sli := make([]string, l) for i := range sli { v, err := d.asString(k) if err != nil { return false, err } sli[i] = v } rv.Set(reflect.ValueOf(sli)) return true, nil case typeBoolSlice: sli := make([]bool, l) for i := range sli { v, err := d.asBool(k) if err != nil { return false, err } sli[i] = v } rv.Set(reflect.ValueOf(sli)) return true, nil case typeFloat32Slice: sli := make([]float32, l) for i := range sli { v, err := d.asFloat32(k) if err != nil { return false, err } sli[i] = v } rv.Set(reflect.ValueOf(sli)) return true, nil case typeFloat64Slice: sli := make([]float64, l) for i := range sli { v, err := d.asFloat64(k) if err != nil { return false, err } sli[i] = v } rv.Set(reflect.ValueOf(sli)) return true, nil case typeInt8Slice: sli := make([]int8, l) for i := range sli { v, err := d.asInt(k) if err != nil { return false, err } sli[i] = int8(v) } rv.Set(reflect.ValueOf(sli)) return true, nil case typeInt16Slice: sli := make([]int16, l) for i := range sli { v, err := d.asInt(k) if err != nil { return false, err } sli[i] = int16(v) } rv.Set(reflect.ValueOf(sli)) return true, nil case typeInt32Slice: sli := make([]int32, l) for i := range sli { v, err := d.asInt(k) if err != nil { return false, err } sli[i] = int32(v) } rv.Set(reflect.ValueOf(sli)) return true, nil case typeInt64Slice: sli := make([]int64, l) for i := range sli { v, err := d.asInt(k) if err != nil { return false, err } sli[i] = v } rv.Set(reflect.ValueOf(sli)) return true, nil case typeUint8Slice: sli := make([]uint8, l) for i := range sli { v, err := d.asUint(k) if err != nil { return false, err } sli[i] = uint8(v) } rv.Set(reflect.ValueOf(sli)) return true, nil case typeUint16Slice: sli := make([]uint16, l) for i := range sli { v, err := d.asUint(k) if err != nil { return false, err } sli[i] = uint16(v) } rv.Set(reflect.ValueOf(sli)) return true, nil case typeUint32Slice: sli := make([]uint32, l) for i := range sli { v, err := d.asUint(k) if err != nil { return false, err } sli[i] = uint32(v) } rv.Set(reflect.ValueOf(sli)) return true, nil case typeUint64Slice: sli := make([]uint64, l) for i := range sli { v, err := d.asUint(k) if err != nil { return false, err } sli[i] = v } rv.Set(reflect.ValueOf(sli)) return true, nil } return false, nil } ================================================ FILE: internal/stream/decoding/slice_test.go ================================================ package decoding import ( "io" "math" "reflect" "testing" "github.com/shamaton/msgpack/v3/def" tu "github.com/shamaton/msgpack/v3/internal/common/testutil" ) func Test_sliceLength(t *testing.T) { method := func(d *decoder) func(byte, reflect.Kind) (int, error) { return d.sliceLength } testcases := AsXXXTestCases[int]{ { Name: "FixArray", Code: def.FixArray + 3, Expected: 3, MethodAsWithCode: method, }, { Name: "Array16.error", Code: def.Array16, Data: []byte{}, ReadCount: 0, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Array16.ok", Code: def.Array16, Data: []byte{0xff, 0xff}, Expected: math.MaxUint16, ReadCount: 1, MethodAsWithCode: method, }, { Name: "Array32.error", Code: def.Array32, Data: []byte{}, ReadCount: 0, Error: io.EOF, MethodAsWithCode: method, }, { Name: "Array32.ok", Code: def.Array32, Data: []byte{0xff, 0xff, 0xff, 0xff}, Expected: math.MaxUint32, ReadCount: 1, MethodAsWithCode: method, }, { Name: "Unexpected", Code: def.Nil, Error: def.ErrCanNotDecode, MethodAsWithCode: method, }, } for _, tc := range testcases { tc.Run(t) } } func Test_asFixedSlice_Int(t *testing.T) { run := func(t *testing.T, v any) { method := func(d *decoder) (bool, error) { rv := reflect.ValueOf(v) return d.asFixedSlice(rv.Elem(), 1) } testcases := AsXXXTestCases[bool]{ { Name: "error", Data: []byte{}, Error: io.EOF, MethodAsCustom: method, }, { Name: "ok", Data: []byte{def.PositiveFixIntMin + 3}, Expected: true, ReadCount: 1, MethodAsCustom: method, }, } for _, tc := range testcases { tc.Run(t) } } v1 := new([]int) run(t, v1) tu.EqualSlice(t, *v1, []int{3}) } func Test_asFixedSlice_Int8(t *testing.T) { run := func(t *testing.T, v any) { method := func(d *decoder) (bool, error) { rv := reflect.ValueOf(v) return d.asFixedSlice(rv.Elem(), 1) } testcases := AsXXXTestCases[bool]{ { Name: "error", Data: []byte{}, Error: io.EOF, MethodAsCustom: method, }, { Name: "ok", Data: []byte{def.PositiveFixIntMin + 4}, Expected: true, ReadCount: 1, MethodAsCustom: method, }, } for _, tc := range testcases { tc.Run(t) } } v1 := new([]int8) run(t, v1) tu.EqualSlice(t, *v1, []int8{4}) } func Test_asFixedSlice_Int16(t *testing.T) { run := func(t *testing.T, v any) { method := func(d *decoder) (bool, error) { rv := reflect.ValueOf(v) return d.asFixedSlice(rv.Elem(), 1) } testcases := AsXXXTestCases[bool]{ { Name: "error", Data: []byte{}, Error: io.EOF, MethodAsCustom: method, }, { Name: "ok", Data: []byte{def.PositiveFixIntMin + 5}, Expected: true, ReadCount: 1, MethodAsCustom: method, }, } for _, tc := range testcases { tc.Run(t) } } v1 := new([]int16) run(t, v1) tu.EqualSlice(t, *v1, []int16{5}) } func Test_asFixedSlice_Int32(t *testing.T) { run := func(t *testing.T, v any) { method := func(d *decoder) (bool, error) { rv := reflect.ValueOf(v) return d.asFixedSlice(rv.Elem(), 1) } testcases := AsXXXTestCases[bool]{ { Name: "error", Data: []byte{}, Error: io.EOF, MethodAsCustom: method, }, { Name: "ok", Data: []byte{def.PositiveFixIntMin + 6}, Expected: true, ReadCount: 1, MethodAsCustom: method, }, } for _, tc := range testcases { tc.Run(t) } } v1 := new([]int32) run(t, v1) tu.EqualSlice(t, *v1, []int32{6}) } func Test_asFixedSlice_Int64(t *testing.T) { run := func(t *testing.T, v any) { method := func(d *decoder) (bool, error) { rv := reflect.ValueOf(v) return d.asFixedSlice(rv.Elem(), 1) } testcases := AsXXXTestCases[bool]{ { Name: "error", Data: []byte{}, Error: io.EOF, MethodAsCustom: method, }, { Name: "ok", Data: []byte{def.PositiveFixIntMin + 7}, Expected: true, ReadCount: 1, MethodAsCustom: method, }, } for _, tc := range testcases { tc.Run(t) } } v1 := new([]int64) run(t, v1) tu.EqualSlice(t, *v1, []int64{7}) } func Test_asFixedSlice_Uint(t *testing.T) { run := func(t *testing.T, v any) { method := func(d *decoder) (bool, error) { rv := reflect.ValueOf(v) return d.asFixedSlice(rv.Elem(), 1) } testcases := AsXXXTestCases[bool]{ { Name: "error", Data: []byte{}, Error: io.EOF, MethodAsCustom: method, }, { Name: "ok", Data: []byte{def.PositiveFixIntMin + 5}, Expected: true, ReadCount: 1, MethodAsCustom: method, }, } for _, tc := range testcases { tc.Run(t) } } v1 := new([]uint) run(t, v1) tu.EqualSlice(t, *v1, []uint{5}) } func Test_asFixedSlice_Uint8(t *testing.T) { run := func(t *testing.T, v any) { method := func(d *decoder) (bool, error) { rv := reflect.ValueOf(v) return d.asFixedSlice(rv.Elem(), 1) } testcases := AsXXXTestCases[bool]{ { Name: "error", Data: []byte{}, Error: io.EOF, MethodAsCustom: method, }, { Name: "ok", Data: []byte{def.PositiveFixIntMin + 6}, Expected: true, ReadCount: 1, MethodAsCustom: method, }, } for _, tc := range testcases { tc.Run(t) } } v1 := new([]uint8) run(t, v1) tu.EqualSlice(t, *v1, []uint8{6}) } func Test_asFixedSlice_Uint16(t *testing.T) { run := func(t *testing.T, v any) { method := func(d *decoder) (bool, error) { rv := reflect.ValueOf(v) return d.asFixedSlice(rv.Elem(), 1) } testcases := AsXXXTestCases[bool]{ { Name: "error", Data: []byte{}, Error: io.EOF, MethodAsCustom: method, }, { Name: "ok", Data: []byte{def.PositiveFixIntMin + 7}, Expected: true, ReadCount: 1, MethodAsCustom: method, }, } for _, tc := range testcases { tc.Run(t) } } v1 := new([]uint16) run(t, v1) tu.EqualSlice(t, *v1, []uint16{7}) } func Test_asFixedSlice_Uint32(t *testing.T) { run := func(t *testing.T, v any) { method := func(d *decoder) (bool, error) { rv := reflect.ValueOf(v) return d.asFixedSlice(rv.Elem(), 1) } testcases := AsXXXTestCases[bool]{ { Name: "error", Data: []byte{}, Error: io.EOF, MethodAsCustom: method, }, { Name: "ok", Data: []byte{def.PositiveFixIntMin + 8}, Expected: true, ReadCount: 1, MethodAsCustom: method, }, } for _, tc := range testcases { tc.Run(t) } } v1 := new([]uint32) run(t, v1) tu.EqualSlice(t, *v1, []uint32{8}) } func Test_asFixedSlice_Uint64(t *testing.T) { run := func(t *testing.T, v any) { method := func(d *decoder) (bool, error) { rv := reflect.ValueOf(v) return d.asFixedSlice(rv.Elem(), 1) } testcases := AsXXXTestCases[bool]{ { Name: "error", Data: []byte{}, Error: io.EOF, MethodAsCustom: method, }, { Name: "ok", Data: []byte{def.PositiveFixIntMin + 9}, Expected: true, ReadCount: 1, MethodAsCustom: method, }, } for _, tc := range testcases { tc.Run(t) } } v1 := new([]uint64) run(t, v1) tu.EqualSlice(t, *v1, []uint64{9}) } func Test_asFixedSlice_Float32(t *testing.T) { run := func(t *testing.T, v any) { method := func(d *decoder) (bool, error) { rv := reflect.ValueOf(v) return d.asFixedSlice(rv.Elem(), 1) } testcases := AsXXXTestCases[bool]{ { Name: "error", Data: []byte{}, Error: io.EOF, MethodAsCustom: method, }, { Name: "ok", Data: []byte{def.Float32, 63, 128, 0, 0}, Expected: true, ReadCount: 2, MethodAsCustom: method, }, } for _, tc := range testcases { tc.Run(t) } } v1 := new([]float32) run(t, v1) tu.EqualSlice(t, *v1, []float32{1}) } func Test_asFixedSlice_Float64(t *testing.T) { run := func(t *testing.T, v any) { method := func(d *decoder) (bool, error) { rv := reflect.ValueOf(v) return d.asFixedSlice(rv.Elem(), 1) } testcases := AsXXXTestCases[bool]{ { Name: "error", Data: []byte{}, Error: io.EOF, MethodAsCustom: method, }, { Name: "ok", Data: []byte{def.Float64, 63, 240, 0, 0, 0, 0, 0, 0}, Expected: true, ReadCount: 2, MethodAsCustom: method, }, } for _, tc := range testcases { tc.Run(t) } } v1 := new([]float64) run(t, v1) tu.EqualSlice(t, *v1, []float64{1}) } func Test_asFixedSlice_String(t *testing.T) { run := func(t *testing.T, v any) { method := func(d *decoder) (bool, error) { rv := reflect.ValueOf(v) return d.asFixedSlice(rv.Elem(), 2) } testcases := AsXXXTestCases[bool]{ { Name: "error", Data: []byte{}, Error: io.EOF, MethodAsCustom: method, }, { Name: "ok", Data: []byte{def.FixStr + 1, 'a', def.FixStr + 1, 'b'}, Expected: true, ReadCount: 4, MethodAsCustom: method, }, } for _, tc := range testcases { tc.Run(t) } } v1 := new([]string) run(t, v1) tu.EqualSlice(t, *v1, []string{"a", "b"}) } func Test_asFixedSlice_Bool(t *testing.T) { run := func(t *testing.T, v any) { method := func(d *decoder) (bool, error) { rv := reflect.ValueOf(v) return d.asFixedSlice(rv.Elem(), 1) } testcases := AsXXXTestCases[bool]{ { Name: "error", Data: []byte{}, Error: io.EOF, MethodAsCustom: method, }, { Name: "ok", Data: []byte{def.True}, Expected: true, ReadCount: 1, MethodAsCustom: method, }, } for _, tc := range testcases { tc.Run(t) } } v1 := new([]bool) run(t, v1) tu.EqualSlice(t, *v1, []bool{true}) } ================================================ FILE: internal/stream/decoding/string.go ================================================ package decoding import ( "encoding/binary" "reflect" "github.com/shamaton/msgpack/v3/def" ) var ( emptyString = "" emptyBytes = []byte{} ) func (d *decoder) isCodeString(code byte) bool { return d.isFixString(code) || code == def.Str8 || code == def.Str16 || code == def.Str32 } func (d *decoder) isFixString(v byte) bool { return def.FixStr <= v && v <= def.FixStr+0x1f } func (d *decoder) stringByteLength(code byte, k reflect.Kind) (int, error) { if def.FixStr <= code && code <= def.FixStr+0x1f { l := int(code - def.FixStr) return l, nil } else if code == def.Str8 { b, err := d.readSize1() if err != nil { return 0, err } return int(b), nil } else if code == def.Str16 { b, err := d.readSize2() if err != nil { return 0, err } return int(binary.BigEndian.Uint16(b)), nil } else if code == def.Str32 { b, err := d.readSize4() if err != nil { return 0, err } return int(binary.BigEndian.Uint32(b)), nil } else if code == def.Nil { return 0, nil } return 0, d.errorTemplate(code, k) } func (d *decoder) asString(k reflect.Kind) (string, error) { code, err := d.readSize1() if err != nil { return emptyString, err } return d.asStringWithCode(code, k) } func (d *decoder) asStringWithCode(code byte, k reflect.Kind) (string, error) { bs, err := d.asStringByteWithCode(code, k) if err != nil { return emptyString, err } return string(bs), nil } func (d *decoder) asStringByte(k reflect.Kind) ([]byte, error) { code, err := d.readSize1() if err != nil { return emptyBytes, err } return d.asStringByteWithCode(code, k) } func (d *decoder) asStringByteWithCode(code byte, k reflect.Kind) ([]byte, error) { l, err := d.stringByteLength(code, k) if err != nil { return emptyBytes, err } return d.asStringByteByLength(l, k) } func (d *decoder) asStringByteByLength(l int, _ reflect.Kind) ([]byte, error) { if l < 1 { return emptyBytes, nil } // avoid common buffer reference return d.copySizeN(l) } ================================================ FILE: internal/stream/decoding/string_test.go ================================================ package decoding import ( "io" "math" "reflect" "testing" "github.com/shamaton/msgpack/v3/def" ) func Test_stringByteLength(t *testing.T) { method := func(d *decoder) func(byte, reflect.Kind) (int, error) { return d.stringByteLength } testcases := AsXXXTestCases[int]{ { Name: "FixStr", Code: def.FixStr + 1, Expected: 1, MethodAsWithCode: method, }, { Name: "Str8.error", Code: def.Str8, Data: []byte{}, Error: io.EOF, ReadCount: 0, MethodAsWithCode: method, }, { Name: "Str8.ok", Code: def.Str8, Data: []byte{0xff}, Expected: math.MaxUint8, ReadCount: 1, MethodAsWithCode: method, }, { Name: "Str16.error", Code: def.Str16, Data: []byte{}, Error: io.EOF, ReadCount: 0, MethodAsWithCode: method, }, { Name: "Str16.ok", Code: def.Str16, Data: []byte{0xff, 0xff}, Expected: math.MaxUint16, ReadCount: 1, MethodAsWithCode: method, }, { Name: "Str32.error", Code: def.Str32, Data: []byte{}, Error: io.EOF, ReadCount: 0, MethodAsWithCode: method, }, { Name: "Str32.ok", Code: def.Str32, Data: []byte{0xff, 0xff, 0xff, 0xff}, Expected: math.MaxUint32, ReadCount: 1, MethodAsWithCode: method, }, { Name: "Nil", Code: def.Nil, Expected: 0, MethodAsWithCode: method, }, { Name: "Unexpected", Code: def.Array16, Error: def.ErrCanNotDecode, MethodAsWithCode: method, }, } for _, tc := range testcases { tc.Run(t) } } func Test_asString(t *testing.T) { method := func(d *decoder) func(reflect.Kind) (string, error) { return d.asString } testcases := AsXXXTestCases[string]{ { Name: "error.code", Data: []byte{}, Error: io.EOF, ReadCount: 0, MethodAs: method, }, { Name: "error.string", Data: []byte{def.FixStr + 1}, Error: io.EOF, ReadCount: 1, MethodAs: method, }, { Name: "ok", Data: []byte{def.FixStr + 1, 'a'}, Expected: "a", ReadCount: 2, MethodAs: method, }, } for _, tc := range testcases { tc.Run(t) } } func Test_asStringByte(t *testing.T) { method := func(d *decoder) func(reflect.Kind) ([]byte, error) { return d.asStringByte } testcases := AsXXXTestCases[[]byte]{ { Name: "error", Data: []byte{def.FixStr + 1}, Error: io.EOF, ReadCount: 1, MethodAs: method, }, { Name: "ok", Data: []byte{def.FixStr + 1, 'a'}, Expected: []byte{'a'}, ReadCount: 2, MethodAs: method, }, } for _, tc := range testcases { tc.Run(t) } } ================================================ FILE: internal/stream/decoding/struct.go ================================================ package decoding import ( "encoding/binary" "reflect" "sync" "github.com/shamaton/msgpack/v3/def" ) type structCacheTypeMap struct { keys [][]byte // fast path detection hasEmbedded bool // fast path (hasEmbedded == false): direct field access simpleIndexes []int // embedded path (hasEmbedded == true): path-based access indexes [][]int // field path (support for embedded structs) } type structCacheTypeArray struct { // fast path detection hasEmbedded bool // fast path (hasEmbedded == false): direct field access simpleIndexes []int // embedded path (hasEmbedded == true): path-based access indexes [][]int // field path (support for embedded structs) } // struct cache map var ( mapSCTM = sync.Map{} mapSCTA = sync.Map{} ) // getFieldByPath returns the field value by following the path of indices. // The bool indicates whether the path was reachable (no nil pointer in the path). func getFieldByPath(rv reflect.Value, path []int, allowAlloc bool) (reflect.Value, bool) { for _, idx := range path { // Handle pointer indirection if needed if rv.Kind() == reflect.Ptr { if rv.IsNil() { if !allowAlloc { return reflect.Value{}, false } // Allocate new value if pointer is nil rv.Set(reflect.New(rv.Type().Elem())) } rv = rv.Elem() } rv = rv.Field(idx) } return rv, true } func (d *decoder) setStruct(code byte, rv reflect.Value, k reflect.Kind) error { if len(extCoders) > 0 { innerType, data, err := d.readIfExtType(code) if err != nil { return err } if data != nil { for i := range extCoders { if extCoders[i].IsType(code, innerType, len(data)) { v, err := extCoders[i].ToValue(code, data, k) if err != nil { return err } // Validate that the receptacle is of the right value type. if rv.Type() == reflect.TypeOf(v) { rv.Set(reflect.ValueOf(v)) return nil } } } } } if d.asArray { return d.setStructFromArray(code, rv, k) } return d.setStructFromMap(code, rv, k) } func (d *decoder) setStructFromArray(code byte, rv reflect.Value, k reflect.Kind) error { // get length l, err := d.sliceLength(code, k) if err != nil { return err } // find or create reference var scta *structCacheTypeArray cache, findCache := mapSCTA.Load(rv.Type()) if !findCache { scta = &structCacheTypeArray{} fields := d.CollectFields(rv.Type(), nil) // detect embedded fields hasEmbedded := false for _, f := range fields { if len(f.Path) > 1 || len(f.OmitPaths) > 0 { hasEmbedded = true break } } scta.hasEmbedded = hasEmbedded for _, field := range fields { if hasEmbedded { scta.indexes = append(scta.indexes, field.Path) } else { scta.simpleIndexes = append(scta.simpleIndexes, field.Path[0]) } } mapSCTA.Store(rv.Type(), scta) } else { scta = cache.(*structCacheTypeArray) } // set value if scta.hasEmbedded { for i := 0; i < l; i++ { if i < len(scta.indexes) { code, err := d.readSize1() if err != nil { return err } allowAlloc := !d.isCodeNil(code) fieldValue, ok := getFieldByPath(rv, scta.indexes[i], allowAlloc) if ok { err = d.decodeWithCode(code, fieldValue) if err != nil { return err } } else if !d.isCodeNil(code) { return d.errorTemplate(code, k) } } else { err = d.jumpOffset() if err != nil { return err } } } } else { for i := 0; i < l; i++ { if i < len(scta.simpleIndexes) { err = d.decode(rv.Field(scta.simpleIndexes[i])) if err != nil { return err } } else { err = d.jumpOffset() if err != nil { return err } } } } return nil } func (d *decoder) setStructFromMap(code byte, rv reflect.Value, k reflect.Kind) error { // get length l, err := d.mapLength(code, k) if err != nil { return err } var sctm *structCacheTypeMap cache, cacheFind := mapSCTM.Load(rv.Type()) if !cacheFind { sctm = &structCacheTypeMap{} fields := d.CollectFields(rv.Type(), nil) // detect embedded fields hasEmbedded := false for _, f := range fields { if len(f.Path) > 1 || len(f.OmitPaths) > 0 { hasEmbedded = true break } } sctm.hasEmbedded = hasEmbedded for _, field := range fields { sctm.keys = append(sctm.keys, []byte(field.Name)) if hasEmbedded { sctm.indexes = append(sctm.indexes, field.Path) } else { sctm.simpleIndexes = append(sctm.simpleIndexes, field.Path[0]) } } mapSCTM.Store(rv.Type(), sctm) } else { sctm = cache.(*structCacheTypeMap) } if sctm.hasEmbedded { for i := 0; i < l; i++ { dataKey, err := d.asStringByte(k) if err != nil { return err } fieldPath := []int(nil) for keyIndex, keyBytes := range sctm.keys { if len(keyBytes) != len(dataKey) { continue } found := true for dataIndex := range dataKey { if dataKey[dataIndex] != keyBytes[dataIndex] { found = false break } } if found { fieldPath = sctm.indexes[keyIndex] break } } if fieldPath != nil { code, err := d.readSize1() if err != nil { return err } allowAlloc := !d.isCodeNil(code) fieldValue, ok := getFieldByPath(rv, fieldPath, allowAlloc) if ok { err = d.decodeWithCode(code, fieldValue) if err != nil { return err } } else if !d.isCodeNil(code) { return d.errorTemplate(code, k) } } else { err = d.jumpOffset() if err != nil { return err } } } } else { for i := 0; i < l; i++ { dataKey, err := d.asStringByte(k) if err != nil { return err } fieldIndex := -1 for keyIndex, keyBytes := range sctm.keys { if len(keyBytes) != len(dataKey) { continue } found := true for dataIndex := range dataKey { if dataKey[dataIndex] != keyBytes[dataIndex] { found = false break } } if found { fieldIndex = sctm.simpleIndexes[keyIndex] break } } if fieldIndex >= 0 { err = d.decode(rv.Field(fieldIndex)) if err != nil { return err } } else { err = d.jumpOffset() if err != nil { return err } } } } return nil } func (d *decoder) jumpOffset() error { code, err := d.readSize1() if err != nil { return err } switch { case code == def.True, code == def.False, code == def.Nil: // do nothing case d.isPositiveFixNum(code) || d.isNegativeFixNum(code): // do nothing case code == def.Uint8, code == def.Int8: _, err = d.readSize1() return err case code == def.Uint16, code == def.Int16: _, err = d.readSize2() return err case code == def.Uint32, code == def.Int32, code == def.Float32: _, err = d.readSize4() return err case code == def.Uint64, code == def.Int64, code == def.Float64: _, err = d.readSize8() return err case d.isFixString(code): _, err = d.readSizeN(int(code - def.FixStr)) return err case code == def.Str8, code == def.Bin8: b, err := d.readSize1() if err != nil { return err } _, err = d.readSizeN(int(b)) return err case code == def.Str16, code == def.Bin16: bs, err := d.readSize2() if err != nil { return err } _, err = d.readSizeN(int(binary.BigEndian.Uint16(bs))) return err case code == def.Str32, code == def.Bin32: bs, err := d.readSize4() if err != nil { return err } _, err = d.readSizeN(int(binary.BigEndian.Uint32(bs))) return err case d.isFixSlice(code): l := int(code - def.FixArray) for i := 0; i < l; i++ { if err = d.jumpOffset(); err != nil { return err } } case code == def.Array16: bs, err := d.readSize2() if err != nil { return err } l := int(binary.BigEndian.Uint16(bs)) for i := 0; i < l; i++ { if err = d.jumpOffset(); err != nil { return err } } case code == def.Array32: bs, err := d.readSize4() if err != nil { return err } l := int(binary.BigEndian.Uint32(bs)) for i := 0; i < l; i++ { if err = d.jumpOffset(); err != nil { return err } } case d.isFixMap(code): l := int(code - def.FixMap) for i := 0; i < l*2; i++ { if err = d.jumpOffset(); err != nil { return err } } case code == def.Map16: bs, err := d.readSize2() if err != nil { return err } l := int(binary.BigEndian.Uint16(bs)) for i := 0; i < l*2; i++ { if err = d.jumpOffset(); err != nil { return err } } case code == def.Map32: bs, err := d.readSize4() if err != nil { return err } l := int(binary.BigEndian.Uint32(bs)) for i := 0; i < l*2; i++ { if err = d.jumpOffset(); err != nil { return err } } case code == def.Fixext1: _, err = d.readSizeN(def.Byte1 + def.Byte1) return err case code == def.Fixext2: _, err = d.readSizeN(def.Byte1 + def.Byte2) return err case code == def.Fixext4: _, err = d.readSizeN(def.Byte1 + def.Byte4) return err case code == def.Fixext8: _, err = d.readSizeN(def.Byte1 + def.Byte8) return err case code == def.Fixext16: _, err = d.readSizeN(def.Byte1 + def.Byte16) return err case code == def.Ext8: b, err := d.readSize1() if err != nil { return err } _, err = d.readSizeN(def.Byte1 + int(b)) return err case code == def.Ext16: bs, err := d.readSize2() if err != nil { return err } _, err = d.readSizeN(def.Byte1 + int(binary.BigEndian.Uint16(bs))) return err case code == def.Ext32: bs, err := d.readSize4() if err != nil { return err } _, err = d.readSizeN(def.Byte1 + int(binary.BigEndian.Uint32(bs))) return err } return nil } ================================================ FILE: internal/stream/decoding/struct_test.go ================================================ package decoding import ( "io" "reflect" "testing" "time" "github.com/shamaton/msgpack/v3/def" tu "github.com/shamaton/msgpack/v3/internal/common/testutil" ) func Test_setStruct_ext(t *testing.T) { run := func(t *testing.T, rv reflect.Value) { method := func(d *decoder) func(byte, reflect.Kind) (any, error) { return func(code byte, k reflect.Kind) (any, error) { return nil, d.setStruct(code, rv, k) } } testcases := AsXXXTestCases[any]{ { Name: "Ext.error", Code: def.Fixext1, Data: []byte{}, ReadCount: 0, Error: io.EOF, MethodAsWithCode: method, }, { Name: "ExtCoder.error", Code: def.Fixext1, Data: []byte{3, 0}, ReadCount: 2, Error: ErrTestExtStreamDecoder, MethodAsWithCode: method, }, { Name: "ExtCoder.ok", Code: def.Fixext4, Data: []byte{255, 0, 0, 0, 0}, ReadCount: 2, MethodAsWithCode: method, }, } for _, tc := range testcases { tc.Run(t) } } ngDec := testExt2StreamDecoder{} AddExtDecoder(&ngDec) defer RemoveExtDecoder(&ngDec) v1 := new(time.Time) run(t, reflect.ValueOf(v1).Elem()) tu.EqualEqualer(t, *v1, time.Unix(0, 0)) } func Test_setStructFromMap(t *testing.T) { run := func(t *testing.T, rv reflect.Value) { method := func(d *decoder) func(byte, reflect.Kind) (any, error) { return func(code byte, k reflect.Kind) (any, error) { return nil, d.setStructFromMap(code, rv, k) } } testcases := AsXXXTestCases[any]{ { Name: "error.length", Code: def.Map16, Data: []byte{}, ReadCount: 0, Error: io.EOF, MethodAsWithCode: method, }, { Name: "error.key", Code: def.Map16, Data: []byte{0, 1}, ReadCount: 1, Error: io.EOF, MethodAsWithCode: method, }, { Name: "error.decode", Code: def.Map16, Data: []byte{0, 1, def.FixStr + 1, 'v', def.Array16}, ReadCount: 4, Error: def.ErrCanNotDecode, MethodAsWithCode: method, }, { Name: "error.jump", Code: def.Map16, Data: []byte{0, 2, def.FixStr + 1, 'v', 0, def.FixStr + 1, 'b'}, ReadCount: 6, Error: io.EOF, MethodAsWithCode: method, }, { Name: "ok", Code: def.Map16, Data: []byte{0, 1, def.FixStr + 1, 'v', def.PositiveFixIntMin + 7}, ReadCount: 4, MethodAsWithCode: method, }, } for _, tc := range testcases { tc.Run(t) } } type st struct { V int `msgpack:"v"` } v1 := new(st) run(t, reflect.ValueOf(v1).Elem()) tu.Equal(t, v1.V, 7) } func Test_setStructFromArray(t *testing.T) { run := func(t *testing.T, rv reflect.Value) { method := func(d *decoder) func(byte, reflect.Kind) (any, error) { return func(code byte, k reflect.Kind) (any, error) { return nil, d.setStructFromArray(code, rv, k) } } testcases := AsXXXTestCases[any]{ { Name: "error.length", Code: def.Array16, Data: []byte{}, ReadCount: 0, Error: io.EOF, MethodAsWithCode: method, }, { Name: "error.key", Code: def.Array16, Data: []byte{0, 1}, ReadCount: 1, Error: io.EOF, MethodAsWithCode: method, }, { Name: "error.decode", Code: def.Array16, Data: []byte{0, 1, def.Array16}, ReadCount: 2, Error: def.ErrCanNotDecode, MethodAsWithCode: method, }, { Name: "error.jump", Code: def.Array16, Data: []byte{0, 2, 0}, ReadCount: 2, Error: io.EOF, MethodAsWithCode: method, }, { Name: "ok", Code: def.Array16, Data: []byte{0, 1, def.PositiveFixIntMin + 8}, ReadCount: 2, MethodAsWithCode: method, }, } for _, tc := range testcases { tc.Run(t) } } type st struct { V int `msgpack:"v"` } v1 := new(st) run(t, reflect.ValueOf(v1).Elem()) tu.Equal(t, v1.V, 8) } func Test_jumpOffset(t *testing.T) { method := func(d *decoder) (any, error) { return nil, d.jumpOffset() } testcases := AsXXXTestCases[any]{ { Name: "error.read.code", Data: []byte{}, ReadCount: 0, Error: io.EOF, MethodAsCustom: method, }, { Name: "True.ok", Data: []byte{def.True}, ReadCount: 1, MethodAsCustom: method, }, { Name: "False.ok", Data: []byte{def.False}, ReadCount: 1, MethodAsCustom: method, }, { Name: "PositiveFixNum.ok", Data: []byte{def.PositiveFixIntMin + 1}, ReadCount: 1, MethodAsCustom: method, }, { Name: "NegativeFixNum.ok", Data: []byte{0xf0}, ReadCount: 1, MethodAsCustom: method, }, { Name: "Uint8.error", Data: []byte{def.Uint8}, ReadCount: 1, Error: io.EOF, MethodAsCustom: method, }, { Name: "Int8.error", Data: []byte{def.Int8}, ReadCount: 1, Error: io.EOF, MethodAsCustom: method, }, { Name: "Uint8.ok", Data: []byte{def.Uint8, 1}, ReadCount: 2, MethodAsCustom: method, }, { Name: "Int8.ok", Data: []byte{def.Int8, 1}, ReadCount: 2, MethodAsCustom: method, }, { Name: "Uint16.error", Data: []byte{def.Uint16}, ReadCount: 1, Error: io.EOF, MethodAsCustom: method, }, { Name: "Int16.error", Data: []byte{def.Int16}, ReadCount: 1, Error: io.EOF, MethodAsCustom: method, }, { Name: "Uint16.ok", Data: []byte{def.Uint16, 0, 1}, ReadCount: 2, MethodAsCustom: method, }, { Name: "Int16.ok", Data: []byte{def.Int16, 0, 1}, ReadCount: 2, MethodAsCustom: method, }, { Name: "Uint32.error", Data: []byte{def.Uint32}, ReadCount: 1, Error: io.EOF, MethodAsCustom: method, }, { Name: "Int32.error", Data: []byte{def.Int32}, ReadCount: 1, Error: io.EOF, MethodAsCustom: method, }, { Name: "Float32.error", Data: []byte{def.Float32}, ReadCount: 1, Error: io.EOF, MethodAsCustom: method, }, { Name: "Uint32.ok", Data: []byte{def.Uint32, 0, 0, 0, 0}, ReadCount: 2, MethodAsCustom: method, }, { Name: "Int32.ok", Data: []byte{def.Int32, 0, 0, 0, 0}, ReadCount: 2, MethodAsCustom: method, }, { Name: "Float32.ok", Data: []byte{def.Float32, 0, 0, 0, 0}, ReadCount: 2, MethodAsCustom: method, }, { Name: "Uint64.error", Data: []byte{def.Uint64}, ReadCount: 1, Error: io.EOF, MethodAsCustom: method, }, { Name: "Int64.error", Data: []byte{def.Int64}, ReadCount: 1, Error: io.EOF, MethodAsCustom: method, }, { Name: "Float64.error", Data: []byte{def.Float64}, ReadCount: 1, Error: io.EOF, MethodAsCustom: method, }, { Name: "Uint64.ok", Data: []byte{def.Uint64, 0, 0, 0, 0, 0, 0, 0, 0}, ReadCount: 2, MethodAsCustom: method, }, { Name: "Int64.ok", Data: []byte{def.Int64, 0, 0, 0, 0, 0, 0, 0, 0}, ReadCount: 2, MethodAsCustom: method, }, { Name: "Float64.ok", Data: []byte{def.Float64, 0, 0, 0, 0, 0, 0, 0, 0}, ReadCount: 2, MethodAsCustom: method, }, { Name: "FixStr.ng", Data: []byte{def.FixStr + 1}, ReadCount: 1, Error: io.EOF, MethodAsCustom: method, }, { Name: "FixStr.ok", Data: []byte{def.FixStr + 1, 0}, ReadCount: 2, MethodAsCustom: method, }, { Name: "Str8.ng.length", Data: []byte{def.Str8}, ReadCount: 1, Error: io.EOF, MethodAsCustom: method, }, { Name: "Str8.ng.str", Data: []byte{def.Str8, 1}, ReadCount: 2, Error: io.EOF, MethodAsCustom: method, }, { Name: "Str8.ok", Data: []byte{def.Str8, 1, 'a'}, ReadCount: 3, MethodAsCustom: method, }, { Name: "Bin8.ng.length", Data: []byte{def.Bin8}, ReadCount: 1, Error: io.EOF, MethodAsCustom: method, }, { Name: "Bin8.ng.str", Data: []byte{def.Bin8, 1}, ReadCount: 2, Error: io.EOF, MethodAsCustom: method, }, { Name: "Bin8.ok", Data: []byte{def.Bin8, 1, 'a'}, ReadCount: 3, MethodAsCustom: method, }, { Name: "Str16.ng.length", Data: []byte{def.Str16}, ReadCount: 1, Error: io.EOF, MethodAsCustom: method, }, { Name: "Str16.ng.str", Data: []byte{def.Str16, 0, 1}, ReadCount: 2, Error: io.EOF, MethodAsCustom: method, }, { Name: "Str16.ok", Data: []byte{def.Str16, 0, 1, 'a'}, ReadCount: 3, MethodAsCustom: method, }, { Name: "Bin16.ng.length", Data: []byte{def.Bin16}, ReadCount: 1, Error: io.EOF, MethodAsCustom: method, }, { Name: "Bin16.ng.str", Data: []byte{def.Bin16, 0, 1}, ReadCount: 2, Error: io.EOF, MethodAsCustom: method, }, { Name: "Bin16.ok", Data: []byte{def.Bin16, 0, 1, 'a'}, ReadCount: 3, MethodAsCustom: method, }, { Name: "Str32.ng.length", Data: []byte{def.Str32}, ReadCount: 1, Error: io.EOF, MethodAsCustom: method, }, { Name: "Str32.ng.str", Data: []byte{def.Str32, 0, 0, 0, 1}, ReadCount: 2, Error: io.EOF, MethodAsCustom: method, }, { Name: "Str32.ok", Data: []byte{def.Str32, 0, 0, 0, 1, 'a'}, ReadCount: 3, MethodAsCustom: method, }, { Name: "Bin32.ng.length", Data: []byte{def.Bin32}, ReadCount: 1, Error: io.EOF, MethodAsCustom: method, }, { Name: "Bin32.ng.str", Data: []byte{def.Bin32, 0, 0, 0, 1}, ReadCount: 2, Error: io.EOF, MethodAsCustom: method, }, { Name: "Bin32.ok", Data: []byte{def.Bin32, 0, 0, 0, 1, 'a'}, ReadCount: 3, MethodAsCustom: method, }, { Name: "FixSlice.ng", Data: []byte{def.FixArray + 1}, ReadCount: 1, Error: io.EOF, MethodAsCustom: method, }, { Name: "FixSlice.ok", Data: []byte{def.FixArray + 1, 0xc1}, ReadCount: 2, MethodAsCustom: method, }, { Name: "Array16.ng.len", Data: []byte{def.Array16}, ReadCount: 1, Error: io.EOF, MethodAsCustom: method, }, { Name: "Array16.ng.jump", Data: []byte{def.Array16, 0, 1}, ReadCount: 2, Error: io.EOF, MethodAsCustom: method, }, { Name: "Array16.ok", Data: []byte{def.Array16, 0, 1, 0xc1}, ReadCount: 3, MethodAsCustom: method, }, { Name: "Array32.ng.len", Data: []byte{def.Array32}, ReadCount: 1, Error: io.EOF, MethodAsCustom: method, }, { Name: "Array32.ng.jump", Data: []byte{def.Array32, 0, 0, 0, 1}, ReadCount: 2, Error: io.EOF, MethodAsCustom: method, }, { Name: "Array32.ok", Data: []byte{def.Array32, 0, 0, 0, 1, 0xc1}, ReadCount: 3, MethodAsCustom: method, }, { Name: "FixMap.ng", Data: []byte{def.FixMap + 1}, ReadCount: 1, Error: io.EOF, MethodAsCustom: method, }, { Name: "FixMap.ok", Data: []byte{def.FixMap + 1, 0xc1, 0xc1}, ReadCount: 3, MethodAsCustom: method, }, { Name: "Map16.ng.len", Data: []byte{def.Map16}, ReadCount: 1, Error: io.EOF, MethodAsCustom: method, }, { Name: "Map16.ng.jump", Data: []byte{def.Map16, 0, 1}, ReadCount: 2, Error: io.EOF, MethodAsCustom: method, }, { Name: "Map16.ok", Data: []byte{def.Map16, 0, 1, 0xc1, 0xc1}, ReadCount: 4, MethodAsCustom: method, }, { Name: "Map32.ng.len", Data: []byte{def.Map32}, ReadCount: 1, Error: io.EOF, MethodAsCustom: method, }, { Name: "Map32.ng.jump", Data: []byte{def.Map32, 0, 0, 0, 1}, ReadCount: 2, Error: io.EOF, MethodAsCustom: method, }, { Name: "Map32.ok", Data: []byte{def.Map32, 0, 0, 0, 1, 0xc1, 0xc1}, ReadCount: 4, MethodAsCustom: method, }, { Name: "Fixext1.ng", Data: []byte{def.Fixext1}, ReadCount: 1, Error: io.EOF, MethodAsCustom: method, }, { Name: "Fixext1.ok", Data: []byte{def.Fixext1, 0, 0}, ReadCount: 2, MethodAsCustom: method, }, { Name: "Fixext2.ng", Data: []byte{def.Fixext2}, ReadCount: 1, Error: io.EOF, MethodAsCustom: method, }, { Name: "Fixext2.ok", Data: []byte{def.Fixext2, 0, 0, 0}, ReadCount: 2, MethodAsCustom: method, }, { Name: "Fixext4.ng", Data: []byte{def.Fixext4}, ReadCount: 1, Error: io.EOF, MethodAsCustom: method, }, { Name: "Fixext4.ok", Data: []byte{def.Fixext4, 0, 0, 0, 0, 0}, ReadCount: 2, MethodAsCustom: method, }, { Name: "Fixext8.ng", Data: []byte{def.Fixext8}, ReadCount: 1, Error: io.EOF, MethodAsCustom: method, }, { Name: "Fixext8.ok", Data: []byte{def.Fixext8, 0, 0, 0, 0, 0, 0, 0, 0, 0}, ReadCount: 2, MethodAsCustom: method, }, { Name: "Fixext16.ng", Data: []byte{def.Fixext16}, ReadCount: 1, Error: io.EOF, MethodAsCustom: method, }, { Name: "Fixext16.ok", Data: []byte{def.Fixext16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, ReadCount: 2, MethodAsCustom: method, }, { Name: "Ext8.ng.size", Data: []byte{def.Ext8}, ReadCount: 1, Error: io.EOF, MethodAsCustom: method, }, { Name: "Ext8.ng.size.n", Data: []byte{def.Ext8, 1}, ReadCount: 2, Error: io.EOF, MethodAsCustom: method, }, { Name: "Ext8.ok", Data: []byte{def.Ext8, 1, 0}, ReadCount: 3, MethodAsCustom: method, }, { Name: "Ext16.ng.size", Data: []byte{def.Ext16}, ReadCount: 1, Error: io.EOF, MethodAsCustom: method, }, { Name: "Ext16.ng.size.n", Data: []byte{def.Ext16, 0, 1}, ReadCount: 2, Error: io.EOF, MethodAsCustom: method, }, { Name: "Ext16.ok", Data: []byte{def.Ext16, 0, 1, 0}, ReadCount: 3, MethodAsCustom: method, }, { Name: "Ext32.ng.size", Data: []byte{def.Ext32}, ReadCount: 1, Error: io.EOF, MethodAsCustom: method, }, { Name: "Ext32.ng.size.n", Data: []byte{def.Ext32, 0, 0, 0, 1}, ReadCount: 2, Error: io.EOF, MethodAsCustom: method, }, { Name: "Ext32.ok", Data: []byte{def.Ext32, 0, 0, 0, 1, 0}, ReadCount: 3, MethodAsCustom: method, }, { Name: "Unexpected", Data: []byte{0xc1}, ReadCount: 1, MethodAsCustom: method, }, } for _, tc := range testcases { tc.Run(t) } } ================================================ FILE: internal/stream/decoding/uint.go ================================================ package decoding import ( "encoding/binary" "reflect" "github.com/shamaton/msgpack/v3/def" ) func (d *decoder) asUint(k reflect.Kind) (uint64, error) { code, err := d.readSize1() if err != nil { return 0, err } return d.asUintWithCode(code, k) } func (d *decoder) asUintWithCode(code byte, k reflect.Kind) (uint64, error) { switch { case d.isPositiveFixNum(code): return uint64(code), nil case d.isNegativeFixNum(code): return uint64(int8(code)), nil case code == def.Uint8: b, err := d.readSize1() if err != nil { return 0, err } return uint64(b), nil case code == def.Int8: b, err := d.readSize1() if err != nil { return 0, err } return uint64(int8(b)), nil case code == def.Uint16: bs, err := d.readSize2() if err != nil { return 0, err } v := binary.BigEndian.Uint16(bs) return uint64(v), nil case code == def.Int16: bs, err := d.readSize2() if err != nil { return 0, err } v := int16(binary.BigEndian.Uint16(bs)) return uint64(v), nil case code == def.Uint32: bs, err := d.readSize4() if err != nil { return 0, err } v := binary.BigEndian.Uint32(bs) return uint64(v), nil case code == def.Int32: bs, err := d.readSize4() if err != nil { return 0, err } v := int32(binary.BigEndian.Uint32(bs)) return uint64(v), nil case code == def.Uint64: bs, err := d.readSize8() if err != nil { return 0, err } return binary.BigEndian.Uint64(bs), nil case code == def.Int64: bs, err := d.readSize8() if err != nil { return 0, err } return binary.BigEndian.Uint64(bs), nil case code == def.Nil: return 0, nil } return 0, d.errorTemplate(code, k) } ================================================ FILE: internal/stream/decoding/uint_test.go ================================================ package decoding import ( "bytes" "io" "math" "reflect" "testing" "github.com/shamaton/msgpack/v3/def" "github.com/shamaton/msgpack/v3/internal/common" tu "github.com/shamaton/msgpack/v3/internal/common/testutil" ) func Test_asUint(t *testing.T) { t.Run("read error", func(t *testing.T) { d := decoder{ r: tu.NewErrReader(), buf: common.GetBuffer(), } v, err := d.asUint(reflect.Uint) tu.IsError(t, err, tu.ErrReaderErr) tu.Equal(t, v, 0) }) t.Run("ok", func(t *testing.T) { d := decoder{ r: bytes.NewReader([]byte{1}), buf: common.GetBuffer(), } v, err := d.asUint(reflect.Uint) tu.NoError(t, err) tu.Equal(t, v, 1) }) } func Test_asUintWithCode(t *testing.T) { testcases := []struct { name string code byte length int expected uint64 errSkip bool }{ { name: "Uint8", code: def.Uint8, length: 1, expected: math.MaxUint8, }, { name: "Int8", code: def.Int8, length: 1, expected: math.MaxUint64, }, { name: "Uint16", code: def.Uint16, length: 2, expected: math.MaxUint16, }, { name: "Int16", code: def.Int16, length: 2, expected: math.MaxUint64, }, { name: "Uint32", code: def.Uint32, length: 4, expected: math.MaxUint32, }, { name: "Int32", code: def.Int32, length: 4, expected: math.MaxUint64, }, { name: "Uint64", code: def.Uint64, length: 8, expected: math.MaxUint64, }, { name: "Int64", code: def.Int64, length: 8, expected: math.MaxUint64, }, { name: "Nil", code: def.Nil, expected: 0, errSkip: true, }, } for _, tc := range testcases { t.Run(tc.name+"", func(t *testing.T) { t.Run("ng", func(t *testing.T) { if tc.errSkip { t.Log("this testcase is skipped by skip flag") return } d := decoder{ r: tu.NewErrReader(), buf: common.GetBuffer(), } defer common.PutBuffer(d.buf) _, err := d.asUintWithCode(tc.code, reflect.String) tu.IsError(t, err, tu.ErrReaderErr) }) t.Run("ok", func(t *testing.T) { data := make([]byte, tc.length) for i := range data { data[i] = 0xff } d := decoder{ r: bytes.NewReader(data), buf: common.GetBuffer(), } defer common.PutBuffer(d.buf) v, err := d.asUintWithCode(tc.code, reflect.String) tu.NoError(t, err) tu.Equal(t, v, tc.expected) p := make([]byte, 1) n, err := d.r.Read(p) tu.IsError(t, err, io.EOF) tu.Equal(t, n, 0) }) }) } } ================================================ FILE: internal/stream/encoding/bool.go ================================================ package encoding import "github.com/shamaton/msgpack/v3/def" func (e *encoder) writeBool(v bool) error { if v { return e.setByte1Int(def.True) } return e.setByte1Int(def.False) } ================================================ FILE: internal/stream/encoding/bool_test.go ================================================ package encoding import ( "testing" "github.com/shamaton/msgpack/v3/def" ) func Test_asBool(t *testing.T) { method := func(e *encoder) func(bool) error { return e.writeBool } testcases := AsXXXTestCases[bool]{ { Name: "True.error", Value: true, BufferSize: 1, PreWriteSize: 1, Method: method, }, { Name: "True.ok", Value: true, Expected: []byte{def.True}, BufferSize: 1, Method: method, }, } testcases.Run(t) } ================================================ FILE: internal/stream/encoding/byte.go ================================================ package encoding import ( "math" "reflect" "github.com/shamaton/msgpack/v3/def" ) var typeByte = reflect.TypeOf(byte(0)) func (e *encoder) isByteSlice(rv reflect.Value) bool { return rv.Type().Elem() == typeByte } func (e *encoder) writeByteSliceLength(l int) error { if l <= math.MaxUint8 { if err := e.setByte1Int(def.Bin8); err != nil { return err } if err := e.setByte1Int(l); err != nil { return err } } else if l <= math.MaxUint16 { if err := e.setByte1Int(def.Bin16); err != nil { return err } if err := e.setByte2Int(l); err != nil { return err } } else if uint(l) <= math.MaxUint32 { if err := e.setByte1Int(def.Bin32); err != nil { return err } if err := e.setByte4Int(l); err != nil { return err } } return nil } ================================================ FILE: internal/stream/encoding/byte_test.go ================================================ package encoding import ( "testing" "github.com/shamaton/msgpack/v3/def" ) func Test_writeByteSliceLength(t *testing.T) { method := func(e *encoder) func(int) error { return e.writeByteSliceLength } testcases := AsXXXTestCases[int]{ { Name: "Bin8.error.def", Value: 5, BufferSize: 1, PreWriteSize: 1, Method: method, }, { Name: "Bin8.error.value", Value: 5, BufferSize: 2, PreWriteSize: 1, Contains: []byte{def.Bin8}, Method: method, }, { Name: "Bin8.ok", Value: 5, Expected: []byte{def.Bin8, 0x05}, BufferSize: 1, Method: method, }, { Name: "Bin16.error.def", Value: 256, BufferSize: 1, PreWriteSize: 1, Method: method, }, { Name: "Bin16.error.value", Value: 256, BufferSize: 3, PreWriteSize: 1, Contains: []byte{def.Bin16}, Method: method, }, { Name: "Bin16.ok", Value: 256, Expected: []byte{def.Bin16, 0x01, 0x00}, BufferSize: 1, Method: method, }, { Name: "Bin32.error.def", Value: 65536, BufferSize: 1, PreWriteSize: 1, Method: method, }, { Name: "Bin32.error.value", Value: 65536, BufferSize: 3, PreWriteSize: 1, Contains: []byte{def.Bin32}, Method: method, }, { Name: "Bin32.ok", Value: 65536, Expected: []byte{def.Bin32, 0x00, 0x01, 0x00, 0x00}, BufferSize: 1, Method: method, }, } testcases.Run(t) } ================================================ FILE: internal/stream/encoding/complex.go ================================================ package encoding import ( "math" "github.com/shamaton/msgpack/v3/def" ) func (e *encoder) writeComplex64(v complex64) error { if err := e.setByte1Int(def.Fixext8); err != nil { return err } if err := e.setByte1Int(int(def.ComplexTypeCode())); err != nil { return err } if err := e.setByte4Uint64(uint64(math.Float32bits(real(v)))); err != nil { return err } if err := e.setByte4Uint64(uint64(math.Float32bits(imag(v)))); err != nil { return err } return nil } func (e *encoder) writeComplex128(v complex128) error { if err := e.setByte1Int(def.Fixext16); err != nil { return err } if err := e.setByte1Int(int(def.ComplexTypeCode())); err != nil { return err } if err := e.setByte8Uint64(math.Float64bits(real(v))); err != nil { return err } if err := e.setByte8Uint64(math.Float64bits(imag(v))); err != nil { return err } return nil } ================================================ FILE: internal/stream/encoding/complex_test.go ================================================ package encoding import ( "testing" "github.com/shamaton/msgpack/v3/def" ) func Test_writeComplex64(t *testing.T) { method := func(e *encoder) func(complex64) error { return e.writeComplex64 } v := complex64(complex(1, 2)) testcases := AsXXXTestCases[complex64]{ { Name: "error.def", Value: v, BufferSize: 1, PreWriteSize: 1, Method: method, }, { Name: "error.code", Value: v, BufferSize: 2, PreWriteSize: 1, Contains: []byte{def.Fixext8}, Method: method, }, { Name: "error.real", Value: v, BufferSize: 3, PreWriteSize: 1, Contains: []byte{def.Fixext8, byte(def.ComplexTypeCode())}, Method: method, }, { Name: "error.imag", Value: v, BufferSize: 7, PreWriteSize: 1, Contains: []byte{ def.Fixext8, byte(def.ComplexTypeCode()), 0x3f, 0x80, 0x00, 0x00, }, Method: method, }, { Name: "ok", Value: v, Expected: []byte{ def.Fixext8, byte(def.ComplexTypeCode()), 0x3f, 0x80, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, }, BufferSize: 1, Method: method, }, } testcases.Run(t) } func Test_writeComplex128(t *testing.T) { method := func(e *encoder) func(complex128) error { return e.writeComplex128 } v := complex128(complex(1, 2)) testcases := AsXXXTestCases[complex128]{ { Name: "error.def", Value: v, BufferSize: 1, PreWriteSize: 1, Method: method, }, { Name: "error.code", Value: v, BufferSize: 2, PreWriteSize: 1, Contains: []byte{def.Fixext16}, Method: method, }, { Name: "error.real", Value: v, BufferSize: 3, PreWriteSize: 1, Contains: []byte{def.Fixext16, byte(def.ComplexTypeCode())}, Method: method, }, { Name: "error.imag", Value: v, BufferSize: 11, PreWriteSize: 1, Contains: []byte{ def.Fixext16, byte(def.ComplexTypeCode()), 0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, Method: method, }, { Name: "ok", Value: v, Expected: []byte{ def.Fixext16, byte(def.ComplexTypeCode()), 0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, BufferSize: 1, Method: method, }, } testcases.Run(t) } ================================================ FILE: internal/stream/encoding/encoding.go ================================================ package encoding import ( "fmt" "io" "reflect" "github.com/shamaton/msgpack/v3/def" "github.com/shamaton/msgpack/v3/internal/common" ) type encoder struct { w io.Writer asArray bool buf *common.Buffer common.Common } // Encode writes MessagePack-encoded byte array of v to writer. func Encode(w io.Writer, v any, asArray bool) error { e := encoder{ w: w, buf: common.GetBuffer(), asArray: asArray, } rv := reflect.ValueOf(v) if rv.Kind() == reflect.Ptr { rv = rv.Elem() if rv.Kind() == reflect.Ptr { rv = rv.Elem() } } err := e.create(rv) if err == nil { err = e.buf.Flush(e.w) } common.PutBuffer(e.buf) return err } func (e *encoder) create(rv reflect.Value) error { switch rv.Kind() { case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: v := rv.Uint() return e.writeUint(v) case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: v := rv.Int() return e.writeInt(v) case reflect.Float32: return e.writeFloat32(rv.Float()) case reflect.Float64: return e.writeFloat64(rv.Float()) case reflect.Bool: return e.writeBool(rv.Bool()) case reflect.String: return e.writeString(rv.String()) case reflect.Complex64: return e.writeComplex64(complex64(rv.Complex())) case reflect.Complex128: return e.writeComplex128(rv.Complex()) case reflect.Slice: if rv.IsNil() { return e.writeNil() } l := rv.Len() // bin format if e.isByteSlice(rv) { if err := e.writeByteSliceLength(l); err != nil { return err } return e.setBytes(rv.Bytes()) } // format if err := e.writeSliceLength(l); err != nil { return err } if find, err := e.writeFixedSlice(rv); err != nil { return err } else if find { return nil } // func elem := rv.Type().Elem() var f structWriteFunc if elem.Kind() == reflect.Struct { f = e.getStructWriter(elem) } else { f = e.create } // objects for i := 0; i < l; i++ { if err := f(rv.Index(i)); err != nil { return err } } case reflect.Array: l := rv.Len() // bin format if e.isByteSlice(rv) { if err := e.writeByteSliceLength(l); err != nil { return err } // objects for i := 0; i < l; i++ { if err := e.setByte1Uint64(rv.Index(i).Uint()); err != nil { return err } } return nil } // format if err := e.writeSliceLength(l); err != nil { return err } // func elem := rv.Type().Elem() var f structWriteFunc if elem.Kind() == reflect.Struct { f = e.getStructWriter(elem) } else { f = e.create } // objects for i := 0; i < l; i++ { if err := f(rv.Index(i)); err != nil { return err } } case reflect.Map: if rv.IsNil() { return e.writeNil() } l := rv.Len() if err := e.writeMapLength(l); err != nil { return err } if find, err := e.writeFixedMap(rv); err != nil { return err } else if find { return nil } // key-value keys := rv.MapKeys() for _, k := range keys { if err := e.create(k); err != nil { return err } if err := e.create(rv.MapIndex(k)); err != nil { return err } } case reflect.Struct: return e.writeStruct(rv) case reflect.Ptr: if rv.IsNil() { return e.writeNil() } return e.create(rv.Elem()) case reflect.Interface: return e.create(rv.Elem()) case reflect.Invalid: return e.writeNil() default: return fmt.Errorf("%v is %w type", rv.Kind(), def.ErrUnsupportedType) } return nil } ================================================ FILE: internal/stream/encoding/encoding_test.go ================================================ package encoding import ( "bytes" "errors" "io" "reflect" "testing" "github.com/shamaton/msgpack/v3/def" "github.com/shamaton/msgpack/v3/internal/common" tu "github.com/shamaton/msgpack/v3/internal/common/testutil" ) const dummyByte = 0xc1 type TestWriter struct { WrittenBytes []byte } var _ io.Writer = (*TestWriter)(nil) var ErrTestWriter = errors.New("expected written error") func (w *TestWriter) Write(p []byte) (n int, err error) { w.WrittenBytes = append(w.WrittenBytes, p...) if bytes.Contains(p, []byte{dummyByte}) { return 0, ErrTestWriter } return len(p), nil } func NewTestWriter() *TestWriter { return &TestWriter{} } type AsXXXTestCase[T any] struct { Name string Value T Expected []byte Contains []byte BufferSize int PreWriteSize int Error error AsArray bool Method func(*encoder) func(T) error MethodForFixed func(*encoder) func(reflect.Value) (bool, error) MethodForStruct func(*encoder) func(reflect.Value) error } type AsXXXTestCases[T any] []AsXXXTestCase[T] func (tcs AsXXXTestCases[T]) Run(t *testing.T) { for _, tc := range tcs { tc.Run(t) } } func (tc *AsXXXTestCase[T]) Run(t *testing.T) { t.Helper() if tc.Method == nil && tc.MethodForFixed == nil && tc.MethodForStruct == nil { t.Fatal("must set either Method or MethodForFixed or MethodForStruct") } method := func(e *encoder) error { if tc.Method != nil { return tc.Method(e)(tc.Value) } if tc.MethodForFixed != nil { _, err := tc.MethodForFixed(e)(reflect.ValueOf(tc.Value)) return err } if tc.MethodForStruct != nil { return tc.MethodForStruct(e)(reflect.ValueOf(tc.Value)) } panic("unreachable") } t.Run(tc.Name, func(t *testing.T) { w := NewTestWriter() e := encoder{ w: w, buf: common.GetBuffer(), Common: common.Common{}, asArray: tc.AsArray, } if tc.BufferSize < tc.PreWriteSize { t.Fatal("buffer size must be greater than pre write size") } e.buf.Data = make([]byte, tc.BufferSize) if tc.PreWriteSize > 0 { for i := 0; i < tc.PreWriteSize; i++ { _ = e.buf.Write(e.w, dummyByte) } } err := method(&e) _ = e.buf.Flush(w) common.PutBuffer(e.buf) if tc.PreWriteSize > 0 { tu.IsError(t, err, ErrTestWriter) if !bytes.Contains(w.WrittenBytes, tc.Contains) { t.Fatalf("[% 02x] does not contain in [% 02x]", tc.Contains, w.WrittenBytes) } return } if tc.Error != nil { tu.IsError(t, err, tc.Error) return } tu.NoError(t, err) tu.EqualSlice(t, w.WrittenBytes, tc.Expected) }) } func TestEncode(t *testing.T) { v := 1 vv := &v w := NewTestWriter() err := Encode(w, &vv, false) tu.NoError(t, err) tu.EqualSlice(t, w.WrittenBytes, []byte{def.PositiveFixIntMin + 1}) } func Test_create(t *testing.T) { method := func(e *encoder) func(reflect.Value) error { return e.create } t.Run("uint8", func(t *testing.T) { value := uint8(1) testcases := AsXXXTestCases[uint8]{ { Name: "error", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForStruct: method, }, { Name: "ok", Value: value, Expected: []byte{def.PositiveFixIntMin + 1}, BufferSize: 1, MethodForStruct: method, }, } testcases.Run(t) }) t.Run("uint16", func(t *testing.T) { value := uint16(1) testcases := AsXXXTestCases[uint16]{ { Name: "error", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForStruct: method, }, { Name: "ok", Value: value, Expected: []byte{def.PositiveFixIntMin + 1}, BufferSize: 1, MethodForStruct: method, }, } testcases.Run(t) }) t.Run("uint32", func(t *testing.T) { value := uint32(1) testcases := AsXXXTestCases[uint32]{ { Name: "error", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForStruct: method, }, { Name: "ok", Value: value, Expected: []byte{def.PositiveFixIntMin + 1}, BufferSize: 1, MethodForStruct: method, }, } testcases.Run(t) }) t.Run("uint64", func(t *testing.T) { value := uint64(1) testcases := AsXXXTestCases[uint64]{ { Name: "error", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForStruct: method, }, { Name: "ok", Value: value, Expected: []byte{def.PositiveFixIntMin + 1}, BufferSize: 1, MethodForStruct: method, }, } testcases.Run(t) }) t.Run("uint", func(t *testing.T) { value := uint(1) testcases := AsXXXTestCases[uint]{ { Name: "error", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForStruct: method, }, { Name: "ok", Value: value, Expected: []byte{def.PositiveFixIntMin + 1}, BufferSize: 1, MethodForStruct: method, }, } testcases.Run(t) }) t.Run("int8", func(t *testing.T) { value := int8(-1) testcases := AsXXXTestCases[int8]{ { Name: "error", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForStruct: method, }, { Name: "ok", Value: value, Expected: []byte{0xff}, BufferSize: 1, MethodForStruct: method, }, } testcases.Run(t) }) t.Run("int16", func(t *testing.T) { value := int16(-1) testcases := AsXXXTestCases[int16]{ { Name: "error", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForStruct: method, }, { Name: "ok", Value: value, Expected: []byte{0xff}, BufferSize: 1, MethodForStruct: method, }, } testcases.Run(t) }) t.Run("int32", func(t *testing.T) { value := int32(-1) testcases := AsXXXTestCases[int32]{ { Name: "error", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForStruct: method, }, { Name: "ok", Value: value, Expected: []byte{0xff}, BufferSize: 1, MethodForStruct: method, }, } testcases.Run(t) }) t.Run("int64", func(t *testing.T) { value := int64(-1) testcases := AsXXXTestCases[int64]{ { Name: "error", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForStruct: method, }, { Name: "ok", Value: value, Expected: []byte{0xff}, BufferSize: 1, MethodForStruct: method, }, } testcases.Run(t) }) t.Run("int", func(t *testing.T) { value := int(-1) testcases := AsXXXTestCases[int]{ { Name: "error", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForStruct: method, }, { Name: "ok", Value: value, Expected: []byte{0xff}, BufferSize: 1, MethodForStruct: method, }, } testcases.Run(t) }) t.Run("float32", func(t *testing.T) { value := float32(1) testcases := AsXXXTestCases[float32]{ { Name: "error", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForStruct: method, }, { Name: "ok", Value: value, Expected: []byte{def.Float32, 0x3f, 0x80, 0x00, 0x00}, BufferSize: 1, MethodForStruct: method, }, } testcases.Run(t) }) t.Run("float64", func(t *testing.T) { value := float64(1) testcases := AsXXXTestCases[float64]{ { Name: "error", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForStruct: method, }, { Name: "ok", Value: value, Expected: []byte{def.Float64, 0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, BufferSize: 1, MethodForStruct: method, }, } testcases.Run(t) }) t.Run("bool", func(t *testing.T) { value := true testcases := AsXXXTestCases[bool]{ { Name: "error", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForStruct: method, }, { Name: "ok", Value: value, Expected: []byte{def.True}, BufferSize: 1, MethodForStruct: method, }, } testcases.Run(t) }) t.Run("string", func(t *testing.T) { value := "a" testcases := AsXXXTestCases[string]{ { Name: "error", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForStruct: method, }, { Name: "ok", Value: value, Expected: []byte{def.FixStr + 1, 0x61}, BufferSize: 1, MethodForStruct: method, }, } testcases.Run(t) }) t.Run("complex64", func(t *testing.T) { value := complex64(complex(1, 2)) testcases := AsXXXTestCases[complex64]{ { Name: "error", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForStruct: method, }, { Name: "ok", Value: value, Expected: []byte{ def.Fixext8, byte(def.ComplexTypeCode()), 0x3f, 0x80, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, }, BufferSize: 1, MethodForStruct: method, }, } testcases.Run(t) }) t.Run("complex128", func(t *testing.T) { value := complex128(complex(1, 2)) testcases := AsXXXTestCases[complex128]{ { Name: "error", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForStruct: method, }, { Name: "ok", Value: value, Expected: []byte{ def.Fixext16, byte(def.ComplexTypeCode()), 0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, BufferSize: 1, MethodForStruct: method, }, } testcases.Run(t) }) type st struct { A int } t.Run("slice", func(t *testing.T) { t.Run("nil", func(t *testing.T) { var value []int testcases := AsXXXTestCases[[]int]{ { Name: "ok", Value: value, Expected: []byte{def.Nil}, BufferSize: 1, MethodForStruct: method, }, } testcases.Run(t) }) t.Run("bin", func(t *testing.T) { value := []byte{1, 2, 3} testcases := AsXXXTestCases[[]byte]{ { Name: "error.length", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForStruct: method, }, { Name: "error.value", Value: value, BufferSize: 3, PreWriteSize: 1, Contains: []byte{def.Bin8, 0x03}, MethodForStruct: method, }, { Name: "ok", Value: value, Expected: []byte{def.Bin8, 0x03, 0x01, 0x02, 0x03}, BufferSize: 1, MethodForStruct: method, }, } testcases.Run(t) }) t.Run("fixed", func(t *testing.T) { value := []int{1, 2, 3} testcases := AsXXXTestCases[[]int]{ { Name: "error.length", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForStruct: method, }, { Name: "error.value", Value: value, BufferSize: 2, PreWriteSize: 1, Contains: []byte{def.FixArray + 3}, MethodForStruct: method, }, { Name: "ok", Value: value, Expected: []byte{def.FixArray + 3, 0x01, 0x02, 0x03}, BufferSize: 1, MethodForStruct: method, }, } testcases.Run(t) }) t.Run("slice_slice", func(t *testing.T) { value := [][]int{{1}} testcases := AsXXXTestCases[[][]int]{ { Name: "error.length", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForStruct: method, }, { Name: "error.value", Value: value, BufferSize: 2, PreWriteSize: 1, Contains: []byte{def.FixArray + 1}, MethodForStruct: method, }, { Name: "ok", Value: value, Expected: []byte{def.FixArray + 1, def.FixArray + 1, 0x01}, BufferSize: 1, MethodForStruct: method, }, } testcases.Run(t) }) t.Run("struct", func(t *testing.T) { value := []st{{1}} testcases := AsXXXTestCases[[]st]{ { Name: "error.length", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForStruct: method, }, { Name: "error.value", Value: value, BufferSize: 2, PreWriteSize: 1, Contains: []byte{def.FixArray + 1}, MethodForStruct: method, }, { Name: "ok", Value: value, Expected: []byte{def.FixArray + 1, def.FixMap + 1, def.FixStr + 1, 0x41, 0x01}, BufferSize: 1, MethodForStruct: method, }, } testcases.Run(t) }) }) t.Run("array", func(t *testing.T) { t.Run("bin", func(t *testing.T) { value := [3]byte{1, 2, 3} testcases := AsXXXTestCases[[3]byte]{ { Name: "error.length", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForStruct: method, }, { Name: "error.value", Value: value, BufferSize: 3, PreWriteSize: 1, Contains: []byte{def.Bin8, 0x03}, MethodForStruct: method, }, { Name: "ok", Value: value, Expected: []byte{def.Bin8, 0x03, 0x01, 0x02, 0x03}, BufferSize: 1, MethodForStruct: method, }, } testcases.Run(t) }) t.Run("fixed", func(t *testing.T) { value := [3]int{1, 2, 3} testcases := AsXXXTestCases[[3]int]{ { Name: "error.length", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForStruct: method, }, { Name: "error.value", Value: value, BufferSize: 2, PreWriteSize: 1, Contains: []byte{def.FixArray + 3}, MethodForStruct: method, }, { Name: "ok", Value: value, Expected: []byte{def.FixArray + 3, 0x01, 0x02, 0x03}, BufferSize: 1, MethodForStruct: method, }, } testcases.Run(t) }) t.Run("slice_slice", func(t *testing.T) { value := [1][]int{{1}} testcases := AsXXXTestCases[[1][]int]{ { Name: "error.length", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForStruct: method, }, { Name: "error.value", Value: value, BufferSize: 2, PreWriteSize: 1, Contains: []byte{def.FixArray + 1}, MethodForStruct: method, }, { Name: "ok", Value: value, Expected: []byte{def.FixArray + 1, def.FixArray + 1, 0x01}, BufferSize: 1, MethodForStruct: method, }, } testcases.Run(t) }) t.Run("struct", func(t *testing.T) { value := [1]st{{1}} testcases := AsXXXTestCases[[1]st]{ { Name: "error.length", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForStruct: method, }, { Name: "error.value", Value: value, BufferSize: 2, PreWriteSize: 1, Contains: []byte{def.FixArray + 1}, MethodForStruct: method, }, { Name: "ok", Value: value, Expected: []byte{def.FixArray + 1, def.FixMap + 1, def.FixStr + 1, 0x41, 0x01}, BufferSize: 1, MethodForStruct: method, }, } testcases.Run(t) }) }) t.Run("map", func(t *testing.T) { t.Run("nil", func(t *testing.T) { var value map[string]int testcases := AsXXXTestCases[map[string]int]{ { Name: "ok", Value: value, Expected: []byte{def.Nil}, BufferSize: 1, MethodForStruct: method, }, } testcases.Run(t) }) t.Run("fixed", func(t *testing.T) { value := map[string]int{"a": 1} testcases := AsXXXTestCases[map[string]int]{ { Name: "error.length", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForStruct: method, }, { Name: "error.value", Value: value, BufferSize: 2, PreWriteSize: 1, Contains: []byte{def.FixMap + 1}, MethodForStruct: method, }, { Name: "ok", Value: value, Expected: []byte{def.FixMap + 1, def.FixStr + 1, 'a', 1}, BufferSize: 1, MethodForStruct: method, }, } testcases.Run(t) }) t.Run("struct", func(t *testing.T) { value := map[string]st{"a": {1}} testcases := AsXXXTestCases[map[string]st]{ { Name: "error.length", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForStruct: method, }, { Name: "error.key", Value: value, BufferSize: 2, PreWriteSize: 1, Contains: []byte{def.FixMap + 1}, MethodForStruct: method, }, { Name: "error.value", Value: value, BufferSize: 4, PreWriteSize: 1, Contains: []byte{def.FixMap + 1, def.FixStr + 1, 'a'}, MethodForStruct: method, }, { Name: "ok", Value: value, Expected: []byte{def.FixMap + 1, def.FixStr + 1, 'a', def.FixMap + 1, def.FixStr + 1, 'A', 1}, BufferSize: 1, MethodForStruct: method, }, } testcases.Run(t) }) }) t.Run("struct", func(t *testing.T) { value := st{1} testcases := AsXXXTestCases[st]{ { Name: "error", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForStruct: method, }, { Name: "ok", Value: value, Expected: []byte{def.FixMap + 1, def.FixStr + 1, 'A', 1}, BufferSize: 1, MethodForStruct: method, }, } testcases.Run(t) }) t.Run("pointer", func(t *testing.T) { var v *int vv := &v value := &vv t.Run("nil", func(t *testing.T) { testcases := AsXXXTestCases[***int]{ { Name: "nil", Value: value, Expected: []byte{def.Nil}, BufferSize: 1, MethodForStruct: method, }, } testcases.Run(t) }) u := 1 v = &u vv = &v value = &vv t.Run("int", func(t *testing.T) { testcases := AsXXXTestCases[***int]{ { Name: "error", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForStruct: method, }, { Name: "ok", Value: value, Expected: []byte{1}, BufferSize: 1, MethodForStruct: method, }, } testcases.Run(t) }) }) t.Run("any", func(t *testing.T) { value := any(1) testcases := AsXXXTestCases[any]{ { Name: "error", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForStruct: method, }, { Name: "ok", Value: value, Expected: []byte{1}, BufferSize: 1, MethodForStruct: method, }, } testcases.Run(t) }) t.Run("invalid", func(t *testing.T) { var value error testcases := AsXXXTestCases[error]{ { Name: "ok", Value: value, Expected: []byte{def.Nil}, BufferSize: 1, MethodForStruct: method, }, } testcases.Run(t) }) t.Run("invalid", func(t *testing.T) { var value func() testcases := AsXXXTestCases[func()]{ { Name: "ok", Value: value, BufferSize: 1, Error: def.ErrUnsupportedType, MethodForStruct: method, }, } testcases.Run(t) }) } ================================================ FILE: internal/stream/encoding/ext.go ================================================ package encoding import ( "reflect" "github.com/shamaton/msgpack/v3/ext" "github.com/shamaton/msgpack/v3/time" ) var ( extCoderMap = map[reflect.Type]ext.StreamEncoder{time.StreamEncoder.Type(): time.StreamEncoder} extCoders = []ext.StreamEncoder{time.StreamEncoder} ) // AddExtEncoder adds encoders for extension types. func AddExtEncoder(f ext.StreamEncoder) { // ignore time if f.Type() == time.Encoder.Type() { return } _, ok := extCoderMap[f.Type()] if !ok { extCoderMap[f.Type()] = f updateExtCoders() } } // RemoveExtEncoder removes encoders for extension types. func RemoveExtEncoder(f ext.StreamEncoder) { // ignore time if f.Type() == time.Encoder.Type() { return } _, ok := extCoderMap[f.Type()] if ok { delete(extCoderMap, f.Type()) updateExtCoders() } } func updateExtCoders() { extCoders = make([]ext.StreamEncoder, len(extCoderMap)) i := 0 for k := range extCoderMap { extCoders[i] = extCoderMap[k] i++ } } ================================================ FILE: internal/stream/encoding/ext_test.go ================================================ package encoding import ( "testing" tu "github.com/shamaton/msgpack/v3/internal/common/testutil" "github.com/shamaton/msgpack/v3/time" ) func Test_AddExtEncoder(t *testing.T) { t.Run("ignore", func(t *testing.T) { AddExtEncoder(time.StreamEncoder) tu.Equal(t, len(extCoders), 1) }) } func Test_RemoveExtEncoder(t *testing.T) { t.Run("ignore", func(t *testing.T) { RemoveExtEncoder(time.StreamEncoder) tu.Equal(t, len(extCoders), 1) }) } ================================================ FILE: internal/stream/encoding/float.go ================================================ package encoding import ( "math" "github.com/shamaton/msgpack/v3/def" ) func (e *encoder) writeFloat32(v float64) error { if err := e.setByte1Int(def.Float32); err != nil { return err } if err := e.setByte4Uint64(uint64(math.Float32bits(float32(v)))); err != nil { return err } return nil } func (e *encoder) writeFloat64(v float64) error { if err := e.setByte1Int(def.Float64); err != nil { return err } if err := e.setByte8Uint64(math.Float64bits(v)); err != nil { return err } return nil } ================================================ FILE: internal/stream/encoding/float_test.go ================================================ package encoding import ( "testing" "github.com/shamaton/msgpack/v3/def" ) func Test_writeFloat32(t *testing.T) { method := func(e *encoder) func(float64) error { return e.writeFloat32 } v := 1.23 testcases := AsXXXTestCases[float64]{ { Name: "error.def", Value: v, BufferSize: 1, PreWriteSize: 1, Method: method, }, { Name: "error.value", Value: v, BufferSize: 2, PreWriteSize: 1, Contains: []byte{def.Float32}, Method: method, }, { Name: "ok", Value: v, Expected: []byte{ def.Float32, 0x3f, 0x9d, 0x70, 0xa4, }, BufferSize: 1, Method: method, }, } testcases.Run(t) } func Test_writeFloat64(t *testing.T) { method := func(e *encoder) func(float64) error { return e.writeFloat64 } v := 1.23 testcases := AsXXXTestCases[float64]{ { Name: "error.def", Value: v, BufferSize: 1, PreWriteSize: 1, Method: method, }, { Name: "error.value", Value: v, BufferSize: 2, PreWriteSize: 1, Contains: []byte{def.Float64}, Method: method, }, { Name: "ok", Value: v, Expected: []byte{ def.Float64, 0x3f, 0xf3, 0xae, 0x14, 0x7a, 0xe1, 0x47, 0xae, }, BufferSize: 1, Method: method, }, } testcases.Run(t) } ================================================ FILE: internal/stream/encoding/int.go ================================================ package encoding import ( "math" "github.com/shamaton/msgpack/v3/def" ) func (e *encoder) isNegativeFixInt64(v int64) bool { return def.NegativeFixintMin <= v && v <= def.NegativeFixintMax } func (e *encoder) writeInt(v int64) error { if v >= 0 { if err := e.writeUint(uint64(v)); err != nil { return err } } else if e.isNegativeFixInt64(v) { if err := e.setByte1Int64(v); err != nil { return err } } else if v >= math.MinInt8 { if err := e.setByte1Int(def.Int8); err != nil { return err } if err := e.setByte1Int64(v); err != nil { return err } } else if v >= math.MinInt16 { if err := e.setByte1Int(def.Int16); err != nil { return err } if err := e.setByte2Int64(v); err != nil { return err } } else if v >= math.MinInt32 { if err := e.setByte1Int(def.Int32); err != nil { return err } if err := e.setByte4Int64(v); err != nil { return err } } else { if err := e.setByte1Int(def.Int64); err != nil { return err } if err := e.setByte8Int64(v); err != nil { return err } } return nil } ================================================ FILE: internal/stream/encoding/int_test.go ================================================ package encoding import ( "math" "testing" "github.com/shamaton/msgpack/v3/def" ) func Test_asInt(t *testing.T) { method := func(e *encoder) func(int64) error { return e.writeInt } testcases := AsXXXTestCases[int64]{ { Name: "Uint.error", Value: math.MaxInt32, BufferSize: 1, PreWriteSize: 1, Method: method, }, { Name: "Uint.ok", Value: math.MaxInt32, Expected: []byte{def.Uint32, 0x7f, 0xff, 0xff, 0xff}, BufferSize: 1, Method: method, }, { Name: "NegativeFix.error", Value: -1, BufferSize: 1, PreWriteSize: 1, Method: method, }, { Name: "NegativeFix.ok", Value: -1, Expected: []byte{0xff}, BufferSize: 1, Method: method, }, { Name: "Int8.error.def", Value: math.MinInt8, BufferSize: 1, PreWriteSize: 1, Method: method, }, { Name: "Int8.error.value", Value: math.MinInt8, BufferSize: 2, PreWriteSize: 1, Contains: []byte{def.Int8}, Method: method, }, { Name: "Int8.ok", Value: math.MinInt8, Expected: []byte{def.Int8, 0x80}, BufferSize: 1, Method: method, }, { Name: "Int16.error.def", Value: math.MinInt16, BufferSize: 1, PreWriteSize: 1, Method: method, }, { Name: "Int16.error.value", Value: math.MinInt16, BufferSize: 3, PreWriteSize: 1, Contains: []byte{def.Int16}, Method: method, }, { Name: "Int16.ok", Value: math.MinInt16, Expected: []byte{def.Int16, 0x80, 0x00}, BufferSize: 1, Method: method, }, { Name: "Int32.error.def", Value: math.MinInt32, BufferSize: 1, PreWriteSize: 1, Method: method, }, { Name: "Int32.error.value", Value: math.MinInt32, BufferSize: 5, PreWriteSize: 1, Contains: []byte{def.Int32}, Method: method, }, { Name: "Int32.ok", Value: math.MinInt32, Expected: []byte{def.Int32, 0x80, 0x00, 0x00, 0x00}, BufferSize: 1, Method: method, }, { Name: "Int64.error.def", Value: math.MinInt64, BufferSize: 1, PreWriteSize: 1, Method: method, }, { Name: "Int64.error.value", Value: math.MinInt64, BufferSize: 9, PreWriteSize: 1, Contains: []byte{def.Int64}, Method: method, }, { Name: "Int64.ok", Value: math.MinInt64, Expected: []byte{def.Int64, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, BufferSize: 1, Method: method, }, } testcases.Run(t) } ================================================ FILE: internal/stream/encoding/map.go ================================================ package encoding import ( "math" "reflect" "github.com/shamaton/msgpack/v3/def" ) func (e *encoder) writeMapLength(l int) error { // format if l <= 0x0f { if err := e.setByte1Int(def.FixMap + l); err != nil { return err } } else if l <= math.MaxUint16 { if err := e.setByte1Int(def.Map16); err != nil { return err } if err := e.setByte2Int(l); err != nil { return err } } else if uint(l) <= math.MaxUint32 { if err := e.setByte1Int(def.Map32); err != nil { return err } if err := e.setByte4Int(l); err != nil { return err } } return nil } func (e *encoder) writeFixedMap(rv reflect.Value) (bool, error) { switch m := rv.Interface().(type) { case map[string]int: for k, v := range m { if err := e.writeString(k); err != nil { return false, err } if err := e.writeInt(int64(v)); err != nil { return false, err } } return true, nil case map[string]uint: for k, v := range m { if err := e.writeString(k); err != nil { return false, err } if err := e.writeUint(uint64(v)); err != nil { return false, err } } return true, nil case map[string]float32: for k, v := range m { if err := e.writeString(k); err != nil { return false, err } if err := e.writeFloat32(float64(v)); err != nil { return false, err } } return true, nil case map[string]float64: for k, v := range m { if err := e.writeString(k); err != nil { return false, err } if err := e.writeFloat64(v); err != nil { return false, err } } return true, nil case map[string]bool: for k, v := range m { if err := e.writeString(k); err != nil { return false, err } if err := e.writeBool(v); err != nil { return false, err } } return true, nil case map[string]string: for k, v := range m { if err := e.writeString(k); err != nil { return false, err } if err := e.writeString(v); err != nil { return false, err } } return true, nil case map[string]int8: for k, v := range m { if err := e.writeString(k); err != nil { return false, err } if err := e.writeInt(int64(v)); err != nil { return false, err } } return true, nil case map[string]int16: for k, v := range m { if err := e.writeString(k); err != nil { return false, err } if err := e.writeInt(int64(v)); err != nil { return false, err } } return true, nil case map[string]int32: for k, v := range m { if err := e.writeString(k); err != nil { return false, err } if err := e.writeInt(int64(v)); err != nil { return false, err } } return true, nil case map[string]int64: for k, v := range m { if err := e.writeString(k); err != nil { return false, err } if err := e.writeInt(int64(v)); err != nil { return false, err } } return true, nil case map[string]uint8: for k, v := range m { if err := e.writeString(k); err != nil { return false, err } if err := e.writeUint(uint64(v)); err != nil { return false, err } } return true, nil case map[string]uint16: for k, v := range m { if err := e.writeString(k); err != nil { return false, err } if err := e.writeUint(uint64(v)); err != nil { return false, err } } return true, nil case map[string]uint32: for k, v := range m { if err := e.writeString(k); err != nil { return false, err } if err := e.writeUint(uint64(v)); err != nil { return false, err } } return true, nil case map[string]uint64: for k, v := range m { if err := e.writeString(k); err != nil { return false, err } if err := e.writeUint(uint64(v)); err != nil { return false, err } } return true, nil case map[int]string: for k, v := range m { if err := e.writeInt(int64(k)); err != nil { return false, err } if err := e.writeString(v); err != nil { return false, err } } return true, nil case map[int]bool: for k, v := range m { if err := e.writeInt(int64(k)); err != nil { return false, err } if err := e.writeBool(v); err != nil { return false, err } } return true, nil case map[uint]string: for k, v := range m { if err := e.writeUint(uint64(k)); err != nil { return false, err } if err := e.writeString(v); err != nil { return false, err } } return true, nil case map[uint]bool: for k, v := range m { if err := e.writeUint(uint64(k)); err != nil { return false, err } if err := e.writeBool(v); err != nil { return false, err } } return true, nil case map[float32]string: for k, v := range m { if err := e.writeFloat32(float64(k)); err != nil { return false, err } if err := e.writeString(v); err != nil { return false, err } } return true, nil case map[float32]bool: for k, v := range m { if err := e.writeFloat32(float64(k)); err != nil { return false, err } if err := e.writeBool(v); err != nil { return false, err } } return true, nil case map[float64]string: for k, v := range m { if err := e.writeFloat64(k); err != nil { return false, err } if err := e.writeString(v); err != nil { return false, err } } return true, nil case map[float64]bool: for k, v := range m { if err := e.writeFloat64(k); err != nil { return false, err } if err := e.writeBool(v); err != nil { return false, err } } return true, nil case map[int8]string: for k, v := range m { if err := e.writeInt(int64(k)); err != nil { return false, err } if err := e.writeString(v); err != nil { return false, err } } return true, nil case map[int8]bool: for k, v := range m { if err := e.writeInt(int64(k)); err != nil { return false, err } if err := e.writeBool(v); err != nil { return false, err } } return true, nil case map[int16]string: for k, v := range m { if err := e.writeInt(int64(k)); err != nil { return false, err } if err := e.writeString(v); err != nil { return false, err } } return true, nil case map[int16]bool: for k, v := range m { if err := e.writeInt(int64(k)); err != nil { return false, err } if err := e.writeBool(v); err != nil { return false, err } } return true, nil case map[int32]string: for k, v := range m { if err := e.writeInt(int64(k)); err != nil { return false, err } if err := e.writeString(v); err != nil { return false, err } } return true, nil case map[int32]bool: for k, v := range m { if err := e.writeInt(int64(k)); err != nil { return false, err } if err := e.writeBool(v); err != nil { return false, err } } return true, nil case map[int64]string: for k, v := range m { if err := e.writeInt(k); err != nil { return false, err } if err := e.writeString(v); err != nil { return false, err } } return true, nil case map[int64]bool: for k, v := range m { if err := e.writeInt(k); err != nil { return false, err } if err := e.writeBool(v); err != nil { return false, err } } return true, nil case map[uint8]string: for k, v := range m { if err := e.writeUint(uint64(k)); err != nil { return false, err } if err := e.writeString(v); err != nil { return false, err } } return true, nil case map[uint8]bool: for k, v := range m { if err := e.writeUint(uint64(k)); err != nil { return false, err } if err := e.writeBool(v); err != nil { return false, err } } return true, nil case map[uint16]string: for k, v := range m { if err := e.writeUint(uint64(k)); err != nil { return false, err } if err := e.writeString(v); err != nil { return false, err } } return true, nil case map[uint16]bool: for k, v := range m { if err := e.writeUint(uint64(k)); err != nil { return false, err } if err := e.writeBool(v); err != nil { return false, err } } return true, nil case map[uint32]string: for k, v := range m { if err := e.writeUint(uint64(k)); err != nil { return false, err } if err := e.writeString(v); err != nil { return false, err } } return true, nil case map[uint32]bool: for k, v := range m { if err := e.writeUint(uint64(k)); err != nil { return false, err } if err := e.writeBool(v); err != nil { return false, err } } return true, nil case map[uint64]string: for k, v := range m { if err := e.writeUint(k); err != nil { return false, err } if err := e.writeString(v); err != nil { return false, err } } return true, nil case map[uint64]bool: for k, v := range m { if err := e.writeUint(k); err != nil { return false, err } if err := e.writeBool(v); err != nil { return false, err } } return true, nil } return false, nil } ================================================ FILE: internal/stream/encoding/map_test.go ================================================ package encoding import ( "math" "reflect" "testing" "github.com/shamaton/msgpack/v3/def" ) func Test_writeMapLength(t *testing.T) { method := func(e *encoder) func(int) error { return e.writeMapLength } testcases := AsXXXTestCases[int]{ { Name: "FixMap.error", Value: 5, BufferSize: 1, PreWriteSize: 1, Method: method, }, { Name: "FixMap.ok", Value: 5, Expected: []byte{def.FixMap + 5}, BufferSize: 1, Method: method, }, { Name: "Map16.error.def", Value: 32, BufferSize: 1, PreWriteSize: 1, Method: method, }, { Name: "Map16.error.value", Value: 32, BufferSize: 3, PreWriteSize: 1, Contains: []byte{def.Map16}, Method: method, }, { Name: "Map16.ok", Value: 32, Expected: []byte{def.Map16, 0x00, 0x20}, BufferSize: 1, Method: method, }, { Name: "Map32.error.def", Value: 65536, BufferSize: 1, PreWriteSize: 1, Method: method, }, { Name: "Map32.error.value", Value: 65536, BufferSize: 3, PreWriteSize: 1, Contains: []byte{def.Map32}, Method: method, }, { Name: "Map32.ok", Value: 65536, Expected: []byte{def.Map32, 0x00, 0x01, 0x00, 0x00}, BufferSize: 1, Method: method, }, } testcases.Run(t) } func Test_writeFixedMap(t *testing.T) { method := func(e *encoder) func(reflect.Value) (bool, error) { return e.writeFixedMap } t.Run("map[string]int", func(t *testing.T) { value := map[string]int{"a": -1} testcases := AsXXXTestCases[map[string]int]{ { Name: "error.key", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "error.value", Value: value, BufferSize: 3, PreWriteSize: 1, Contains: []byte{def.FixStr + 1, 0x61}, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{def.FixStr + 1, 0x61, 0xff}, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("map[string]uint", func(t *testing.T) { value := map[string]uint{"a": 1} testcases := AsXXXTestCases[map[string]uint]{ { Name: "error.key", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "error.value", Value: value, BufferSize: 3, PreWriteSize: 1, Contains: []byte{def.FixStr + 1, 0x61}, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{def.FixStr + 1, 0x61, 0x01}, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("map[string]float32", func(t *testing.T) { value := map[string]float32{"a": 1} testcases := AsXXXTestCases[map[string]float32]{ { Name: "error.key", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "error.value", Value: value, BufferSize: 3, PreWriteSize: 1, Contains: []byte{def.FixStr + 1, 0x61}, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{def.FixStr + 1, 0x61, def.Float32, 0x3f, 0x80, 0x00, 0x00}, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("map[string]float64", func(t *testing.T) { value := map[string]float64{"a": 1} testcases := AsXXXTestCases[map[string]float64]{ { Name: "error.key", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "error.value", Value: value, BufferSize: 3, PreWriteSize: 1, Contains: []byte{def.FixStr + 1, 0x61}, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{ def.FixStr + 1, 0x61, def.Float64, 0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("map[string]bool", func(t *testing.T) { value := map[string]bool{"a": true} testcases := AsXXXTestCases[map[string]bool]{ { Name: "error.key", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "error.value", Value: value, BufferSize: 3, PreWriteSize: 1, Contains: []byte{def.FixStr + 1, 0x61}, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{def.FixStr + 1, 0x61, def.True}, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("map[string]string", func(t *testing.T) { value := map[string]string{"a": "b"} testcases := AsXXXTestCases[map[string]string]{ { Name: "error.key", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "error.value", Value: value, BufferSize: 3, PreWriteSize: 1, Contains: []byte{def.FixStr + 1, 0x61}, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{def.FixStr + 1, 0x61, def.FixStr + 1, 0x62}, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("map[string]int8", func(t *testing.T) { value := map[string]int8{"a": math.MinInt8} testcases := AsXXXTestCases[map[string]int8]{ { Name: "error.key", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "error.value", Value: value, BufferSize: 3, PreWriteSize: 1, Contains: []byte{def.FixStr + 1, 0x61}, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{ def.FixStr + 1, 0x61, def.Int8, 0x80, }, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("map[string]int16", func(t *testing.T) { value := map[string]int16{"a": math.MinInt16} testcases := AsXXXTestCases[map[string]int16]{ { Name: "error.key", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "error.value", Value: value, BufferSize: 3, PreWriteSize: 1, Contains: []byte{def.FixStr + 1, 0x61}, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{ def.FixStr + 1, 0x61, def.Int16, 0x80, 0x00, }, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("map[string]int32", func(t *testing.T) { value := map[string]int32{"a": math.MinInt32} testcases := AsXXXTestCases[map[string]int32]{ { Name: "error.key", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "error.value", Value: value, BufferSize: 3, PreWriteSize: 1, Contains: []byte{def.FixStr + 1, 0x61}, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{ def.FixStr + 1, 0x61, def.Int32, 0x80, 0x00, 0x00, 0x00, }, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("map[string]int64", func(t *testing.T) { value := map[string]int64{"a": math.MinInt64} testcases := AsXXXTestCases[map[string]int64]{ { Name: "error.key", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "error.value", Value: value, BufferSize: 3, PreWriteSize: 1, Contains: []byte{def.FixStr + 1, 0x61}, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{ def.FixStr + 1, 0x61, def.Int64, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("map[string]uint8", func(t *testing.T) { value := map[string]uint8{"a": math.MaxUint8} testcases := AsXXXTestCases[map[string]uint8]{ { Name: "error.key", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "error.value", Value: value, BufferSize: 3, PreWriteSize: 1, Contains: []byte{def.FixStr + 1, 0x61}, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{ def.FixStr + 1, 0x61, def.Uint8, 0xff, }, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("map[string]uint16", func(t *testing.T) { value := map[string]uint16{"a": math.MaxUint16} testcases := AsXXXTestCases[map[string]uint16]{ { Name: "error.key", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "error.value", Value: value, BufferSize: 3, PreWriteSize: 1, Contains: []byte{def.FixStr + 1, 0x61}, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{ def.FixStr + 1, 0x61, def.Uint16, 0xff, 0xff, }, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("map[string]uint32", func(t *testing.T) { value := map[string]uint32{"a": math.MaxUint32} testcases := AsXXXTestCases[map[string]uint32]{ { Name: "error.key", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "error.value", Value: value, BufferSize: 3, PreWriteSize: 1, Contains: []byte{def.FixStr + 1, 0x61}, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{ def.FixStr + 1, 0x61, def.Uint32, 0xff, 0xff, 0xff, 0xff, }, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("map[string]uint64", func(t *testing.T) { value := map[string]uint64{"a": math.MaxUint64} testcases := AsXXXTestCases[map[string]uint64]{ { Name: "error.key", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "error.value", Value: value, BufferSize: 3, PreWriteSize: 1, Contains: []byte{def.FixStr + 1, 0x61}, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{ def.FixStr + 1, 0x61, def.Uint64, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, }, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("map[int]string", func(t *testing.T) { value := map[int]string{math.MinInt8: "a"} testcases := AsXXXTestCases[map[int]string]{ { Name: "error.key", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "error.value", Value: value, BufferSize: 3, PreWriteSize: 1, Contains: []byte{def.Int8, 0x80}, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{def.Int8, 0x80, def.FixStr + 1, 0x61}, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("map[int]bool", func(t *testing.T) { value := map[int]bool{math.MinInt8: true} testcases := AsXXXTestCases[map[int]bool]{ { Name: "error.key", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "error.value", Value: value, BufferSize: 3, PreWriteSize: 1, Contains: []byte{def.Int8, 0x80}, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{def.Int8, 0x80, def.True}, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("map[uint]string", func(t *testing.T) { value := map[uint]string{math.MaxUint8: "a"} testcases := AsXXXTestCases[map[uint]string]{ { Name: "error.key", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "error.value", Value: value, BufferSize: 3, PreWriteSize: 1, Contains: []byte{def.Uint8, 0xff}, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{def.Uint8, 0xff, def.FixStr + 1, 0x61}, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("map[uint]bool", func(t *testing.T) { value := map[uint]bool{math.MaxUint8: true} testcases := AsXXXTestCases[map[uint]bool]{ { Name: "error.key", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "error.value", Value: value, BufferSize: 3, PreWriteSize: 1, Contains: []byte{def.Uint8, 0xff}, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{def.Uint8, 0xff, def.True}, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("map[float32]string", func(t *testing.T) { value := map[float32]string{1: "a"} testcases := AsXXXTestCases[map[float32]string]{ { Name: "error.key", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "error.value", Value: value, BufferSize: 6, PreWriteSize: 1, Contains: []byte{def.Float32, 0x3f, 0x80, 0x00, 0x00}, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{ def.Float32, 0x3f, 0x80, 0x00, 0x00, def.FixStr + 1, 0x61, }, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("map[float32]bool", func(t *testing.T) { value := map[float32]bool{1: true} testcases := AsXXXTestCases[map[float32]bool]{ { Name: "error.key", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "error.value", Value: value, BufferSize: 6, PreWriteSize: 1, Contains: []byte{def.Float32, 0x3f, 0x80, 0x00, 0x00}, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{ def.Float32, 0x3f, 0x80, 0x00, 0x00, def.True, }, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("map[float64]string", func(t *testing.T) { value := map[float64]string{1: "a"} testcases := AsXXXTestCases[map[float64]string]{ { Name: "error.key", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "error.value", Value: value, BufferSize: 10, PreWriteSize: 1, Contains: []byte{def.Float64, 0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{ def.Float64, 0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, def.FixStr + 1, 0x61, }, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("map[float64]bool", func(t *testing.T) { value := map[float64]bool{1: true} testcases := AsXXXTestCases[map[float64]bool]{ { Name: "error.key", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "error.value", Value: value, BufferSize: 10, PreWriteSize: 1, Contains: []byte{def.Float64, 0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{ def.Float64, 0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, def.True, }, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("map[int8]string", func(t *testing.T) { value := map[int8]string{math.MinInt8: "a"} testcases := AsXXXTestCases[map[int8]string]{ { Name: "error.key", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "error.value", Value: value, BufferSize: 3, PreWriteSize: 1, Contains: []byte{def.Int8, 0x80}, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{ def.Int8, 0x80, def.FixStr + 1, 0x61, }, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("map[int8]bool", func(t *testing.T) { value := map[int8]bool{math.MinInt8: true} testcases := AsXXXTestCases[map[int8]bool]{ { Name: "error.key", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "error.value", Value: value, BufferSize: 3, PreWriteSize: 1, Contains: []byte{def.Int8, 0x80}, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{ def.Int8, 0x80, def.True, }, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("map[int16]string", func(t *testing.T) { value := map[int16]string{math.MinInt16: "a"} testcases := AsXXXTestCases[map[int16]string]{ { Name: "error.key", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "error.value", Value: value, BufferSize: 4, PreWriteSize: 1, Contains: []byte{def.Int16, 0x80, 0x00}, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{ def.Int16, 0x80, 0x00, def.FixStr + 1, 0x61, }, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("map[int16]bool", func(t *testing.T) { value := map[int16]bool{math.MinInt16: true} testcases := AsXXXTestCases[map[int16]bool]{ { Name: "error.key", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "error.value", Value: value, BufferSize: 4, PreWriteSize: 1, Contains: []byte{def.Int16, 0x80, 0x00}, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{ def.Int16, 0x80, 0x00, def.True, }, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("map[int32]string", func(t *testing.T) { value := map[int32]string{math.MinInt32: "a"} testcases := AsXXXTestCases[map[int32]string]{ { Name: "error.key", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "error.value", Value: value, BufferSize: 6, PreWriteSize: 1, Contains: []byte{def.Int32, 0x80, 0x00, 0x00, 0x00}, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{ def.Int32, 0x80, 0x00, 0x00, 0x00, def.FixStr + 1, 0x61, }, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("map[int32]bool", func(t *testing.T) { value := map[int32]bool{math.MinInt32: true} testcases := AsXXXTestCases[map[int32]bool]{ { Name: "error.key", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "error.value", Value: value, BufferSize: 6, PreWriteSize: 1, Contains: []byte{def.Int32, 0x80, 0x00, 0x00, 0x00}, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{ def.Int32, 0x80, 0x00, 0x00, 0x00, def.True, }, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("map[int64]string", func(t *testing.T) { value := map[int64]string{math.MinInt64: "a"} testcases := AsXXXTestCases[map[int64]string]{ { Name: "error.key", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "error.value", Value: value, BufferSize: 10, PreWriteSize: 1, Contains: []byte{def.Int64, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{ def.Int64, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, def.FixStr + 1, 0x61, }, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("map[int64]bool", func(t *testing.T) { value := map[int64]bool{math.MinInt64: true} testcases := AsXXXTestCases[map[int64]bool]{ { Name: "error.key", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "error.value", Value: value, BufferSize: 10, PreWriteSize: 1, Contains: []byte{def.Int64, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{ def.Int64, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, def.True, }, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("map[uint8]string", func(t *testing.T) { value := map[uint8]string{math.MaxUint8: "a"} testcases := AsXXXTestCases[map[uint8]string]{ { Name: "error.key", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "error.value", Value: value, BufferSize: 3, PreWriteSize: 1, Contains: []byte{def.Uint8, 0xff}, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{ def.Uint8, 0xff, def.FixStr + 1, 0x61, }, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("map[uint8]bool", func(t *testing.T) { value := map[uint8]bool{math.MaxUint8: true} testcases := AsXXXTestCases[map[uint8]bool]{ { Name: "error.key", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "error.value", Value: value, BufferSize: 3, PreWriteSize: 1, Contains: []byte{def.Uint8, 0xff}, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{ def.Uint8, 0xff, def.True, }, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("map[uint16]string", func(t *testing.T) { value := map[uint16]string{math.MaxUint16: "a"} testcases := AsXXXTestCases[map[uint16]string]{ { Name: "error.key", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "error.value", Value: value, BufferSize: 4, PreWriteSize: 1, Contains: []byte{def.Uint16, 0xff, 0xff}, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{ def.Uint16, 0xff, 0xff, def.FixStr + 1, 0x61, }, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("map[uint16]bool", func(t *testing.T) { value := map[uint16]bool{math.MaxUint16: true} testcases := AsXXXTestCases[map[uint16]bool]{ { Name: "error.key", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "error.value", Value: value, BufferSize: 4, PreWriteSize: 1, Contains: []byte{def.Uint16, 0xff, 0xff}, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{ def.Uint16, 0xff, 0xff, def.True, }, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("map[uint32]string", func(t *testing.T) { value := map[uint32]string{math.MaxUint32: "a"} testcases := AsXXXTestCases[map[uint32]string]{ { Name: "error.key", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "error.value", Value: value, BufferSize: 6, PreWriteSize: 1, Contains: []byte{def.Uint32, 0xff, 0xff, 0xff, 0xff}, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{ def.Uint32, 0xff, 0xff, 0xff, 0xff, def.FixStr + 1, 0x61, }, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("map[uint32]bool", func(t *testing.T) { value := map[uint32]bool{math.MaxUint32: true} testcases := AsXXXTestCases[map[uint32]bool]{ { Name: "error.key", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "error.value", Value: value, BufferSize: 6, PreWriteSize: 1, Contains: []byte{def.Uint32, 0xff, 0xff, 0xff, 0xff}, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{ def.Uint32, 0xff, 0xff, 0xff, 0xff, def.True, }, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("map[uint64]string", func(t *testing.T) { value := map[uint64]string{math.MaxUint64: "a"} testcases := AsXXXTestCases[map[uint64]string]{ { Name: "error.key", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "error.value", Value: value, BufferSize: 10, PreWriteSize: 1, Contains: []byte{def.Uint64, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{ def.Uint64, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, def.FixStr + 1, 0x61, }, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("map[uint64]bool", func(t *testing.T) { value := map[uint64]bool{math.MaxUint64: true} testcases := AsXXXTestCases[map[uint64]bool]{ { Name: "error.key", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "error.value", Value: value, BufferSize: 10, PreWriteSize: 1, Contains: []byte{def.Uint64, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{ def.Uint64, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, def.True, }, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) } ================================================ FILE: internal/stream/encoding/nil.go ================================================ package encoding import "github.com/shamaton/msgpack/v3/def" func (e *encoder) writeNil() error { return e.setByte1Int(def.Nil) } ================================================ FILE: internal/stream/encoding/set.go ================================================ package encoding func (e *encoder) setByte1Int64(value int64) error { return e.buf.Write(e.w, byte(value)) } func (e *encoder) setByte2Int64(value int64) error { return e.buf.Write(e.w, byte(value>>8), byte(value), ) } func (e *encoder) setByte4Int64(value int64) error { return e.buf.Write(e.w, byte(value>>24), byte(value>>16), byte(value>>8), byte(value), ) } func (e *encoder) setByte8Int64(value int64) error { return e.buf.Write(e.w, byte(value>>56), byte(value>>48), byte(value>>40), byte(value>>32), byte(value>>24), byte(value>>16), byte(value>>8), byte(value), ) } func (e *encoder) setByte1Uint64(value uint64) error { return e.buf.Write(e.w, byte(value)) } func (e *encoder) setByte2Uint64(value uint64) error { return e.buf.Write(e.w, byte(value>>8), byte(value), ) } func (e *encoder) setByte4Uint64(value uint64) error { return e.buf.Write(e.w, byte(value>>24), byte(value>>16), byte(value>>8), byte(value), ) } func (e *encoder) setByte8Uint64(value uint64) error { return e.buf.Write(e.w, byte(value>>56), byte(value>>48), byte(value>>40), byte(value>>32), byte(value>>24), byte(value>>16), byte(value>>8), byte(value), ) } func (e *encoder) setByte1Int(value int) error { return e.buf.Write(e.w, byte(value), ) } func (e *encoder) setByte2Int(value int) error { return e.buf.Write(e.w, byte(value>>8), byte(value), ) } func (e *encoder) setByte4Int(value int) error { return e.buf.Write(e.w, byte(value>>24), byte(value>>16), byte(value>>8), byte(value), ) } func (e *encoder) setBytes(bs []byte) error { return e.buf.Write(e.w, bs...) } ================================================ FILE: internal/stream/encoding/slice.go ================================================ package encoding import ( "math" "reflect" "github.com/shamaton/msgpack/v3/def" ) func (e *encoder) writeSliceLength(l int) error { // format size if l <= 0x0f { if err := e.setByte1Int(def.FixArray + l); err != nil { return err } } else if l <= math.MaxUint16 { if err := e.setByte1Int(def.Array16); err != nil { return err } if err := e.setByte2Int(l); err != nil { return err } } else if uint(l) <= math.MaxUint32 { if err := e.setByte1Int(def.Array32); err != nil { return err } if err := e.setByte4Int(l); err != nil { return err } } return nil } func (e *encoder) writeFixedSlice(rv reflect.Value) (bool, error) { switch sli := rv.Interface().(type) { case []int: for _, v := range sli { if err := e.writeInt(int64(v)); err != nil { return false, err } } return true, nil case []uint: for _, v := range sli { if err := e.writeUint(uint64(v)); err != nil { return false, err } } return true, nil case []string: for _, v := range sli { if err := e.writeString(v); err != nil { return false, err } } return true, nil case []float32: for _, v := range sli { if err := e.writeFloat32(float64(v)); err != nil { return false, err } } return true, nil case []float64: for _, v := range sli { if err := e.writeFloat64(float64(v)); err != nil { return false, err } } return true, nil case []bool: for _, v := range sli { if err := e.writeBool(v); err != nil { return false, err } } return true, nil case []int8: for _, v := range sli { if err := e.writeInt(int64(v)); err != nil { return false, err } } return true, nil case []int16: for _, v := range sli { if err := e.writeInt(int64(v)); err != nil { return false, err } } return true, nil case []int32: for _, v := range sli { if err := e.writeInt(int64(v)); err != nil { return false, err } } return true, nil case []int64: for _, v := range sli { if err := e.writeInt(v); err != nil { return false, err } } return true, nil case []uint8: for _, v := range sli { if err := e.writeUint(uint64(v)); err != nil { return false, err } } return true, nil case []uint16: for _, v := range sli { if err := e.writeUint(uint64(v)); err != nil { return false, err } } return true, nil case []uint32: for _, v := range sli { if err := e.writeUint(uint64(v)); err != nil { return false, err } } return true, nil case []uint64: for _, v := range sli { if err := e.writeUint(v); err != nil { return false, err } } return true, nil } return false, nil } ================================================ FILE: internal/stream/encoding/slice_test.go ================================================ package encoding import ( "math" "reflect" "testing" "github.com/shamaton/msgpack/v3/def" ) func Test_writeSliceLength(t *testing.T) { method := func(e *encoder) func(int) error { return e.writeSliceLength } testcases := AsXXXTestCases[int]{ { Name: "FixArray.error", Value: 5, BufferSize: 1, PreWriteSize: 1, Method: method, }, { Name: "FixArray.ok", Value: 5, Expected: []byte{def.FixArray + 5}, BufferSize: 1, Method: method, }, { Name: "Array16.error.def", Value: 32, BufferSize: 1, PreWriteSize: 1, Method: method, }, { Name: "Array16.error.value", Value: 32, BufferSize: 3, PreWriteSize: 1, Contains: []byte{def.Array16}, Method: method, }, { Name: "Array16.ok", Value: 32, Expected: []byte{def.Array16, 0x00, 0x20}, BufferSize: 1, Method: method, }, { Name: "Array32.error.def", Value: 65536, BufferSize: 1, PreWriteSize: 1, Method: method, }, { Name: "Array32.error.value", Value: 65536, BufferSize: 3, PreWriteSize: 1, Contains: []byte{def.Array32}, Method: method, }, { Name: "Array32.ok", Value: 65536, Expected: []byte{def.Array32, 0x00, 0x01, 0x00, 0x00}, BufferSize: 1, Method: method, }, } testcases.Run(t) } func Test_writeFixedSlice(t *testing.T) { method := func(e *encoder) func(reflect.Value) (bool, error) { return e.writeFixedSlice } t.Run("[]int", func(t *testing.T) { value := []int{-1, -2, -3} testcases := AsXXXTestCases[[]int]{ { Name: "error", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{0xff, 0xfe, 0xfd}, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("[]uint", func(t *testing.T) { value := []uint{1, 2, 3} testcases := AsXXXTestCases[[]uint]{ { Name: "error", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{0x01, 0x02, 0x03}, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("[]string", func(t *testing.T) { value := []string{"a", "b", "c"} testcases := AsXXXTestCases[[]string]{ { Name: "error", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{0xa1, 0x61, 0xa1, 0x62, 0xa1, 0x63}, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("[]float32", func(t *testing.T) { value := []float32{4, 5, 6} testcases := AsXXXTestCases[[]float32]{ { Name: "error", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{def.Float32, 0x40, 0x80, 0x00, 0x00, def.Float32, 0x40, 0xa0, 0x00, 0x00, def.Float32, 0x40, 0xc0, 0x00, 0x00}, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("[]float64", func(t *testing.T) { value := []float64{4, 5, 6} testcases := AsXXXTestCases[[]float64]{ { Name: "error", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{ def.Float64, 0x40, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, def.Float64, 0x40, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, def.Float64, 0x40, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("[]bool", func(t *testing.T) { value := []bool{true, false, true} testcases := AsXXXTestCases[[]bool]{ { Name: "error", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{def.True, def.False, def.True}, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("[]int8", func(t *testing.T) { value := []int8{math.MinInt8, math.MinInt8 + 1, math.MinInt8 + 2} testcases := AsXXXTestCases[[]int8]{ { Name: "error", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{ def.Int8, 0x80, def.Int8, 0x81, def.Int8, 0x82, }, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("[]int16", func(t *testing.T) { value := []int16{math.MinInt16, math.MinInt16 + 1, math.MinInt16 + 2} testcases := AsXXXTestCases[[]int16]{ { Name: "error", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{ def.Int16, 0x80, 0x00, def.Int16, 0x80, 0x01, def.Int16, 0x80, 0x02, }, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("[]int32", func(t *testing.T) { value := []int32{math.MinInt32, math.MinInt32 + 1, math.MinInt32 + 2} testcases := AsXXXTestCases[[]int32]{ { Name: "error", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{ def.Int32, 0x80, 0x00, 0x00, 0x00, def.Int32, 0x80, 0x00, 0x00, 0x01, def.Int32, 0x80, 0x00, 0x00, 0x02, }, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("[]int64", func(t *testing.T) { value := []int64{math.MinInt64, math.MinInt64 + 1, math.MinInt64 + 2} testcases := AsXXXTestCases[[]int64]{ { Name: "error", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{ def.Int64, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, def.Int64, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, def.Int64, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, }, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("[]uint8", func(t *testing.T) { value := []uint8{math.MaxUint8, math.MaxUint8 - 1, math.MaxUint8 - 2} testcases := AsXXXTestCases[[]uint8]{ { Name: "error", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{ def.Uint8, 0xff, def.Uint8, 0xfe, def.Uint8, 0xfd, }, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("[]uint16", func(t *testing.T) { value := []uint16{math.MaxUint16, math.MaxUint16 - 1, math.MaxUint16 - 2} testcases := AsXXXTestCases[[]uint16]{ { Name: "error", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{ def.Uint16, 0xff, 0xff, def.Uint16, 0xff, 0xfe, def.Uint16, 0xff, 0xfd, }, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("[]uint32", func(t *testing.T) { value := []uint32{math.MaxUint32, math.MaxUint32 - 1, math.MaxUint32 - 2} testcases := AsXXXTestCases[[]uint32]{ { Name: "error", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{ def.Uint32, 0xff, 0xff, 0xff, 0xff, def.Uint32, 0xff, 0xff, 0xff, 0xfe, def.Uint32, 0xff, 0xff, 0xff, 0xfd, }, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) t.Run("[]uint64", func(t *testing.T) { value := []uint64{math.MaxUint64, math.MaxUint64 - 1, math.MaxUint64 - 2} testcases := AsXXXTestCases[[]uint64]{ { Name: "error", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForFixed: method, }, { Name: "ok", Value: value, Expected: []byte{ def.Uint64, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, def.Uint64, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, def.Uint64, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, }, BufferSize: 1, MethodForFixed: method, }, } testcases.Run(t) }) } ================================================ FILE: internal/stream/encoding/string.go ================================================ package encoding import ( "math" "unsafe" "github.com/shamaton/msgpack/v3/def" ) func (e *encoder) writeString(str string) error { // NOTE : unsafe strBytes := *(*[]byte)(unsafe.Pointer(&str)) l := len(strBytes) if l < 32 { if err := e.setByte1Int(def.FixStr + l); err != nil { return err } } else if l <= math.MaxUint8 { if err := e.setByte1Int(def.Str8); err != nil { return err } if err := e.setByte1Int(l); err != nil { return err } } else if l <= math.MaxUint16 { if err := e.setByte1Int(def.Str16); err != nil { return err } if err := e.setByte2Int(l); err != nil { return err } } else { if err := e.setByte1Int(def.Str32); err != nil { return err } if err := e.setByte4Int(l); err != nil { return err } } return e.setBytes(strBytes) } ================================================ FILE: internal/stream/encoding/string_test.go ================================================ package encoding import ( "math" "strings" "testing" "github.com/shamaton/msgpack/v3/def" ) func Test_writeString(t *testing.T) { method := func(e *encoder) func(string) error { return e.writeString } str8 := strings.Repeat("a", math.MaxUint8) str16 := strings.Repeat("a", math.MaxUint16) str32 := strings.Repeat("a", math.MaxUint16+1) testcases := AsXXXTestCases[string]{ { Name: "Str8.error.def", Value: str8, BufferSize: 1, PreWriteSize: 1, Method: method, }, { Name: "Str8.error.length", Value: str8, BufferSize: 2, PreWriteSize: 1, Contains: []byte{def.Str8}, Method: method, }, { Name: "Str8.error.string", Value: str8, BufferSize: 3, PreWriteSize: 1, Contains: []byte{def.Str8, 0xff}, Method: method, }, { Name: "Str8.ok", Value: str8, Expected: append( []byte{def.Str8, 0xff}, []byte(str8)..., ), BufferSize: 1, Method: method, }, { Name: "Str16.error.def", Value: str16, BufferSize: 1, PreWriteSize: 1, Method: method, }, { Name: "Str16.error.length", Value: str16, BufferSize: 2, PreWriteSize: 1, Contains: []byte{def.Str16}, Method: method, }, { Name: "Str16.error.string", Value: str16, BufferSize: 4, PreWriteSize: 1, Contains: []byte{def.Str16, 0xff, 0xff}, Method: method, }, { Name: "Str16.ok", Value: str16, Expected: append( []byte{def.Str16, 0xff, 0xff}, []byte(str16)..., ), BufferSize: 1, Method: method, }, { Name: "Str32.error.def", Value: str32, BufferSize: 1, PreWriteSize: 1, Method: method, }, { Name: "Str32.error.length", Value: str32, BufferSize: 2, PreWriteSize: 1, Contains: []byte{def.Str32}, Method: method, }, { Name: "Str32.error.string", Value: str32, BufferSize: 6, PreWriteSize: 1, Contains: []byte{def.Str32, 0x00, 0x01, 0x00, 0x00}, Method: method, }, { Name: "Str32.ok", Value: str32, Expected: append( []byte{def.Str32, 0x00, 0x01, 0x00, 0x00}, []byte(str32)..., ), BufferSize: 1, Method: method, }, } testcases.Run(t) } ================================================ FILE: internal/stream/encoding/struct.go ================================================ package encoding import ( "math" "reflect" "sync" "github.com/shamaton/msgpack/v3/def" "github.com/shamaton/msgpack/v3/ext" "github.com/shamaton/msgpack/v3/internal/common" ) type structCache struct { // common fields names []string omits []bool noOmit bool // fast path detection hasEmbedded bool // fast path (hasEmbedded == false): direct field access simpleIndexes []int // embedded path (hasEmbedded == true): path-based access indexes [][]int // field path (support for embedded structs) omitPaths [][][]int // embedded omitempty parent paths common.Common } var cachemap = sync.Map{} type structWriteFunc func(rv reflect.Value) error // getFieldByPath returns the field value by following the path of indices. // The bool indicates whether the path was reachable (no nil pointer in the path). func getFieldByPath(rv reflect.Value, path []int) (reflect.Value, bool) { for _, idx := range path { // Handle pointer indirection if needed if rv.Kind() == reflect.Ptr { if rv.IsNil() { // Return invalid value if pointer is nil return reflect.Value{}, false } rv = rv.Elem() } rv = rv.Field(idx) } return rv, true } func shouldOmitByParent(rv reflect.Value, omitPaths [][]int) bool { for _, path := range omitPaths { parentValue, ok := getFieldByPath(rv, path) if !ok || parentValue.IsZero() { return true } } return false } func (e *encoder) getStructWriter(typ reflect.Type) structWriteFunc { for i := range extCoders { if extCoders[i].Type() == typ { return func(rv reflect.Value) error { w := ext.CreateStreamWriter(e.w, e.buf) return extCoders[i].Write(w, rv) } } } if e.asArray { return e.writeStructArray } return e.writeStructMap } func (e *encoder) writeStruct(rv reflect.Value) error { for i := range extCoders { if extCoders[i].Type() == rv.Type() { w := ext.CreateStreamWriter(e.w, e.buf) return extCoders[i].Write(w, rv) } } if e.asArray { return e.writeStructArray(rv) } return e.writeStructMap(rv) } func (e *encoder) writeStructArray(rv reflect.Value) error { c := e.getStructCache(rv) // write format var num int if c.hasEmbedded { num = len(c.indexes) } else { num = len(c.simpleIndexes) } if num <= 0x0f { if err := e.setByte1Int(def.FixArray + num); err != nil { return err } } else if num <= math.MaxUint16 { if err := e.setByte1Int(def.Array16); err != nil { return err } if err := e.setByte2Int(num); err != nil { return err } } else if uint(num) <= math.MaxUint32 { if err := e.setByte1Int(def.Array32); err != nil { return err } if err := e.setByte4Int(num); err != nil { return err } } if c.hasEmbedded { for i := 0; i < num; i++ { fieldValue, ok := getFieldByPath(rv, c.indexes[i]) if shouldOmitByParent(rv, c.omitPaths[i]) || !ok { fieldValue = reflect.Value{} } if err := e.create(fieldValue); err != nil { return err } } } else { for i := 0; i < num; i++ { if err := e.create(rv.Field(c.simpleIndexes[i])); err != nil { return err } } } return nil } func (e *encoder) writeStructMap(rv reflect.Value) error { c := e.getStructCache(rv) l := 0 if c.hasEmbedded { num := len(c.indexes) for i := 0; i < num; i++ { fieldValue, ok := getFieldByPath(rv, c.indexes[i]) if shouldOmitByParent(rv, c.omitPaths[i]) || !ok { continue } if c.noOmit || !c.omits[i] || !fieldValue.IsZero() { l++ } } } else { num := len(c.simpleIndexes) for i := 0; i < num; i++ { if c.noOmit || !c.omits[i] || !rv.Field(c.simpleIndexes[i]).IsZero() { l++ } } } // format size if l <= 0x0f { if err := e.setByte1Int(def.FixMap + l); err != nil { return err } } else if l <= math.MaxUint16 { if err := e.setByte1Int(def.Map16); err != nil { return err } if err := e.setByte2Int(l); err != nil { return err } } else if uint(l) <= math.MaxUint32 { if err := e.setByte1Int(def.Map32); err != nil { return err } if err := e.setByte4Int(l); err != nil { return err } } if c.hasEmbedded { num := len(c.indexes) for i := 0; i < num; i++ { fieldValue, ok := getFieldByPath(rv, c.indexes[i]) if shouldOmitByParent(rv, c.omitPaths[i]) || !ok { continue } if c.noOmit || !c.omits[i] || !fieldValue.IsZero() { if err := e.writeString(c.names[i]); err != nil { return err } if err := e.create(fieldValue); err != nil { return err } } } } else { num := len(c.simpleIndexes) for i := 0; i < num; i++ { fieldValue := rv.Field(c.simpleIndexes[i]) if c.noOmit || !c.omits[i] || !fieldValue.IsZero() { if err := e.writeString(c.names[i]); err != nil { return err } if err := e.create(fieldValue); err != nil { return err } } } } return nil } func (e *encoder) getStructCache(rv reflect.Value) *structCache { t := rv.Type() cache, find := cachemap.Load(t) if find { return cache.(*structCache) } c := &structCache{} fields := e.CollectFields(t, nil) // detect embedded fields hasEmbedded := false for _, f := range fields { if len(f.Path) > 1 || len(f.OmitPaths) > 0 { hasEmbedded = true break } } c.hasEmbedded = hasEmbedded omitCount := 0 for _, field := range fields { c.names = append(c.names, field.Name) c.omits = append(c.omits, field.Omit) if hasEmbedded { c.indexes = append(c.indexes, field.Path) c.omitPaths = append(c.omitPaths, field.OmitPaths) } else { c.simpleIndexes = append(c.simpleIndexes, field.Path[0]) } if field.Omit { omitCount++ } } c.noOmit = omitCount == 0 cachemap.Store(t, c) return c } ================================================ FILE: internal/stream/encoding/struct_test.go ================================================ package encoding import ( "math" "reflect" "testing" "time" "github.com/shamaton/msgpack/v3/def" tu "github.com/shamaton/msgpack/v3/internal/common/testutil" ) func Test_writeStruct(t *testing.T) { method := func(e *encoder) func(reflect.Value) error { return e.writeStruct } t.Run("Ext", func(t *testing.T) { value := time.Time{} testcases := AsXXXTestCases[time.Time]{ { Name: "error", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForStruct: method, }, { Name: "ok", Value: value, Expected: []byte{ def.Ext8, 12, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xf1, 0x88, 0x6e, 0x09, 0x00, }, BufferSize: 1, MethodForStruct: method, }, } testcases.Run(t) }) t.Run("Array", func(t *testing.T) { st, _, b := tu.CreateStruct(1) value := reflect.ValueOf(st).Elem().Interface() testcases := AsXXXTestCases[any]{ { Name: "error", Value: value, BufferSize: 1, PreWriteSize: 1, AsArray: true, MethodForStruct: method, }, { Name: "ok", Value: value, Expected: append([]byte{def.FixArray + 1}, b...), AsArray: true, BufferSize: 1, MethodForStruct: method, }, } testcases.Run(t) }) t.Run("Map", func(t *testing.T) { st, b, _ := tu.CreateStruct(1) value := reflect.ValueOf(st).Elem().Interface() testcases := AsXXXTestCases[any]{ { Name: "error", Value: value, BufferSize: 1, PreWriteSize: 1, AsArray: false, MethodForStruct: method, }, { Name: "ok", Value: value, Expected: append([]byte{def.FixMap + 1}, b...), AsArray: false, BufferSize: 1, MethodForStruct: method, }, } testcases.Run(t) }) } func Test_writeStructArray(t *testing.T) { method := func(e *encoder) func(reflect.Value) error { return e.writeStructArray } t.Run("FixArray", func(t *testing.T) { st, _, b := tu.CreateStruct(0x0f) value := reflect.ValueOf(st).Elem().Interface() testcases := AsXXXTestCases[any]{ { Name: "error.def", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForStruct: method, }, { Name: "error.value", Value: value, BufferSize: 2, PreWriteSize: 1, Contains: []byte{def.FixArray + byte(0x0f)}, MethodForStruct: method, }, { Name: "ok", Value: value, Expected: append([]byte{def.FixArray + byte(0x0f)}, b...), BufferSize: 1, MethodForStruct: method, }, } testcases.Run(t) }) t.Run("Array16", func(t *testing.T) { st, _, b := tu.CreateStruct(math.MaxUint16) value := reflect.ValueOf(st).Elem().Interface() testcases := AsXXXTestCases[any]{ { Name: "error.def", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForStruct: method, }, { Name: "error.num", Value: value, BufferSize: 2, PreWriteSize: 1, Contains: []byte{def.Array16}, MethodForStruct: method, }, { Name: "error.value", Value: value, BufferSize: 4, PreWriteSize: 1, Contains: []byte{def.Array16, 0xff, 0xff}, MethodForStruct: method, }, { Name: "ok", Value: value, Expected: append([]byte{def.Array16, 0xff, 0xff}, b...), BufferSize: 1, MethodForStruct: method, }, } testcases.Run(t) }) t.Run("Array32", func(t *testing.T) { st, _, b := tu.CreateStruct(math.MaxUint16 + 1) value := reflect.ValueOf(st).Elem().Interface() testcases := AsXXXTestCases[any]{ { Name: "error.def", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForStruct: method, }, { Name: "error.num", Value: value, BufferSize: 2, PreWriteSize: 1, Contains: []byte{def.Array32}, MethodForStruct: method, }, { Name: "error.value", Value: value, BufferSize: 6, PreWriteSize: 1, Contains: []byte{def.Array32, 0x00, 0x01, 0x00, 0x00}, MethodForStruct: method, }, { Name: "ok", Value: value, Expected: append([]byte{def.Array32, 0x00, 0x01, 0x00, 0x00}, b...), BufferSize: 1, MethodForStruct: method, }, } testcases.Run(t) }) } func Test_writeStructMap(t *testing.T) { method := func(e *encoder) func(reflect.Value) error { return e.writeStructMap } t.Run("FixMap", func(t *testing.T) { st, b, _ := tu.CreateStruct(0x0f) value := reflect.ValueOf(st).Elem().Interface() testcases := AsXXXTestCases[any]{ { Name: "error.def", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForStruct: method, }, { Name: "error.key", Value: value, BufferSize: 2, PreWriteSize: 1, Contains: []byte{def.FixMap + byte(0x0f)}, MethodForStruct: method, }, { Name: "error.value", Value: value, BufferSize: 5, PreWriteSize: 1, Contains: append([]byte{def.FixMap + byte(0x0f)}, b[:3]...), MethodForStruct: method, }, { Name: "ok", Value: value, Expected: append([]byte{def.FixMap + byte(0x0f)}, b...), BufferSize: 1, MethodForStruct: method, }, } testcases.Run(t) }) t.Run("Map16", func(t *testing.T) { st, b, _ := tu.CreateStruct(math.MaxUint16) value := reflect.ValueOf(st).Elem().Interface() testcases := AsXXXTestCases[any]{ { Name: "error.def", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForStruct: method, }, { Name: "error.num", Value: value, BufferSize: 2, PreWriteSize: 1, Contains: []byte{def.Map16}, MethodForStruct: method, }, { Name: "error.key", Value: value, BufferSize: 4, PreWriteSize: 1, Contains: []byte{def.Map16, 0xff, 0xff}, MethodForStruct: method, }, { Name: "error.value", Value: value, BufferSize: 7, PreWriteSize: 1, Contains: append([]byte{def.Map16, 0xff, 0xff}, b[:3]...), MethodForStruct: method, }, { Name: "ok", Value: value, Expected: append([]byte{def.Map16, 0xff, 0xff}, b...), BufferSize: 1, MethodForStruct: method, }, } testcases.Run(t) }) t.Run("Map32", func(t *testing.T) { st, b, _ := tu.CreateStruct(math.MaxUint16 + 1) value := reflect.ValueOf(st).Elem().Interface() testcases := AsXXXTestCases[any]{ { Name: "error.def", Value: value, BufferSize: 1, PreWriteSize: 1, MethodForStruct: method, }, { Name: "error.num", Value: value, BufferSize: 2, PreWriteSize: 1, Contains: []byte{def.Map32}, MethodForStruct: method, }, { Name: "error.key", Value: value, BufferSize: 6, PreWriteSize: 1, Contains: []byte{def.Map32, 0x00, 0x01, 0x00, 0x00}, MethodForStruct: method, }, { Name: "error.value", Value: value, BufferSize: 9, PreWriteSize: 1, Contains: append([]byte{def.Map32, 0x00, 0x01, 0x00, 0x00}, b[:3]...), MethodForStruct: method, }, { Name: "ok", Value: value, Expected: append([]byte{def.Map32, 0x00, 0x01, 0x00, 0x00}, b...), BufferSize: 1, MethodForStruct: method, }, } testcases.Run(t) }) } ================================================ FILE: internal/stream/encoding/uint.go ================================================ package encoding import ( "math" "github.com/shamaton/msgpack/v3/def" ) func (e *encoder) writeUint(v uint64) error { if v <= math.MaxInt8 { if err := e.setByte1Uint64(v); err != nil { return err } } else if v <= math.MaxUint8 { if err := e.setByte1Int(def.Uint8); err != nil { return err } if err := e.setByte1Uint64(v); err != nil { return err } } else if v <= math.MaxUint16 { if err := e.setByte1Int(def.Uint16); err != nil { return err } if err := e.setByte2Uint64(v); err != nil { return err } } else if v <= math.MaxUint32 { if err := e.setByte1Int(def.Uint32); err != nil { return err } if err := e.setByte4Uint64(v); err != nil { return err } } else { if err := e.setByte1Int(def.Uint64); err != nil { return err } if err := e.setByte8Uint64(v); err != nil { return err } } return nil } ================================================ FILE: internal/stream/encoding/uint_test.go ================================================ package encoding import ( "math" "testing" "github.com/shamaton/msgpack/v3/def" ) func Test_asUint(t *testing.T) { method := func(e *encoder) func(uint64) error { return e.writeUint } testcases := AsXXXTestCases[uint64]{ { Name: "PositiveFix.error", Value: math.MaxInt8, BufferSize: 1, PreWriteSize: 1, Method: method, }, { Name: "PositiveFix.ok", Value: math.MaxInt8, Expected: []byte{0x7f}, BufferSize: 1, Method: method, }, { Name: "Uint8.error.def", Value: math.MaxUint8, BufferSize: 1, PreWriteSize: 1, Method: method, }, { Name: "Uint8.error.value", Value: math.MaxUint8, BufferSize: 2, PreWriteSize: 1, Contains: []byte{def.Uint8}, Method: method, }, { Name: "Uint8.ok", Value: math.MaxUint8, Expected: []byte{def.Uint8, 0xff}, BufferSize: 1, Method: method, }, { Name: "Uint16.error.def", Value: math.MaxUint16, BufferSize: 1, PreWriteSize: 1, Method: method, }, { Name: "Uint16.error.value", Value: math.MaxUint16, BufferSize: 3, PreWriteSize: 1, Contains: []byte{def.Uint16}, Method: method, }, { Name: "Uint16.ok", Value: math.MaxUint16, Expected: []byte{def.Uint16, 0xff, 0xff}, BufferSize: 1, Method: method, }, { Name: "Uint32.error.def", Value: math.MaxUint32, BufferSize: 1, PreWriteSize: 1, Method: method, }, { Name: "Uint32.error.value", Value: math.MaxUint32, BufferSize: 5, PreWriteSize: 1, Contains: []byte{def.Uint32}, Method: method, }, { Name: "Uint32.ok", Value: math.MaxUint32, Expected: []byte{def.Uint32, 0xff, 0xff, 0xff, 0xff}, BufferSize: 1, Method: method, }, { Name: "Uint64.error.def", Value: math.MaxUint64, BufferSize: 1, PreWriteSize: 1, Method: method, }, { Name: "Uint64.error.value", Value: math.MaxUint64, BufferSize: 9, PreWriteSize: 1, Contains: []byte{def.Uint64}, Method: method, }, { Name: "Uint64.ok", Value: math.MaxUint64, Expected: []byte{def.Uint64, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, BufferSize: 1, Method: method, }, } testcases.Run(t) } ================================================ FILE: msgpack.go ================================================ package msgpack import ( "fmt" "io" "github.com/shamaton/msgpack/v3/def" "github.com/shamaton/msgpack/v3/ext" "github.com/shamaton/msgpack/v3/internal/decoding" "github.com/shamaton/msgpack/v3/internal/encoding" streamdecoding "github.com/shamaton/msgpack/v3/internal/stream/decoding" streamencoding "github.com/shamaton/msgpack/v3/internal/stream/encoding" "github.com/shamaton/msgpack/v3/time" ) // StructAsArray is encoding option. // If this option sets true, default encoding sets to array-format. var StructAsArray = false // Marshal returns the MessagePack-encoded byte array of v. func Marshal(v interface{}) ([]byte, error) { return encoding.Encode(v, StructAsArray) } // MarshalWrite writes MessagePack-encoded byte array of v to writer. func MarshalWrite(w io.Writer, v interface{}) error { return streamencoding.Encode(w, v, StructAsArray) } // Unmarshal analyzes the MessagePack-encoded data and stores // the result into the pointer of v. func Unmarshal(data []byte, v interface{}) error { return decoding.Decode(data, v, StructAsArray) } // UnmarshalRead reads the MessagePack-encoded data from reader and stores // the result into the pointer of v. func UnmarshalRead(r io.Reader, v interface{}) error { return streamdecoding.Decode(r, v, StructAsArray) } // AddExtCoder adds encoders for extension types. func AddExtCoder(e ext.Encoder, d ext.Decoder) error { if e.Code() != d.Code() { return fmt.Errorf("code different %d:%d", e.Code(), d.Code()) } encoding.AddExtEncoder(e) decoding.AddExtDecoder(d) return nil } // AddExtStreamCoder adds stream encoders for extension types. func AddExtStreamCoder(e ext.StreamEncoder, d ext.StreamDecoder) error { if e.Code() != d.Code() { return fmt.Errorf("code different %d:%d", e.Code(), d.Code()) } streamencoding.AddExtEncoder(e) streamdecoding.AddExtDecoder(d) return nil } // RemoveExtCoder removes encoders for extension types. func RemoveExtCoder(e ext.Encoder, d ext.Decoder) error { if e.Code() != d.Code() { return fmt.Errorf("code different %d:%d", e.Code(), d.Code()) } encoding.RemoveExtEncoder(e) decoding.RemoveExtDecoder(d) return nil } // RemoveExtStreamCoder removes stream encoders for extension types. func RemoveExtStreamCoder(e ext.StreamEncoder, d ext.StreamDecoder) error { if e.Code() != d.Code() { return fmt.Errorf("code different %d:%d", e.Code(), d.Code()) } streamencoding.RemoveExtEncoder(e) streamdecoding.RemoveExtDecoder(d) return nil } // SetComplexTypeCode sets def.complexTypeCode func SetComplexTypeCode(code int8) { def.SetComplexTypeCode(code) } // SetDecodedTimeAsUTC sets decoded time.Time values to UTC timezone. func SetDecodedTimeAsUTC() { time.SetDecodedAsLocal(false) } // SetDecodedTimeAsLocal sets decoded time.Time values to local timezone. func SetDecodedTimeAsLocal() { time.SetDecodedAsLocal(true) } ================================================ FILE: msgpack_example_test.go ================================================ package msgpack_test import ( "bytes" "fmt" "net" "reflect" "github.com/shamaton/msgpack/v3" "github.com/shamaton/msgpack/v3/def" "github.com/shamaton/msgpack/v3/ext" ) func ExampleAddExtCoder() { err := msgpack.AddExtCoder(&IPNetEncoder{}, &IPNetDecoder{}) if err != nil { panic(err) } v1 := net.IPNet{IP: net.IP{127, 0, 0, 1}, Mask: net.IPMask{255, 255, 255, 0}} r1, err := msgpack.Marshal(v1) if err != nil { panic(err) } fmt.Printf("encode: % 02x\n", r1) var v2 net.IPNet err = msgpack.Unmarshal(r1, &v2) if err != nil { panic(err) } fmt.Println("decode:", v2) // Output: // encode: c7 0c 32 31 32 37 2e 30 2e 30 2e 31 2f 32 34 // decode: {127.0.0.0 ffffff00} } func ExampleAddExtStreamCoder() { err := msgpack.AddExtStreamCoder(&IPNetStreamEncoder{}, &IPNetStreamDecoder{}) if err != nil { panic(err) } v1 := net.IPNet{IP: net.IP{127, 0, 0, 1}, Mask: net.IPMask{255, 255, 255, 0}} buf := bytes.Buffer{} err = msgpack.MarshalWrite(&buf, v1) if err != nil { panic(err) } fmt.Printf("encode: % 02x\n", buf.Bytes()) var v2 net.IPNet err = msgpack.UnmarshalRead(&buf, &v2) if err != nil { panic(err) } fmt.Println("decode:", v2) // Output: // encode: c7 0c 32 31 32 37 2e 30 2e 30 2e 31 2f 32 34 // decode: {127.0.0.0 ffffff00} } const ipNetCode = 50 type IPNetDecoder struct { ext.DecoderCommon } var _ ext.Decoder = (*IPNetDecoder)(nil) func (td *IPNetDecoder) Code() int8 { return ipNetCode } func (td *IPNetDecoder) IsType(offset int, d *[]byte) bool { code, offset := td.ReadSize1(offset, d) if code == def.Ext8 { _, offset = td.ReadSize1(offset, d) t, _ := td.ReadSize1(offset, d) return int8(t) == td.Code() } return false } func (td *IPNetDecoder) AsValue(offset int, k reflect.Kind, d *[]byte) (any, int, error) { code, offset := td.ReadSize1(offset, d) switch code { case def.Ext8: // size size, offset := td.ReadSize1(offset, d) // code _, offset = td.ReadSize1(offset, d) // value data, offset := td.ReadSizeN(offset, int(size), d) _, v, err := net.ParseCIDR(string(data)) if err != nil { return net.IPNet{}, 0, fmt.Errorf("failed to parse CIDR: %w", err) } if v == nil { return net.IPNet{}, 0, fmt.Errorf("parsed CIDR is nil") } return *v, offset, nil } return net.IPNet{}, 0, fmt.Errorf("should not reach this line!! code %x decoding %v", td.Code(), k) } type IPNetStreamDecoder struct{} var _ ext.StreamDecoder = (*IPNetStreamDecoder)(nil) func (td *IPNetStreamDecoder) Code() int8 { return ipNetCode } func (td *IPNetStreamDecoder) IsType(code byte, innerType int8, _ int) bool { return code == def.Ext8 && innerType == td.Code() } func (td *IPNetStreamDecoder) ToValue(code byte, data []byte, k reflect.Kind) (any, error) { if code == def.Ext8 { _, v, err := net.ParseCIDR(string(data)) if err != nil { return net.IPNet{}, fmt.Errorf("failed to parse CIDR: %w", err) } if v == nil { return net.IPNet{}, fmt.Errorf("parsed CIDR is nil") } return *v, nil } return net.IPNet{}, fmt.Errorf("should not reach this line!! code %x decoding %v", td.Code(), k) } type IPNetEncoder struct { ext.EncoderCommon } var _ ext.Encoder = (*IPNetEncoder)(nil) func (s *IPNetEncoder) Code() int8 { return ipNetCode } func (s *IPNetEncoder) Type() reflect.Type { return reflect.TypeOf(net.IPNet{}) } func (s *IPNetEncoder) CalcByteSize(value reflect.Value) (int, error) { v := value.Interface().(net.IPNet) return def.Byte1 + def.Byte1 + def.Byte1 + len(v.String()), nil } func (s *IPNetEncoder) WriteToBytes(value reflect.Value, offset int, bytes *[]byte) int { v := value.Interface().(net.IPNet) data := v.String() offset = s.SetByte1Int(def.Ext8, offset, bytes) offset = s.SetByte1Int(len(data), offset, bytes) offset = s.SetByte1Int(int(s.Code()), offset, bytes) offset = s.SetBytes([]byte(data), offset, bytes) return offset } type IPNetStreamEncoder struct{} var _ ext.StreamEncoder = (*IPNetStreamEncoder)(nil) func (s *IPNetStreamEncoder) Code() int8 { return ipNetCode } func (s *IPNetStreamEncoder) Type() reflect.Type { return reflect.TypeOf(net.IPNet{}) } func (s *IPNetStreamEncoder) Write(w ext.StreamWriter, value reflect.Value) error { v := value.Interface().(net.IPNet) data := v.String() if err := w.WriteByte1Int(def.Ext8); err != nil { return err } if err := w.WriteByte1Int(len(data)); err != nil { return err } if err := w.WriteByte1Int(int(s.Code())); err != nil { return err } if err := w.WriteBytes([]byte(data)); err != nil { return err } return nil } ================================================ FILE: msgpack_test.go ================================================ package msgpack_test import ( "bytes" "encoding/binary" "encoding/hex" "encoding/json" "errors" "fmt" "math" "math/rand" "reflect" "slices" "strings" "testing" "time" "github.com/shamaton/msgpack/v3" "github.com/shamaton/msgpack/v3/def" "github.com/shamaton/msgpack/v3/ext" tu "github.com/shamaton/msgpack/v3/internal/common/testutil" extTime "github.com/shamaton/msgpack/v3/time" ) var now time.Time func init() { n := time.Now() now = time.Unix(n.Unix(), int64(n.Nanosecond())).UTC() } func TestInt(t *testing.T) { { args := []encdecArg[int]{ { n: "FixInt", v: -8, c: func(d []byte) bool { return def.NegativeFixintMin <= int8(d[0]) && int8(d[0]) <= def.NegativeFixintMax }, }, { n: "Int8", v: -108, c: func(d []byte) bool { return d[0] == def.Int8 }, }, { n: "Int16", v: -30108, c: func(d []byte) bool { return d[0] == def.Int16 }, }, { n: "Int32", v: -1030108, c: func(d []byte) bool { return d[0] == def.Int32 }, }, } encdec(t, args...) } { arg := encdecArg[int64]{ n: "Int64", v: int64(math.MinInt64 + 12345), c: func(d []byte) bool { return d[0] == def.Int64 }, } encdec(t, arg) } // error { arg := encdecArg[uint8]{ n: "ErrorDecToUint8", v: -8, c: func(d []byte) bool { return def.NegativeFixintMin <= int8(d[0]) && int8(d[0]) <= def.NegativeFixintMax }, e: "value different", } encdec(t, arg) } { arg := encdecArg[int32]{ n: "ErrorDecToInt32", v: int64(math.MinInt64 + 12345), c: func(d []byte) bool { return d[0] == def.Int64 }, e: "value different", } encdec(t, arg) } } func TestUint(t *testing.T) { { args := []encdecArg[uint]{ { n: "FixUint", v: uint(8), c: func(d []byte) bool { return def.PositiveFixIntMin <= uint8(d[0]) && uint8(d[0]) <= def.PositiveFixIntMax }, }, { n: "Uint8", v: uint(130), c: func(d []byte) bool { return d[0] == def.Uint8 }, }, { n: "Uint16", v: uint(30130), c: func(d []byte) bool { return d[0] == def.Uint16 }, }, { n: "Uint32", v: uint(1030130), c: func(d []byte) bool { return d[0] == def.Uint32 }, }, } encdec(t, args...) } { arg := encdecArg[uint64]{ n: "Uint64", v: uint64(math.MaxUint64 - 12345), c: func(d []byte) bool { return d[0] == def.Uint64 }, } encdec(t, arg) } } func TestFloat(t *testing.T) { t.Run("Float32", func(t *testing.T) { c := func(d []byte) bool { return d[0] == def.Float32 } args := []encdecArg[float32]{{ n: "0", v: float32(0), c: c, }, { n: "-1", v: float32(-1), c: c, }, { n: "SmallestNonzeroFloat", v: float32(math.SmallestNonzeroFloat32), c: c, }, { n: "MaxFloat", v: float32(math.MaxFloat32), c: c, }} encdec(t, args...) }) t.Run("Float64", func(t *testing.T) { c := func(d []byte) bool { return d[0] == def.Float64 } args := []encdecArg[float64]{{ n: "0", v: float64(0), c: c, }, { n: "-1", v: float64(-1), c: c, }, { n: "SmallestNonzeroFloat", v: math.SmallestNonzeroFloat64, c: c, }, { n: "MaxFloat", v: math.MaxFloat64, c: c, }} encdec(t, args...) }) t.Run("FloatToInt", func(t *testing.T) { args := []encdecArg[int]{ { n: "FromFloat32", v: float32(2.345), vc: func(v int) error { if v != 2 { return fmt.Errorf("different value: %d", v) } return nil }, skipEq: true, }, { n: "FromFloat64", v: float64(6.789), vc: func(v int) error { if v != 6 { return fmt.Errorf("different value: %d", v) } return nil }, skipEq: true, }, } encdec(t, args...) }) // error t.Run("Float32ToFloat64", func(t *testing.T) { arg := encdecArg[float64]{ n: "NotEqual", v: float32(math.MaxFloat32), c: func(d []byte) bool { return d[0] == def.Float32 }, e: "value different", } encdec(t, arg) }) t.Run("Float64ToFloat32", func(t *testing.T) { arg := encdecArg[float32]{ n: "ErrorDecToFloat32", v: math.MaxFloat64, c: func(d []byte) bool { return d[0] == def.Float64 }, e: "invalid code cb decoding", } encdec(t, arg) }) t.Run("Float64ToString", func(t *testing.T) { arg := encdecArg[string]{ n: "ErrorDecToString", v: math.MaxFloat64, c: func(d []byte) bool { return d[0] == def.Float64 }, e: "invalid code cb decoding", } encdec(t, arg) }) } func TestBool(t *testing.T) { t.Run("Bool", func(t *testing.T) { args := []encdecArg[bool]{ { n: "True", v: true, c: func(d []byte) bool { return d[0] == def.True }, }, { n: "False", v: false, c: func(d []byte) bool { return d[0] == def.False }, }, } encdec(t, args...) }) // error t.Run("BoolToUint8", func(t *testing.T) { arg := encdecArg[uint8]{ n: "ErrorDecToUint8", v: true, c: func(d []byte) bool { return d[0] == def.True }, e: "invalid code c3 decoding", } encdec(t, arg) }) } func TestNil(t *testing.T) { t.Run("Nil", func(t *testing.T) { args := []encdecArg[*map[any]any]{ { n: "Nil", v: nil, c: func(d []byte) bool { return d[0] == def.Nil }, vc: func(v *map[any]any) error { if v != nil { return fmt.Errorf("not nil: %v", v) } return nil }, }, } encdec(t, args...) }) } func TestString(t *testing.T) { // len 31 const base = "abcdefghijklmnopqrstuvwxyz12345" t.Run("String", func(t *testing.T) { args := []encdecArg[string]{ { n: "EmptyString", v: "", c: func(d []byte) bool { return def.FixStr <= d[0] && d[0] < def.FixStr+32 }, }, { n: "FixStr", v: strings.Repeat(base, 1), c: func(d []byte) bool { return def.FixStr <= d[0] && d[0] < def.FixStr+32 }, }, { n: "Str8", v: strings.Repeat(base, 8), c: func(d []byte) bool { return d[0] == def.Str8 }, }, { n: "Str16", v: strings.Repeat(base, (math.MaxUint16/len(base))-1), c: func(d []byte) bool { return d[0] == def.Str16 }, }, { n: "Str32", v: strings.Repeat(base, (math.MaxUint16/len(base))+1), c: func(d []byte) bool { return d[0] == def.Str32 }, }, } encdec(t, args...) }) // type different t.Run("StringToBytes", func(t *testing.T) { arg := encdecArg[[]byte]{ n: "TypeDifferentButSameValue", v: strings.Repeat(base, 8), c: func(d []byte) bool { return d[0] == def.Str8 }, e: "value different", } encdec(t, arg) }) t.Run("BytesToString", func(t *testing.T) { arg := encdecArg[string]{ n: "TypeDifferentButSameValue", v: []byte(base), c: func(d []byte) bool { return d[0] == def.Bin8 }, e: "value different", } encdec(t, arg) }) } func TestComplex(t *testing.T) { t.Run("Complex64", func(t *testing.T) { args := []encdecArg[complex64]{ { n: "To64", v: complex64(complex(1, 2)), c: func(d []byte) bool { return d[0] == def.Fixext8 && int8(d[1]) == def.ComplexTypeCode() }, }, { n: "Nil", v: nil, e: "invalid code c0 decoding", }, } encdec(t, args...) args2 := []encdecArg[complex128]{ { n: "To128", v: complex64(complex(1, 2)), c: func(d []byte) bool { return d[0] == def.Fixext8 && int8(d[1]) == def.ComplexTypeCode() }, vc: func(t complex128) error { if imag(t) == 0 || real(t) == 0 { return fmt.Errorf("somthing wrong %v", t) } return nil }, skipEq: true, }, } encdec(t, args2...) }) t.Run("Complex128", func(t *testing.T) { args := []encdecArg[complex128]{ { n: "To128", v: complex(math.MaxFloat64, math.SmallestNonzeroFloat64), c: func(d []byte) bool { return d[0] == def.Fixext16 && int8(d[1]) == def.ComplexTypeCode() }, }, { n: "Nil", v: nil, e: "invalid code c0 decoding", }, } encdec(t, args...) args2 := []encdecArg[complex64]{ { n: "To64", v: complex(math.MaxFloat64, math.SmallestNonzeroFloat64), c: func(d []byte) bool { return d[0] == def.Fixext16 && int8(d[1]) == def.ComplexTypeCode() }, vc: func(t complex64) error { if imag(t) != 0 || real(t) == 0 { return fmt.Errorf("somthing wrong %v", t) } return nil }, skipEq: true, }, } encdec(t, args2...) }) t.Run("ComplexTypeCode", func(t *testing.T) { b64, err := msgpack.Marshal(complex64(complex(3, 4))) NoError(t, err) b128, err := msgpack.Marshal(complex128(complex(5, 6))) NoError(t, err) // change complex type code msgpack.SetComplexTypeCode(int8(-99)) data := []struct { name string b []byte errStr string }{ {"From64", b64, "fixext8"}, {"From128", b128, "fixext16"}, } var r64 complex64 var r128 complex128 for _, d := range data { for _, u := range unmarshallers { t.Run(d.name+"To64"+u.name, func(t *testing.T) { ErrorContains(t, u.u(d.b, &r64), d.errStr) }) t.Run(d.name+"To128"+u.name, func(t *testing.T) { ErrorContains(t, u.u(d.b, &r128), d.errStr) }) } } }) } func TestAny(t *testing.T) { f := func(v interface{}) error { b, err := msgpack.Marshal(v) if err != nil { return err } var r interface{} err = msgpack.Unmarshal(b, &r) if err != nil { return err } if fmt.Sprintf("%v", v) != fmt.Sprintf("%v", r) { return fmt.Errorf("different value %v, %v", v, r) } return err } a1 := make([]int, math.MaxUint16) a2 := make([]int, math.MaxUint16+1) m1 := map[string]int{} m2 := map[string]int{} for i := range a1 { a1[i] = i m1[fmt.Sprint(i)] = 1 } for i := range a2 { a2[i] = i m2[fmt.Sprint(i)] = 1 } vars := []interface{}{ true, false, 1, math.MaxUint8, math.MaxUint16, math.MaxUint32, math.MaxUint32 + 1, -1, math.MinInt8, math.MinInt16, math.MinInt32, math.MinInt32 - 1, math.MaxFloat32, math.MaxFloat64, "a", strings.Repeat("a", math.MaxUint8), strings.Repeat("a", math.MaxUint16), strings.Repeat("a", math.MaxUint16+1), []byte(strings.Repeat("a", math.MaxUint8)), []byte(strings.Repeat("a", math.MaxUint16)), []byte(strings.Repeat("a", math.MaxUint16+1)), []interface{}{1, "a", 1.23}, a1, a2, map[interface{}]interface{}{"1": 1, 1.23: "a"}, m1, m2, time.Unix(now.Unix(), int64(now.Nanosecond())).UTC(), } for i, v := range vars { if err := f(v); err != nil { t.Error(i, err) } } t.Run("Any", func(t *testing.T) { args := []encdecArg[any]{ {n: "true", v: any(true)}, {n: "false", v: any(false)}, {n: "1", v: any(1)}, {n: "MaxUint8", v: any(math.MaxUint8)}, {n: "MaxUint16", v: any(math.MaxUint16)}, {n: "MaxUint32", v: any(math.MaxUint32)}, {n: "MaxUint32+1", v: any(math.MaxUint32 + 1)}, {n: "-1", v: any(-1)}, {n: "MinInt8", v: any(math.MinInt8)}, {n: "MinInt16", v: any(math.MinInt16)}, {n: "MinInt32", v: any(math.MinInt32)}, {n: "MinInt32-1", v: any(math.MinInt32 - 1)}, {n: "MaxFloat32", v: any(math.MaxFloat32)}, {n: "MaxFloat64", v: any(math.MaxFloat64)}, {n: "Str1", v: any("a")}, {n: "Str255", v: any(strings.Repeat("a", math.MaxUint8))}, {n: "Str65535", v: any(strings.Repeat("a", math.MaxUint16))}, {n: "Str65536", v: any(strings.Repeat("a", math.MaxUint16+1))}, {n: "Bin255", v: any([]byte(strings.Repeat("a", math.MaxUint8)))}, {n: "Bin65535", v: any([]byte(strings.Repeat("a", math.MaxUint16)))}, {n: "Bin65536", v: any([]byte(strings.Repeat("a", math.MaxUint16+1)))}, {n: "Slice3", v: any([]any{1, "a", 1.23})}, {n: "Slice65535", v: any(a1)}, {n: "Slice65536", v: any(a2)}, {n: "Map3", v: any(map[any]any{"1": 1, 1.23: "a"})}, {n: "Map65535", v: any(m1)}, {n: "Map65536", v: any(m2)}, {n: "Time", v: any(time.Unix(now.Unix(), int64(now.Nanosecond())).UTC())}, } for i := range args { i := i args[i].skipEq = true args[i].vc = func(t any) error { if fmt.Sprintf("%v", args[i].v) != fmt.Sprintf("%v", t) { return fmt.Errorf("different value %v, %v", args[i].v, t) } return nil } } encdec(t, args...) }) // error t.Run("AnyError", func(t *testing.T) { var r any err := msgpack.Unmarshal([]byte{def.Ext32}, &r) ErrorContains(t, err, "too short bytes") }) } func TestBin(t *testing.T) { makeByteSlice := func(len int) []byte { v := make([]byte, len) for i := range v { v[i] = byte(rand.Intn(0xff)) } return v } t.Run("Slice", func(t *testing.T) { args := []encdecArg[[]byte]{ { n: "Bin8", v: makeByteSlice(128), c: func(d []byte) bool { return d[0] == def.Bin8 }, }, { n: "Bin16", v: makeByteSlice(31280), c: func(d []byte) bool { return d[0] == def.Bin16 }, }, { n: "Bin32", v: makeByteSlice(1031280), c: func(d []byte) bool { return d[0] == def.Bin32 }, }, } encdec(t, args...) }) t.Run("Array", func(t *testing.T) { var ( a128 [128]byte a31280 [31280]byte a1031280 [1031280]byte ) for i := range a128 { a128[i] = byte(rand.Intn(0xff)) } for i := range a31280 { a31280[i] = byte(rand.Intn(0xff)) } for i := range a1031280 { a1031280[i] = byte(rand.Intn(0xff)) } args128 := []encdecArg[[128]byte]{ { n: "Bin8", v: a128, c: func(d []byte) bool { return d[0] == def.Bin8 }, }, } encdec(t, args128...) args31280 := []encdecArg[[31280]byte]{ { n: "Bin16", v: a31280, c: func(d []byte) bool { return d[0] == def.Bin16 }, }, } encdec(t, args31280...) args1031280 := []encdecArg[[1031280]byte]{ { n: "Bin32", v: a1031280, c: func(d []byte) bool { return d[0] == def.Bin32 }, }, } encdec(t, args1031280...) args := []encdecArg[[1]byte]{ { n: "Nil", v: nil, c: func(d []byte) bool { return d[0] == def.Nil }, e: "value different", }, } encdec(t, args...) }) t.Run("Error", func(t *testing.T) { args1 := []encdecArg[[1]byte]{ { n: "Nil", v: nil, c: func(d []byte) bool { return d[0] == def.Nil }, e: "value different", }, } encdec(t, args1...) var a128 [128]byte for i := range a128 { a128[i] = byte(rand.Intn(0xff)) } args2 := []encdecArg[[127]byte]{ { n: "Len", v: a128, c: func(d []byte) bool { return d[0] == def.Bin8 }, e: "[127]uint8 len is 127, but msgpack has 128 elements", }, } encdec(t, args2...) }) } func TestArray(t *testing.T) { t.Run("IntSlice", func(t *testing.T) { args := []encdecArg[[]int]{ { n: "Nil", v: ([]int)(nil), c: func(d []byte) bool { return d[0] == def.Nil }, }, { n: "FixArray", v: make([]int, 15), c: func(d []byte) bool { return def.FixArray <= d[0] && d[0] <= def.FixArray+0x0f }, }, { n: "Array16", v: make([]int, 30015), c: func(d []byte) bool { return d[0] == def.Array16 }, }, { n: "Array32", v: make([]int, 1030015), c: func(d []byte) bool { return d[0] == def.Array32 }, }, } encdec(t, args...) }) t.Run("FloatArray", func(t *testing.T) { var v [8]float32 for i := range v { v[i] = float32(rand.Intn(0xff)) } args := []encdecArg[[8]float32]{ { n: "FixArray", v: v, c: func(d []byte) bool { return def.FixArray <= d[0] && d[0] <= def.FixArray+0x0f }, }, } encdec(t, args...) }) t.Run("StringArray", func(t *testing.T) { var v [31280]string for i := range v { v[i] = "a" } args := []encdecArg[[31280]string]{ { n: "Array16", v: v, c: func(d []byte) bool { return d[0] == def.Array16 }, }, } encdec(t, args...) }) t.Run("BoolArray", func(t *testing.T) { var v [1031280]bool for i := range v { v[i] = rand.Intn(0xff) > 0x7f } args := []encdecArg[[1031280]bool]{ { n: "Array32", v: v, c: func(d []byte) bool { return d[0] == def.Array32 }, }, } encdec(t, args...) }) t.Run("SliceToArray", func(t *testing.T) { args := []encdecArg[[1]int]{ { n: "Int", v: ([]int)(nil), c: func(d []byte) bool { return d[0] == def.Nil }, e: "value different", }, } encdec(t, args...) }) t.Run("StringToBytes", func(t *testing.T) { const v = "abcde" args := []encdecArg[[5]byte]{ { n: "Test", v: v, skipEq: true, vc: func(t [5]byte) error { if v != string(t[:]) { return fmt.Errorf("value different %v, %v", v, string(t[:])) } return nil }, }, } encdec(t, args...) }) } func TestFixedSlice(t *testing.T) { c := func(d []byte) bool { return def.FixArray <= d[0] && d[0] <= def.FixArray+0x0f } t.Run("IntSlice", func(t *testing.T) { args := []encdecArg[[]int]{ { v: []int{-1, 1}, c: c, }, } encdec(t, args...) }) t.Run("UintSlice", func(t *testing.T) { args := []encdecArg[[]uint]{ { v: []uint{0, 100}, c: c, }, } encdec(t, args...) }) t.Run("Int8Slice", func(t *testing.T) { args := []encdecArg[[]int8]{ { v: []int8{math.MinInt8, math.MaxInt8}, c: c, }, } encdec(t, args...) }) t.Run("Int16Slice", func(t *testing.T) { args := []encdecArg[[]int16]{ { v: []int16{math.MinInt16, math.MaxInt16}, c: c, }, } encdec(t, args...) }) t.Run("Int32Slice", func(t *testing.T) { args := []encdecArg[[]int32]{ { v: []int32{math.MinInt32, math.MaxInt32}, c: c, }, } encdec(t, args...) }) t.Run("Int64Slice", func(t *testing.T) { args := []encdecArg[[]int64]{ { v: []int64{math.MinInt64, math.MaxInt64}, c: c, }, } encdec(t, args...) }) t.Run("Uint8Slice", func(t *testing.T) { args := []encdecArg[[]uint8]{ { v: []uint8{0, math.MaxUint8}, c: func(d []byte) bool { // byte array return def.Bin8 == d[0] }, }, } encdec(t, args...) }) t.Run("Uint16Slice", func(t *testing.T) { args := []encdecArg[[]uint16]{ { v: []uint16{0, math.MaxUint16}, c: c, }, } encdec(t, args...) }) t.Run("Uint32Slice", func(t *testing.T) { args := []encdecArg[[]uint32]{ { v: []uint32{0, math.MaxUint32}, c: c, }, } encdec(t, args...) }) t.Run("Uint64Slice", func(t *testing.T) { args := []encdecArg[[]uint64]{ { v: []uint64{0, math.MaxUint64}, c: c, }, } encdec(t, args...) }) t.Run("Float32Slice", func(t *testing.T) { args := []encdecArg[[]float32]{ { v: []float32{math.SmallestNonzeroFloat32, math.MaxFloat32}, c: c, }, } encdec(t, args...) }) t.Run("Float64Slice", func(t *testing.T) { args := []encdecArg[[]float64]{ { v: []float64{math.SmallestNonzeroFloat64, math.MaxFloat64}, c: c, }, } encdec(t, args...) }) t.Run("StringSlice", func(t *testing.T) { args := []encdecArg[[]string]{ { v: []string{"aaa", "bbb"}, c: c, }, } encdec(t, args...) }) t.Run("BoolSlice", func(t *testing.T) { args := []encdecArg[[]bool]{ { v: []bool{true, false}, c: c, }, } encdec(t, args...) }) } func TestFixedMap(t *testing.T) { c := func(d []byte) bool { return def.FixMap <= d[0] && d[0] <= def.FixMap+0x0f } t.Run("MapStringInt", func(t *testing.T) { args := []encdecArg[map[string]int]{ { v: map[string]int{"a": 1, "b": 2}, c: c, }, } encdec(t, args...) }) t.Run("MapStringUint", func(t *testing.T) { args := []encdecArg[map[string]uint]{ { v: map[string]uint{"a": math.MaxUint32, "b": 0}, c: c, }, } encdec(t, args...) }) t.Run("MapStringString", func(t *testing.T) { args := []encdecArg[map[string]string]{ { v: map[string]string{"a": "12345", "abcdefghijklmnopqrstuvwxyz": "abcdefghijklmnopqrstuvwxyz"}, c: c, }, } encdec(t, args...) }) t.Run("MapStringFloat32", func(t *testing.T) { args := []encdecArg[map[string]float32]{ { v: map[string]float32{"a": math.MaxFloat32, "b": math.SmallestNonzeroFloat32}, c: c, }, } encdec(t, args...) }) t.Run("MapStringFloat64", func(t *testing.T) { args := []encdecArg[map[string]float64]{ { v: map[string]float64{"a": math.MaxFloat64, "b": math.SmallestNonzeroFloat64}, c: c, }, } encdec(t, args...) }) t.Run("MapStringBool", func(t *testing.T) { args := []encdecArg[map[string]bool]{ { v: map[string]bool{"a": true, "b": false}, c: c, }, } encdec(t, args...) }) t.Run("MapStringInt8", func(t *testing.T) { args := []encdecArg[map[string]int8]{ { v: map[string]int8{"a": math.MinInt8, "b": math.MaxInt8}, c: c, }, } encdec(t, args...) }) t.Run("MapStringInt16", func(t *testing.T) { args := []encdecArg[map[string]int16]{ { v: map[string]int16{"a": math.MaxInt16, "b": math.MinInt16}, c: c, }, } encdec(t, args...) }) t.Run("MapStringInt32", func(t *testing.T) { args := []encdecArg[map[string]int32]{ { v: map[string]int32{"a": math.MaxInt32, "b": math.MinInt32}, c: c, }, } encdec(t, args...) }) t.Run("MapStringInt64", func(t *testing.T) { args := []encdecArg[map[string]int64]{ { v: map[string]int64{"a": math.MinInt64, "b": math.MaxInt64}, c: c, }, } encdec(t, args...) }) t.Run("MapStringUint8", func(t *testing.T) { args := []encdecArg[map[string]uint8]{ { v: map[string]uint8{"a": 0, "b": math.MaxUint8}, c: c, }, } encdec(t, args...) }) t.Run("MapStringUint16", func(t *testing.T) { args := []encdecArg[map[string]uint16]{ { v: map[string]uint16{"a": 0, "b": math.MaxUint16}, c: c, }, } encdec(t, args...) }) t.Run("MapStringUint32", func(t *testing.T) { args := []encdecArg[map[string]uint32]{ { v: map[string]uint32{"a": 0, "b": math.MaxUint32}, c: c, }, } encdec(t, args...) }) t.Run("MapStringUint64", func(t *testing.T) { args := []encdecArg[map[string]uint64]{ { v: map[string]uint64{"a": 0, "b": math.MaxUint64}, c: c, }, } encdec(t, args...) }) t.Run("MapIntString", func(t *testing.T) { args := []encdecArg[map[int]string]{ { v: map[int]string{0: "a", 1: "b"}, c: c, }, } encdec(t, args...) }) t.Run("MapIntBool", func(t *testing.T) { args := []encdecArg[map[int]bool]{ { v: map[int]bool{1: true, 2: false}, c: c, }, } encdec(t, args...) }) t.Run("MapUintString", func(t *testing.T) { args := []encdecArg[map[uint]string]{ { v: map[uint]string{0: "a", 1: "b"}, c: c, }, } encdec(t, args...) }) t.Run("MapUintBool", func(t *testing.T) { args := []encdecArg[map[uint]bool]{ { v: map[uint]bool{0: true, 255: false}, c: c, }, } encdec(t, args...) }) t.Run("MapFloat32String", func(t *testing.T) { args := []encdecArg[map[float32]string]{ { v: map[float32]string{math.MaxFloat32: "a", math.SmallestNonzeroFloat32: "b"}, c: c, }, } encdec(t, args...) }) t.Run("MapFloat32Bool", func(t *testing.T) { args := []encdecArg[map[float32]bool]{ { v: map[float32]bool{math.SmallestNonzeroFloat32: true, math.MaxFloat32: false}, c: c, }, } encdec(t, args...) }) t.Run("MapFloat64String", func(t *testing.T) { args := []encdecArg[map[float64]string]{ { v: map[float64]string{math.MaxFloat64: "a", math.SmallestNonzeroFloat64: "b"}, c: c, }, } encdec(t, args...) }) t.Run("MapFloat64Bool", func(t *testing.T) { args := []encdecArg[map[float64]bool]{ { v: map[float64]bool{math.SmallestNonzeroFloat64: true, math.MaxFloat64: false}, c: c, }, } encdec(t, args...) }) t.Run("MapInt8String", func(t *testing.T) { args := []encdecArg[map[int8]string]{ { v: map[int8]string{math.MinInt8: "a", math.MaxInt8: "b"}, c: c, }, } encdec(t, args...) }) t.Run("MapInt8Bool", func(t *testing.T) { args := []encdecArg[map[int8]bool]{ { v: map[int8]bool{math.MinInt8: true, math.MaxInt8: false}, c: c, }, } encdec(t, args...) }) t.Run("MapInt16String", func(t *testing.T) { args := []encdecArg[map[int16]string]{ { v: map[int16]string{math.MaxInt16: "a", math.MinInt16: "b"}, c: c, }, } encdec(t, args...) }) t.Run("MapInt16Bool", func(t *testing.T) { args := []encdecArg[map[int16]bool]{ { v: map[int16]bool{math.MaxInt16: true, math.MinInt16: false}, c: c, }, } encdec(t, args...) }) t.Run("MapInt32String", func(t *testing.T) { args := []encdecArg[map[int32]string]{ { v: map[int32]string{math.MinInt32: "a", math.MaxInt32: "b"}, c: c, }, } encdec(t, args...) }) t.Run("MapInt32Bool", func(t *testing.T) { args := []encdecArg[map[int32]bool]{ { v: map[int32]bool{math.MinInt32: true, math.MaxInt32: false}, c: c, }, } encdec(t, args...) }) t.Run("MapInt64String", func(t *testing.T) { args := []encdecArg[map[int64]string]{ { v: map[int64]string{math.MaxInt64: "a", math.MinInt64: "b"}, c: c, }, } encdec(t, args...) }) t.Run("MapInt64Bool", func(t *testing.T) { args := []encdecArg[map[int64]bool]{ { v: map[int64]bool{math.MaxInt64: true, math.MinInt64: false}, c: c, }, } encdec(t, args...) }) t.Run("MapUint8String", func(t *testing.T) { args := []encdecArg[map[uint8]string]{ { v: map[uint8]string{0: "a", math.MaxUint8: "b"}, c: c, }, } encdec(t, args...) }) t.Run("MapUint8Bool", func(t *testing.T) { args := []encdecArg[map[uint8]bool]{ { v: map[uint8]bool{0: true, math.MaxUint8: false}, c: c, }, } encdec(t, args...) }) t.Run("MapUint16String", func(t *testing.T) { args := []encdecArg[map[uint16]string]{ { v: map[uint16]string{0: "a", math.MaxUint16: "b"}, c: c, }, } encdec(t, args...) }) t.Run("MapUint16Bool", func(t *testing.T) { args := []encdecArg[map[uint16]bool]{ { v: map[uint16]bool{0: true, math.MaxUint16: false}, c: c, }, } encdec(t, args...) }) t.Run("MapUint32String", func(t *testing.T) { args := []encdecArg[map[uint32]string]{ { v: map[uint32]string{0: "a", math.MaxUint32: "b"}, c: c, }, } encdec(t, args...) }) t.Run("MapUint32Bool", func(t *testing.T) { args := []encdecArg[map[uint32]bool]{ { v: map[uint32]bool{0: true, math.MaxUint32: false}, c: c, }, } encdec(t, args...) }) t.Run("MapUint64String", func(t *testing.T) { args := []encdecArg[map[uint64]string]{ { v: map[uint64]string{0: "a", math.MaxUint64: "b"}, c: c, }, } encdec(t, args...) }) t.Run("MapUint64Bool", func(t *testing.T) { args := []encdecArg[map[uint64]bool]{ { v: map[uint64]bool{0: true, math.MaxUint64: false}, c: c, }, } encdec(t, args...) }) } func TestTime(t *testing.T) { t.Run("Time", func(t *testing.T) { args := []encdecArg[time.Time]{ { n: "Fixext4", v: time.Unix(now.Unix(), 0).UTC(), c: func(d []byte) bool { return d[0] == def.Fixext4 }, }, { n: "Fixext8", v: time.Unix(now.Unix(), int64(now.Nanosecond())).UTC(), c: func(d []byte) bool { return d[0] == def.Fixext8 }, }, } encdec(t, args...) }) t.Run("Error", func(t *testing.T) { now := time.Now().Unix() nowByte := make([]byte, 4) binary.BigEndian.PutUint32(nowByte, uint32(now)) var r time.Time c := def.TimeStamp for _, u := range unmarshallers { t.Run(u.name+"64formats", func(t *testing.T) { nanoByte := make([]byte, 64) for i := range nanoByte[:30] { nanoByte[i] = 0xff } b := append([]byte{def.Fixext8, byte(c)}, nanoByte...) err := u.u(b, &r) ErrorContains(t, err, "timestamp 64 formats") }) t.Run(u.name+"96formats", func(t *testing.T) { nanoByte := make([]byte, 96) for i := range nanoByte[:32] { nanoByte[i] = 0xff } b := append([]byte{def.Ext8, byte(12), byte(c)}, nanoByte...) err := u.u(b, &r) ErrorContains(t, err, "timestamp 96 formats") }) } t.Run("ShouldNotReach", func(t *testing.T) { notReach := []byte{def.Fixext1} _, _, err := extTime.Decoder.AsValue(0, reflect.Bool, ¬Reach) ErrorContains(t, err, "should not reach this line") }) t.Run("ShouldNotReachStream", func(t *testing.T) { _, err := extTime.StreamDecoder.ToValue(def.Fixext1, nil, reflect.Bool) ErrorContains(t, err, "should not reach this line") }) }) loc := time.Local utc8 := time.FixedZone("UTC-8", -8*60*60) time.Local = utc8 t.Cleanup(func() { time.Local = loc msgpack.SetDecodedTimeAsUTC() }) t.Run("Default", func(t *testing.T) { args := []encdecArg[time.Time]{ { n: "case", v: time.Unix(now.Unix(), 0), vc: func(r time.Time) error { tu.Equal(t, now.Unix(), r.Unix()) tu.Equal(t, r.Location(), time.UTC) return nil }, skipEq: true, // skip equal check because of location difference }, } encdec(t, args...) }) t.Run("UTC", func(t *testing.T) { msgpack.SetDecodedTimeAsUTC() args := []encdecArg[time.Time]{ { n: "case", v: time.Unix(now.Unix(), 0), vc: func(r time.Time) error { tu.Equal(t, now.Unix(), r.Unix()) tu.Equal(t, r.Location(), time.UTC) return nil }, skipEq: true, // skip equal check because of location difference }, } encdec(t, args...) }) t.Run("Local", func(t *testing.T) { msgpack.SetDecodedTimeAsLocal() args := []encdecArg[time.Time]{ { n: "case", v: time.Unix(now.Unix(), 0), vc: func(r time.Time) error { tu.Equal(t, r.Location(), time.Local) tu.Equal(t, r.Location(), utc8) return nil }, }, } encdec(t, args...) }) } func TestMap(t *testing.T) { mapIntInt := func(l int) map[int]int { m := make(map[int]int, l) for i := 0; i < l; i++ { m[i] = i + 1 } return m } t.Run("MapCode", func(t *testing.T) { args := []encdecArg[map[int]int]{ { n: "FixMap", v: map[int]int{1: 2, 3: 4, 5: 6, 7: 8, 9: 10}, c: func(d []byte) bool { return def.FixMap <= d[0] && d[0] <= def.FixMap+0x0f }, }, { n: "Map16", v: mapIntInt(1000), c: func(d []byte) bool { return d[0] == def.Map16 }, }, { n: "Map32", v: mapIntInt(math.MaxUint16 + 1), c: func(d []byte) bool { return d[0] == def.Map32 }, }, } encdec(t, args...) }) t.Run("DiffType", func(t *testing.T) { v := mapIntInt(100) args := []encdecArg[map[uint]uint]{ { n: "IntIntToUintUint", v: v, c: func(d []byte) bool { return d[0] == def.Map16 }, skipEq: true, vc: func(r map[uint]uint) error { for k, vv := range v { if rv, ok := r[uint(k)]; !ok || rv != uint(vv) { return fmt.Errorf("value differentee") } } if len(v) != len(r) { return fmt.Errorf("value different. v:%d, r:%d", len(v), len(r)) } return nil }, }, } encdec(t, args...) }) // error t.Run("Error", func(t *testing.T) { v1 := make(map[string]int, 100) for i := 0; i < 100; i++ { v1[fmt.Sprintf("%03d", i)] = i } v2 := make(map[int]string, 100) for i := 0; i < 100; i++ { v2[i] = fmt.Sprint(i % 10) } args := []encdecArg[map[int]int]{ { n: "InvalidKey", v: v1, c: func(d []byte) bool { return d[0] == def.Map16 }, e: "invalid code a3 decoding", }, { n: "InvalidValue", v: v2, c: func(d []byte) bool { return d[0] == def.Map16 }, e: "invalid code a1 decoding", }, } encdec(t, args...) }) } func TestPointer(t *testing.T) { t.Run("Pointer", func(t *testing.T) { v := 250 vv := &v var vvv *int args := []encdecArg[*int]{ { n: "Int", v: vv, c: func(d []byte) bool { return d[0] == def.Uint8 }, }, { n: "Nil", v: vvv, c: func(d []byte) bool { return d[0] == def.Nil }, }, } encdec(t, args...) }) // error t.Run("ReceiverMustBePointer", func(t *testing.T) { for _, u := range unmarshallers { var r int t.Run(u.name, func(t *testing.T) { err := u.u([]byte{def.Nil}, r) ErrorContains(t, err, "receiver not pointer") }) } }) } func TestUnsupported(t *testing.T) { b := []byte{0xc0} t.Run("Uintptr", func(t *testing.T) { for _, m := range marshallers { t.Run(m.name, func(t *testing.T) { var v uintptr _, err := m.m(v) ErrorContains(t, err, "uintptr is unsupported type") }) } for _, u := range unmarshallers { t.Run(u.name, func(t *testing.T) { var r uintptr err := u.u(b, &r) ErrorContains(t, err, "uintptr is unsupported type") }) } }) t.Run("Chan", func(t *testing.T) { for _, m := range marshallers { t.Run(m.name, func(t *testing.T) { var v chan string _, err := m.m(v) ErrorContains(t, err, "chan is unsupported type") }) } for _, u := range unmarshallers { t.Run(u.name, func(t *testing.T) { var r chan string err := u.u(b, &r) ErrorContains(t, err, "chan is unsupported type") }) } }) t.Run("Func", func(t *testing.T) { for _, m := range marshallers { t.Run(m.name, func(t *testing.T) { var v func() _, err := m.m(v) ErrorContains(t, err, "func is unsupported type") }) } for _, u := range unmarshallers { t.Run(u.name, func(t *testing.T) { var r func() err := u.u(b, &r) ErrorContains(t, err, "func is unsupported type") }) } }) t.Run("Error", func(t *testing.T) { // error reflect kind is invalid. current version set nil (0xc0) for _, m := range marshallers { for _, u := range unmarshallers { t.Run(m.name+u.name, func(t *testing.T) { var v, r error bb, err := m.m(v) NoError(t, err) if bb[0] != def.Nil { t.Fatalf("code is different %d, %d", b[0], def.Nil) } err = u.u(b, &r) NoError(t, err) }) } } }) { // error reflect kind is invalid. current version set nil (0xc0) var v, r error bb, err := msgpack.Marshal(v) if err != nil { t.Error(err) } if bb[0] != def.Nil { t.Errorf("code is different %d, %d", bb[0], def.Nil) } err = msgpack.Unmarshal(b, &r) if err != nil { t.Error(err) } if r != nil { t.Error("error should be nil") } } } ///////////////////////////////////////////////////////////////// func TestStruct(t *testing.T) { t.Run("Code", func(t *testing.T) { testStructCode(t) }) t.Run("Tag", func(t *testing.T) { testStructTag(t) }) t.Run("Omitempty", func(t *testing.T) { TestStructTagOmitempty(t) }) t.Run("Array", func(t *testing.T) { testStructArray(t) }) t.Run("Embedded", func(t *testing.T) { testEmbedded(t) }) t.Run("EmbeddedStruct", func(t *testing.T) { testEmbeddedStruct(t) }) t.Run("Jump", func(t *testing.T) { testStructJump(t) }) t.Run("UseCase", func(t *testing.T) { b := msgpack.StructAsArray defer func() { msgpack.StructAsArray = b }() testStructUseCase(t) msgpack.StructAsArray = true testStructUseCase(t) }) } func testEmbedded(t *testing.T) { type Emb struct { Int int } type A struct { Emb } v := A{Emb: Emb{Int: 2}} arg := encdecArg[A]{ v: v, vc: func(t A) error { if v.Int != t.Int { return fmt.Errorf("value is different %v, %v", v, t) } return nil }, } encdec(t, arg) } func testEmbeddedStruct(t *testing.T) { b := msgpack.StructAsArray defer func() { msgpack.StructAsArray = b }() t.Run("SimpleEmbedded", func(t *testing.T) { type Embedded struct { EmbeddedField int Name string } type Parent struct { Embedded ParentField string } original := Parent{ Embedded: Embedded{EmbeddedField: 42, Name: "test"}, ParentField: "parent", } // Test with msgpack _, err := msgpack.MarshalAsMap(original) if err != nil { t.Fatalf("msgpack.Marshal failed: %v", err) } var msgDecoded Parent // Test stream encoding/decoding and cross-compatibility for _, isArray := range []bool{false, true} { msgpack.StructAsArray = isArray encdec(t, encdecArg[Parent]{ v: original, skipEq: true, vc: func(p Parent) error { // Verify fields are promoted tu.Equal(t, p.EmbeddedField, 42) tu.Equal(t, p.Name, "test") tu.Equal(t, p.ParentField, "parent") msgDecoded = p return nil }, }) } // Compare with JSON jsonBytes, _ := json.Marshal(original) var jsonDecoded Parent _ = json.Unmarshal(jsonBytes, &jsonDecoded) if msgDecoded.EmbeddedField != jsonDecoded.EmbeddedField || msgDecoded.Name != jsonDecoded.Name || msgDecoded.ParentField != jsonDecoded.ParentField { t.Errorf("msgpack and json results differ:\nmsgpack: %+v\njson: %+v", msgDecoded, jsonDecoded) } }) t.Run("EmbeddedWithTag", func(t *testing.T) { type Embedded struct { Field int } type Parent struct { Embedded `msgpack:"emb"` Regular string } original := Parent{ Embedded: Embedded{Field: 99}, Regular: "value", } _, err := msgpack.MarshalAsMap(original) if err != nil { t.Fatalf("msgpack.Marshal failed: %v", err) } var msgDecoded Parent // Test stream encoding/decoding and cross-compatibility for _, isArray := range []bool{false, true} { msgpack.StructAsArray = isArray encdec(t, encdecArg[Parent]{ v: original, skipEq: true, vc: func(p Parent) error { // Tagged embedded struct should not be promoted //nolint:staticcheck // QF1008 for test tu.Equal(t, p.Embedded.Field, 99) tu.Equal(t, p.Regular, "value") msgDecoded = p return nil }, }) } // Compare with JSON jsonBytes, _ := json.Marshal(original) var jsonDecoded Parent _ = json.Unmarshal(jsonBytes, &jsonDecoded) //nolint:staticcheck // QF1008 for test if msgDecoded.Embedded.Field != jsonDecoded.Embedded.Field || msgDecoded.Regular != jsonDecoded.Regular { t.Errorf("msgpack and json results differ:\nmsgpack: %+v\njson: %+v", msgDecoded, jsonDecoded) } }) t.Run("MultiLevelEmbedding", func(t *testing.T) { type Deep struct { DeepField int } type Middle struct { Deep MiddleField string } type Top struct { Middle TopField bool } original := Top{ Middle: Middle{ Deep: Deep{DeepField: 100}, MiddleField: "middle", }, TopField: true, } _, err := msgpack.MarshalAsMap(original) if err != nil { t.Fatalf("msgpack.Marshal failed: %v", err) } var msgDecoded Top // Test stream encoding/decoding and cross-compatibility for _, isArray := range []bool{false, true} { msgpack.StructAsArray = isArray encdec(t, encdecArg[Top]{ v: original, skipEq: true, vc: func(p Top) error { tu.Equal(t, p.DeepField, 100) tu.Equal(t, p.MiddleField, "middle") tu.Equal(t, p.TopField, true) msgDecoded = p return nil }, }) } // Compare with JSON jsonBytes, _ := json.Marshal(original) var jsonDecoded Top _ = json.Unmarshal(jsonBytes, &jsonDecoded) if msgDecoded.DeepField != jsonDecoded.DeepField || msgDecoded.MiddleField != jsonDecoded.MiddleField || msgDecoded.TopField != jsonDecoded.TopField { t.Errorf("msgpack and json results differ:\nmsgpack: %+v\njson: %+v", msgDecoded, jsonDecoded) } }) t.Run("FieldShadowing", func(t *testing.T) { type Base struct { Name string Age int } type Derived struct { Base Name string // Shadows Base.Name } original := Derived{ Base: Base{Name: "base", Age: 30}, Name: "derived", } _, err := msgpack.MarshalAsMap(original) if err != nil { t.Fatalf("msgpack.Marshal failed: %v", err) } var msgDecoded Derived // Test stream encoding/decoding and cross-compatibility for _, isArray := range []bool{false, true} { msgpack.StructAsArray = isArray encdec(t, encdecArg[Derived]{ v: original, skipEq: true, vc: func(p Derived) error { tu.Equal(t, p.Name, "derived") tu.Equal(t, p.Age, 30) tu.Equal(t, p.Base.Name, "") msgDecoded = p return nil }, }) } // Compare with JSON jsonBytes, _ := json.Marshal(original) var jsonDecoded Derived _ = json.Unmarshal(jsonBytes, &jsonDecoded) if msgDecoded.Name != jsonDecoded.Name || msgDecoded.Age != jsonDecoded.Age { t.Errorf("msgpack and json results differ:\nmsgpack: %+v\njson: %+v", msgDecoded, jsonDecoded) } }) t.Run("TaggedFieldPriority", func(t *testing.T) { type Tagged struct { Name string `msgpack:"Name" json:"Name"` } type Plain struct { Name string } type Derived struct { Tagged Plain } original := Derived{ Tagged: Tagged{Name: "tagged"}, Plain: Plain{Name: "plain"}, } _, err := msgpack.MarshalAsMap(original) if err != nil { t.Fatalf("msgpack.Marshal failed: %v", err) } var msgDecoded Derived for _, isArray := range []bool{false, true} { msgpack.StructAsArray = isArray encdec(t, encdecArg[Derived]{ v: original, skipEq: true, vc: func(p Derived) error { tu.Equal(t, p.Tagged.Name, "tagged") tu.Equal(t, p.Plain.Name, "") msgDecoded = p return nil }, }) } jsonBytes, _ := json.Marshal(original) var jsonDecoded Derived _ = json.Unmarshal(jsonBytes, &jsonDecoded) if msgDecoded.Tagged.Name != jsonDecoded.Tagged.Name || msgDecoded.Plain.Name != jsonDecoded.Plain.Name { t.Errorf("msgpack and json results differ:\nmsgpack: %+v\njson: %+v", msgDecoded, jsonDecoded) } }) t.Run("EmbeddedOmitEmpty", func(t *testing.T) { type Embedded struct { A int B string } type Parent struct { Embedded `msgpack:",omitempty"` C int } msgpack.StructAsArray = false encdec(t, encdecArg[map[string]any]{ v: Parent{Embedded: Embedded{}, C: 1}, skipEq: true, vc: func(p map[string]any) error { if _, ok := p["A"]; ok { return fmt.Errorf("embedded field A should be omitted") } if _, ok := p["B"]; ok { return fmt.Errorf("embedded field B should be omitted") } if _, ok := p["C"]; !ok { return fmt.Errorf("field C should be present") } return nil }, }) encdec(t, encdecArg[map[string]any]{ v: Parent{Embedded: Embedded{A: 1, B: "b"}, C: 2}, skipEq: true, vc: func(p map[string]any) error { if _, ok := p["A"]; !ok { return fmt.Errorf("embedded field A should be present") } if _, ok := p["B"]; !ok { return fmt.Errorf("embedded field B should be present") } if _, ok := p["C"]; !ok { return fmt.Errorf("field C should be present") } return nil }, }) msgpack.StructAsArray = true encdec(t, encdecArg[Parent]{ v: Parent{Embedded: Embedded{}, C: 1}, c: func(d []byte) bool { return len(d) == 4 && d[0] == def.FixArray+0x03 && d[1] == 0x01 && d[2] == def.Nil && d[3] == def.Nil }, vc: func(p Parent) error { if p.A != 0 || p.B != "" || p.C != 1 { return fmt.Errorf("unexpected array decode: %+v", p) } return nil }, }) encdec(t, encdecArg[Parent]{ v: Parent{Embedded: Embedded{A: 1, B: "b"}, C: 2}, c: func(d []byte) bool { return len(d) == 5 && d[0] == def.FixArray+0x03 && d[1] == 0x02 && d[2] == 0x01 && d[3] == 0xa1 && d[4] == 'b' }, vc: func(p Parent) error { if p.A != 1 || p.B != "b" || p.C != 2 { return fmt.Errorf("unexpected array decode: %+v", p) } return nil }, }) }) t.Run("AmbiguousFields", func(t *testing.T) { type A struct { Field string } type B struct { Field string } type Derived struct { A B } original := Derived{ A: A{Field: "from A"}, B: B{Field: "from B"}, } _, err := msgpack.MarshalAsMap(original) if err != nil { t.Fatalf("msgpack.Marshal failed: %v", err) } // Decode to map to check what fields are present var msgDecoded map[string]interface{} // Test stream encoding/decoding and cross-compatibility for _, isArray := range []bool{false} { msgpack.StructAsArray = isArray encdec(t, encdecArg[map[string]any]{ v: original, skipEq: true, vc: func(p map[string]any) error { // Ambiguous field should be omitted if _, exists := p["Field"]; exists { t.Errorf("Ambiguous field 'Field' should be omitted") } msgDecoded = p return nil }, }) } // Compare with JSON jsonBytes, _ := json.Marshal(original) var jsonMap map[string]interface{} _ = json.Unmarshal(jsonBytes, &jsonMap) if len(msgDecoded) != len(jsonMap) { t.Errorf("Field count differs: msgpack=%d, json=%d", len(msgDecoded), len(jsonMap)) } }) t.Run("PointerEmbedded", func(t *testing.T) { type Base struct { Value int } type Derived struct { *Base Extra string } original := Derived{ Base: &Base{Value: 123}, Extra: "extra", } _, err := msgpack.MarshalAsMap(original) if err != nil { t.Fatalf("msgpack.Marshal failed: %v", err) } var msgDecoded Derived // Test stream encoding/decoding and cross-compatibility for _, isArray := range []bool{false, true} { msgpack.StructAsArray = isArray encdec(t, encdecArg[Derived]{ v: original, skipEq: true, vc: func(p Derived) error { if p.Base == nil { return fmt.Errorf("base pointer should not be nil after unmarshal") } tu.Equal(t, p.Value, 123) tu.Equal(t, p.Extra, "extra") msgDecoded = p return nil }, }) } // Compare with JSON jsonBytes, _ := json.Marshal(original) var jsonDecoded Derived _ = json.Unmarshal(jsonBytes, &jsonDecoded) if msgDecoded.Value != jsonDecoded.Value || msgDecoded.Extra != jsonDecoded.Extra { t.Errorf("msgpack and json results differ:\nmsgpack: %+v\njson: %+v", msgDecoded, jsonDecoded) } }) t.Run("PointerEmbeddedNil", func(t *testing.T) { type Base struct { Value int } type Derived struct { *Base Extra string } original := Derived{ Base: nil, Extra: "extra", } _, err := msgpack.MarshalAsMap(original) if err != nil { t.Fatalf("msgpack.Marshal failed: %v", err) } msgpack.StructAsArray = false encdec(t, encdecArg[Derived]{ v: original, skipEq: true, vc: func(p Derived) error { if p.Base != nil { return fmt.Errorf("base pointer should be nil after unmarshal") } tu.Equal(t, p.Extra, "extra") return nil }, }) msgpack.StructAsArray = true encdec(t, encdecArg[Derived]{ v: original, skipEq: true, vc: func(p Derived) error { if p.Base != nil { return fmt.Errorf("base pointer should be nil after unmarshal") } tu.Equal(t, p.Extra, "extra") return nil }, }) /* var msgDecoded Derived for _, isArray := range []bool{false, true} { msgpack.StructAsArray = isArray encdec(t, encdecArg[Derived]{ v: original, skipEq: true, vc: func(p Derived) error { if p.Base != nil { return fmt.Errorf("base pointer should be nil after unmarshal") } tu.Equal(t, p.Extra, "extra") msgDecoded = p return nil }, }) } // Compare with JSON jsonBytes, _ := json.Marshal(original) var jsonDecoded Derived _ = json.Unmarshal(jsonBytes, &jsonDecoded) tu.Equal(t, jsonDecoded, msgDecoded) */ original.Base = &Base{Value: 123} for _, isArray := range []bool{false, true} { msgpack.StructAsArray = isArray encdec(t, encdecArg[Derived]{ v: original, skipEq: true, vc: func(p Derived) error { //nolint:staticcheck // QF1008 for test tu.Equal(t, p.Base.Value, 123) tu.Equal(t, p.Extra, "extra") return nil }, }) } }) t.Run("TagSameAsStructName", func(t *testing.T) { // Edge case: tag with same name as struct field type Inner struct { Value int } type Outer struct { Inner `msgpack:"Inner"` Other string } original := Outer{ Inner: Inner{Value: 456}, Other: "test", } _, err := msgpack.MarshalAsMap(original) if err != nil { t.Fatalf("msgpack.Marshal failed: %v", err) } var msgDecoded Outer // Test stream encoding/decoding and cross-compatibility for _, isArray := range []bool{false, true} { msgpack.StructAsArray = isArray encdec(t, encdecArg[Outer]{ v: original, skipEq: true, vc: func(p Outer) error { //nolint:staticcheck // QF1008 for test tu.Equal(t, p.Inner.Value, 456) tu.Equal(t, p.Other, "test") msgDecoded = p return nil }, }) } // Compare with JSON jsonBytes, _ := json.Marshal(original) var jsonDecoded Outer _ = json.Unmarshal(jsonBytes, &jsonDecoded) //nolint:staticcheck // QF1008 for test if msgDecoded.Inner.Value != jsonDecoded.Inner.Value || msgDecoded.Other != jsonDecoded.Other { t.Errorf("msgpack and json results differ:\nmsgpack: %+v\njson: %+v", msgDecoded, jsonDecoded) } }) t.Run("MixedEmbeddedAndRegular", func(t *testing.T) { type Timestamp struct { CreatedAt string UpdatedAt string } type Document struct { Timestamp ID int Content string Title string } original := Document{ Timestamp: Timestamp{ CreatedAt: "2024-01-01", UpdatedAt: "2024-01-02", }, ID: 42, Content: "test content", Title: "test title", } _, err := msgpack.MarshalAsMap(original) if err != nil { t.Fatalf("msgpack.Marshal failed: %v", err) } var msgDecoded Document // Test stream encoding/decoding and cross-compatibility for _, isArray := range []bool{false, true} { msgpack.StructAsArray = isArray encdec(t, encdecArg[Document]{ v: original, skipEq: true, vc: func(p Document) error { tu.Equal(t, p.CreatedAt, "2024-01-01") tu.Equal(t, p.UpdatedAt, "2024-01-02") tu.Equal(t, p.ID, 42) tu.Equal(t, p.Content, "test content") tu.Equal(t, p.Title, "test title") msgDecoded = p return nil }, }) } // Compare with JSON jsonBytes, _ := json.Marshal(original) var jsonDecoded Document _ = json.Unmarshal(jsonBytes, &jsonDecoded) if msgDecoded.CreatedAt != jsonDecoded.CreatedAt || msgDecoded.UpdatedAt != jsonDecoded.UpdatedAt || msgDecoded.ID != jsonDecoded.ID || msgDecoded.Content != jsonDecoded.Content || msgDecoded.Title != jsonDecoded.Title { t.Errorf("msgpack and json results differ:\nmsgpack: %+v\njson: %+v", msgDecoded, jsonDecoded) } }) t.Run("EmbeddedWithPrivateFields", func(t *testing.T) { type Base struct { Public string private int } type Derived struct { Base Extra string } original := Derived{ Base: Base{Public: "public", private: 123}, Extra: "extra", } _, err := msgpack.MarshalAsMap(original) if err != nil { t.Fatalf("msgpack.Marshal failed: %v", err) } var msgDecoded Derived // Test stream encoding/decoding and cross-compatibility for _, isArray := range []bool{false, true} { msgpack.StructAsArray = isArray encdec(t, encdecArg[Derived]{ v: original, skipEq: true, vc: func(p Derived) error { // Only public fields should be marshaled tu.Equal(t, p.Public, "public") tu.Equal(t, p.Extra, "extra") tu.Equal(t, p.private, 0) // private field should be zero value msgDecoded = p return nil }, }) } // Compare with JSON jsonBytes, _ := json.Marshal(original) var jsonDecoded Derived _ = json.Unmarshal(jsonBytes, &jsonDecoded) if msgDecoded.Public != jsonDecoded.Public || msgDecoded.Extra != jsonDecoded.Extra { t.Errorf("msgpack and json results differ:\nmsgpack: %+v\njson: %+v", msgDecoded, jsonDecoded) } }) t.Run("ComplexNestedEmbedding", func(t *testing.T) { type A struct { FieldA string } type B struct { A FieldB int } type C struct { B FieldC bool } type D struct { C FieldD float64 } original := D{ C: C{ B: B{ A: A{FieldA: "a"}, FieldB: 1, }, FieldC: true, }, FieldD: 3.14, } _, err := msgpack.MarshalAsMap(original) if err != nil { t.Fatalf("msgpack.Marshal failed: %v", err) } var msgDecoded D // Test stream encoding/decoding and cross-compatibility for _, isArray := range []bool{false, true} { msgpack.StructAsArray = isArray encdec(t, encdecArg[D]{ v: original, skipEq: true, vc: func(p D) error { // All fields should be promoted to top level tu.Equal(t, p.FieldA, "a") tu.Equal(t, p.FieldB, 1) tu.Equal(t, p.FieldC, true) tu.Equal(t, p.FieldD, 3.14) msgDecoded = p return nil }, }) } // Compare with JSON jsonBytes, _ := json.Marshal(original) var jsonDecoded D _ = json.Unmarshal(jsonBytes, &jsonDecoded) if msgDecoded.FieldA != jsonDecoded.FieldA || msgDecoded.FieldB != jsonDecoded.FieldB || msgDecoded.FieldC != jsonDecoded.FieldC || msgDecoded.FieldD != jsonDecoded.FieldD { t.Errorf("msgpack and json results differ:\nmsgpack: %+v\njson: %+v", msgDecoded, jsonDecoded) } }) } func testStructTag(t *testing.T) { type vSt struct { One int `msgpack:"Three"` Two string `msgpack:"four"` Hfn bool `msgpack:"-"` } type rSt struct { Three int Four string `msgpack:"four"` Hfn bool } v := vSt{One: 1, Two: "2", Hfn: true} msgpack.StructAsArray = false arg := encdecArg[rSt]{ v: v, c: func(d []byte) bool { return d[0] == def.FixMap+0x02 }, skipEq: true, vc: func(r rSt) error { if v.One != r.Three || v.Two != r.Four || r.Hfn != false { return fmt.Errorf("error: %v, %v", v, r) } return nil }, } encdec(t, arg) } func TestStructTagOmitempty(t *testing.T) { type vSt struct { One int `msgpack:",omitempty"` Two string `msgpack:"four,omitempty"` Hfn bool `msgpack:"-,omitempty"` Slice []int `msgpack:",omitempty"` } type rSt struct { One int Four string `msgpack:"four"` Hfn bool `msgpack:"-"` Slice []int } cases := []struct { name string v vSt c func(d []byte) bool array bool }{ { name: "all empty", v: vSt{}, c: func(d []byte) bool { return d[0] == def.FixMap+0x00 && len(d) == 1 }, }, { name: "int", v: vSt{One: 1}, c: func(d []byte) bool { return d[0] == def.FixMap+0x01 }, }, { name: "string", v: vSt{Two: "2"}, c: func(d []byte) bool { return d[0] == def.FixMap+0x01 && d[1] == 0xa4 && d[2] == 'f' && d[3] == 'o' && d[4] == 'u' && d[5] == 'r' }, }, { name: "bool", v: vSt{Hfn: true}, c: func(d []byte) bool { return d[0] == def.FixMap+0x00 && len(d) == 1 }, }, { name: "slice", v: vSt{Slice: []int{}}, c: func(d []byte) bool { return d[0] == def.FixMap+0x01 && d[1] == 0xa5 && d[2] == 'S' && d[3] == 'l' && d[4] == 'i' && d[5] == 'c' && d[6] == 'e' && d[7] == def.FixArray+0x00 }, }, { name: "slice values", v: vSt{Slice: []int{1, 2, 3}}, c: func(d []byte) bool { return d[0] == def.FixMap+0x01 && d[1] == 0xa5 && d[2] == 'S' && d[3] == 'l' && d[4] == 'i' && d[5] == 'c' && d[6] == 'e' && d[7] == def.FixArray+0x03 && d[8] == 0x01 && d[9] == 0x02 && d[10] == 0x03 }, }, { name: "array all empty", v: vSt{}, c: func(d []byte) bool { return d[0] == def.FixArray+0x03 }, array: true, }, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { msgpack.StructAsArray = c.array v := c.v arg := encdecArg[rSt]{ v: v, c: c.c, skipEq: true, vc: func(r rSt) error { if v.One != r.One || v.Two != r.Four || r.Hfn != false || !slices.Equal(v.Slice, r.Slice) { return fmt.Errorf("error: %v, %v", v, r) } return nil }, } encdec(t, arg) }) } } func testStructArray(t *testing.T) { type vSt struct { One int Two string Ten float32 Skip float32 } type rSt struct { Three int Four string Tem float32 } v := vSt{One: 1, Two: "2", Ten: 1.234} msgpack.StructAsArray = true arg := encdecArg[rSt]{ v: v, c: func(d []byte) bool { return d[0] == def.FixArray+0x04 }, skipEq: true, vc: func(r rSt) error { if v.One != r.Three || v.Two != r.Four || v.Ten != r.Tem { return fmt.Errorf("error: %v, %v", v, r) } return nil }, } encdec(t, arg) } func testStructCode(t *testing.T) { type st1 struct { Int int } type st16 struct { I1 int I2 int I3 int I4 int I5 int I6 int I7 int I8 int I9 int I10 int I11 int I12 int I13 int I14 int I15 int I16 int } v1 := st1{Int: math.MinInt32} v16 := st16{ I1: 1, I2: 2, I3: 3, I4: 4, I5: 5, I6: 6, I7: 7, I8: 8, I9: 9, I10: 10, I11: 11, I12: 12, I13: 13, I14: 14, I15: 15, I16: 16, } t.Run("Map", func(t *testing.T) { t.Run("st1", func(t *testing.T) { arg := encdecArg[st1]{ v: v1, c: func(d []byte) bool { return def.FixMap <= d[0] && d[0] <= def.FixMap+0x0f }, } encdec(t, arg) }) t.Run("st16", func(t *testing.T) { arg := encdecArg[st16]{ v: v16, c: func(d []byte) bool { return d[0] == def.Map16 }, } encdec(t, arg) }) }) msgpack.StructAsArray = true defer func() { msgpack.StructAsArray = false }() t.Run("Array", func(t *testing.T) { t.Run("st1", func(t *testing.T) { arg := encdecArg[st1]{ v: v1, c: func(d []byte) bool { return def.FixArray <= d[0] && d[0] <= def.FixArray+0x0f }, } encdec(t, arg) }) t.Run("st16", func(t *testing.T) { arg := encdecArg[st16]{ v: v16, c: func(d []byte) bool { return d[0] == def.Array16 }, } encdec(t, arg) }) }) } func testStructUseCase(t *testing.T) { type child3 struct { Int int } type child2 struct { Int2Uint map[int]uint Float2Bool map[float32]bool Duration2Struct map[time.Duration]child3 } type child struct { IntArray []int UintArray []uint FloatArray []float32 BoolArray []bool StringArray []string TimeArray []time.Time DurationArray []time.Duration Child child2 } type st struct { Int8 int8 Int16 int16 Int32 int32 Int64 int64 Uint8 byte Uint16 uint16 Uint32 uint32 Uint64 uint64 Float float32 Double float64 Bool bool String string Pointer *int Nil *int Time time.Time Duration time.Duration Child child Child3Array []child3 } p := rand.Int() v := &st{ Int32: -32, Int8: -8, Int16: -16, Int64: -64, Uint32: 32, Uint8: 8, Uint16: 16, Uint64: 64, Float: 1.23, Double: 2.3456, Bool: true, String: "Parent", Pointer: &p, Nil: nil, Time: now, Duration: 123 * time.Second, // child Child: child{ IntArray: []int{-1, -2, -3, -4, -5}, UintArray: []uint{1, 2, 3, 4, 5}, FloatArray: []float32{-1.2, -3.4, -5.6, -7.8}, BoolArray: []bool{true, true, false, false, true}, StringArray: []string{"str", "ing", "arr", "ay"}, TimeArray: []time.Time{now, now, now}, DurationArray: []time.Duration{1 * time.Nanosecond, 2 * time.Nanosecond}, // childchild Child: child2{ Int2Uint: map[int]uint{-1: 2, -3: 4}, Float2Bool: map[float32]bool{-1.1: true, -2.2: false}, Duration2Struct: map[time.Duration]child3{1 * time.Hour: {Int: 1}, 2 * time.Hour: {Int: 2}}, }, }, Child3Array: []child3{{Int: 100}, {Int: 1000000}, {Int: 100000000}}, } msgpack.StructAsArray = false encdec(t, encdecArg[st]{n: "Map", v: v}) msgpack.StructAsArray = true encdec(t, encdecArg[st]{n: "Array", v: v}) msgpack.StructAsArray = false } func testStructJump(t *testing.T) { type v1 struct{ A interface{} } type r1 struct{ B interface{} } a1 := make([]int, math.MaxUint16) a2 := make([]int, math.MaxUint16+1) m1 := map[string]int{} m2 := map[string]int{} for i := range a1 { a1[i] = i m1[fmt.Sprint(i)] = 1 } for i := range a2 { a2[i] = i m2[fmt.Sprint(i)] = 1 } vs := []v1{ {A: true}, {A: 1}, {A: -1}, {A: math.MaxUint8}, {A: math.MinInt8}, {A: math.MaxUint16}, {A: math.MinInt16}, {A: math.MaxUint32 + 1}, {A: math.MinInt32 - 1}, {A: math.MaxFloat64}, {A: "a"}, {A: strings.Repeat("b", math.MaxUint8)}, {A: []byte(strings.Repeat("c", math.MaxUint8))}, {A: strings.Repeat("e", math.MaxUint16)}, {A: []byte(strings.Repeat("d", math.MaxUint16))}, {A: strings.Repeat("f", math.MaxUint16+1)}, {A: []byte(strings.Repeat("g", math.MaxUint16+1))}, {A: []int{1}}, {A: a1}, {A: a2}, {A: map[string]int{"a": 1}}, {A: m1}, {A: m2}, {A: time.Unix(now.Unix(), int64(now.Nanosecond()))}, } msgpack.StructAsArray = false for _, v := range vs { n := fmt.Sprint(v) if len(n) > 32 { n = n[:32] } arg := encdecArg[r1]{ n: n, v: v, skipEq: true, vc: func(r r1) error { if fmt.Sprint(v.A) == fmt.Sprint(r.B) { return fmt.Errorf("value equal %v, %v", v, r) } return nil }, } encdec(t, arg) } } ///////////////////////////////////////////////////////////// func TestExt(t *testing.T) { err := msgpack.AddExtCoder(encoder, decoder) NoError(t, err) err = msgpack.AddExtStreamCoder(streamEncoder, streamDecoder) NoError(t, err) { v := ExtInt{ Int8: math.MinInt8, Int16: math.MinInt16, Int32: math.MinInt32, Int64: math.MinInt32 - 1, Uint8: math.MaxUint8, Uint16: math.MaxUint16, Uint32: math.MaxUint32, Uint64: math.MaxUint32 + 1, Byte2Int: rand.Intn(math.MaxUint16), Byte4Int: rand.Intn(math.MaxInt32) - rand.Intn(math.MaxInt32-1), Byte4Uint32: math.MaxUint32 - 1, Bytes: []byte{1, 2, 3}, // 3 } encdec(t, encdecArg[ExtInt]{n: "AddCoder", v: v}) } err = msgpack.RemoveExtCoder(encoder, decoder) NoError(t, err) err = msgpack.RemoveExtStreamCoder(streamEncoder, streamDecoder) NoError(t, err) { v := ExtInt{ Int8: math.MinInt8, Int16: math.MinInt16, Int32: math.MinInt32, Int64: math.MinInt32 - 1, Uint8: math.MaxUint8, Uint16: math.MaxUint16, Uint32: math.MaxUint32, Uint64: math.MaxUint32 + 1, Byte2Int: rand.Intn(math.MaxUint16), Byte4Int: rand.Intn(math.MaxInt32) - rand.Intn(math.MaxInt32-1), Byte4Uint32: math.MaxUint32 - 1, Bytes: []byte{4, 5, 6}, // 3 } encdec(t, encdecArg[ExtInt]{n: "RemoveCoder", v: v}) } t.Run("ErrorExtCoder", func(t *testing.T) { err = msgpack.AddExtCoder(&testExt2Encoder{}, &testExt2Decoder{}) ErrorContains(t, err, "code different") err = msgpack.RemoveExtCoder(&testExt2Encoder{}, &testExt2Decoder{}) ErrorContains(t, err, "code different") err = msgpack.AddExtStreamCoder(&testExt2StreamEncoder{}, &testExt2StreamDecoder{}) ErrorContains(t, err, "code different") err = msgpack.RemoveExtStreamCoder(&testExt2StreamEncoder{}, &testExt2StreamDecoder{}) ErrorContains(t, err, "code different") }) } type ExtStruct struct { Int8 int8 Int16 int16 Int32 int32 Int64 int64 Uint8 uint8 Uint16 uint16 Uint32 uint32 Uint64 uint64 Byte2Int int Byte4Int int Byte4Uint32 uint32 Bytes []byte // 3 } type ExtInt ExtStruct var decoder = new(testDecoder) type testDecoder struct { ext.DecoderCommon } var _ ext.Decoder = (*testDecoder)(nil) var extIntCode = int8(-2) func (td *testDecoder) Code() int8 { return extIntCode } func (td *testDecoder) IsType(offset int, d *[]byte) bool { code, offset := td.ReadSize1(offset, d) if code == def.Ext8 { c, offset := td.ReadSize1(offset, d) t, _ := td.ReadSize1(offset, d) return c == 15+15+10+3 && int8(t) == td.Code() } return false } func (td *testDecoder) AsValue(offset int, k reflect.Kind, d *[]byte) (interface{}, int, error) { code, offset := td.ReadSize1(offset, d) switch code { case def.Ext8: // size _, offset = td.ReadSize1(offset, d) // code _, offset = td.ReadSize1(offset, d) i8, offset := td.ReadSize1(offset, d) i16, offset := td.ReadSize2(offset, d) i32, offset := td.ReadSize4(offset, d) i64, offset := td.ReadSize8(offset, d) u8, offset := td.ReadSize1(offset, d) u16, offset := td.ReadSize2(offset, d) u32, offset := td.ReadSize4(offset, d) u64, offset := td.ReadSize8(offset, d) b16, offset := td.ReadSize2(offset, d) b32, offset := td.ReadSize4(offset, d) bu32, offset := td.ReadSize4(offset, d) bs, offset := td.ReadSizeN(offset, 3, d) return ExtInt{ Int8: int8(i8), Int16: int16(binary.BigEndian.Uint16(i16)), Int32: int32(binary.BigEndian.Uint32(i32)), Int64: int64(binary.BigEndian.Uint64(i64)), Uint8: u8, Uint16: binary.BigEndian.Uint16(u16), Uint32: binary.BigEndian.Uint32(u32), Uint64: binary.BigEndian.Uint64(u64), Byte2Int: int(binary.BigEndian.Uint16(b16)), Byte4Int: int(int32(binary.BigEndian.Uint32(b32))), Byte4Uint32: binary.BigEndian.Uint32(bu32), Bytes: bs, }, offset, nil } return ExtInt{}, 0, fmt.Errorf("should not reach this line!! code %x decoding %v", code, k) } var streamDecoder = new(testStreamDecoder) type testStreamDecoder struct{} var _ ext.StreamDecoder = (*testStreamDecoder)(nil) func (td *testStreamDecoder) Code() int8 { return extIntCode } func (td *testStreamDecoder) IsType(code byte, innerType int8, dataLength int) bool { if code == def.Ext8 { return dataLength == 15+15+10+3 && innerType == td.Code() } return false } func (td *testStreamDecoder) ToValue(code byte, data []byte, k reflect.Kind) (any, error) { switch code { case def.Ext8: i8 := data[:1] i16 := data[1:3] i32 := data[3:7] i64 := data[7:15] u8 := data[15:16] u16 := data[16:18] u32 := data[18:22] u64 := data[22:30] b16 := data[30:32] b32 := data[32:36] bu32 := data[36:40] bs := data[40:43] return ExtInt{ Int8: int8(i8[0]), Int16: int16(binary.BigEndian.Uint16(i16)), Int32: int32(binary.BigEndian.Uint32(i32)), Int64: int64(binary.BigEndian.Uint64(i64)), Uint8: u8[0], Uint16: binary.BigEndian.Uint16(u16), Uint32: binary.BigEndian.Uint32(u32), Uint64: binary.BigEndian.Uint64(u64), Byte2Int: int(binary.BigEndian.Uint16(b16)), Byte4Int: int(int32(binary.BigEndian.Uint32(b32))), Byte4Uint32: binary.BigEndian.Uint32(bu32), Bytes: bs, }, nil } return ExtInt{}, fmt.Errorf("should not reach this line!! code %x decoding %v", code, k) } var encoder = new(testEncoder) type testEncoder struct { ext.EncoderCommon } var _ ext.Encoder = (*testEncoder)(nil) func (s *testEncoder) Code() int8 { return extIntCode } func (s *testEncoder) Type() reflect.Type { return reflect.TypeOf(ExtInt{}) } func (s *testEncoder) CalcByteSize(value reflect.Value) (int, error) { t := value.Interface().(ExtInt) return def.Byte1 + def.Byte1 + def.Byte1 + 15 + 15 + 10 + len(t.Bytes), nil } func (s *testEncoder) WriteToBytes(value reflect.Value, offset int, bytes *[]byte) int { t := value.Interface().(ExtInt) offset = s.SetByte1Int(def.Ext8, offset, bytes) offset = s.SetByte1Int(15+15+10+len(t.Bytes), offset, bytes) offset = s.SetByte1Int(int(s.Code()), offset, bytes) offset = s.SetByte1Int64(int64(t.Int8), offset, bytes) offset = s.SetByte2Int64(int64(t.Int16), offset, bytes) offset = s.SetByte4Int64(int64(t.Int32), offset, bytes) offset = s.SetByte8Int64(t.Int64, offset, bytes) offset = s.SetByte1Uint64(uint64(t.Uint8), offset, bytes) offset = s.SetByte2Uint64(uint64(t.Uint16), offset, bytes) offset = s.SetByte4Uint64(uint64(t.Uint32), offset, bytes) offset = s.SetByte8Uint64(t.Uint64, offset, bytes) offset = s.SetByte2Int(t.Byte2Int, offset, bytes) offset = s.SetByte4Int(t.Byte4Int, offset, bytes) offset = s.SetByte4Uint32(t.Byte4Uint32, offset, bytes) offset = s.SetBytes(t.Bytes, offset, bytes) return offset } var streamEncoder = new(testStreamEncoder) type testStreamEncoder struct{} var _ ext.StreamEncoder = (*testStreamEncoder)(nil) func (s *testStreamEncoder) Code() int8 { return extIntCode } func (s *testStreamEncoder) Type() reflect.Type { return reflect.TypeOf(ExtInt{}) } func (s *testStreamEncoder) Write(w ext.StreamWriter, value reflect.Value) error { t := value.Interface().(ExtInt) if err := w.WriteByte1Int(def.Ext8); err != nil { return err } if err := w.WriteByte1Int(15 + 15 + 10 + len(t.Bytes)); err != nil { return err } if err := w.WriteByte1Int(int(s.Code())); err != nil { return err } if err := w.WriteByte1Int64(int64(t.Int8)); err != nil { return err } if err := w.WriteByte2Int64(int64(t.Int16)); err != nil { return err } if err := w.WriteByte4Int64(int64(t.Int32)); err != nil { return err } if err := w.WriteByte8Int64(t.Int64); err != nil { return err } if err := w.WriteByte1Uint64(uint64(t.Uint8)); err != nil { return err } if err := w.WriteByte2Uint64(uint64(t.Uint16)); err != nil { return err } if err := w.WriteByte4Uint64(uint64(t.Uint32)); err != nil { return err } if err := w.WriteByte8Uint64(t.Uint64); err != nil { return err } if err := w.WriteByte2Int(t.Byte2Int); err != nil { return err } if err := w.WriteByte4Int(t.Byte4Int); err != nil { return err } if err := w.WriteByte4Uint32(t.Byte4Uint32); err != nil { return err } if err := w.WriteBytes(t.Bytes); err != nil { return err } return nil } ///////////////////////////////////////////////////////// type Ext2Struct struct { V int } type Ext2Int Ext2Struct const ( testExt2DecoderCode = 3 testExt2EncoderCode = -3 ) type testExt2Decoder struct{} var _ ext.Decoder = (*testExt2Decoder)(nil) func (td *testExt2Decoder) Code() int8 { return testExt2DecoderCode } func (td *testExt2Decoder) IsType(_ int, _ *[]byte) bool { return false } func (td *testExt2Decoder) AsValue(_ int, k reflect.Kind, _ *[]byte) (interface{}, int, error) { return Ext2Int{}, 0, fmt.Errorf("should not reach this line!! code %x decoding %v", td.Code(), k) } type testExt2StreamDecoder struct{} var _ ext.StreamDecoder = (*testExt2StreamDecoder)(nil) func (td *testExt2StreamDecoder) Code() int8 { return testExt2DecoderCode } func (td *testExt2StreamDecoder) IsType(_ byte, _ int8, _ int) bool { return false } func (td *testExt2StreamDecoder) ToValue(_ byte, _ []byte, k reflect.Kind) (any, error) { return Ext2Int{}, fmt.Errorf("should not reach this line!! code %x decoding %v", td.Code(), k) } type testExt2Encoder struct { ext.EncoderCommon } var _ ext.Encoder = (*testExt2Encoder)(nil) func (s *testExt2Encoder) Code() int8 { return testExt2EncoderCode } func (s *testExt2Encoder) Type() reflect.Type { return reflect.TypeOf(ExtInt{}) } func (s *testExt2Encoder) CalcByteSize(_ reflect.Value) (int, error) { return 0, nil } func (s *testExt2Encoder) WriteToBytes(_ reflect.Value, offset int, _ *[]byte) int { return offset } type testExt2StreamEncoder struct{} var _ ext.StreamEncoder = (*testExt2StreamEncoder)(nil) func (s *testExt2StreamEncoder) Code() int8 { return testExt2EncoderCode } func (s *testExt2StreamEncoder) Type() reflect.Type { return reflect.TypeOf(ExtInt{}) } func (s *testExt2StreamEncoder) Write(_ ext.StreamWriter, _ reflect.Value) error { return fmt.Errorf("should not reach this line!! code %x", s.Code()) } ///////////////////////////////////////////////////////// type Issue44Struct1 struct { Bin8 []byte Bin16 []byte Bin32 []byte } type Issue44Struct2 struct { S1 *Issue44Struct1 S2 *Issue44Struct2 // should not blow up on recursion Bin8 []byte Bin16 []byte Bin32 []byte } type Issue44Struct3 struct { Bin8 string Bin16 string Bin32 string } type Issue44Struct4 struct { S1 *Issue44Struct3 S2 *Issue44Struct4 // should not blow up on recursion Bin8 string Bin16 string Bin32 string } // https://github.com/shamaton/msgpack/issues/44 func TestIssue44(t *testing.T) { v := &Issue44Struct2{ S1: &Issue44Struct1{ Bin8: []byte("a"), Bin16: []byte(strings.Repeat("b", 256)), Bin32: []byte(strings.Repeat("c", 256*256)), }, S2: &Issue44Struct2{ Bin8: []byte("d"), Bin16: []byte(strings.Repeat("e", 256)), Bin32: []byte(strings.Repeat("f", 256*256)), }, Bin8: []byte("g"), Bin16: []byte(strings.Repeat("h", 256)), Bin32: []byte(strings.Repeat("i", 256*256)), } t.Run("byte slice", func(t *testing.T) { b, err := msgpack.Marshal(v) if err != nil { panic(err) } buf := bytes.NewBuffer(nil) if err = msgpack.MarshalWrite(buf, v); err != nil { panic(err) } if !reflect.DeepEqual(buf.Bytes(), b) { t.Fatalf("Marshal is not equal simple: [% 02x], streaming: [% 02x]", buf.Bytes(), b) } v2 := &Issue44Struct2{} if err = msgpack.Unmarshal(b, v2); err != nil { panic(err) } if !reflect.DeepEqual(v, v2) { t.Fatalf("Unmarshal is not equal orig: '%#v', got '%#v'", v, v2) } v3 := &Issue44Struct2{} if err = msgpack.UnmarshalRead(buf, v3); err != nil { panic(err) } if !reflect.DeepEqual(v, v3) { t.Fatalf("Unmarshal Streaming is not equal orig: '%#v', got '%#v'", v, v3) } }) t.Run("string", func(t *testing.T) { vv := &Issue44Struct4{ S1: &Issue44Struct3{ Bin8: "a", Bin16: strings.Repeat("b", 256), Bin32: strings.Repeat("c", 256*256), }, S2: &Issue44Struct4{ Bin8: "d", Bin16: strings.Repeat("e", 256), Bin32: strings.Repeat("f", 256*256), }, Bin8: "g", Bin16: strings.Repeat("h", 256), Bin32: strings.Repeat("i", 256*256), } b, err := msgpack.Marshal(vv) if err != nil { panic(err) } buf := bytes.NewBuffer(nil) if err = msgpack.MarshalWrite(buf, vv); err != nil { panic(err) } if !reflect.DeepEqual(buf.Bytes(), b) { t.Fatalf("Marshal is not equal simple: [% 02x], streaming: [% 02x]", buf.Bytes(), b) } v2 := &Issue44Struct2{} if err = msgpack.Unmarshal(b, v2); err != nil { panic(err) } if !reflect.DeepEqual(v, v2) { t.Fatalf("Unmarshal is not equal orig: '%#v', got '%#v'", v, v2) } v3 := &Issue44Struct2{} if err = msgpack.UnmarshalRead(buf, v3); err != nil { panic(err) } if !reflect.DeepEqual(v, v3) { t.Fatalf("Unmarshal Streaming is not equal orig: '%#v', got '%#v'", v, v3) } }) } ///////////////////////////////////////////////////////// type ( checker func(data []byte) bool marshaller func(v any) ([]byte, error) unmarshaller func(data []byte, v any) error ) var marshallers = []struct { name string m marshaller }{ {"Marshal", msgpack.Marshal}, {"MarshalWrite", func(v any) ([]byte, error) { buf := bytes.Buffer{} err := msgpack.MarshalWrite(&buf, v) return buf.Bytes(), err }}, } var unmarshallers = []struct { name string u unmarshaller }{ {"Unmarshal", msgpack.Unmarshal}, {"UnmarshalRead", func(data []byte, v any) error { return msgpack.UnmarshalRead(bytes.NewReader(data), v) }}, } type encdecArg[T any] struct { n string v any r T c checker skipEq bool vc func(t T) error e string } func encdec[T any](t *testing.T, args ...encdecArg[T]) { t.Helper() for _, arg := range args { t.Run(arg.n, func(t *testing.T) { for _, m := range marshallers { for _, u := range unmarshallers { t.Run(m.name+"-"+u.name, func(t *testing.T) { var e error defer func() { if e != nil { if len(arg.e) < 1 { t.Fatalf("unexpected error: %v", e) } if !strings.Contains(e.Error(), arg.e) { t.Fatalf("error does not contain '%s'. err: %v", arg.e, e) } } else { if len(arg.e) > 0 { t.Fatalf("error should occur, but nil. expected: %s", arg.e) } } }() d, err := m.m(arg.v) if err != nil { e = err return } if arg.c != nil && !arg.c(d) { e = fmt.Errorf("different %s", hex.Dump(d)) return } if err = u.u(d, &arg.r); err != nil { e = err return } if !arg.skipEq { if err = equalCheck(arg.v, arg.r); err != nil { e = err return } } if arg.vc != nil { if err = arg.vc(arg.r); err != nil { e = err return } } }) } } }) } } // for check value func getValue(v interface{}) interface{} { rv := reflect.ValueOf(v) if rv.Kind() == reflect.Ptr { rv = rv.Elem() } if rv.Kind() == reflect.Ptr { rv = rv.Elem() } if rv.Kind() == reflect.Invalid { return nil } return rv.Interface() } func equalCheck(in, out interface{}) error { i := getValue(in) o := getValue(out) if !reflect.DeepEqual(i, o) { return errors.New(fmt.Sprint("value different \n[in]:", i, " \n[out]:", o)) } return nil } func NoError(t *testing.T, err error) { t.Helper() if err != nil { t.Fatal(err) } } func ErrorContains(t *testing.T, err error, errStr string) { if err == nil { t.Fatal("error should occur") } if !strings.Contains(err.Error(), errStr) { t.Fatalf("error does not contain '%s'. err: %v", errStr, err) } } ================================================ FILE: testdata/crashers/007424b0caabdaf653d7efeb3ce6c1c127f628c6 ================================================ 0000000 ================================================ FILE: testdata/crashers/01d1844ec3cdec0e8d60d81fcded73f920270e9c ================================================ 000000 ================================================ FILE: testdata/crashers/049b2af75fc4ee7160c7ea9bad41a5167b501d32 ================================================ 000 ================================================ FILE: testdata/crashers/051d081de15ae4680b4156046787aae72ae0adcc ================================================ 0 ================================================ FILE: testdata/crashers/063fea9d12105a80ee56a482aac428543fcf8d49 ================================================ 0000000000000 ================================================ FILE: testdata/crashers/080c57e02ab7e704e2a2e93aad7674a574dc9d69 ================================================ 000000000 ================================================ FILE: testdata/crashers/0d45579ce9ef9c1ca6e945d3f21ad31097f0c3c0 ================================================ 0000000000000000000000000 ================================================ FILE: testdata/crashers/0f7994b18fb75394c81005c925dd1ae153ad57ad ================================================ 000 ================================================ FILE: testdata/crashers/121a9af889bd4ca2266be5a4f680d3bead8d02d6 ================================================ ================================================ FILE: testdata/crashers/19eb9972c2a406f15bbae850ac33b42ebd88f7a7 ================================================ 00000000000 ================================================ FILE: testdata/crashers/1a3492dbf0b80d08f12ce49c620239e48b74c08c ================================================ 0000000000 ================================================ FILE: testdata/crashers/1ae8bba51072f9ff2bdf587596cfb0edf8a5cd12 ================================================ 0000000000000000000000000000000000000 ================================================ FILE: testdata/crashers/1b274070ce90f8ccdf0fc33a4afd4e1455766f8b ================================================ ================================================ FILE: testdata/crashers/1d49f1635e901393bf65a263810508bea7ec9d51 ================================================ 00 ================================================ FILE: testdata/crashers/1e2c5c5ac7754f3e3fd858ce644b6621da2f4d03 ================================================ 0000000000 ================================================ FILE: testdata/crashers/236cfc19fdc31facdfe306e479a52bb3c08cb540 ================================================ 00000 ================================================ FILE: testdata/crashers/2478b7ed4fe72f5030a22146ecf365adba0dc780 ================================================ 0000 ================================================ FILE: testdata/crashers/24b5d7868ba6df6da0e26365e10fc15bcf72cca6 ================================================ 00 ================================================ FILE: testdata/crashers/255b827f8c0a593c5ee7610cdbd31c656f166a37 ================================================ 00 ================================================ FILE: testdata/crashers/2a8cbfc34aa4aefa008e9e18276e32fa5e8f56d8 ================================================ 0000 ================================================ FILE: testdata/crashers/2ba0c664eff181eeb2b101f60c5ca66397f4c386 ================================================ 00000 ================================================ FILE: testdata/crashers/2c34cfd27e783430e24c2af05361af3540b4d6ad ================================================ 0 ================================================ FILE: testdata/crashers/2c4dd5af849b5cda2b7769dc52706e5fa746de73 ================================================ 00 ================================================ FILE: testdata/crashers/2cdb9f5adf53f1054ce8e6ceba007396ce6b734b ================================================ 00000000 ================================================ FILE: testdata/crashers/30140397fe38ee61f01eff44b5cfa48285e47889 ================================================ ================================================ FILE: testdata/crashers/321500ed9a1796dca3d559d8f129ed31a8790035 ================================================ 000000000000000000000 ================================================ FILE: testdata/crashers/3219e4d65ac8671a452900514bc3c7aea2e71be3 ================================================ 00000000000000000000000000000 ================================================ FILE: testdata/crashers/36a5cc93968072de9d120643033589df4f316997 ================================================ 0000000000000000 ================================================ FILE: testdata/crashers/3abe9a29f2b95f34da65d88b969e58f4b46b6309 ================================================ 0000000000000000000 ================================================ FILE: testdata/crashers/3e5a9e8b50fcdcbe08f8138de6b95b5cd5b69bf2 ================================================ 00000000000000000 ================================================ FILE: testdata/crashers/3f0062c38d031b015f1a8bb82bd370b65d5ca35a ================================================ 0000000000000000 ================================================ FILE: testdata/crashers/4670a8e0d21a624c0d42fd6dc76ef408d2b4e195 ================================================ 0 ================================================ FILE: testdata/crashers/47393c214b8f5a3d3c098e3985d24bd1e8d82a85 ================================================ 000000 ================================================ FILE: testdata/crashers/48947b6f943721ce84ef81e887a16bde2759f7ab ================================================ 000000000000000000000000000000000000 ================================================ FILE: testdata/crashers/49989f6a8875777519c822a92c821cbb95bfc360 ================================================ 000000000 ================================================ FILE: testdata/crashers/4aaffe149efbd0ab6f122a4dc6e9e9aa0a602799 ================================================ 00000000000000 ================================================ FILE: testdata/crashers/4b65bf83d9b292f65bb5d1b056d68a4811732ef3 ================================================ 0 ================================================ FILE: testdata/crashers/4c156cb396c967d81bf3615cbd5ba74221a9c622 ================================================ NilBoolãIntМFloat32@IVFloat64 !oStringf ================================================ FILE: testdata/crashers/4fb8cfeaaac80a1c829b22a43089ef470bcfe5b8 ================================================ ================================================ FILE: testdata/crashers/502804b3665c5eb935d6f66e7677f75242f516bb ================================================ 00000000000000000 ================================================ FILE: testdata/crashers/56ff31c3b76d37929e45071a36ca5aa0bc386e09 ================================================ 0000000000000000000000000000000 ================================================ FILE: testdata/crashers/5bc56c0f00870625b5016a8d242ebe5c2a58802b ================================================ 00000000000000000000000000 ================================================ FILE: testdata/crashers/5e8c489081abe671d760e1840b526f43bc7e6aa3 ================================================ 000000000000000000000@IVFloat64 !oString ================================================ FILE: testdata/crashers/5ec02b6349403b523d33576c7e2de27fb345edf0 ================================================ 0000 ================================================ FILE: testdata/crashers/6351ed5a9ac9cbb709ad15b4193edb52b8d37e84 ================================================ 0 ================================================ FILE: testdata/crashers/673480b070ea0508c3510627c81a4b519fb7d2f5 ================================================ 0 ================================================ FILE: testdata/crashers/683f06bff1a62b73193352f06a7194f0bf60ffb8 ================================================ 000000000000000000000 ================================================ FILE: testdata/crashers/68a5e75e9b42928454b320fe8f64ea107e61f60f ================================================ 000000 ================================================ FILE: testdata/crashers/68be1e92e40e77926e1d9a5c2d800da023f6a4e9 ================================================ 0000000000000000000000000 ================================================ FILE: testdata/crashers/6b7395dabfc4d5a76e276f222c64c4bf7771ffb7 ================================================ 00000000000 ================================================ FILE: testdata/crashers/707235f5352edef615d64d00d83fcf92c9dffa57 ================================================ 00000000000000000000000000000000000000000000 ================================================ FILE: testdata/crashers/71819c849b41ee2f4a5b08321b9fc8bf21326ddf ================================================ 0 ================================================ FILE: testdata/crashers/730377e1be92f426661e434d4f1cf20a10e16991 ================================================ 00 ================================================ FILE: testdata/crashers/73728d128f628772a64990a72340d9fd7987e29b ================================================ 0 ================================================ FILE: testdata/crashers/73cc8fe38acd489c2b1fed0a66222aac4e04cc98 ================================================ 00000000000000000000 ================================================ FILE: testdata/crashers/74fe2a4034b08b5ecb014b2596e7f21ab55ea729 ================================================ ================================================ FILE: testdata/crashers/79aed363731b28a60abf16190df3d60fd1d88e54 ================================================ 00000 ================================================ FILE: testdata/crashers/7a1d04a0253fbb956c8119c40dc917cb460dabb0 ================================================ NilBool£IntFloat32@IVFloat64 !oStri ================================================ FILE: testdata/crashers/7a96d469f87b8c0fa00a944669469db9c8246714 ================================================ 000 ================================================ FILE: testdata/crashers/7b21df3bb234142e7b2dfc81f84eb621f9c7a617 ================================================ 000000000000 ================================================ FILE: testdata/crashers/8611c05469f8e623e1e035e578d1d50ff2a54e11 ================================================ 0000 ================================================ FILE: testdata/crashers/861648845b817281bffa42d8ca1c46443cef5cd8 ================================================ 000000000000000000000000000000000 ================================================ FILE: testdata/crashers/86f9eb6ebcae33f4d3663f90adf430f6d7b5e57c ================================================ 00000000000000000000000000000 ================================================ FILE: testdata/crashers/8a90278d26f66fd78e4e38da237c201707bf7ab3 ================================================ 0000 ================================================ FILE: testdata/crashers/8de4d4284d8227a753986639b1d6e698bc683d8a ================================================ 000000000000000000000000000 ================================================ FILE: testdata/crashers/8e4cb24fea75d067b0d9a5598fdeb66490777000 ================================================ 000000000000000000000000000000000 ================================================ FILE: testdata/crashers/964ac50bf26ecb3d0253e5e288484776bbf0abdf ================================================ 000000000 ================================================ FILE: testdata/crashers/98d2aa10f9c5e6d9b1a6fb3dcc6583aa5631882a ================================================ 0000000 ================================================ FILE: testdata/crashers/9ae8dabd9621f44592757414f83d26df04e55bca ================================================ 000000000000000000000000000 ================================================ FILE: testdata/crashers/9b982d96821f364683a7a9cc2495cccc7d5bb705 ================================================ 0000000000000000000000000 ================================================ FILE: testdata/crashers/9ded617601151e2a3dde370fe42f206f6dd38f42 ================================================ 00 ================================================ FILE: testdata/crashers/9e2487eee9a3b9f415c36aed893ed2da3d3bace6 ================================================ ================================================ FILE: testdata/crashers/9f733d3115030339e63922d6e35920f9dba1207e ================================================ 00000000000000000000000 ================================================ FILE: testdata/crashers/a0bb3eef10ad343fdf00adc2c7a2fab0e4dd679f ================================================ 00000000000000000000000000000000000 ================================================ FILE: testdata/crashers/a611449daf97af318de6495f22c907ecb1f83076 ================================================ 0000000 ================================================ FILE: testdata/crashers/a8c1d81ff50e57abc043f8bbcc87ce0e123285a7 ================================================ 00000000000 ================================================ FILE: testdata/crashers/aa1a1bd5b7953186af6c9f2e8f757247fdffa9ce ================================================ NilBoolãIntМFloat32@IVFloat64˪ !oStringfoo ================================================ FILE: testdata/crashers/b7471e724dfba20b71265eb8f8315ff5add6ccad ================================================ ================================================ FILE: testdata/crashers/b9df95e660f5bc5d66f2ad7c49339cb3d62d2a99 ================================================ 00000000 ================================================ FILE: testdata/crashers/bc22b685617fec260cb94cb1ab9822e393924a35 ================================================ 0000000000000000000 ================================================ FILE: testdata/crashers/c1a1ed4a1a749c9e38da920c66a4c425e169566b ================================================ 0000000000000 ================================================ FILE: testdata/crashers/c41c1a47577c69284cae60d785ddb240dc3739db ================================================ 0000000000000000000000000 ================================================ FILE: testdata/crashers/c4ee5a4632d4f9a51ba57a70cfa2cae832460857 ================================================ 000 ================================================ FILE: testdata/crashers/c9388db618e924f41e419ccddc068b443657ffa4 ================================================ ================================================ FILE: testdata/crashers/ce7c29332788aa93186591aceaad307ffbdf9735 ================================================ 00 ================================================ FILE: testdata/crashers/da39a3ee5e6b4b0d3255bfef95601890afd80709 ================================================ ================================================ FILE: testdata/crashers/df0c1e8999119939ea4f07c2daeb57b868bb0719 ================================================ 000 ================================================ FILE: testdata/crashers/e2d6b6d4d9b8346067d2252bfbcefff99a24ed21 ================================================ 0000000 ================================================ FILE: testdata/crashers/e62ebb1ab82b8cf630f2b393d89c367dd0b25924 ================================================ 000000000 ================================================ FILE: testdata/crashers/e906f0d49a1b91f0b93d64c36bf5a764924ce45f ================================================ 000000000000000 ================================================ FILE: testdata/crashers/ead2a4908cb1750ae6f619656ebf74b153edab25 ================================================ 000000000000000000 ================================================ FILE: testdata/crashers/eae5f62c3fc844275194f17d530d26ceafcf941b ================================================ 0 ================================================ FILE: testdata/crashers/edb2dfda8124a5c5935dd1d449f493e868d9f9c0 ================================================ 000000000000000000000000000 ================================================ FILE: testdata/crashers/f023f3307ef4fe6b9d8ea2c0ea4fb3bc954f2888 ================================================ 00000000000000000000000 ================================================ FILE: testdata/crashers/f1ec5f959e75412b01270a861f1f1ecdfda1cb9c ================================================ 0 ================================================ FILE: testdata/crashers/f4aaa2a22e1038e4abda4636592d330fc9fc693b ================================================ 000 ================================================ FILE: testdata/crashers/f7cb64ffc60ab1f9e0aecbd9c560af1d743fe314 ================================================ 00000 ================================================ FILE: testdata/crashers/f86f8b65ecd59bddb8df4850b538b863021ea14a ================================================ 000000000000000000 ================================================ FILE: testdata/crashers/f87f13e299cb66c824237b662a5623647d3af86e ================================================ 00000000000 ================================================ FILE: testdata/crashers/fa85f6319c6d39864527629c9b25c4b086f398b7 ================================================ 00000000000000 ================================================ FILE: testdata/crashers/fc7077d7ef5f47af404835e9ea6401f258b27f83 ================================================ 0000000000000000000000000000000 ================================================ FILE: time/decode.go ================================================ package time import ( "encoding/binary" "fmt" "reflect" "time" "github.com/shamaton/msgpack/v3/def" "github.com/shamaton/msgpack/v3/ext" ) var zero = time.Unix(0, 0) var Decoder = new(timeDecoder) type timeDecoder struct { ext.DecoderCommon } var _ ext.Decoder = (*timeDecoder)(nil) func (td *timeDecoder) Code() int8 { return def.TimeStamp } func (td *timeDecoder) readSize1Safe(index int, d *[]byte) (byte, int, bool) { if len(*d) < index+def.Byte1 { return 0, 0, false } v, next := td.ReadSize1(index, d) return v, next, true } func (td *timeDecoder) readSize4Safe(index int, d *[]byte) ([]byte, int, bool) { if len(*d) < index+def.Byte4 { return nil, 0, false } v, next := td.ReadSize4(index, d) return v, next, true } func (td *timeDecoder) readSize8Safe(index int, d *[]byte) ([]byte, int, bool) { if len(*d) < index+def.Byte8 { return nil, 0, false } v, next := td.ReadSize8(index, d) return v, next, true } func (td *timeDecoder) IsType(offset int, d *[]byte) bool { code, offset, ok := td.readSize1Safe(offset, d) if !ok { return false } switch code { case def.Fixext4: t, _, ok := td.readSize1Safe(offset, d) if !ok || int8(t) != td.Code() { return false } _, _, ok = td.readSize4Safe(offset+def.Byte1, d) return ok case def.Fixext8: t, _, ok := td.readSize1Safe(offset, d) if !ok || int8(t) != td.Code() { return false } _, _, ok = td.readSize8Safe(offset+def.Byte1, d) return ok case def.Ext8: l, offset, ok := td.readSize1Safe(offset, d) if !ok { return false } t, _, ok := td.readSize1Safe(offset, d) if !ok || l != 12 || int8(t) != td.Code() { return false } _, _, ok = td.readSize4Safe(offset+def.Byte1, d) if !ok { return false } _, _, ok = td.readSize8Safe(offset+def.Byte1+def.Byte4, d) return ok } return false } func (td *timeDecoder) AsValue(offset int, k reflect.Kind, d *[]byte) (interface{}, int, error) { code, offset, ok := td.readSize1Safe(offset, d) if !ok { return zero, 0, def.ErrTooShortBytes } switch code { case def.Fixext4: _, offset, ok = td.readSize1Safe(offset, d) if !ok { return zero, 0, def.ErrTooShortBytes } bs, offset, ok := td.readSize4Safe(offset, d) if !ok { return zero, 0, def.ErrTooShortBytes } v := time.Unix(int64(binary.BigEndian.Uint32(bs)), 0) if decodeAsLocal { return v, offset, nil } return v.UTC(), offset, nil case def.Fixext8: _, offset, ok = td.readSize1Safe(offset, d) if !ok { return zero, 0, def.ErrTooShortBytes } bs, offset, ok := td.readSize8Safe(offset, d) if !ok { return zero, 0, def.ErrTooShortBytes } data64 := binary.BigEndian.Uint64(bs) nano := int64(data64 >> 34) if nano > 999999999 { return zero, 0, fmt.Errorf("in timestamp 64 formats, nanoseconds must not be larger than 999999999 : %d", nano) } v := time.Unix(int64(data64&0x00000003ffffffff), nano) if decodeAsLocal { return v, offset, nil } return v.UTC(), offset, nil case def.Ext8: _, offset, ok = td.readSize1Safe(offset, d) if !ok { return zero, 0, def.ErrTooShortBytes } _, offset, ok = td.readSize1Safe(offset, d) if !ok { return zero, 0, def.ErrTooShortBytes } nanobs, offset, ok := td.readSize4Safe(offset, d) if !ok { return zero, 0, def.ErrTooShortBytes } secbs, offset, ok := td.readSize8Safe(offset, d) if !ok { return zero, 0, def.ErrTooShortBytes } nano := binary.BigEndian.Uint32(nanobs) if nano > 999999999 { return zero, 0, fmt.Errorf("in timestamp 96 formats, nanoseconds must not be larger than 999999999 : %d", nano) } sec := binary.BigEndian.Uint64(secbs) v := time.Unix(int64(sec), int64(nano)) if decodeAsLocal { return v, offset, nil } return v.UTC(), offset, nil } return zero, 0, fmt.Errorf("should not reach this line!! code %x decoding %v", code, k) } ================================================ FILE: time/decode_stream.go ================================================ package time import ( "encoding/binary" "fmt" "reflect" "time" "github.com/shamaton/msgpack/v3/def" "github.com/shamaton/msgpack/v3/ext" ) var StreamDecoder = new(timeStreamDecoder) type timeStreamDecoder struct{} var _ ext.StreamDecoder = (*timeStreamDecoder)(nil) func (td *timeStreamDecoder) Code() int8 { return def.TimeStamp } func (td *timeStreamDecoder) IsType(code byte, innerType int8, dataLength int) bool { switch code { case def.Fixext4, def.Fixext8: return innerType == td.Code() case def.Ext8: return innerType == td.Code() && dataLength == 12 } return false } func (td *timeStreamDecoder) ToValue(code byte, data []byte, k reflect.Kind) (interface{}, error) { switch code { case def.Fixext4: if len(data) < def.Byte4 { return zero, def.ErrTooShortBytes } v := time.Unix(int64(binary.BigEndian.Uint32(data)), 0) if decodeAsLocal { return v, nil } return v.UTC(), nil case def.Fixext8: if len(data) < def.Byte8 { return zero, def.ErrTooShortBytes } data64 := binary.BigEndian.Uint64(data) nano := int64(data64 >> 34) if nano > 999999999 { return zero, fmt.Errorf("in timestamp 64 formats, nanoseconds must not be larger than 999999999 : %d", nano) } v := time.Unix(int64(data64&0x00000003ffffffff), nano) if decodeAsLocal { return v, nil } return v.UTC(), nil case def.Ext8: if len(data) < def.Byte4+def.Byte8 { return zero, def.ErrTooShortBytes } nano := binary.BigEndian.Uint32(data[:4]) if nano > 999999999 { return zero, fmt.Errorf("in timestamp 96 formats, nanoseconds must not be larger than 999999999 : %d", nano) } sec := binary.BigEndian.Uint64(data[4:12]) v := time.Unix(int64(sec), int64(nano)) if decodeAsLocal { return v, nil } return v.UTC(), nil } return zero, fmt.Errorf("should not reach this line!! code %x decoding %v", code, k) } ================================================ FILE: time/decode_stream_test.go ================================================ package time import ( "encoding/binary" "reflect" "testing" "time" "github.com/shamaton/msgpack/v3/def" tu "github.com/shamaton/msgpack/v3/internal/common/testutil" ) func TestStreamDecodeCode(t *testing.T) { decoder := StreamDecoder code := decoder.Code() tu.Equal(t, code, def.TimeStamp) } func TestStreamDecodeIsType(t *testing.T) { decoder := StreamDecoder ts := int8(def.TimeStamp) tests := []struct { name string code byte innerType int8 dataLength int expected bool }{ { name: "Fixext4 with TimeStamp", code: def.Fixext4, innerType: ts, dataLength: 4, expected: true, }, { name: "Fixext4 with wrong type", code: def.Fixext4, innerType: 0x00, dataLength: 4, expected: false, }, { name: "Fixext8 with TimeStamp", code: def.Fixext8, innerType: ts, dataLength: 8, expected: true, }, { name: "Fixext8 with wrong type", code: def.Fixext8, innerType: 0x00, dataLength: 8, expected: false, }, { name: "Ext8 with length 12 and TimeStamp", code: def.Ext8, innerType: ts, dataLength: 12, expected: true, }, { name: "Ext8 with wrong length", code: def.Ext8, innerType: ts, dataLength: 10, expected: false, }, { name: "Ext8 with wrong type", code: def.Ext8, innerType: 0x00, dataLength: 12, expected: false, }, { name: "Wrong format", code: def.Nil, innerType: ts, dataLength: 0, expected: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := decoder.IsType(tt.code, tt.innerType, tt.dataLength) tu.Equal(t, result, tt.expected) }) } } func TestStreamDecodeToValueFixext4(t *testing.T) { decoder := StreamDecoder tests := []struct { name string unixTime int64 }{ { name: "Unix epoch", unixTime: 0, }, { name: "Small timestamp", unixTime: 1000, }, { name: "Large timestamp", unixTime: 1<<32 - 1, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Create Fixext4 format data (without header) data := make([]byte, 4) binary.BigEndian.PutUint32(data, uint32(tt.unixTime)) value, err := decoder.ToValue(def.Fixext4, data, reflect.TypeOf(time.Time{}).Kind()) tu.NoError(t, err) timeValue, ok := value.(time.Time) if !ok { t.Fatalf("Expected time.Time, got %T", value) } tu.Equal(t, timeValue.Unix(), tt.unixTime) tu.Equal(t, timeValue.Nanosecond(), 0) }) } } func TestStreamDecodeToValueFixext8(t *testing.T) { decoder := StreamDecoder tests := []struct { name string unixTime int64 nanosecond int64 }{ { name: "With small nanoseconds", unixTime: 1000, nanosecond: 123456789, }, { name: "With max nanoseconds", unixTime: 67890, nanosecond: 999999999, }, { name: "Boundary at 2^34-1", unixTime: (1 << 34) - 1, nanosecond: 999999999, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Create Fixext8 format data (without header) data := make([]byte, 8) // Pack nanoseconds in upper 30 bits and seconds in lower 34 bits data64 := uint64(tt.nanosecond)<<34 | uint64(tt.unixTime) binary.BigEndian.PutUint64(data, data64) value, err := decoder.ToValue(def.Fixext8, data, reflect.TypeOf(time.Time{}).Kind()) tu.NoError(t, err) timeValue, ok := value.(time.Time) if !ok { t.Fatalf("Expected time.Time, got %T", value) } tu.Equal(t, timeValue.Unix(), tt.unixTime) tu.Equal(t, int64(timeValue.Nanosecond()), tt.nanosecond) }) } } func TestStreamDecodeToValueExt8(t *testing.T) { decoder := StreamDecoder tests := []struct { name string unixTime int64 nanosecond int32 }{ { name: "Large timestamp at 2^34", unixTime: 1 << 34, nanosecond: 123456789, }, { name: "Very large timestamp", unixTime: 253402300799, nanosecond: 999999999, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Create Ext8 format data (without header) data := make([]byte, 12) binary.BigEndian.PutUint32(data[:4], uint32(tt.nanosecond)) binary.BigEndian.PutUint64(data[4:12], uint64(tt.unixTime)) value, err := decoder.ToValue(def.Ext8, data, reflect.TypeOf(time.Time{}).Kind()) tu.NoError(t, err) timeValue, ok := value.(time.Time) if !ok { t.Fatalf("Expected time.Time, got %T", value) } tu.Equal(t, timeValue.Unix(), tt.unixTime) tu.Equal(t, int64(timeValue.Nanosecond()), int64(tt.nanosecond)) }) } } func TestStreamDecodeToValueErrors(t *testing.T) { decoder := StreamDecoder t.Run("Fixext8 with invalid nanoseconds", func(t *testing.T) { data := make([]byte, 8) // Set nanoseconds > 999999999 (invalid) invalidNano := uint64(1000000000) data64 := invalidNano<<34 | 1000 binary.BigEndian.PutUint64(data, data64) _, err := decoder.ToValue(def.Fixext8, data, reflect.TypeOf(time.Time{}).Kind()) tu.ErrorContains(t, err, "in timestamp 64 formats") }) t.Run("Ext8 with invalid nanoseconds", func(t *testing.T) { data := make([]byte, 12) // Set nanoseconds > 999999999 (invalid) binary.BigEndian.PutUint32(data[:4], 1000000000) binary.BigEndian.PutUint64(data[4:12], 1000) _, err := decoder.ToValue(def.Ext8, data, reflect.TypeOf(time.Time{}).Kind()) tu.ErrorContains(t, err, "in timestamp 96 formats") }) t.Run("Invalid format code", func(t *testing.T) { data := []byte{0} _, err := decoder.ToValue(def.Nil, data, reflect.TypeOf(time.Time{}).Kind()) tu.ErrorContains(t, err, "should not reach") }) } func TestStreamDecodeToValueTooShort(t *testing.T) { decoder := StreamDecoder testcases := []struct { name string code byte data []byte }{ {name: "Fixext4", code: def.Fixext4, data: []byte{}}, {name: "Fixext8", code: def.Fixext8, data: []byte{}}, {name: "Ext8", code: def.Ext8, data: []byte{}}, } for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { _, err := decoder.ToValue(tc.code, tc.data, reflect.TypeOf(time.Time{}).Kind()) tu.IsError(t, err, def.ErrTooShortBytes) }) } } func TestStreamDecodeTimezone(t *testing.T) { decoder := StreamDecoder tests := []struct { name string time time.Time format byte createData func(time.Time) []byte }{ { name: "Fixext4", time: time.Unix(1000, 0), format: def.Fixext4, createData: func(testTime time.Time) []byte { data := make([]byte, 4) binary.BigEndian.PutUint32(data, uint32(testTime.Unix())) return data }, }, { name: "Fixext8", time: time.Unix(1000, 123456789), format: def.Fixext8, createData: func(testTime time.Time) []byte { data := make([]byte, 8) data64 := uint64(testTime.Nanosecond())<<34 | uint64(testTime.Unix()) binary.BigEndian.PutUint64(data, data64) return data }, }, { name: "Ext8", time: time.Unix(1<<34, 123456789), format: def.Ext8, createData: func(testTime time.Time) []byte { data := make([]byte, 12) binary.BigEndian.PutUint32(data[:4], uint32(testTime.Nanosecond())) binary.BigEndian.PutUint64(data[4:12], uint64(testTime.Unix())) return data }, }, } for _, tt := range tests { t.Run(tt.name+" - Decode as local (default)", func(t *testing.T) { // Set to local timezone SetDecodedAsLocal(true) defer SetDecodedAsLocal(true) // Reset to default data := tt.createData(tt.time) value, err := decoder.ToValue(tt.format, data, reflect.TypeOf(time.Time{}).Kind()) tu.NoError(t, err) timeValue, ok := value.(time.Time) if !ok { t.Fatalf("Expected time.Time, got %T", value) } // Should be in local timezone tu.Equal(t, timeValue.Location(), time.Local) }) t.Run(tt.name+" - Decode as UTC", func(t *testing.T) { // Set to UTC timezone SetDecodedAsLocal(false) defer SetDecodedAsLocal(true) // Reset to default data := tt.createData(tt.time) value, err := decoder.ToValue(tt.format, data, reflect.TypeOf(time.Time{}).Kind()) tu.NoError(t, err) timeValue, ok := value.(time.Time) if !ok { t.Fatalf("Expected time.Time, got %T", value) } // Should be in UTC timezone tu.Equal(t, timeValue.Location(), time.UTC) }) } } func TestStreamDecodeRoundTrip(t *testing.T) { decoder := StreamDecoder tests := []struct { name string time time.Time }{ { name: "Fixext4 format", time: time.Unix(1000, 0), }, { name: "Fixext8 format", time: time.Unix(67890, 123456789), }, { name: "Ext8 format", time: time.Unix(17179869184, 987654321), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Use encode_stream to encode var encodedData []byte secs := uint64(tt.time.Unix()) if secs>>34 == 0 { data := uint64(tt.time.Nanosecond())<<34 | secs if data&0xffffffff00000000 == 0 { // Fixext4 encodedData = make([]byte, 4) binary.BigEndian.PutUint32(encodedData, uint32(data)) // Decode decodedValue, err := decoder.ToValue(def.Fixext4, encodedData, reflect.TypeOf(time.Time{}).Kind()) tu.NoError(t, err) decodedTime, ok := decodedValue.(time.Time) if !ok { t.Fatalf("Expected time.Time, got %T", decodedValue) } tu.Equal(t, decodedTime.Unix(), tt.time.Unix()) tu.Equal(t, decodedTime.Nanosecond(), tt.time.Nanosecond()) return } // Fixext8 encodedData = make([]byte, 8) binary.BigEndian.PutUint64(encodedData, data) // Decode decodedValue, err := decoder.ToValue(def.Fixext8, encodedData, reflect.TypeOf(time.Time{}).Kind()) tu.NoError(t, err) decodedTime, ok := decodedValue.(time.Time) if !ok { t.Fatalf("Expected time.Time, got %T", decodedValue) } tu.Equal(t, decodedTime.Unix(), tt.time.Unix()) tu.Equal(t, decodedTime.Nanosecond(), tt.time.Nanosecond()) return } // Ext8 encodedData = make([]byte, 12) binary.BigEndian.PutUint32(encodedData[:4], uint32(tt.time.Nanosecond())) binary.BigEndian.PutUint64(encodedData[4:12], secs) // Decode decodedValue, err := decoder.ToValue(def.Ext8, encodedData, reflect.TypeOf(time.Time{}).Kind()) tu.NoError(t, err) decodedTime, ok := decodedValue.(time.Time) if !ok { t.Fatalf("Expected time.Time, got %T", decodedValue) } tu.Equal(t, decodedTime.Unix(), tt.time.Unix()) tu.Equal(t, decodedTime.Nanosecond(), tt.time.Nanosecond()) }) } } ================================================ FILE: time/decode_test.go ================================================ package time import ( "encoding/binary" "reflect" "testing" "time" "github.com/shamaton/msgpack/v3/def" tu "github.com/shamaton/msgpack/v3/internal/common/testutil" ) func TestDecodeCode(t *testing.T) { decoder := Decoder code := decoder.Code() tu.Equal(t, code, def.TimeStamp) } func TestDecodeIsType(t *testing.T) { decoder := Decoder ts := def.TimeStamp tests := []struct { name string data []byte expected bool }{ { name: "Fixext4 with TimeStamp", data: []byte{def.Fixext4, byte(ts), 0, 0, 0, 0}, expected: true, }, { name: "Fixext4 with wrong type", data: []byte{def.Fixext4, 0x00, 0, 0, 0, 0}, expected: false, }, { name: "Fixext8 with TimeStamp", data: []byte{def.Fixext8, byte(ts), 0, 0, 0, 0, 0, 0, 0, 0}, expected: true, }, { name: "Fixext8 with wrong type", data: []byte{def.Fixext8, 0x00, 0, 0, 0, 0, 0, 0, 0, 0}, expected: false, }, { name: "Ext8 with length 12 and TimeStamp", data: []byte{def.Ext8, 12, byte(ts), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, expected: true, }, { name: "Ext8 with wrong length", data: []byte{def.Ext8, 10, byte(ts), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, expected: false, }, { name: "Ext8 with wrong type", data: []byte{def.Ext8, 12, 0x00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, expected: false, }, { name: "Wrong format", data: []byte{def.Nil}, expected: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := decoder.IsType(0, &tt.data) tu.Equal(t, result, tt.expected) }) } } func TestDecodeIsTypeShortInput(t *testing.T) { decoder := Decoder ts := def.TimeStamp testcases := []struct { name string data []byte }{ {name: "Fixext4 missing type", data: []byte{def.Fixext4}}, {name: "Fixext8 missing type", data: []byte{def.Fixext8}}, {name: "Ext8 missing length", data: []byte{def.Ext8}}, {name: "Ext8 missing type", data: []byte{def.Ext8, 12}}, {name: "Fixext4 missing payload", data: []byte{def.Fixext4, byte(ts)}}, {name: "Fixext8 missing payload", data: []byte{def.Fixext8, byte(ts)}}, {name: "Ext8 missing payload", data: []byte{def.Ext8, 12, byte(ts)}}, } for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { tu.Equal(t, decoder.IsType(0, &tc.data), false) }) } } func TestDecodeAsValueFixext4(t *testing.T) { decoder := Decoder ts := def.TimeStamp tests := []struct { name string unixTime int64 }{ { name: "Unix epoch", unixTime: 0, }, { name: "Small timestamp", unixTime: 1000, }, { name: "Large timestamp", unixTime: 1<<32 - 1, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Create Fixext4 format data data := make([]byte, 6) data[0] = def.Fixext4 data[1] = byte(ts) binary.BigEndian.PutUint32(data[2:], uint32(tt.unixTime)) value, offset, err := decoder.AsValue(0, reflect.TypeOf(time.Time{}).Kind(), &data) tu.NoError(t, err) tu.Equal(t, offset, 6) timeValue, ok := value.(time.Time) if !ok { t.Fatalf("Expected time.Time, got %T", value) } tu.Equal(t, timeValue.Unix(), tt.unixTime) tu.Equal(t, timeValue.Nanosecond(), 0) }) } } func TestDecodeAsValueFixext8(t *testing.T) { decoder := Decoder ts := def.TimeStamp tests := []struct { name string unixTime int64 nanosecond int64 }{ { name: "With small nanoseconds", unixTime: 1000, nanosecond: 123456789, }, { name: "With max nanoseconds", unixTime: 67890, nanosecond: 999999999, }, { name: "Boundary at 2^34-1", unixTime: (1 << 34) - 1, nanosecond: 999999999, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Create Fixext8 format data data := make([]byte, 10) data[0] = def.Fixext8 data[1] = byte(ts) // Pack nanoseconds in upper 30 bits and seconds in lower 34 bits data64 := uint64(tt.nanosecond)<<34 | uint64(tt.unixTime) binary.BigEndian.PutUint64(data[2:], data64) value, offset, err := decoder.AsValue(0, reflect.TypeOf(time.Time{}).Kind(), &data) tu.NoError(t, err) tu.Equal(t, offset, 10) timeValue, ok := value.(time.Time) if !ok { t.Fatalf("Expected time.Time, got %T", value) } tu.Equal(t, timeValue.Unix(), tt.unixTime) tu.Equal(t, int64(timeValue.Nanosecond()), tt.nanosecond) }) } } func TestDecodeAsValueExt8(t *testing.T) { decoder := Decoder ts := def.TimeStamp tests := []struct { name string unixTime int64 nanosecond int32 }{ { name: "Large timestamp at 2^34", unixTime: 1 << 34, nanosecond: 123456789, }, { name: "Very large timestamp", unixTime: 253402300799, nanosecond: 999999999, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Create Ext8 format data data := make([]byte, 15) data[0] = def.Ext8 data[1] = 12 // length data[2] = byte(ts) binary.BigEndian.PutUint32(data[3:], uint32(tt.nanosecond)) binary.BigEndian.PutUint64(data[7:], uint64(tt.unixTime)) value, offset, err := decoder.AsValue(0, reflect.TypeOf(time.Time{}).Kind(), &data) tu.NoError(t, err) tu.Equal(t, offset, 15) timeValue, ok := value.(time.Time) if !ok { t.Fatalf("Expected time.Time, got %T", value) } tu.Equal(t, timeValue.Unix(), tt.unixTime) tu.Equal(t, int64(timeValue.Nanosecond()), int64(tt.nanosecond)) }) } } func TestDecodeAsValueErrors(t *testing.T) { decoder := Decoder ts := def.TimeStamp t.Run("Fixext8 with invalid nanoseconds", func(t *testing.T) { data := make([]byte, 10) data[0] = def.Fixext8 data[1] = byte(ts) // Set nanoseconds > 999999999 (invalid) invalidNano := uint64(1000000000) data64 := invalidNano<<34 | 1000 binary.BigEndian.PutUint64(data[2:], data64) _, _, err := decoder.AsValue(0, reflect.TypeOf(time.Time{}).Kind(), &data) tu.ErrorContains(t, err, "in timestamp 64 formats") }) t.Run("Ext8 with invalid nanoseconds", func(t *testing.T) { data := make([]byte, 15) data[0] = def.Ext8 data[1] = 12 data[2] = byte(ts) // Set nanoseconds > 999999999 (invalid) binary.BigEndian.PutUint32(data[3:], 1000000000) binary.BigEndian.PutUint64(data[7:], 1000) _, _, err := decoder.AsValue(0, reflect.TypeOf(time.Time{}).Kind(), &data) tu.ErrorContains(t, err, "in timestamp 96 formats") }) t.Run("Invalid format code", func(t *testing.T) { data := []byte{def.Nil} _, _, err := decoder.AsValue(0, reflect.TypeOf(time.Time{}).Kind(), &data) tu.ErrorContains(t, err, "should not reach") }) } func TestDecodeAsValueTooShort(t *testing.T) { decoder := Decoder ts := def.TimeStamp testcases := []struct { name string data []byte }{ {name: "Fixext4 missing type", data: []byte{def.Fixext4}}, {name: "Fixext4 missing payload", data: []byte{def.Fixext4, byte(ts)}}, {name: "Fixext8 missing type", data: []byte{def.Fixext8}}, {name: "Fixext8 missing payload", data: []byte{def.Fixext8, byte(ts)}}, {name: "Ext8 missing length", data: []byte{def.Ext8}}, {name: "Ext8 missing type", data: []byte{def.Ext8, 12}}, {name: "Ext8 missing payload", data: []byte{def.Ext8, 12, byte(ts)}}, } for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { _, _, err := decoder.AsValue(0, reflect.TypeOf(time.Time{}).Kind(), &tc.data) tu.IsError(t, err, def.ErrTooShortBytes) }) } } func TestDecodeTimezone(t *testing.T) { decoder := Decoder ts := def.TimeStamp tests := []struct { name string time time.Time createData func(time.Time) []byte }{ { name: "Fixext4", time: time.Unix(1000, 0), createData: func(testTime time.Time) []byte { data := make([]byte, 6) data[0] = def.Fixext4 data[1] = byte(ts) binary.BigEndian.PutUint32(data[2:], uint32(testTime.Unix())) return data }, }, { name: "Fixext8", time: time.Unix(1000, 123456789), createData: func(testTime time.Time) []byte { data := make([]byte, 10) data[0] = def.Fixext8 data[1] = byte(ts) data64 := uint64(testTime.Nanosecond())<<34 | uint64(testTime.Unix()) binary.BigEndian.PutUint64(data[2:], data64) return data }, }, { name: "Ext8", time: time.Unix(1<<34, 123456789), createData: func(testTime time.Time) []byte { data := make([]byte, 15) data[0] = def.Ext8 data[1] = 12 data[2] = byte(ts) binary.BigEndian.PutUint32(data[3:], uint32(testTime.Nanosecond())) binary.BigEndian.PutUint64(data[7:], uint64(testTime.Unix())) return data }, }, } for _, tt := range tests { t.Run(tt.name+" - Decode as local (default)", func(t *testing.T) { // Set to local timezone SetDecodedAsLocal(true) defer SetDecodedAsLocal(true) // Reset to default data := tt.createData(tt.time) value, _, err := decoder.AsValue(0, reflect.TypeOf(time.Time{}).Kind(), &data) tu.NoError(t, err) timeValue, ok := value.(time.Time) if !ok { t.Fatalf("Expected time.Time, got %T", value) } // Should be in local timezone tu.Equal(t, timeValue.Location(), time.Local) }) t.Run(tt.name+" - Decode as UTC", func(t *testing.T) { // Set to UTC timezone SetDecodedAsLocal(false) defer SetDecodedAsLocal(true) // Reset to default data := tt.createData(tt.time) value, _, err := decoder.AsValue(0, reflect.TypeOf(time.Time{}).Kind(), &data) tu.NoError(t, err) timeValue, ok := value.(time.Time) if !ok { t.Fatalf("Expected time.Time, got %T", value) } // Should be in UTC timezone tu.Equal(t, timeValue.Location(), time.UTC) }) } } func TestDecodeRoundTrip(t *testing.T) { encoder := Encoder decoder := Decoder tests := []struct { name string time time.Time }{ { name: "Fixext4 format", time: time.Unix(1000, 0), }, { name: "Fixext8 format", time: time.Unix(67890, 123456789), }, { name: "Ext8 format", time: time.Unix(17179869184, 987654321), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Encode value := reflect.ValueOf(tt.time) size, err := encoder.CalcByteSize(value) tu.NoError(t, err) bytes := make([]byte, size) encoder.WriteToBytes(value, 0, &bytes) // Decode decodedValue, offset, err := decoder.AsValue(0, reflect.TypeOf(time.Time{}).Kind(), &bytes) tu.NoError(t, err) tu.Equal(t, offset, size) decodedTime, ok := decodedValue.(time.Time) if !ok { t.Fatalf("Expected time.Time, got %T", decodedValue) } // Compare Unix time and nanoseconds tu.Equal(t, decodedTime.Unix(), tt.time.Unix()) tu.Equal(t, decodedTime.Nanosecond(), tt.time.Nanosecond()) }) } } ================================================ FILE: time/encode.go ================================================ package time import ( "reflect" "time" "github.com/shamaton/msgpack/v3/def" "github.com/shamaton/msgpack/v3/ext" ) var Encoder = new(timeEncoder) type timeEncoder struct { ext.EncoderCommon } var typeOf = reflect.TypeOf(time.Time{}) func (td *timeEncoder) Code() int8 { return def.TimeStamp } func (s *timeEncoder) Type() reflect.Type { return typeOf } func (s *timeEncoder) CalcByteSize(value reflect.Value) (int, error) { t := value.Interface().(time.Time) secs := uint64(t.Unix()) if secs>>34 == 0 { data := uint64(t.Nanosecond())<<34 | secs if data&0xffffffff00000000 == 0 { return def.Byte1 + def.Byte1 + def.Byte4, nil } return def.Byte1 + def.Byte1 + def.Byte8, nil } return def.Byte1 + def.Byte1 + def.Byte1 + def.Byte4 + def.Byte8, nil } func (s *timeEncoder) WriteToBytes(value reflect.Value, offset int, bytes *[]byte) int { t := value.Interface().(time.Time) secs := uint64(t.Unix()) if secs>>34 == 0 { data := uint64(t.Nanosecond())<<34 | secs if data&0xffffffff00000000 == 0 { offset = s.SetByte1Int(def.Fixext4, offset, bytes) offset = s.SetByte1Int(def.TimeStamp, offset, bytes) offset = s.SetByte4Uint64(data, offset, bytes) return offset } offset = s.SetByte1Int(def.Fixext8, offset, bytes) offset = s.SetByte1Int(def.TimeStamp, offset, bytes) offset = s.SetByte8Uint64(data, offset, bytes) return offset } offset = s.SetByte1Int(def.Ext8, offset, bytes) offset = s.SetByte1Int(12, offset, bytes) offset = s.SetByte1Int(def.TimeStamp, offset, bytes) offset = s.SetByte4Int(t.Nanosecond(), offset, bytes) offset = s.SetByte8Uint64(secs, offset, bytes) return offset } ================================================ FILE: time/encode_stream.go ================================================ package time import ( "reflect" "time" "github.com/shamaton/msgpack/v3/def" "github.com/shamaton/msgpack/v3/ext" ) var StreamEncoder = new(timeStreamEncoder) type timeStreamEncoder struct{} var _ ext.StreamEncoder = (*timeStreamEncoder)(nil) func (timeStreamEncoder) Code() int8 { return def.TimeStamp } func (timeStreamEncoder) Type() reflect.Type { return typeOf } func (e timeStreamEncoder) Write(w ext.StreamWriter, value reflect.Value) error { t := value.Interface().(time.Time) secs := uint64(t.Unix()) if secs>>34 == 0 { data := uint64(t.Nanosecond())<<34 | secs if data&0xffffffff00000000 == 0 { if err := w.WriteByte1Int(def.Fixext4); err != nil { return err } if err := w.WriteByte1Int(def.TimeStamp); err != nil { return err } if err := w.WriteByte4Uint64(data); err != nil { return err } return nil } if err := w.WriteByte1Int(def.Fixext8); err != nil { return err } if err := w.WriteByte1Int(def.TimeStamp); err != nil { return err } if err := w.WriteByte8Uint64(data); err != nil { return err } return nil } if err := w.WriteByte1Int(def.Ext8); err != nil { return err } if err := w.WriteByte1Int(12); err != nil { return err } if err := w.WriteByte1Int(def.TimeStamp); err != nil { return err } if err := w.WriteByte4Int(t.Nanosecond()); err != nil { return err } if err := w.WriteByte8Uint64(secs); err != nil { return err } return nil } ================================================ FILE: time/encode_stream_test.go ================================================ package time import ( "bytes" "encoding/binary" "errors" "reflect" "testing" "time" "github.com/shamaton/msgpack/v3/def" "github.com/shamaton/msgpack/v3/ext" "github.com/shamaton/msgpack/v3/internal/common" tu "github.com/shamaton/msgpack/v3/internal/common/testutil" ) func TestStreamCode(t *testing.T) { encoder := StreamEncoder code := encoder.Code() tu.Equal(t, code, def.TimeStamp) } func TestStreamType(t *testing.T) { encoder := StreamEncoder typ := encoder.Type() expected := reflect.TypeOf(time.Time{}) tu.Equal(t, typ, expected) } func TestStreamWrite(t *testing.T) { tests := []struct { name string time time.Time expectedLen int expectedFormat string }{ { name: "Fixext4 format (32-bit timestamp, no nanoseconds)", time: time.Unix(0, 0), expectedLen: 6, // 1 (Fixext4) + 1 (TimeStamp) + 4 (data) expectedFormat: "fixext4", }, { name: "Fixext4 format (small timestamp, no nanoseconds)", time: time.Unix(1000, 0), expectedLen: 6, expectedFormat: "fixext4", }, { name: "Fixext8 format (needs 64-bit but secs fits in 34 bits)", time: time.Unix(17179869183, 999999999), // (1 << 34) - 1 expectedLen: 10, // 1 (Fixext8) + 1 (TimeStamp) + 8 (data) expectedFormat: "fixext8", }, { name: "Ext8 format (secs >= 2^34)", time: time.Unix(17179869184, 123456789), // 1 << 34 expectedLen: 15, // 1 (Ext8) + 1 (12) + 1 (TimeStamp) + 4 (nsec) + 8 (secs) expectedFormat: "ext8", }, { name: "Ext8 format (large timestamp)", time: time.Unix(253402300799, 999999999), // 9999-12-31 23:59:59 expectedLen: 15, expectedFormat: "ext8", }, } encoder := StreamEncoder for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { value := reflect.ValueOf(tt.time) buf := &bytes.Buffer{} buffer := common.GetBuffer() defer common.PutBuffer(buffer) w := ext.CreateStreamWriter(buf, buffer) err := encoder.Write(w, value) tu.NoError(t, err) // Flush buffer to writer err = buffer.Flush(buf) tu.NoError(t, err) b := buf.Bytes() tu.Equal(t, len(b), tt.expectedLen) // Verify format type switch tt.expectedFormat { case "fixext4": tu.Equal(t, b[0], def.Fixext4) tu.Equal(t, int8(b[1]), def.TimeStamp) case "fixext8": tu.Equal(t, b[0], def.Fixext8) tu.Equal(t, int8(b[1]), def.TimeStamp) case "ext8": tu.Equal(t, b[0], def.Ext8) tu.Equal(t, b[1], 12) tu.Equal(t, int8(b[2]), def.TimeStamp) default: t.Errorf("Unknown expected format: %s", tt.expectedFormat) } }) } } func TestStreamWriteEdgeCases(t *testing.T) { encoder := StreamEncoder tests := []struct { name string time time.Time }{ { name: "Unix epoch", time: time.Unix(0, 0), }, { name: "Maximum nanoseconds", time: time.Unix(1000, 999999999), }, { name: "Boundary at 2^34 - 1", time: time.Unix((1<<34)-1, 0), }, { name: "Boundary at 2^34", time: time.Unix(1<<34, 0), }, { name: "Negative Unix timestamp", time: time.Unix(-1, 0), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { value := reflect.ValueOf(tt.time) buf := &bytes.Buffer{} buffer := common.GetBuffer() defer common.PutBuffer(buffer) w := ext.CreateStreamWriter(buf, buffer) err := encoder.Write(w, value) tu.NoError(t, err) // Flush buffer to writer err = buffer.Flush(buf) tu.NoError(t, err) b := buf.Bytes() // Verify basic structure is valid if len(b) < 2 { t.Error("Byte slice too short") } }) } } func TestStreamEncodedDataAccuracy(t *testing.T) { encoder := StreamEncoder tests := []struct { name string time time.Time }{ { name: "Fixext4 - simple", time: time.Unix(12345, 0), }, { name: "Fixext8 - with nanoseconds", time: time.Unix(67890, 123456789), }, { name: "Ext8 - large", time: time.Unix(17179869184, 987654321), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { value := reflect.ValueOf(tt.time) buf := &bytes.Buffer{} buffer := common.GetBuffer() defer common.PutBuffer(buffer) w := ext.CreateStreamWriter(buf, buffer) err := encoder.Write(w, value) tu.NoError(t, err) // Flush buffer to writer err = buffer.Flush(buf) tu.NoError(t, err) b := buf.Bytes() // Verify we can extract the correct time back switch b[0] { case def.Fixext4: data := binary.BigEndian.Uint32(b[2:6]) secs := int64(data) tu.Equal(t, secs, tt.time.Unix()) case def.Fixext8: data := binary.BigEndian.Uint64(b[2:10]) nano := int64(data >> 34) secs := int64(data & 0x00000003ffffffff) tu.Equal(t, secs, tt.time.Unix()) tu.Equal(t, nano, int64(tt.time.Nanosecond())) case def.Ext8: nano := binary.BigEndian.Uint32(b[3:7]) secs := binary.BigEndian.Uint64(b[7:15]) tu.Equal(t, int64(secs), tt.time.Unix()) tu.Equal(t, int64(nano), int64(tt.time.Nanosecond())) } }) } } func TestStreamWriteWithVariousNanoseconds(t *testing.T) { encoder := StreamEncoder tests := []struct { name string time time.Time expectFmt byte description string }{ { name: "Zero nanoseconds", time: time.Unix(1000, 0), expectFmt: def.Fixext4, description: "Should use Fixext4 when nanoseconds is 0", }, { name: "Small nanoseconds (1)", time: time.Unix(1000, 1), expectFmt: def.Fixext8, description: "Should use Fixext8 with nanoseconds", }, { name: "Mid nanoseconds", time: time.Unix(1000, 500000000), expectFmt: def.Fixext8, description: "Should use Fixext8 with mid-range nanoseconds", }, { name: "Max nanoseconds", time: time.Unix(1000, 999999999), expectFmt: def.Fixext8, description: "Should use Fixext8 with max nanoseconds", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { value := reflect.ValueOf(tt.time) buf := &bytes.Buffer{} buffer := common.GetBuffer() defer common.PutBuffer(buffer) w := ext.CreateStreamWriter(buf, buffer) err := encoder.Write(w, value) tu.NoError(t, err) // Flush buffer to writer err = buffer.Flush(buf) tu.NoError(t, err) b := buf.Bytes() tu.Equal(t, b[0], tt.expectFmt) }) } } type testErrWriter struct { ErrorBytes []byte Count int } func (w *testErrWriter) Write(p []byte) (n int, err error) { if bytes.Equal(w.ErrorBytes, p) { return 0, errors.New("equal bytes error") } return len(p), nil } func TestStreamWriteErrors(t *testing.T) { encoder := StreamEncoder ts := def.TimeStamp tests := []struct { name string timeValue time.Time errorBytes []byte prepareSize int prepareBytes []byte }{ // Fixext4 tests { name: "Fixext4 - Error on writing Fixext4 type byte", timeValue: time.Unix(1000, 0), errorBytes: []byte{255}, prepareSize: 1, prepareBytes: []byte{255}, }, { name: "Fixext4 - Error on writing TimeStamp type byte", timeValue: time.Unix(1000, 0), errorBytes: []byte{def.Fixext4}, prepareSize: 1, }, { name: "Fixext4 - Error on writing 4 bytes of data", timeValue: time.Unix(1000, 0), errorBytes: []byte{def.Fixext4, byte(ts)}, prepareSize: 2, }, // Fixext8 tests { name: "Fixext8 - Error on writing Fixext8 type byte", timeValue: time.Unix(1000, 1), errorBytes: []byte{255}, prepareSize: 1, prepareBytes: []byte{255}, }, { name: "Fixext8 - Error on writing TimeStamp type byte", timeValue: time.Unix(1000, 1), errorBytes: []byte{def.Fixext8}, prepareSize: 1, }, { name: "Fixext8 - Error on writing 8 bytes of data", timeValue: time.Unix(1000, 1), errorBytes: []byte{def.Fixext8, byte(ts)}, prepareSize: 2, }, // Ext8 tests { name: "Ext8 - Error on writing Ext8 type byte", timeValue: time.Unix(1<<34, 0), errorBytes: []byte{255}, prepareSize: 1, prepareBytes: []byte{255}, }, { name: "Ext8 - Error on writing length byte", timeValue: time.Unix(1<<34, 0), errorBytes: []byte{def.Ext8}, prepareSize: 1, }, { name: "Ext8 - Error on writing TimeStamp type byte", timeValue: time.Unix(1<<34, 0), errorBytes: []byte{def.Ext8, 12}, prepareSize: 2, }, { name: "Ext8 - Error on writing 4 bytes of nanoseconds", timeValue: time.Unix(1<<34, 0), errorBytes: []byte{def.Ext8, 12, byte(ts)}, prepareSize: 3, }, { name: "Ext8 - Error on writing 8 bytes of seconds", timeValue: time.Unix(1<<34, 123456789), errorBytes: []byte{def.Ext8, 12, byte(ts), 0x07, 0x5b, 0xcd, 0x15}, prepareSize: 7, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { buffer := &common.Buffer{Data: make([]byte, tt.prepareSize)} err := buffer.Write(nil, tt.prepareBytes...) tu.NoError(t, err) errWriter := &testErrWriter{ErrorBytes: tt.errorBytes} w := ext.CreateStreamWriter(errWriter, buffer) value := reflect.ValueOf(tt.timeValue) err = encoder.Write(w, value) tu.Error(t, err) }) } } ================================================ FILE: time/encode_test.go ================================================ package time import ( "encoding/binary" "reflect" "testing" "time" "github.com/shamaton/msgpack/v3/def" tu "github.com/shamaton/msgpack/v3/internal/common/testutil" ) func TestCode(t *testing.T) { encoder := Encoder code := encoder.Code() tu.Equal(t, code, def.TimeStamp) } func TestType(t *testing.T) { encoder := Encoder typ := encoder.Type() expected := reflect.TypeOf(time.Time{}) tu.Equal(t, typ, expected) } func TestCalcByteSize(t *testing.T) { encoder := Encoder tests := []struct { name string time time.Time expected int }{ { name: "Fixext4 - epoch", time: time.Unix(0, 0), expected: def.Byte1 + def.Byte1 + def.Byte4, // 6 }, { name: "Fixext4 - small timestamp", time: time.Unix(1000, 0), expected: def.Byte1 + def.Byte1 + def.Byte4, // 6 }, { name: "Fixext8 - with nanoseconds", time: time.Unix(1000, 999999999), expected: def.Byte1 + def.Byte1 + def.Byte8, // 10 }, { name: "Fixext8 - boundary (2^34-1)", time: time.Unix((1<<34)-1, 0), expected: def.Byte1 + def.Byte1 + def.Byte8, // 10 }, { name: "Ext8 - large timestamp (2^34)", time: time.Unix(1<<34, 0), expected: def.Byte1 + def.Byte1 + def.Byte1 + def.Byte4 + def.Byte8, // 15 }, { name: "Ext8 - large timestamp with nanoseconds", time: time.Unix(1<<34, 123456789), expected: def.Byte1 + def.Byte1 + def.Byte1 + def.Byte4 + def.Byte8, // 15 }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { value := reflect.ValueOf(tt.time) size, err := encoder.CalcByteSize(value) tu.NoError(t, err) tu.Equal(t, size, tt.expected) }) } } func TestEncodedDataAccuracy(t *testing.T) { encoder := Encoder tests := []struct { name string time time.Time }{ { name: "Fixext4 - simple", time: time.Unix(12345, 0), }, { name: "Fixext8 - with nanoseconds", time: time.Unix(67890, 123456789), }, { name: "Ext8 - large", time: time.Unix(17179869184, 987654321), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { value := reflect.ValueOf(tt.time) size, err := encoder.CalcByteSize(value) tu.NoError(t, err) bytes := make([]byte, size) encoder.WriteToBytes(value, 0, &bytes) // Verify we can extract the correct time back switch bytes[0] { case def.Fixext4: data := binary.BigEndian.Uint32(bytes[2:6]) secs := int64(data) tu.Equal(t, secs, tt.time.Unix()) case def.Fixext8: data := binary.BigEndian.Uint64(bytes[2:10]) nano := int64(data >> 34) secs := int64(data & 0x00000003ffffffff) tu.Equal(t, secs, tt.time.Unix()) tu.Equal(t, nano, int64(tt.time.Nanosecond())) case def.Ext8: nano := binary.BigEndian.Uint32(bytes[3:7]) secs := binary.BigEndian.Uint64(bytes[7:15]) tu.Equal(t, int64(secs), tt.time.Unix()) tu.Equal(t, int64(nano), int64(tt.time.Nanosecond())) } }) } } func TestWriteToBytes(t *testing.T) { tests := []struct { name string time time.Time expectedLen int expectedFormat string }{ { name: "Fixext4 format (32-bit timestamp, no nanoseconds)", time: time.Unix(0, 0), expectedLen: 6, // 1 (Fixext4) + 1 (TimeStamp) + 4 (data) expectedFormat: "fixext4", }, { name: "Fixext4 format (small timestamp, no nanoseconds)", time: time.Unix(1000, 0), expectedLen: 6, expectedFormat: "fixext4", }, { name: "Fixext8 format (needs 64-bit but secs fits in 34 bits)", time: time.Unix(17179869183, 999999999), // (1 << 34) - 1 expectedLen: 10, // 1 (Fixext8) + 1 (TimeStamp) + 8 (data) expectedFormat: "fixext8", }, { name: "Ext8 format (secs >= 2^34)", time: time.Unix(17179869184, 123456789), // 1 << 34 expectedLen: 15, // 1 (Ext8) + 1 (12) + 1 (TimeStamp) + 4 (nsec) + 8 (secs) expectedFormat: "ext8", }, { name: "Ext8 format (large timestamp)", time: time.Unix(253402300799, 999999999), // 9999-12-31 23:59:59 expectedLen: 15, expectedFormat: "ext8", }, } encoder := Encoder for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { value := reflect.ValueOf(tt.time) // Calculate expected byte size size, err := encoder.CalcByteSize(value) tu.NoError(t, err) tu.Equal(t, size, tt.expectedLen) // Create byte slice bytes := make([]byte, size) // Write to bytes offset := encoder.WriteToBytes(value, 0, &bytes) tu.Equal(t, offset, tt.expectedLen) // Verify format type switch tt.expectedFormat { case "fixext4": tu.Equal(t, bytes[0], def.Fixext4) tu.Equal(t, int8(bytes[1]), def.TimeStamp) case "fixext8": tu.Equal(t, bytes[0], def.Fixext8) tu.Equal(t, int8(bytes[1]), def.TimeStamp) case "ext8": tu.Equal(t, bytes[0], def.Ext8) tu.Equal(t, bytes[1], 12) tu.Equal(t, int8(bytes[2]), def.TimeStamp) default: t.Errorf("Unknown expected format: %s", tt.expectedFormat) } }) } } func TestWriteToBytesOffset(t *testing.T) { encoder := Encoder testTime := time.Unix(1000000000, 123456789) value := reflect.ValueOf(testTime) size, err := encoder.CalcByteSize(value) tu.NoError(t, err) // Test with non-zero offset offset := 10 bytes := make([]byte, offset+size) newOffset := encoder.WriteToBytes(value, offset, &bytes) tu.Equal(t, newOffset, offset+size) // Verify the data is written at correct position if bytes[offset] != def.Fixext4 && bytes[offset] != def.Fixext8 && bytes[offset] != def.Ext8 { t.Errorf("Data not written at correct offset") } } func TestWriteToBytesEdgeCases(t *testing.T) { encoder := Encoder tests := []struct { name string time time.Time }{ { name: "Unix epoch", time: time.Unix(0, 0), }, { name: "Maximum nanoseconds", time: time.Unix(1000, 999999999), }, { name: "Boundary at 2^34 - 1", time: time.Unix((1<<34)-1, 0), }, { name: "Boundary at 2^34", time: time.Unix(1<<34, 0), }, { name: "Negative Unix timestamp", time: time.Unix(-1, 0), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { value := reflect.ValueOf(tt.time) size, err := encoder.CalcByteSize(value) tu.NoError(t, err) bytes := make([]byte, size) offset := encoder.WriteToBytes(value, 0, &bytes) tu.Equal(t, offset, size) // Verify basic structure is valid if len(bytes) < 2 { t.Error("Byte slice too short") } }) } } func TestWriteToBytesWithVariousNanoseconds(t *testing.T) { encoder := Encoder tests := []struct { name string time time.Time expectFmt byte description string }{ { name: "Zero nanoseconds", time: time.Unix(1000, 0), expectFmt: def.Fixext4, description: "Should use Fixext4 when nanoseconds is 0", }, { name: "Small nanoseconds (1)", time: time.Unix(1000, 1), expectFmt: def.Fixext8, description: "Should use Fixext8 with nanoseconds", }, { name: "Mid nanoseconds", time: time.Unix(1000, 500000000), expectFmt: def.Fixext8, description: "Should use Fixext8 with mid-range nanoseconds", }, { name: "Max nanoseconds", time: time.Unix(1000, 999999999), expectFmt: def.Fixext8, description: "Should use Fixext8 with max nanoseconds", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { value := reflect.ValueOf(tt.time) size, err := encoder.CalcByteSize(value) tu.NoError(t, err) bytes := make([]byte, size) encoder.WriteToBytes(value, 0, &bytes) tu.Equal(t, bytes[0], tt.expectFmt) }) } } ================================================ FILE: time/time.go ================================================ package time var decodeAsLocal = false // SetDecodedAsLocal sets the decoded time to local time. func SetDecodedAsLocal(b bool) { decodeAsLocal = b } ================================================ FILE: unmarshal_ext_test.go ================================================ package msgpack_test import ( "errors" "testing" "github.com/shamaton/msgpack/v3" "github.com/shamaton/msgpack/v3/def" ) func TestUnmarshalTruncatedTimestampExtReturnsTooShort(t *testing.T) { ts := def.TimeStamp testcases := []struct { name string data []byte }{ {name: "fixext4", data: []byte{def.Fixext4, byte(ts)}}, {name: "fixext8", data: []byte{def.Fixext8, byte(ts)}}, } methods := []struct { name string fn func([]byte, interface{}) error }{ {name: "Unmarshal", fn: msgpack.Unmarshal}, {name: "UnmarshalAsArray", fn: msgpack.UnmarshalAsArray}, {name: "UnmarshalAsMap", fn: msgpack.UnmarshalAsMap}, } for _, method := range methods { for _, tc := range testcases { t.Run(method.name+"/"+tc.name, func(t *testing.T) { var v interface{} err := method.fn(tc.data, &v) if !errors.Is(err, def.ErrTooShortBytes) { t.Fatalf("expected %v, got %v", def.ErrTooShortBytes, err) } }) } } }