feat: add display UI with splash screen, dashboard, and RSSI sparkline

Implement full SSD1306 OLED display support:
- Splash screen on boot (1.5s) with project name, version, board
- Dashboard showing radio state, config, counters, RSSI/SNR
- RSSI history sparkline (64-sample ring buffer, 2px-wide bars)
- DisplayOn/DisplayOff host commands routed via dedicated channel
- Pure render functions against generic DrawTarget (no hardware coupling)
- Display on/off control bypasses radio task entirely

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
swaitsandClaude Opus 4.6 committed 2026-03-29 20:42:54 -06:00
1 parent 7064110293
commit 3102fc747a
17624 files changed
+4564928 -38

No files matched your search

+1
View File
@@ -0,0 +1 @@
{"files":{".cargo_vcs_info.json":"fa36d6389b358d23d8f055be96aa8f5711754c3b7f65777320f6bf85573a8235","Cargo.lock":"f6273fea06ff1a8da2b633cdca234846abbbc4f08ad689f990d647e7bfe7a6e6","Cargo.toml":"8f1de19506db838be4fc72b5c14c7a414073e9ef8222b7b7e18fa2e005c1e05e","Cargo.toml.orig":"ec846871cb12f3e868a9950e7ef1d7ff7710dba1d5073beb1d4293b179adaaff","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"177540cad091a40e8071db310bc3b6115c4e329a92a234609b60c154b008a888","README.md":"f74dbb06448938d8926d28997d603ef6fc6c1265aebbd831cdc26d87a44436f4","src/accumulator.rs":"0c4e4d8ec302878793c0193c2141f5c934341c7a8d9ee046fc8f2d27ad5cd6a2","src/de/deserializer.rs":"20f5ff69404628e4d765f58ceb91b6ac8879ce998aef6dfe49a2fdb2c5f20ac2","src/de/flavors.rs":"558b4daadaf3c6892e914a5ef3533ba3b1a696bd093a1ed471154cc11f9b4b91","src/de/mod.rs":"0672b81c7bb1e0e1cc9f812661631e2b43dcd2ddba8dbb89d8d2cb72375f35a7","src/eio.rs":"0b99abc945654b0e3ecd1d16fa2b908e0c61a74b615d3e16fc22b3028e15970e","src/error.rs":"07f66178e86c46ae53f00df5ef6e778ab3f2d075e08084086e67cca132539379","src/fixint.rs":"41a8734a6ff99bb129d944be9228fbd9dfbcc7a514acf6305098299f5726f7ee","src/lib.rs":"026c0feb3d0b44b9208024b86d47d185087611b16b500627459ace28b86be1f9","src/max_size.rs":"d08926697cff90893fbd0c02dd43ba6be611335d6b540c1000e03d30d0ed20ec","src/ser/flavors.rs":"9be0b2ae5f9012b1183ad4102df8369a29864b1c615d62a3d6666a6b2884432d","src/ser/mod.rs":"2ef1490316948536890dfbb6d19f5051b464a0c4e28efb50a72dbb8306653cdc","src/ser/serializer.rs":"e8ac1136c617d622e8897a0469295605239d1000f772cafa0d64f18e1a52e5bc","src/varint.rs":"d40e63d0e13752c562f5116da2cbc786f608815a97076f94bf56b701b39120d4","tests/accumulator.rs":"1424dd6af14a2d54b7b234ce2f41b7e618fd92d42e4ecb63636fd3e85d28faf4","tests/crc.rs":"d255be56c9b877bc18076720b93d9d287dd1024ba642cd36bfa26ba6232cce38","tests/loopback.rs":"a6bf82b01f144d138771b594d08d6b3aea6447fc35b4e5c67c68ae12742009f1","tests/max_size.rs":"074487dc54725a0b54066b61b663bad75d9e18e0921ec1743ce434563adeb9fd"},"package":"6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24"}
+6
View File
@@ -0,0 +1,6 @@
{
"git": {
"sha1": "718aa6a6850456017c19eeff67303c633f875736"
},
"path_in_vcs": "source/postcard"
}
Generated Vendored
+380
View File
@@ -0,0 +1,380 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "approx"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
dependencies = [
"num-traits",
]
[[package]]
name = "atomic-polyfill"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4"
dependencies = [
"critical-section",
]
[[package]]
name = "autocfg"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cobs"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1"
dependencies = [
"thiserror",
]
[[package]]
name = "crc"
version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675"
dependencies = [
"crc-catalog",
]
[[package]]
name = "crc-catalog"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
[[package]]
name = "critical-section"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
[[package]]
name = "defmt"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "548d977b6da32fa1d1fda2876453da1e7df63ad0304c8b3dae4dbe7b96f39b78"
dependencies = [
"bitflags",
"defmt-macros",
]
[[package]]
name = "defmt-macros"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d4fc12a85bcf441cfe44344c4b72d58493178ce635338a3f3b78943aceb258e"
dependencies = [
"defmt-parser",
"proc-macro-error2",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "defmt-parser"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e"
dependencies = [
"thiserror",
]
[[package]]
name = "embedded-io"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced"
[[package]]
name = "embedded-io"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"
[[package]]
name = "hash32"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67"
dependencies = [
"byteorder",
]
[[package]]
name = "heapless"
version = "0.7.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f"
dependencies = [
"atomic-polyfill",
"hash32",
"rustc_version",
"serde",
"spin",
"stable_deref_trait",
]
[[package]]
name = "lock_api"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "nalgebra"
version = "0.33.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26aecdf64b707efd1310e3544d709c5c0ac61c13756046aaaba41be5c4f66a3b"
dependencies = [
"approx",
"num-complex",
"num-rational",
"num-traits",
"simba",
"typenum",
]
[[package]]
name = "num-complex"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
dependencies = [
"num-traits",
]
[[package]]
name = "num-integer"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
dependencies = [
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
dependencies = [
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "paste"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "postcard"
version = "1.1.3"
dependencies = [
"cobs",
"crc",
"defmt",
"embedded-io 0.4.0",
"embedded-io 0.6.1",
"heapless",
"nalgebra",
"postcard-derive",
"serde",
]
[[package]]
name = "postcard-derive"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68f049d94cb6dda6938cc8a531d2898e7c08d71c6de63d8e67123cca6cdde2cc"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "proc-macro-error-attr2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
dependencies = [
"proc-macro2",
"quote",
]
[[package]]
name = "proc-macro-error2"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
dependencies = [
"proc-macro-error-attr2",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rustc_version"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
dependencies = [
"semver",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "semver"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
[[package]]
name = "serde"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "simba"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3a386a501cd104797982c15ae17aafe8b9261315b5d07e3ec803f2ea26be0fa"
dependencies = [
"approx",
"num-complex",
"num-traits",
"paste",
]
[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
dependencies = [
"lock_api",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "syn"
version = "2.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "typenum"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
+136
View File
@@ -0,0 +1,136 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
name = "postcard"
version = "1.1.3"
authors = ["James Munns <james@onevariable.com>"]
build = false
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "A no_std + serde compatible message library for Rust"
documentation = "https://docs.rs/postcard/"
readme = "README.md"
keywords = [
"serde",
"cobs",
"framing",
]
categories = [
"embedded",
"no-std",
]
license = "MIT OR Apache-2.0"
repository = "https://github.com/jamesmunns/postcard"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = [
"--cfg",
"docsrs",
]
[features]
alloc = [
"serde/alloc",
"embedded-io-04?/alloc",
"embedded-io-06?/alloc",
]
core-num-saturating = []
crc = ["dep:crc"]
default = ["heapless-cas"]
defmt = ["dep:defmt"]
embedded-io = ["dep:embedded-io-04"]
embedded-io-04 = ["dep:embedded-io-04"]
embedded-io-06 = ["dep:embedded-io-06"]
experimental-derive = ["postcard-derive"]
heapless = ["dep:heapless"]
heapless-cas = [
"heapless",
"dep:heapless",
"heapless/cas",
]
nalgebra-v0_33 = ["nalgebra_v0_33"]
paste = []
postcard-derive = ["dep:postcard-derive"]
use-crc = ["crc"]
use-defmt = ["defmt"]
use-std = [
"serde/std",
"alloc",
]
[lib]
name = "postcard"
path = "src/lib.rs"
[[test]]
name = "accumulator"
path = "tests/accumulator.rs"
[[test]]
name = "crc"
path = "tests/crc.rs"
[[test]]
name = "loopback"
path = "tests/loopback.rs"
[[test]]
name = "max_size"
path = "tests/max_size.rs"
[dependencies.cobs]
version = "0.3.0"
default-features = false
[dependencies.crc]
version = "3.0.1"
optional = true
[dependencies.defmt]
version = "1"
optional = true
[dependencies.embedded-io-04]
version = "0.4"
optional = true
package = "embedded-io"
[dependencies.embedded-io-06]
version = "0.6"
optional = true
package = "embedded-io"
[dependencies.heapless]
version = "0.7.0"
features = ["serde"]
optional = true
default-features = false
[dependencies.nalgebra_v0_33]
version = "0.33.0"
optional = true
default-features = false
package = "nalgebra"
[dependencies.postcard-derive]
version = "0.2.0"
optional = true
[dependencies.serde]
version = "1.0.100"
features = ["derive"]
default-features = false
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+25
View File
@@ -0,0 +1,25 @@
Copyright (c) 2019 Anthony James Munns
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.
+146
View File
@@ -0,0 +1,146 @@
# Postcard
[![Documentation](https://docs.rs/postcard/badge.svg)](https://docs.rs/postcard)
Postcard is a `#![no_std]` focused serializer and deserializer for Serde.
Postcard aims to be convenient for developers in constrained environments, while
allowing for flexibility to customize behavior as needed.
## Design Goals
1. Design primarily for `#![no_std]` usage, in embedded or other constrained contexts
2. Support a maximal set of `serde` features, so `postcard` can be used as a drop in replacement
3. Avoid special differences in code between communication code written for a microcontroller or a desktop/server PC
4. Be resource efficient - memory usage, code size, developer time, and CPU time; in that order
5. Allow library users to customize the serialization and deserialization behavior to fit their bespoke needs
## Format Stability
As of v1.0.0, `postcard` has a documented and stable wire format. More information about this
wire format can be found in the `spec/` folder of the Postcard repository, or viewed online
at <https://postcard.jamesmunns.com>.
Work towards the Postcard Specification and portions of the Postcard 1.0 Release
were sponsored by Mozilla Corporation.
## Variable Length Data
All signed and unsigned integers larger than eight bits are encoded using a [Varint].
This includes the length of array slices, as well as the discriminant of `enums`.
For more information, see the [Varint] chapter of the wire specification.
[Varint]: https://postcard.jamesmunns.com/wire-format.html#varint-encoded-integers
## Example - Serialization/Deserialization
Postcard can serialize and deserialize messages similar to other `serde` formats.
Using the default `heapless` feature to serialize to a `heapless::Vec<u8>`:
```rust
use core::ops::Deref;
use serde::{Serialize, Deserialize};
use postcard::{from_bytes, to_vec};
use heapless::Vec;
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
struct RefStruct<'a> {
bytes: &'a [u8],
str_s: &'a str,
}
let message = "hElLo";
let bytes = [0x01, 0x10, 0x02, 0x20];
let output: Vec<u8, 11> = to_vec(&RefStruct {
bytes: &bytes,
str_s: message,
}).unwrap();
assert_eq!(
&[0x04, 0x01, 0x10, 0x02, 0x20, 0x05, b'h', b'E', b'l', b'L', b'o',],
output.deref()
);
let out: RefStruct = from_bytes(output.deref()).unwrap();
assert_eq!(
out,
RefStruct {
bytes: &bytes,
str_s: message,
}
);
```
Or the optional `alloc` feature to serialize to an `alloc::vec::Vec<u8>`:
```rust
use core::ops::Deref;
use serde::{Serialize, Deserialize};
use postcard::{from_bytes, to_allocvec};
extern crate alloc;
use alloc::vec::Vec;
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
struct RefStruct<'a> {
bytes: &'a [u8],
str_s: &'a str,
}
let message = "hElLo";
let bytes = [0x01, 0x10, 0x02, 0x20];
let output: Vec<u8> = to_allocvec(&RefStruct {
bytes: &bytes,
str_s: message,
}).unwrap();
assert_eq!(
&[0x04, 0x01, 0x10, 0x02, 0x20, 0x05, b'h', b'E', b'l', b'L', b'o',],
output.deref()
);
let out: RefStruct = from_bytes(output.deref()).unwrap();
assert_eq!(
out,
RefStruct {
bytes: &bytes,
str_s: message,
}
);
```
## Flavors
`postcard` supports a system called `Flavors`, which are used to modify the way
postcard serializes or processes serialized data. These flavors act as "plugins" or "middlewares"
during the serialization or deserialization process, and can be combined to obtain complex protocol formats.
See the documentation of the `ser_flavors` or `de_flavors` modules for more information on usage.
## Setup - `Cargo.toml`
Don't forget to add [the `no-std` subset](https://serde.rs/no-std.html) of `serde` along with `postcard` to the `[dependencies]` section of your `Cargo.toml`!
```toml
[dependencies]
postcard = "1.0.0"
# By default, `serde` has the `std` feature enabled, which makes it unsuitable for embedded targets
# disabling default-features fixes this
serde = { version = "1.0.*", default-features = false }
```
## License
Licensed under either of
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
<http://www.apache.org/licenses/LICENSE-2.0>)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
at your option.
### Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
dual licensed as above, without any additional terms or conditions.
+376
View File
@@ -0,0 +1,376 @@
//! An accumulator used to collect chunked COBS data and deserialize it.
use serde::Deserialize;
/// An accumulator used to collect chunked COBS data and deserialize it.
///
/// This is often useful when you receive "parts" of the message at a time, for example when draining
/// a serial port buffer that may not contain an entire uninterrupted message.
///
/// # Examples
///
/// Deserialize a struct by reading chunks from a [`Read`]er.
///
/// ```rust
/// use postcard::accumulator::{CobsAccumulator, FeedResult};
/// use serde::Deserialize;
/// use std::io::Read;
///
/// # let mut input_buf = [0u8; 256];
/// # #[derive(serde::Serialize, Deserialize, Debug, PartialEq, Eq)]
/// # struct MyData {
/// # a: u32,
/// # b: bool,
/// # c: [u8; 16],
/// # }
/// let input = /* Anything that implements the `Read` trait */
/// # postcard::to_slice_cobs(&MyData {
/// # a: 0xabcdef00,
/// # b: true,
/// # c: [0xab; 16],
/// # }, &mut input_buf).unwrap();
/// # let mut input = &input[..];
///
/// let mut raw_buf = [0u8; 32];
/// let mut cobs_buf: CobsAccumulator<256> = CobsAccumulator::new();
///
/// while let Ok(ct) = input.read(&mut raw_buf) {
/// // Finished reading input
/// if ct == 0 {
/// break;
/// }
///
/// let buf = &raw_buf[..ct];
/// let mut window = &buf[..];
///
/// 'cobs: while !window.is_empty() {
/// window = match cobs_buf.feed::<MyData>(&window) {
/// FeedResult::Consumed => break 'cobs,
/// FeedResult::OverFull(new_wind) => new_wind,
/// FeedResult::DeserError(new_wind) => new_wind,
/// FeedResult::Success { data, remaining } => {
/// // Do something with `data: MyData` here.
///
/// dbg!(data);
///
/// remaining
/// }
/// };
/// }
/// }
/// ```
///
/// [`Read`]: std::io::Read
#[cfg_attr(feature = "use-defmt", derive(defmt::Format))]
pub struct CobsAccumulator<const N: usize> {
buf: [u8; N],
idx: usize,
}
/// The result of feeding the accumulator.
#[cfg_attr(feature = "use-defmt", derive(defmt::Format))]
pub enum FeedResult<'a, T> {
/// Consumed all data, still pending.
Consumed,
/// Buffer was filled. Contains remaining section of input, if any.
OverFull(&'a [u8]),
/// Reached end of chunk, but deserialization failed. Contains remaining section of input, if any.
DeserError(&'a [u8]),
/// Deserialization complete. Contains deserialized data and remaining section of input, if any.
Success {
/// Deserialize data.
data: T,
/// Remaining data left in the buffer after deserializing.
remaining: &'a [u8],
},
}
impl<const N: usize> Default for CobsAccumulator<N> {
fn default() -> Self {
Self::new()
}
}
impl<const N: usize> CobsAccumulator<N> {
/// Create a new accumulator.
pub const fn new() -> Self {
CobsAccumulator {
buf: [0; N],
idx: 0,
}
}
/// Appends data to the internal buffer and attempts to deserialize the accumulated data into
/// `T`.
#[inline]
pub fn feed<'a, T>(&mut self, input: &'a [u8]) -> FeedResult<'a, T>
where
T: for<'de> Deserialize<'de>,
{
self.feed_ref(input)
}
/// Appends data to the internal buffer and attempts to deserialize the accumulated data into
/// `T`.
///
/// This differs from feed, as it allows the `T` to reference data within the internal buffer, but
/// mutably borrows the accumulator for the lifetime of the deserialization.
/// If `T` does not require the reference, the borrow of `self` ends at the end of the function.
pub fn feed_ref<'de, 'a, T>(&'de mut self, input: &'a [u8]) -> FeedResult<'a, T>
where
T: Deserialize<'de>,
{
if input.is_empty() {
return FeedResult::Consumed;
}
let zero_pos = input.iter().position(|&i| i == 0);
if let Some(n) = zero_pos {
// Yes! We have an end of message here.
// Add one to include the zero in the "take" portion
// of the buffer, rather than in "release".
let (take, release) = input.split_at(n + 1);
// Does it fit?
if (self.idx + take.len()) <= N {
// Aw yiss - add to array
self.extend_unchecked(take);
let retval = match crate::from_bytes_cobs::<T>(&mut self.buf[..self.idx]) {
Ok(t) => FeedResult::Success {
data: t,
remaining: release,
},
Err(_) => FeedResult::DeserError(release),
};
self.idx = 0;
retval
} else {
self.idx = 0;
FeedResult::OverFull(release)
}
} else {
// Does it fit?
if (self.idx + input.len()) > N {
// nope
let new_start = N - self.idx;
self.idx = 0;
FeedResult::OverFull(&input[new_start..])
} else {
// yup!
self.extend_unchecked(input);
FeedResult::Consumed
}
}
}
/// Extend the internal buffer with the given input.
///
/// # Panics
///
/// Will panic if the input does not fit in the internal buffer.
fn extend_unchecked(&mut self, input: &[u8]) {
let new_end = self.idx + input.len();
self.buf[self.idx..new_end].copy_from_slice(input);
self.idx = new_end;
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn loop_test() {
#[derive(serde::Serialize, Deserialize, Debug, PartialEq, Eq)]
struct Demo {
a: u32,
b: u8,
}
let mut raw_buf = [0u8; 64];
let mut cobs_buf: CobsAccumulator<64> = CobsAccumulator::new();
let ser = crate::to_slice_cobs(&Demo { a: 10, b: 20 }, &mut raw_buf).unwrap();
if let FeedResult::Success { data, remaining } = cobs_buf.feed(ser) {
assert_eq!(Demo { a: 10, b: 20 }, data);
assert_eq!(remaining.len(), 0);
} else {
panic!()
}
}
#[test]
#[cfg(feature = "heapless")]
fn double_loop_test() {
#[derive(serde::Serialize, Deserialize, Debug, PartialEq, Eq)]
struct Demo {
a: u32,
b: u8,
}
let mut cobs_buf: CobsAccumulator<64> = CobsAccumulator::new();
let mut ser = crate::to_vec_cobs::<_, 128>(&Demo { a: 10, b: 20 }).unwrap();
let ser2 = crate::to_vec_cobs::<_, 128>(&Demo {
a: 256854231,
b: 115,
})
.unwrap();
ser.extend(ser2);
let (demo1, ser) = if let FeedResult::Success { data, remaining } = cobs_buf.feed(&ser[..])
{
(data, remaining)
} else {
panic!()
};
assert_eq!(Demo { a: 10, b: 20 }, demo1);
let demo2 = if let FeedResult::Success { data, remaining } = cobs_buf.feed(ser) {
assert_eq!(remaining.len(), 0);
data
} else {
panic!()
};
assert_eq!(Demo { a: 10, b: 20 }, demo1);
assert_eq!(
Demo {
a: 256854231,
b: 115
},
demo2
);
}
#[test]
#[cfg(feature = "heapless")]
fn loop_test_ref() {
#[derive(serde::Serialize, Deserialize, Debug, PartialEq, Eq)]
struct Demo<'a> {
a: u32,
b: u8,
c: &'a str,
}
let mut cobs_buf: CobsAccumulator<64> = CobsAccumulator::new();
let ser = crate::to_vec_cobs::<_, 128>(&Demo {
a: 10,
b: 20,
c: "test",
})
.unwrap();
if let FeedResult::Success { data, remaining } = cobs_buf.feed_ref(&ser[..]) {
assert_eq!(
Demo {
a: 10,
b: 20,
c: "test"
},
data
);
assert_eq!(remaining.len(), 0);
} else {
panic!()
}
}
#[test]
#[cfg(feature = "heapless")]
fn double_loop_test_ref() {
#[derive(serde::Serialize, Deserialize, Debug, PartialEq, Eq)]
struct Demo<'a> {
a: u32,
b: u8,
c: &'a str,
}
let mut cobs_buf: CobsAccumulator<64> = CobsAccumulator::new();
let mut ser = crate::to_vec_cobs::<_, 128>(&Demo {
a: 10,
b: 20,
c: "test",
})
.unwrap();
let ser2 = crate::to_vec_cobs::<_, 128>(&Demo {
a: 256854231,
b: 115,
c: "different test",
})
.unwrap();
ser.extend(ser2);
let (data, ser) =
if let FeedResult::Success { data, remaining } = cobs_buf.feed_ref(&ser[..]) {
(data, remaining)
} else {
panic!()
};
assert!(
Demo {
a: 10,
b: 20,
c: "test"
} == data
);
let demo2 = if let FeedResult::Success { data, remaining } = cobs_buf.feed_ref(ser) {
assert!(remaining.is_empty());
data
} else {
panic!()
};
// Uncommenting the below line causes the test to no-longer compile, as cobs_buf would then be mutably borrowed twice
//assert!(Demo { a: 10, b: 20, c : "test" } == data);
assert!(
Demo {
a: 256854231,
b: 115,
c: "different test"
} == demo2
);
}
#[test]
#[cfg(feature = "heapless")]
fn extend_unchecked_in_bounds_test() {
// Test bug present in revision abcb407:
// extend_unchecked may be passed slice with size 1 greater than accumulator buffer causing panic
#[derive(serde::Serialize, Deserialize, Debug, PartialEq, Eq)]
struct Demo {
data: [u8; 10],
}
let data = crate::to_vec_cobs::<_, 128>(&Demo { data: [0xcc; 10] }).unwrap();
assert_eq!(data.len(), 12); // 1 byte for offset + 1 sentinel byte appended
// Accumulator has 1 byte less space than encoded message
let mut acc: CobsAccumulator<11> = CobsAccumulator::new();
assert!(matches!(
acc.feed::<Demo>(&data[..]),
FeedResult::OverFull(_)
));
// Accumulator is juuuuust right
let mut acc: CobsAccumulator<12> = CobsAccumulator::new();
assert!(matches!(
acc.feed::<Demo>(&data[..]),
FeedResult::Success { .. }
));
}
}
+602
View File
@@ -0,0 +1,602 @@
use serde::de::{self, DeserializeSeed, IntoDeserializer, Visitor};
use crate::de::flavors::{Flavor, Slice};
use crate::error::{Error, Result};
use crate::varint::{max_of_last_byte, varint_max};
use core::marker::PhantomData;
/// A `serde` compatible deserializer, generic over “Flavors” of deserializing plugins.
///
/// Please note that postcard messages are not self-describing and therefore incompatible with
/// [internally tagged enums](https://serde.rs/enum-representations.html#internally-tagged).
pub struct Deserializer<'de, F: Flavor<'de>> {
flavor: F,
_plt: PhantomData<&'de ()>,
}
impl<'de, F> Deserializer<'de, F>
where
F: Flavor<'de> + 'de,
{
/// Obtain a Deserializer from a slice of bytes
pub fn from_flavor(flavor: F) -> Self {
Deserializer {
flavor,
_plt: PhantomData,
}
}
/// Return the remaining (unused) bytes in the Deserializer along with any
/// additional data provided by the [`Flavor`]
pub fn finalize(self) -> Result<F::Remainder> {
self.flavor.finalize()
}
}
impl<'de> Deserializer<'de, Slice<'de>> {
/// Obtain a Deserializer from a slice of bytes
pub fn from_bytes(input: &'de [u8]) -> Self {
Deserializer {
flavor: Slice::new(input),
_plt: PhantomData,
}
}
}
impl<'de, F: Flavor<'de>> Deserializer<'de, F> {
#[cfg(target_pointer_width = "16")]
#[inline(always)]
fn try_take_varint_usize(&mut self) -> Result<usize> {
self.try_take_varint_u16().map(|u| u as usize)
}
#[cfg(target_pointer_width = "32")]
#[inline(always)]
fn try_take_varint_usize(&mut self) -> Result<usize> {
self.try_take_varint_u32().map(|u| u as usize)
}
#[cfg(target_pointer_width = "64")]
#[inline(always)]
fn try_take_varint_usize(&mut self) -> Result<usize> {
self.try_take_varint_u64().map(|u| u as usize)
}
#[inline]
fn try_take_varint_u16(&mut self) -> Result<u16> {
let mut out = 0;
for i in 0..varint_max::<u16>() {
let val = self.flavor.pop()?;
let carry = (val & 0x7F) as u16;
out |= carry << (7 * i);
if (val & 0x80) == 0 {
if i == varint_max::<u16>() - 1 && val > max_of_last_byte::<u16>() {
return Err(Error::DeserializeBadVarint);
} else {
return Ok(out);
}
}
}
Err(Error::DeserializeBadVarint)
}
#[inline]
fn try_take_varint_u32(&mut self) -> Result<u32> {
let mut out = 0;
for i in 0..varint_max::<u32>() {
let val = self.flavor.pop()?;
let carry = (val & 0x7F) as u32;
out |= carry << (7 * i);
if (val & 0x80) == 0 {
if i == varint_max::<u32>() - 1 && val > max_of_last_byte::<u32>() {
return Err(Error::DeserializeBadVarint);
} else {
return Ok(out);
}
}
}
Err(Error::DeserializeBadVarint)
}
#[inline]
fn try_take_varint_u64(&mut self) -> Result<u64> {
let mut out = 0;
for i in 0..varint_max::<u64>() {
let val = self.flavor.pop()?;
let carry = (val & 0x7F) as u64;
out |= carry << (7 * i);
if (val & 0x80) == 0 {
if i == varint_max::<u64>() - 1 && val > max_of_last_byte::<u64>() {
return Err(Error::DeserializeBadVarint);
} else {
return Ok(out);
}
}
}
Err(Error::DeserializeBadVarint)
}
#[inline]
fn try_take_varint_u128(&mut self) -> Result<u128> {
let mut out = 0;
for i in 0..varint_max::<u128>() {
let val = self.flavor.pop()?;
let carry = (val & 0x7F) as u128;
out |= carry << (7 * i);
if (val & 0x80) == 0 {
if i == varint_max::<u128>() - 1 && val > max_of_last_byte::<u128>() {
return Err(Error::DeserializeBadVarint);
} else {
return Ok(out);
}
}
}
Err(Error::DeserializeBadVarint)
}
}
struct SeqAccess<'a, 'b, F: Flavor<'b>> {
deserializer: &'a mut Deserializer<'b, F>,
len: usize,
}
impl<'a, 'b: 'a, F: Flavor<'b>> serde::de::SeqAccess<'b> for SeqAccess<'a, 'b, F> {
type Error = Error;
#[inline]
fn next_element_seed<V: DeserializeSeed<'b>>(&mut self, seed: V) -> Result<Option<V::Value>> {
if self.len > 0 {
self.len -= 1;
Ok(Some(DeserializeSeed::deserialize(
seed,
&mut *self.deserializer,
)?))
} else {
Ok(None)
}
}
#[inline]
fn size_hint(&self) -> Option<usize> {
match self.deserializer.flavor.size_hint() {
Some(size) if size < self.len => None,
_ => Some(self.len),
}
}
}
struct MapAccess<'a, 'b, F: Flavor<'b>> {
deserializer: &'a mut Deserializer<'b, F>,
len: usize,
}
impl<'a, 'b: 'a, F: Flavor<'b>> serde::de::MapAccess<'b> for MapAccess<'a, 'b, F> {
type Error = Error;
#[inline]
fn next_key_seed<K: DeserializeSeed<'b>>(&mut self, seed: K) -> Result<Option<K::Value>> {
if self.len > 0 {
self.len -= 1;
Ok(Some(DeserializeSeed::deserialize(
seed,
&mut *self.deserializer,
)?))
} else {
Ok(None)
}
}
#[inline]
fn next_value_seed<V: DeserializeSeed<'b>>(&mut self, seed: V) -> Result<V::Value> {
DeserializeSeed::deserialize(seed, &mut *self.deserializer)
}
#[inline]
fn size_hint(&self) -> Option<usize> {
Some(self.len)
}
}
impl<'de, F: Flavor<'de>> de::Deserializer<'de> for &mut Deserializer<'de, F> {
type Error = Error;
#[inline]
fn is_human_readable(&self) -> bool {
false
}
// Postcard does not support structures not known at compile time
#[inline]
fn deserialize_any<V>(self, _visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
// We wont ever support this.
Err(Error::WontImplement)
}
// Take a boolean encoded as a u8
#[inline]
fn deserialize_bool<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
let val = match self.flavor.pop()? {
0 => false,
1 => true,
_ => return Err(Error::DeserializeBadBool),
};
visitor.visit_bool(val)
}
#[inline]
fn deserialize_i8<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
visitor.visit_i8(self.flavor.pop()? as i8)
}
#[inline]
fn deserialize_i16<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
let v = self.try_take_varint_u16()?;
visitor.visit_i16(de_zig_zag_i16(v))
}
#[inline]
fn deserialize_i32<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
let v = self.try_take_varint_u32()?;
visitor.visit_i32(de_zig_zag_i32(v))
}
#[inline]
fn deserialize_i64<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
let v = self.try_take_varint_u64()?;
visitor.visit_i64(de_zig_zag_i64(v))
}
#[inline]
fn deserialize_i128<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
let v = self.try_take_varint_u128()?;
visitor.visit_i128(de_zig_zag_i128(v))
}
#[inline]
fn deserialize_u8<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
visitor.visit_u8(self.flavor.pop()?)
}
#[inline]
fn deserialize_u16<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
let v = self.try_take_varint_u16()?;
visitor.visit_u16(v)
}
#[inline]
fn deserialize_u32<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
let v = self.try_take_varint_u32()?;
visitor.visit_u32(v)
}
#[inline]
fn deserialize_u64<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
let v = self.try_take_varint_u64()?;
visitor.visit_u64(v)
}
#[inline]
fn deserialize_u128<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
let v = self.try_take_varint_u128()?;
visitor.visit_u128(v)
}
#[inline]
fn deserialize_f32<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
let bytes = self.flavor.try_take_n_temp(4)?;
let mut buf = [0u8; 4];
buf.copy_from_slice(bytes);
visitor.visit_f32(f32::from_bits(u32::from_le_bytes(buf)))
}
#[inline]
fn deserialize_f64<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
let bytes = self.flavor.try_take_n_temp(8)?;
let mut buf = [0u8; 8];
buf.copy_from_slice(bytes);
visitor.visit_f64(f64::from_bits(u64::from_le_bytes(buf)))
}
#[inline]
fn deserialize_char<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
let sz = self.try_take_varint_usize()?;
if sz > 4 {
return Err(Error::DeserializeBadChar);
}
let bytes: &[u8] = self.flavor.try_take_n_temp(sz)?;
// we pass the character through string conversion because
// this handles transforming the array of code units to a
// codepoint. we can't use char::from_u32() because it expects
// an already-processed codepoint.
let character = core::str::from_utf8(bytes)
.map_err(|_| Error::DeserializeBadChar)?
.chars()
.next()
.ok_or(Error::DeserializeBadChar)?;
visitor.visit_char(character)
}
#[inline]
fn deserialize_str<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
let sz = self.try_take_varint_usize()?;
let bytes: &'de [u8] = self.flavor.try_take_n(sz)?;
let str_sl = core::str::from_utf8(bytes).map_err(|_| Error::DeserializeBadUtf8)?;
visitor.visit_borrowed_str(str_sl)
}
#[inline]
fn deserialize_string<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
let sz = self.try_take_varint_usize()?;
let bytes: &[u8] = self.flavor.try_take_n_temp(sz)?;
let str_sl = core::str::from_utf8(bytes).map_err(|_| Error::DeserializeBadUtf8)?;
visitor.visit_str(str_sl)
}
#[inline]
fn deserialize_bytes<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
let sz = self.try_take_varint_usize()?;
let bytes: &'de [u8] = self.flavor.try_take_n(sz)?;
visitor.visit_borrowed_bytes(bytes)
}
#[inline]
fn deserialize_byte_buf<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
let sz = self.try_take_varint_usize()?;
let bytes: &[u8] = self.flavor.try_take_n_temp(sz)?;
visitor.visit_bytes(bytes)
}
#[inline]
fn deserialize_option<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
match self.flavor.pop()? {
0 => visitor.visit_none(),
1 => visitor.visit_some(self),
_ => Err(Error::DeserializeBadOption),
}
}
// In Serde, unit means an anonymous value containing no data.
// Unit is not actually encoded in Postcard.
#[inline]
fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
visitor.visit_unit()
}
// Unit struct means a named value containing no data.
// Unit structs are not actually encoded in Postcard.
#[inline]
fn deserialize_unit_struct<V>(self, _name: &'static str, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
self.deserialize_unit(visitor)
}
#[inline]
fn deserialize_newtype_struct<V>(self, _name: &'static str, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
visitor.visit_newtype_struct(self)
}
#[inline]
fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
let len = self.try_take_varint_usize()?;
visitor.visit_seq(SeqAccess {
deserializer: self,
len,
})
}
#[inline]
fn deserialize_tuple<V>(self, len: usize, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
visitor.visit_seq(SeqAccess {
deserializer: self,
len,
})
}
#[inline]
fn deserialize_tuple_struct<V>(
self,
_name: &'static str,
len: usize,
visitor: V,
) -> Result<V::Value>
where
V: Visitor<'de>,
{
self.deserialize_tuple(len, visitor)
}
#[inline]
fn deserialize_map<V>(self, visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
let len = self.try_take_varint_usize()?;
visitor.visit_map(MapAccess {
deserializer: self,
len,
})
}
#[inline]
fn deserialize_struct<V>(
self,
_name: &'static str,
fields: &'static [&'static str],
visitor: V,
) -> Result<V::Value>
where
V: Visitor<'de>,
{
self.deserialize_tuple(fields.len(), visitor)
}
#[inline]
fn deserialize_enum<V>(
self,
_name: &'static str,
_variants: &'static [&'static str],
visitor: V,
) -> Result<V::Value>
where
V: Visitor<'de>,
{
visitor.visit_enum(self)
}
// As a binary format, Postcard does not encode identifiers
#[inline]
fn deserialize_identifier<V>(self, _visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
// Will not support
Err(Error::WontImplement)
}
#[inline]
fn deserialize_ignored_any<V>(self, _visitor: V) -> Result<V::Value>
where
V: Visitor<'de>,
{
// Will not support
Err(Error::WontImplement)
}
}
impl<'de, F: Flavor<'de>> serde::de::VariantAccess<'de> for &mut Deserializer<'de, F> {
type Error = Error;
#[inline]
fn unit_variant(self) -> Result<()> {
Ok(())
}
#[inline]
fn newtype_variant_seed<V: DeserializeSeed<'de>>(self, seed: V) -> Result<V::Value> {
DeserializeSeed::deserialize(seed, self)
}
#[inline]
fn tuple_variant<V: Visitor<'de>>(self, len: usize, visitor: V) -> Result<V::Value> {
serde::de::Deserializer::deserialize_tuple(self, len, visitor)
}
#[inline]
fn struct_variant<V: Visitor<'de>>(
self,
fields: &'static [&'static str],
visitor: V,
) -> Result<V::Value> {
serde::de::Deserializer::deserialize_tuple(self, fields.len(), visitor)
}
}
impl<'de, F: Flavor<'de>> serde::de::EnumAccess<'de> for &mut Deserializer<'de, F> {
type Error = Error;
type Variant = Self;
#[inline]
fn variant_seed<V: DeserializeSeed<'de>>(self, seed: V) -> Result<(V::Value, Self)> {
let varint = self.try_take_varint_u32()?;
let v = DeserializeSeed::deserialize(seed, varint.into_deserializer())?;
Ok((v, self))
}
}
fn de_zig_zag_i16(n: u16) -> i16 {
((n >> 1) as i16) ^ (-((n & 0b1) as i16))
}
fn de_zig_zag_i32(n: u32) -> i32 {
((n >> 1) as i32) ^ (-((n & 0b1) as i32))
}
fn de_zig_zag_i64(n: u64) -> i64 {
((n >> 1) as i64) ^ (-((n & 0b1) as i64))
}
fn de_zig_zag_i128(n: u128) -> i128 {
((n >> 1) as i128) ^ (-((n & 0b1) as i128))
}
+642
View File
@@ -0,0 +1,642 @@
//! # Deserialization Flavors
//!
//! "Flavors" in `postcard` are used as modifiers to the serialization or deserialization
//! process. Flavors typically modify one or both of the following:
//!
//! 1. The source medium of the deserialization, e.g. whether the data is serialized from a `[u8]` slice, or some other container
//! 2. The format of the deserialization, such as if the original data is encoded in a COBS format, contains a CRC32 checksum
//! appended to the message, etc.
//!
//! Flavors are implemented using the [`Flavor`] trait, which acts as a "middleware" for retrieving the bytes before they
//! are passed to `serde` for deserialization
//!
//! Multiple flavors may be combined to obtain a desired combination of behavior and storage.
//! When flavors are combined, it is expected that the storage flavor (such as [`Slice`]) is the innermost flavor.
//!
//! Custom flavors may be defined by users of the `postcard` crate, however some commonly useful flavors have been provided in
//! this module. If you think your custom flavor would be useful to others, PRs adding flavors are very welcome!
//!
//! ## Usability
//!
//! Flavors may not always be convenient to use directly, as they may expose some implementation details of how the
//! inner workings of the flavor behaves. It is typical to provide a convenience method for using a flavor, to prevent
//! the user from having to specify generic parameters, setting correct initialization values, or handling the output of
//! the flavor correctly. See `postcard::from_bytes()` for an example of this.
//!
//! ## When to use (multiple) flavors
//!
//! Combining flavors are nice for convenience, as they perform potentially multiple steps of
//! serialization at one time.
//!
//! This can often be more memory efficient, as intermediate buffers are not typically required.
//!
//! ## When NOT to use (multiple) flavors
//!
//! The downside of passing deserialization through multiple steps is that it is typically slower than
//! performing each step serially. Said simply, "cobs decoding while deserializing" is often slower
//! than "cobs decode then deserialize", due to the ability to handle longer "runs" of data in each
//! stage. The downside is that if these stages can not be performed in-place on the buffer, you
//! will need additional buffers for each stage.
//!
//! Additionally, deserializating flavors can be more restrictive or difficult to work with than
//! serialization flavors, as deserialization may require that the deserialized types borrow some
//! portion of the original message.
//!
//! ## Examples
//!
//! ### Using a single flavor
//!
//! In the first example, we use the `Slice` flavor, to retrieve the serialized output from a `[u8]` slice.
//! No other modification is made to the serialization process.
//!
//! ```rust
//! use postcard::{
//! de_flavors::Slice,
//! Deserializer,
//! };
//! use serde::Deserialize;
//!
//! #[derive(Deserialize, Debug, PartialEq)]
//! struct Tup(u8, u8, u8);
//!
//! let msg = [0x04, 0x00, 0x04, 0x01, 0x02, 0x03];
//! let slice = Slice::new(&msg);
//! let mut deserializer = Deserializer::from_flavor(slice);
//! let t = Tup::deserialize(&mut deserializer).unwrap();
//! assert_eq!(t, Tup(4, 0, 4));
//! let remainder = deserializer.finalize().unwrap();
//! assert_eq!(remainder, &[1, 2, 3]);
//! ```
use crate::{Error, Result};
use core::marker::PhantomData;
/// The deserialization Flavor trait
///
/// This is used as the primary way to decode serialized data from some kind of buffer,
/// or modify that data in a middleware style pattern.
///
/// See the module level docs for an example of how flavors are used.
pub trait Flavor<'de>: 'de {
/// The remaining data of this flavor after deserializing has completed.
///
/// Typically, this includes the remaining buffer that was not used for
/// deserialization, and in cases of more complex flavors, any additional
/// information that was decoded or otherwise calculated during
/// the deserialization process.
type Remainder: 'de;
/// The source of data retrieved for deserialization.
///
/// This is typically some sort of data buffer, or another Flavor, when
/// chained behavior is desired
type Source: 'de;
/// Obtain the next byte for deserialization
fn pop(&mut self) -> Result<u8>;
/// Returns the number of bytes remaining in the message, if known.
///
/// # Implementation notes
///
/// It is not enforced that this number is exactly correct.
/// A flavor may yield less or more bytes than the what is hinted at by
/// this function.
///
/// `size_hint()` is primarily intended to be used for optimizations such as
/// reserving space for deserialized items, but must not be trusted to
/// e.g., omit bounds checks in unsafe code. An incorrect implementation of
/// `size_hint()` should not lead to memory safety violations.
///
/// That said, the implementation should provide a correct estimation,
/// because otherwise it would be a violation of the trait’s protocol.
///
/// The default implementation returns `None` which is correct for any flavor.
fn size_hint(&self) -> Option<usize> {
None
}
/// Attempt to take the next `ct` bytes from the serialized message.
///
/// This variant borrows the data from the input for zero-copy deserialization. If zero-copy
/// deserialization is not necessary, prefer to use `try_take_n_temp` instead.
fn try_take_n(&mut self, ct: usize) -> Result<&'de [u8]>;
/// Attempt to take the next `ct` bytes from the serialized message.
///
/// This variant does not guarantee that the returned value is borrowed from the input, so it
/// cannot be used for zero-copy deserialization, but it also avoids needing to potentially
/// allocate a data in a temporary buffer.
///
/// This variant should be used instead of `try_take_n`
/// if zero-copy deserialization is not necessary.
///
/// It is only necessary to implement this method if the flavor requires storing data in a
/// temporary buffer in order to implement the borrow semantics, e.g. the `std::io::Read`
/// flavor.
fn try_take_n_temp<'a>(&'a mut self, ct: usize) -> Result<&'a [u8]>
where
'de: 'a,
{
self.try_take_n(ct)
}
/// Complete the deserialization process.
///
/// This is typically called separately, after the `serde` deserialization
/// has completed.
fn finalize(self) -> Result<Self::Remainder>;
}
/// A simple [`Flavor`] representing the deserialization from a borrowed slice
pub struct Slice<'de> {
// This string starts with the input data and characters are truncated off
// the beginning as data is parsed.
pub(crate) cursor: *const u8,
pub(crate) end: *const u8,
pub(crate) _pl: PhantomData<&'de [u8]>,
}
impl<'de> Slice<'de> {
/// Create a new [Slice] from the given buffer
pub fn new(sli: &'de [u8]) -> Self {
let range = sli.as_ptr_range();
Self {
cursor: range.start,
end: range.end,
_pl: PhantomData,
}
}
}
impl<'de> Flavor<'de> for Slice<'de> {
type Remainder = &'de [u8];
type Source = &'de [u8];
#[inline]
fn pop(&mut self) -> Result<u8> {
if self.cursor == self.end {
Err(Error::DeserializeUnexpectedEnd)
} else {
// SAFETY: `self.cursor` is in-bounds and won't be incremented past `self.end` as we
// have checked above.
unsafe {
let res = Ok(*self.cursor);
self.cursor = self.cursor.add(1);
res
}
}
}
#[inline]
fn size_hint(&self) -> Option<usize> {
Some((self.end as usize) - (self.cursor as usize))
}
#[inline]
fn try_take_n(&mut self, ct: usize) -> Result<&'de [u8]> {
let remain = (self.end as usize) - (self.cursor as usize);
if remain < ct {
Err(Error::DeserializeUnexpectedEnd)
} else {
// SAFETY: `self.cursor` is valid for `ct` elements and won't be incremented past `self.end` as we
// have checked above.
unsafe {
let sli = core::slice::from_raw_parts(self.cursor, ct);
self.cursor = self.cursor.add(ct);
Ok(sli)
}
}
}
/// Return the remaining (unused) bytes in the Deserializer
fn finalize(self) -> Result<&'de [u8]> {
let remain = (self.end as usize) - (self.cursor as usize);
// SAFETY: `self.cursor` is valid for `remain` elements
unsafe { Ok(core::slice::from_raw_parts(self.cursor, remain)) }
}
}
/// Support for [`std::io`] or `embedded-io` traits
#[cfg(any(
feature = "embedded-io-04",
feature = "embedded-io-06",
feature = "use-std"
))]
pub mod io {
use crate::{Error, Result};
use core::marker::PhantomData;
struct SlidingBuffer<'de> {
cursor: *mut u8,
end: *const u8,
_pl: PhantomData<&'de [u8]>,
}
impl<'de> SlidingBuffer<'de> {
pub fn new(sli: &'de mut [u8]) -> Self {
let range = sli.as_mut_ptr_range();
Self {
cursor: range.start,
end: range.end,
_pl: PhantomData,
}
}
#[inline]
fn take_n(&mut self, ct: usize) -> Result<&'de mut [u8]> {
let remain = (self.end as usize) - (self.cursor as usize);
let buff = if remain < ct {
return Err(Error::DeserializeUnexpectedEnd);
} else {
// SAFETY: `self.cursor` is valid for `ct` elements and won't be incremented
// past `self.end` as we have checked above.
unsafe {
let sli = core::slice::from_raw_parts_mut(self.cursor, ct);
self.cursor = self.cursor.add(ct);
sli
}
};
Ok(buff)
}
#[inline]
fn take_n_temp(&mut self, ct: usize) -> Result<&mut [u8]> {
let remain = (self.end as usize) - (self.cursor as usize);
let buff = if remain < ct {
return Err(Error::DeserializeUnexpectedEnd);
} else {
unsafe {
let sli = core::slice::from_raw_parts_mut(self.cursor, ct);
sli
}
};
Ok(buff)
}
fn complete(self) -> Result<&'de mut [u8]> {
let remain = (self.end as usize) - (self.cursor as usize);
// SAFETY: `self.cursor` is valid for `remain` elements
unsafe { Ok(core::slice::from_raw_parts_mut(self.cursor, remain)) }
}
}
/// Support for [`embedded_io`](crate::eio::embedded_io) traits
#[cfg(any(feature = "embedded-io-04", feature = "embedded-io-06"))]
pub mod eio {
use super::super::Flavor;
use super::SlidingBuffer;
use crate::{Error, Result};
/// Wrapper over a [`embedded_io`](crate::eio::embedded_io)::[`Read`](crate::eio::Read) and a sliding buffer to implement the [`Flavor`] trait
pub struct EIOReader<'de, T>
where
T: crate::eio::Read,
{
reader: T,
buff: SlidingBuffer<'de>,
}
impl<'de, T> EIOReader<'de, T>
where
T: crate::eio::Read,
{
/// Create a new [`EIOReader`] from a reader and a buffer.
///
/// `buff` must have enough space to hold all data read during the deserialisation.
pub fn new(reader: T, buff: &'de mut [u8]) -> Self {
Self {
reader,
buff: SlidingBuffer::new(buff),
}
}
}
impl<'de, T> Flavor<'de> for EIOReader<'de, T>
where
T: crate::eio::Read + 'de,
{
type Remainder = (T, &'de mut [u8]);
type Source = &'de [u8];
#[inline]
fn pop(&mut self) -> Result<u8> {
let mut val = [0; 1];
self.reader
.read_exact(&mut val)
.map_err(|_| Error::DeserializeUnexpectedEnd)?;
Ok(val[0])
}
#[inline]
fn size_hint(&self) -> Option<usize> {
None
}
#[inline]
fn try_take_n(&mut self, ct: usize) -> Result<&'de [u8]> {
let buff = self.buff.take_n(ct)?;
self.reader
.read_exact(buff)
.map_err(|_| Error::DeserializeUnexpectedEnd)?;
Ok(buff)
}
#[inline]
fn try_take_n_temp<'a>(&'a mut self, ct: usize) -> Result<&'a [u8]>
where
'de: 'a,
{
let buff = self.buff.take_n_temp(ct)?;
self.reader
.read_exact(buff)
.map_err(|_| Error::DeserializeUnexpectedEnd)?;
Ok(buff)
}
/// Return the remaining (unused) bytes in the Deserializer
fn finalize(self) -> Result<(T, &'de mut [u8])> {
let buf = self.buff.complete()?;
Ok((self.reader, buf))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pop() {
let mut reader = EIOReader::new(&[0xAA, 0xBB, 0xCC][..], &mut []);
assert_eq!(reader.pop(), Ok(0xAA));
assert_eq!(reader.pop(), Ok(0xBB));
assert_eq!(reader.pop(), Ok(0xCC));
assert_eq!(reader.pop(), Err(Error::DeserializeUnexpectedEnd));
}
#[test]
fn test_try_take_n() {
let mut buf = [0; 8];
let mut reader = EIOReader::new(&[0xAA, 0xBB, 0xCC, 0xDD, 0xEE][..], &mut buf);
assert_eq!(reader.try_take_n(2), Ok(&[0xAA, 0xBB][..]));
assert_eq!(reader.try_take_n(2), Ok(&[0xCC, 0xDD][..]));
assert_eq!(reader.try_take_n(2), Err(Error::DeserializeUnexpectedEnd));
}
}
}
/// Support for [`std::io`] traits
#[allow(clippy::module_inception)]
#[cfg(feature = "use-std")]
pub mod io {
use super::super::Flavor;
use super::SlidingBuffer;
use crate::{Error, Result};
/// Wrapper over a [`std::io::Read`] and a sliding buffer to implement the [Flavor] trait
pub struct IOReader<'de, T>
where
T: std::io::Read,
{
reader: T,
buff: SlidingBuffer<'de>,
}
impl<'de, T> IOReader<'de, T>
where
T: std::io::Read,
{
/// Create a new [`IOReader`] from a reader and a buffer.
///
/// `buff` must have enough space to hold all data read during the deserialisation.
pub fn new(reader: T, buff: &'de mut [u8]) -> Self {
Self {
reader,
buff: SlidingBuffer::new(buff),
}
}
}
impl<'de, T> Flavor<'de> for IOReader<'de, T>
where
T: std::io::Read + 'de,
{
type Remainder = (T, &'de mut [u8]);
type Source = &'de [u8];
#[inline]
fn pop(&mut self) -> Result<u8> {
let mut val = [0; 1];
self.reader
.read_exact(&mut val)
.map_err(|_| Error::DeserializeUnexpectedEnd)?;
Ok(val[0])
}
#[inline]
fn size_hint(&self) -> Option<usize> {
None
}
#[inline]
fn try_take_n(&mut self, ct: usize) -> Result<&'de [u8]> {
let buff = self.buff.take_n(ct)?;
self.reader
.read_exact(buff)
.map_err(|_| Error::DeserializeUnexpectedEnd)?;
Ok(buff)
}
#[inline]
fn try_take_n_temp<'a>(&'a mut self, ct: usize) -> Result<&'a [u8]>
where
'de: 'a,
{
let buff = self.buff.take_n_temp(ct)?;
self.reader
.read_exact(buff)
.map_err(|_| Error::DeserializeUnexpectedEnd)?;
Ok(buff)
}
/// Return the remaining (unused) bytes in the Deserializer
fn finalize(self) -> Result<(T, &'de mut [u8])> {
let buf = self.buff.complete()?;
Ok((self.reader, buf))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pop() {
let mut reader = IOReader::new(&[0xAA, 0xBB, 0xCC][..], &mut []);
assert_eq!(reader.pop(), Ok(0xAA));
assert_eq!(reader.pop(), Ok(0xBB));
assert_eq!(reader.pop(), Ok(0xCC));
assert_eq!(reader.pop(), Err(Error::DeserializeUnexpectedEnd));
}
#[test]
fn test_try_take_n() {
let mut buf = [0; 8];
let mut reader = IOReader::new(&[0xAA, 0xBB, 0xCC, 0xDD, 0xEE][..], &mut buf);
assert_eq!(reader.try_take_n(2), Ok(&[0xAA, 0xBB][..]));
assert_eq!(reader.try_take_n(2), Ok(&[0xCC, 0xDD][..]));
assert_eq!(reader.try_take_n(2), Err(Error::DeserializeUnexpectedEnd));
}
}
}
}
////////////////////////////////////////
// CRC
////////////////////////////////////////
/// This Cyclic Redundancy Check flavor applies [the CRC crate's `Algorithm`](https://docs.rs/crc/latest/crc/struct.Algorithm.html) struct on
/// the serialized data.
///
/// The flavor will check the CRC assuming that it has been appended to the bytes.
/// CRCs are used for error detection when reading data back.
/// Requires the `crc` feature.
///
/// More on CRCs: <https://en.wikipedia.org/wiki/Cyclic_redundancy_check>.
#[cfg(feature = "use-crc")]
#[cfg_attr(docsrs, doc(cfg(feature = "use-crc")))]
pub mod crc {
use core::convert::TryInto;
use crc::Digest;
use crc::Width;
use serde::Deserialize;
use super::Flavor;
use super::Slice;
use crate::Deserializer;
use crate::Error;
use crate::Result;
/// Manages CRC modifications as a flavor.
pub struct CrcModifier<'de, B, W>
where
B: Flavor<'de>,
W: Width,
{
flav: B,
digest: Digest<'de, W>,
}
impl<'de, B, W> CrcModifier<'de, B, W>
where
B: Flavor<'de>,
W: Width,
{
/// Create a new Crc modifier Flavor.
pub fn new(bee: B, digest: Digest<'de, W>) -> Self {
Self { flav: bee, digest }
}
}
macro_rules! impl_flavor {
($int:ty, $from_bytes:ident, $take_from_bytes:ident) => {
impl<'de, B> Flavor<'de> for CrcModifier<'de, B, $int>
where
B: Flavor<'de>,
{
type Remainder = B::Remainder;
type Source = B::Source;
#[inline]
fn pop(&mut self) -> Result<u8> {
match self.flav.pop() {
Ok(byte) => {
self.digest.update(&[byte]);
Ok(byte)
}
e @ Err(_) => e,
}
}
#[inline]
fn size_hint(&self) -> Option<usize> {
self.flav.size_hint()
}
#[inline]
fn try_take_n(&mut self, ct: usize) -> Result<&'de [u8]> {
match self.flav.try_take_n(ct) {
Ok(bytes) => {
self.digest.update(bytes);
Ok(bytes)
}
e @ Err(_) => e,
}
}
fn finalize(mut self) -> Result<Self::Remainder> {
match self.flav.try_take_n(core::mem::size_of::<$int>()) {
Ok(prev_crc_bytes) => match self.flav.finalize() {
Ok(remainder) => {
let crc = self.digest.finalize();
let le_bytes = prev_crc_bytes
.try_into()
.map_err(|_| Error::DeserializeBadEncoding)?;
let prev_crc = <$int>::from_le_bytes(le_bytes);
if crc == prev_crc {
Ok(remainder)
} else {
Err(Error::DeserializeBadCrc)
}
}
e @ Err(_) => e,
},
Err(e) => Err(e),
}
}
}
/// Deserialize a message of type `T` from a byte slice with a Crc. The unused portion (if any)
/// of the byte slice is not returned.
pub fn $from_bytes<'a, T>(s: &'a [u8], digest: Digest<'a, $int>) -> Result<T>
where
T: Deserialize<'a>,
{
let flav = CrcModifier::new(Slice::new(s), digest);
let mut deserializer = Deserializer::from_flavor(flav);
let r = T::deserialize(&mut deserializer)?;
let _ = deserializer.finalize()?;
Ok(r)
}
/// Deserialize a message of type `T` from a byte slice with a Crc. The unused portion (if any)
/// of the byte slice is returned for further usage
pub fn $take_from_bytes<'a, T>(
s: &'a [u8],
digest: Digest<'a, $int>,
) -> Result<(T, &'a [u8])>
where
T: Deserialize<'a>,
{
let flav = CrcModifier::new(Slice::new(s), digest);
let mut deserializer = Deserializer::from_flavor(flav);
let t = T::deserialize(&mut deserializer)?;
Ok((t, deserializer.finalize()?))
}
};
}
impl_flavor!(u8, from_bytes_u8, take_from_bytes_u8);
impl_flavor!(u16, from_bytes_u16, take_from_bytes_u16);
impl_flavor!(u32, from_bytes_u32, take_from_bytes_u32);
impl_flavor!(u64, from_bytes_u64, take_from_bytes_u64);
impl_flavor!(u128, from_bytes_u128, take_from_bytes_u128);
}
+612
View File
@@ -0,0 +1,612 @@
use cobs::{decode_in_place, decode_in_place_report};
use serde::Deserialize;
pub(crate) mod deserializer;
pub mod flavors;
use crate::error::{Error, Result};
use deserializer::Deserializer;
/// Deserialize a message of type `T` from a byte slice. The unused portion (if any)
/// of the byte slice is not returned.
pub fn from_bytes<'a, T>(s: &'a [u8]) -> Result<T>
where
T: Deserialize<'a>,
{
let mut deserializer = Deserializer::from_bytes(s);
let t = T::deserialize(&mut deserializer)?;
Ok(t)
}
/// Deserialize a message of type `T` from a cobs-encoded byte slice.
///
/// The unused portion (if any) of the byte slice is not returned.
/// The used portion of the input slice is modified during deserialization (even if an error is returned).
/// Therefore, if this is not desired, pass a clone of the original slice.
pub fn from_bytes_cobs<'a, T>(s: &'a mut [u8]) -> Result<T>
where
T: Deserialize<'a>,
{
let sz = decode_in_place(s).map_err(|_| Error::DeserializeBadEncoding)?;
from_bytes::<T>(&s[..sz])
}
/// Deserialize a message of type `T` from a cobs-encoded byte slice.
///
/// The unused portion (if any) of the byte slice is returned for further usage.
/// The used portion of the input slice is modified during deserialization (even if an error is returned).
/// Therefore, if this is not desired, pass a clone of the original slice.
pub fn take_from_bytes_cobs<'a, T>(s: &'a mut [u8]) -> Result<(T, &'a mut [u8])>
where
T: Deserialize<'a>,
{
let mut report = decode_in_place_report(s).map_err(|_| Error::DeserializeBadEncoding)?;
// The report does not include terminator bytes. If there is one in the
// buffer right AFTER the message, also include it.
if s.get(report.src_used) == Some(&0) {
report.src_used += 1;
}
// First split off the amount used for the "destination", which includes our now
// decoded message to deserialize
let (dst_used, dst_unused) = s.split_at_mut(report.dst_used);
// Then create a slice that includes the unused bytes, but DON'T include the
// excess bytes that were "shrunk" away from the original message
let (_unused, src_unused) = dst_unused.split_at_mut(report.src_used - report.dst_used);
Ok((from_bytes::<T>(dst_used)?, src_unused))
}
/// Deserialize a message of type `T` from a byte slice. The unused portion (if any)
/// of the byte slice is returned for further usage
pub fn take_from_bytes<'a, T>(s: &'a [u8]) -> Result<(T, &'a [u8])>
where
T: Deserialize<'a>,
{
let mut deserializer = Deserializer::from_bytes(s);
let t = T::deserialize(&mut deserializer)?;
Ok((t, deserializer.finalize()?))
}
/// Deserialize a message of type `T` from a [`embedded_io`](crate::eio::embedded_io)::[`Read`](crate::eio::Read).
#[cfg(any(feature = "embedded-io-04", feature = "embedded-io-06"))]
pub fn from_eio<'a, T, R>(val: (R, &'a mut [u8])) -> Result<(T, (R, &'a mut [u8]))>
where
T: Deserialize<'a>,
R: crate::eio::Read + 'a,
{
let flavor = flavors::io::eio::EIOReader::new(val.0, val.1);
let mut deserializer = Deserializer::from_flavor(flavor);
let t = T::deserialize(&mut deserializer)?;
Ok((t, deserializer.finalize()?))
}
/// Deserialize a message of type `T` from a [`std::io::Read`].
#[cfg(feature = "use-std")]
pub fn from_io<'a, T, R>(val: (R, &'a mut [u8])) -> Result<(T, (R, &'a mut [u8]))>
where
T: Deserialize<'a>,
R: std::io::Read + 'a,
{
let flavor = flavors::io::io::IOReader::new(val.0, val.1);
let mut deserializer = Deserializer::from_flavor(flavor);
let t = T::deserialize(&mut deserializer)?;
Ok((t, deserializer.finalize()?))
}
/// Conveniently deserialize a message of type `T` from a byte slice with a Crc. The unused portion (if any)
/// of the byte slice is not returned.
///
/// See the `de_flavors::crc` module for the complete set of functions.
#[cfg(feature = "use-crc")]
#[cfg_attr(docsrs, doc(cfg(feature = "use-crc")))]
#[inline]
pub fn from_bytes_crc32<'a, T>(s: &'a [u8], digest: crc::Digest<'a, u32>) -> Result<T>
where
T: Deserialize<'a>,
{
flavors::crc::from_bytes_u32(s, digest)
}
/// Conveniently deserialize a message of type `T` from a byte slice with a Crc. The unused portion (if any)
/// of the byte slice is returned for further usage
///
/// See the `de_flavors::crc` module for the complete set of functions.
#[cfg(feature = "use-crc")]
#[cfg_attr(docsrs, doc(cfg(feature = "use-crc")))]
#[inline]
pub fn take_from_bytes_crc32<'a, T>(
s: &'a [u8],
digest: crc::Digest<'a, u32>,
) -> Result<(T, &'a [u8])>
where
T: Deserialize<'a>,
{
flavors::crc::take_from_bytes_u32(s, digest)
}
////////////////////////////////////////////////////////////////////////////////
#[cfg(feature = "heapless")]
#[cfg(test)]
mod test_heapless {
use super::*;
use crate::{ser::to_vec, to_vec_cobs, varint::varint_max};
use core::fmt::Write;
use core::ops::Deref;
use heapless::{FnvIndexMap, String, Vec};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[test]
fn de_u8() {
let output: Vec<u8, 1> = to_vec(&0x05u8).unwrap();
assert_eq!(&[5], output.deref());
let out: u8 = from_bytes(output.deref()).unwrap();
assert_eq!(out, 0x05);
}
#[test]
fn de_u16() {
let output: Vec<u8, { varint_max::<u16>() }> = to_vec(&0xA5C7u16).unwrap();
assert_eq!(&[0xC7, 0xCB, 0x02], output.deref());
let out: u16 = from_bytes(output.deref()).unwrap();
assert_eq!(out, 0xA5C7);
}
#[test]
fn de_u32() {
let output: Vec<u8, { varint_max::<u32>() }> = to_vec(&0xCDAB3412u32).unwrap();
assert_eq!(&[0x92, 0xE8, 0xAC, 0xED, 0x0C], output.deref());
let out: u32 = from_bytes(output.deref()).unwrap();
assert_eq!(out, 0xCDAB3412u32);
}
#[test]
fn de_u64() {
let output: Vec<u8, { varint_max::<u64>() }> = to_vec(&0x1234_5678_90AB_CDEFu64).unwrap();
assert_eq!(
&[0xEF, 0x9B, 0xAF, 0x85, 0x89, 0xCF, 0x95, 0x9A, 0x12],
output.deref()
);
let out: u64 = from_bytes(output.deref()).unwrap();
assert_eq!(out, 0x1234_5678_90AB_CDEFu64);
}
#[test]
fn de_u128() {
let output: Vec<u8, { varint_max::<u128>() }> =
to_vec(&0x1234_5678_90AB_CDEF_1234_5678_90AB_CDEFu128).unwrap();
assert_eq!(
&[
0xEF, 0x9B, 0xAF, 0x85, 0x89, 0xCF, 0x95, 0x9A, 0x92, 0xDE, 0xB7, 0xDE, 0x8A, 0x92,
0x9E, 0xAB, 0xB4, 0x24,
],
output.deref()
);
let out: u128 = from_bytes(output.deref()).unwrap();
assert_eq!(out, 0x1234_5678_90AB_CDEF_1234_5678_90AB_CDEFu128);
}
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
struct BasicU8S {
st: u16,
ei: u8,
ote: u128,
sf: u64,
tt: u32,
}
#[test]
fn de_struct_unsigned() {
let data = BasicU8S {
st: 0xABCD,
ei: 0xFE,
ote: 0x1234_4321_ABCD_DCBA_1234_4321_ABCD_DCBA,
sf: 0x1234_4321_ABCD_DCBA,
tt: 0xACAC_ACAC,
};
const SZ: usize = varint_max::<u16>()
+ 1
+ varint_max::<u128>()
+ varint_max::<u64>()
+ varint_max::<u32>();
let output: Vec<u8, SZ> = to_vec(&data).unwrap();
assert_eq!(
&[
0xCD, 0xD7, 0x02, 0xFE, 0xBA, 0xB9, 0xB7, 0xDE, 0x9A, 0xE4, 0x90, 0x9A, 0x92, 0xF4,
0xF2, 0xEE, 0xBC, 0xB5, 0xC8, 0xA1, 0xB4, 0x24, 0xBA, 0xB9, 0xB7, 0xDE, 0x9A, 0xE4,
0x90, 0x9A, 0x12, 0xAC, 0xD9, 0xB2, 0xE5, 0x0A
],
output.deref()
);
let out: BasicU8S = from_bytes(output.deref()).unwrap();
assert_eq!(out, data);
}
#[test]
fn de_byte_slice() {
let input: &[u8] = &[1u8, 2, 3, 4, 5, 6, 7, 8];
let output: Vec<u8, 9> = to_vec(input).unwrap();
assert_eq!(
&[0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08],
output.deref()
);
let out: Vec<u8, 128> = from_bytes(output.deref()).unwrap();
assert_eq!(input, out.deref());
let mut input: Vec<u8, 1024> = Vec::new();
for i in 0..1024 {
input.push((i & 0xFF) as u8).unwrap();
}
let output: Vec<u8, 2048> = to_vec(input.deref()).unwrap();
assert_eq!(&[0x80, 0x08], &output.deref()[..2]);
assert_eq!(output.len(), 1026);
for (i, val) in output.deref()[2..].iter().enumerate() {
assert_eq!((i & 0xFF) as u8, *val);
}
let de: Vec<u8, 1024> = from_bytes(output.deref()).unwrap();
assert_eq!(input.deref(), de.deref());
}
#[test]
fn de_str() {
let input: &str = "hello, postcard!";
let output: Vec<u8, 17> = to_vec(input).unwrap();
assert_eq!(0x10, output.deref()[0]);
assert_eq!(input.as_bytes(), &output.deref()[1..]);
let mut input: String<1024> = String::new();
for _ in 0..256 {
write!(&mut input, "abcd").unwrap();
}
let output: Vec<u8, 2048> = to_vec(input.deref()).unwrap();
assert_eq!(&[0x80, 0x08], &output.deref()[..2]);
assert_eq!(output.len(), 1026);
for ch in output.deref()[2..].chunks(4) {
assert_eq!("abcd", core::str::from_utf8(ch).unwrap());
}
let de: String<1024> = from_bytes(output.deref()).unwrap();
assert_eq!(input.deref(), de.deref());
}
#[allow(dead_code)]
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
enum BasicEnum {
Bib,
Bim,
Bap,
}
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
struct EnumStruct {
eight: u8,
sixt: u16,
}
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
enum DataEnum {
Bib(u16),
Bim(u64),
Bap(u8),
Kim(EnumStruct),
Chi { a: u8, b: u32 },
Sho(u16, u8),
}
#[test]
fn enums() {
let output: Vec<u8, 1> = to_vec(&BasicEnum::Bim).unwrap();
assert_eq!(&[0x01], output.deref());
let out: BasicEnum = from_bytes(output.deref()).unwrap();
assert_eq!(out, BasicEnum::Bim);
let output: Vec<u8, { 1 + varint_max::<u64>() }> =
to_vec(&DataEnum::Bim(u64::MAX)).unwrap();
assert_eq!(
&[0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01],
output.deref()
);
let output: Vec<u8, { 1 + varint_max::<u16>() }> =
to_vec(&DataEnum::Bib(u16::MAX)).unwrap();
assert_eq!(&[0x00, 0xFF, 0xFF, 0x03], output.deref());
let out: DataEnum = from_bytes(output.deref()).unwrap();
assert_eq!(out, DataEnum::Bib(u16::MAX));
let output: Vec<u8, 2> = to_vec(&DataEnum::Bap(u8::MAX)).unwrap();
assert_eq!(&[0x02, 0xFF], output.deref());
let out: DataEnum = from_bytes(output.deref()).unwrap();
assert_eq!(out, DataEnum::Bap(u8::MAX));
let output: Vec<u8, 8> = to_vec(&DataEnum::Kim(EnumStruct {
eight: 0xF0,
sixt: 0xACAC,
}))
.unwrap();
assert_eq!(&[0x03, 0xF0, 0xAC, 0xD9, 0x02], output.deref());
let out: DataEnum = from_bytes(output.deref()).unwrap();
assert_eq!(
out,
DataEnum::Kim(EnumStruct {
eight: 0xF0,
sixt: 0xACAC
})
);
let output: Vec<u8, 8> = to_vec(&DataEnum::Chi {
a: 0x0F,
b: 0xC7C7C7C7,
})
.unwrap();
assert_eq!(&[0x04, 0x0F, 0xC7, 0x8F, 0x9F, 0xBE, 0x0C], output.deref());
let out: DataEnum = from_bytes(output.deref()).unwrap();
assert_eq!(
out,
DataEnum::Chi {
a: 0x0F,
b: 0xC7C7C7C7
}
);
let output: Vec<u8, 8> = to_vec(&DataEnum::Sho(0x6969, 0x07)).unwrap();
assert_eq!(&[0x05, 0xE9, 0xD2, 0x01, 0x07], output.deref());
let out: DataEnum = from_bytes(output.deref()).unwrap();
assert_eq!(out, DataEnum::Sho(0x6969, 0x07));
}
#[test]
fn tuples() {
let output: Vec<u8, 128> = to_vec(&(1u8, 10u32, "Hello!")).unwrap();
assert_eq!(
&[1u8, 0x0A, 0x06, b'H', b'e', b'l', b'l', b'o', b'!'],
output.deref()
);
let out: (u8, u32, &str) = from_bytes(output.deref()).unwrap();
assert_eq!(out, (1u8, 10u32, "Hello!"));
}
#[derive(Debug, Eq, PartialEq)]
pub struct ByteSliceStruct<'a> {
bytes: &'a [u8],
}
impl Serialize for ByteSliceStruct<'_> {
fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
// Serialization is generic for all slice types, so the default serialization of byte
// slices does not use `Serializer::serialize_bytes`.
serializer.serialize_bytes(self.bytes)
}
}
impl<'a, 'de> Deserialize<'de> for ByteSliceStruct<'a>
where
'de: 'a,
{
fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
// Deserialization of byte slices is specialized for byte slices, so the default
// deserialization will call `Deserializer::deserialize_bytes`.
Ok(Self {
bytes: Deserialize::deserialize(deserializer)?,
})
}
}
#[test]
fn bytes() {
let x: &[u8; 32] = &[0u8; 32];
let output: Vec<u8, 128> = to_vec(x).unwrap();
assert_eq!(output.len(), 32);
let out: [u8; 32] = from_bytes(output.deref()).unwrap();
assert_eq!(out, [0u8; 32]);
let x: &[u8] = &[0u8; 32];
let output: Vec<u8, 128> = to_vec(x).unwrap();
assert_eq!(output.len(), 33);
let out: &[u8] = from_bytes(output.deref()).unwrap();
assert_eq!(out, [0u8; 32]);
let x = ByteSliceStruct { bytes: &[0u8; 32] };
let output: Vec<u8, 128> = to_vec(&x).unwrap();
assert_eq!(output.len(), 33);
let out: ByteSliceStruct<'_> = from_bytes(output.deref()).unwrap();
assert_eq!(out, ByteSliceStruct { bytes: &[0u8; 32] });
}
#[test]
fn chars() {
let x: char = 'a';
let output: Vec<u8, 5> = to_vec(&x).unwrap();
assert_eq!(output.len(), 2);
let out: char = from_bytes(output.deref()).unwrap();
assert_eq!(out, 'a');
}
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
pub struct NewTypeStruct(u32);
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
pub struct TupleStruct((u8, u16));
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
pub struct DualTupleStruct(u8, u16);
#[test]
fn structs() {
let output: Vec<u8, 4> = to_vec(&NewTypeStruct(5)).unwrap();
assert_eq!(&[0x05], output.deref());
let out: NewTypeStruct = from_bytes(output.deref()).unwrap();
assert_eq!(out, NewTypeStruct(5));
let output: Vec<u8, 3> = to_vec(&TupleStruct((0xA0, 0x1234))).unwrap();
assert_eq!(&[0xA0, 0xB4, 0x24], output.deref());
let out: TupleStruct = from_bytes(output.deref()).unwrap();
assert_eq!(out, TupleStruct((0xA0, 0x1234)));
let output: Vec<u8, 3> = to_vec(&DualTupleStruct(0xA0, 0x1234)).unwrap();
assert_eq!(&[0xA0, 0xB4, 0x24], output.deref());
let out: DualTupleStruct = from_bytes(output.deref()).unwrap();
assert_eq!(out, DualTupleStruct(0xA0, 0x1234));
}
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
struct RefStruct<'a> {
bytes: &'a [u8],
str_s: &'a str,
}
#[test]
fn ref_struct() {
let message = "hElLo";
let bytes = [0x01, 0x10, 0x02, 0x20];
let output: Vec<u8, 11> = to_vec(&RefStruct {
bytes: &bytes,
str_s: message,
})
.unwrap();
assert_eq!(
&[0x04, 0x01, 0x10, 0x02, 0x20, 0x05, b'h', b'E', b'l', b'L', b'o',],
output.deref()
);
let out: RefStruct<'_> = from_bytes(output.deref()).unwrap();
assert_eq!(
out,
RefStruct {
bytes: &bytes,
str_s: message,
}
);
}
#[test]
fn unit() {
let output: Vec<u8, 1> = to_vec(&()).unwrap();
assert_eq!(output.len(), 0);
let _: () = from_bytes(output.deref()).unwrap();
}
#[test]
fn heapless_data() {
let mut input: Vec<u8, 4> = Vec::new();
input.extend_from_slice(&[0x01, 0x02, 0x03, 0x04]).unwrap();
let output: Vec<u8, 5> = to_vec(&input).unwrap();
assert_eq!(&[0x04, 0x01, 0x02, 0x03, 0x04], output.deref());
let out: Vec<u8, 4> = from_bytes(output.deref()).unwrap();
assert_eq!(out, input);
let mut input: String<8> = String::new();
write!(&mut input, "helLO!").unwrap();
let output: Vec<u8, 7> = to_vec(&input).unwrap();
assert_eq!(&[0x06, b'h', b'e', b'l', b'L', b'O', b'!'], output.deref());
let out: String<8> = from_bytes(output.deref()).unwrap();
assert_eq!(input, out);
let mut input: FnvIndexMap<u8, u8, 4> = FnvIndexMap::new();
input.insert(0x01, 0x05).unwrap();
input.insert(0x02, 0x06).unwrap();
input.insert(0x03, 0x07).unwrap();
input.insert(0x04, 0x08).unwrap();
let output: Vec<u8, 100> = to_vec(&input).unwrap();
assert_eq!(
&[0x04, 0x01, 0x05, 0x02, 0x06, 0x03, 0x07, 0x04, 0x08],
output.deref()
);
let out: FnvIndexMap<u8, u8, 4> = from_bytes(output.deref()).unwrap();
assert_eq!(input, out);
}
#[test]
fn cobs_test() {
let message = "hElLo";
let bytes = [0x01, 0x00, 0x02, 0x20];
let input = RefStruct {
bytes: &bytes,
str_s: message,
};
let output: Vec<u8, 11> = to_vec(&input).unwrap();
let mut encode_buf = [0u8; 32];
let sz = cobs::encode(output.deref(), &mut encode_buf);
let out = from_bytes_cobs::<RefStruct<'_>>(&mut encode_buf[..sz]).unwrap();
assert_eq!(input, out);
}
#[test]
fn take_from_includes_terminator() {
// With the null terminator
let mut output: Vec<u8, 32> = to_vec_cobs(&(4i32, 0u8, 4u64)).unwrap();
let (val, remain) = take_from_bytes_cobs::<(i32, u8, u64)>(&mut output).unwrap();
assert_eq!((4, 0, 4), val);
assert_eq!(remain.len(), 0);
// without the null terminator
let mut output: Vec<u8, 32> = to_vec_cobs(&(4i32, 0u8, 4u64)).unwrap();
assert_eq!(output.pop(), Some(0));
let (val, remain) = take_from_bytes_cobs::<(i32, u8, u64)>(&mut output).unwrap();
assert_eq!((4, 0, 4), val);
assert_eq!(remain.len(), 0);
}
}
#[cfg(any(feature = "alloc", feature = "use-std"))]
#[cfg(test)]
mod test_alloc {
extern crate alloc;
use super::*;
use alloc::vec;
use serde::Deserialize;
#[derive(Debug, Deserialize, PartialEq)]
struct ZSTStruct;
#[test]
fn zst_vec() {
assert_eq!(from_bytes(&[3]), Ok(vec![ZSTStruct, ZSTStruct, ZSTStruct]));
assert_eq!(
from_bytes(&[4]),
Ok(vec![ZSTStruct, ZSTStruct, ZSTStruct, ZSTStruct])
);
}
#[test]
fn vec() {
assert_eq!(
from_bytes::<Vec<u8>>(&[8, 255, 255, 255, 0, 0, 0, 0, 0]),
Ok(vec![255, 255, 255, 0, 0, 0, 0, 0])
);
// This won't actually prove anything since tests will likely always be
// run on devices with larger amounts of memory, but it can't hurt.
assert_eq!(
from_bytes::<Vec<u8>>(&[(1 << 7) | 8, 255, 255, 255, 0, 0, 0, 0, 0]),
Err(Error::DeserializeUnexpectedEnd)
);
}
}
+20
View File
@@ -0,0 +1,20 @@
// We disable all embedded-io versions but the most recent in docs.rs, because we use
// --all-features which doesn't work with non-additive features.
#[cfg(all(feature = "embedded-io-04", feature = "embedded-io-06", not(docsrs)))]
compile_error!("Only one version of `embedded-io` must be enabled through features");
#[cfg(all(feature = "embedded-io-04", not(docsrs)))]
mod version_impl {
pub use embedded_io_04 as embedded_io;
pub use embedded_io_04::blocking::{Read, Write};
}
#[cfg(feature = "embedded-io-06")]
mod version_impl {
pub use embedded_io_06 as embedded_io;
pub use embedded_io_06::{Read, Write};
}
// All versions should export the appropriate items
#[cfg(any(feature = "embedded-io-04", feature = "embedded-io-06"))]
pub use version_impl::{embedded_io, Read, Write};
+96
View File
@@ -0,0 +1,96 @@
use core::fmt::{Display, Formatter};
use serde::{Deserialize, Serialize};
/// This is the error type used by Postcard
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "use-defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum Error {
/// This is a feature that postcard will never implement
WontImplement,
/// This is a feature that postcard intends to support, but does not yet
NotYetImplemented,
/// The serialize buffer is full
SerializeBufferFull,
/// The length of a sequence must be known
SerializeSeqLengthUnknown,
/// Hit the end of buffer, expected more data
DeserializeUnexpectedEnd,
/// Found a varint that didn't terminate. Is the usize too big for this platform?
DeserializeBadVarint,
/// Found a bool that wasn't 0 or 1
DeserializeBadBool,
/// Found an invalid unicode char
DeserializeBadChar,
/// Tried to parse invalid utf-8
DeserializeBadUtf8,
/// Found an Option discriminant that wasn't 0 or 1
DeserializeBadOption,
/// Found an enum discriminant that was > `u32::MAX`
DeserializeBadEnum,
/// The original data was not well encoded
DeserializeBadEncoding,
/// Bad CRC while deserializing
DeserializeBadCrc,
/// Serde Serialization Error
SerdeSerCustom,
/// Serde Deserialization Error
SerdeDeCustom,
/// Error while processing `collect_str` during serialization
CollectStrError,
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
use Error::*;
write!(
f,
"{}",
match self {
WontImplement => "This is a feature that PostCard will never implement",
NotYetImplemented => {
"This is a feature that Postcard intends to support, but does not yet"
}
SerializeBufferFull => "The serialize buffer is full",
SerializeSeqLengthUnknown => "The length of a sequence must be known",
DeserializeUnexpectedEnd => "Hit the end of buffer, expected more data",
DeserializeBadVarint => {
"Found a varint that didn't terminate. Is the usize too big for this platform?"
}
DeserializeBadBool => "Found a bool that wasn't 0 or 1",
DeserializeBadChar => "Found an invalid unicode char",
DeserializeBadUtf8 => "Tried to parse invalid utf-8",
DeserializeBadOption => "Found an Option discriminant that wasn't 0 or 1",
DeserializeBadEnum => "Found an enum discriminant that was > u32::max_value()",
DeserializeBadEncoding => "The original data was not well encoded",
DeserializeBadCrc => "Bad CRC while deserializing",
SerdeSerCustom => "Serde Serialization Error",
SerdeDeCustom => "Serde Deserialization Error",
CollectStrError => "Error while processing `collect_str` during serialization",
}
)
}
}
/// This is the Result type used by Postcard.
pub type Result<T> = ::core::result::Result<T, Error>;
impl serde::ser::Error for Error {
fn custom<T>(_msg: T) -> Self
where
T: Display,
{
Error::SerdeSerCustom
}
}
impl serde::de::Error for Error {
fn custom<T>(_msg: T) -> Self
where
T: Display,
{
Error::SerdeDeCustom
}
}
impl serde::ser::StdError for Error {}
+191
View File
@@ -0,0 +1,191 @@
//! # Fixed Size Integers
//!
//! In some cases, the use of variably length encoded data may not be
//! preferable. These modules, for use with `#[serde(with = ...)]`
//! "opt out" of variable length encoding.
//!
//! Support explicitly not provided for `usize` or `isize`, as
//! these types would not be portable between systems of different
//! pointer widths.
//!
//! Although all data in Postcard is typically encoded in little-endian
//! order, these modules provide a choice to the user to encode the data
//! in either little or big endian form, which may be useful for zero-copy
//! applications.
use serde::{Deserialize, Serialize, Serializer};
/// Use with the `#[serde(with = "postcard::fixint::le")]` field attribute.
///
/// Disables varint serialization/deserialization for the specified integer
/// field. The integer will always be serialized in the same way as a fixed
/// size array, in **Little Endian** order on the wire.
///
/// ```rust
/// # use serde::Serialize;
/// #[derive(Serialize)]
/// pub struct DefinitelyLittleEndian {
/// #[serde(with = "postcard::fixint::le")]
/// x: u16,
/// }
/// ```
pub mod le {
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use super::LE;
/// Serialize the integer value as a little-endian fixed-size array.
pub fn serialize<S, T>(val: &T, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: Copy,
LE<T>: Serialize,
{
LE(*val).serialize(serializer)
}
/// Deserialize the integer value from a little-endian fixed-size array.
pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
LE<T>: Deserialize<'de>,
{
LE::<T>::deserialize(deserializer).map(|x| x.0)
}
}
/// Disables varint serialization/deserialization for the specified integer field.
///
/// Use with the `#[serde(with = "postcard::fixint::be")]` field attribute.
/// The integer will always be serialized in the same way as a fixed
/// size array, in **Big Endian** order on the wire.
///
/// ```rust
/// # use serde::Serialize;
/// #[derive(Serialize)]
/// pub struct DefinitelyBigEndian {
/// #[serde(with = "postcard::fixint::be")]
/// x: u16,
/// }
/// ```
pub mod be {
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use super::BE;
/// Serialize the integer value as a big-endian fixed-size array.
pub fn serialize<S, T>(val: &T, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: Copy,
BE<T>: Serialize,
{
BE(*val).serialize(serializer)
}
/// Deserialize the integer value from a big-endian fixed-size array.
pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
BE<T>: Deserialize<'de>,
{
BE::<T>::deserialize(deserializer).map(|x| x.0)
}
}
#[doc(hidden)]
pub struct LE<T>(T);
#[doc(hidden)]
pub struct BE<T>(T);
macro_rules! impl_fixint {
($( $int:ty ),*) => {
$(
impl Serialize for LE<$int> {
#[inline]
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.0.to_le_bytes().serialize(serializer)
}
}
impl<'de> Deserialize<'de> for LE<$int> {
#[inline]
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
<_ as Deserialize>::deserialize(deserializer)
.map(<$int>::from_le_bytes)
.map(Self)
}
}
impl Serialize for BE<$int> {
#[inline]
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.0.to_be_bytes().serialize(serializer)
}
}
impl<'de> Deserialize<'de> for BE<$int> {
#[inline]
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
<_ as Deserialize>::deserialize(deserializer)
.map(<$int>::from_be_bytes)
.map(Self)
}
}
)*
};
}
impl_fixint![i16, i32, i64, i128, u16, u32, u64, u128];
#[cfg(test)]
mod tests {
use serde::{Deserialize, Serialize};
#[test]
fn test_little_endian() {
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
pub struct DefinitelyLE {
#[serde(with = "crate::fixint::le")]
x: u16,
}
let input = DefinitelyLE { x: 0xABCD };
let mut buf = [0; 32];
let serialized = crate::to_slice(&input, &mut buf).unwrap();
assert_eq!(serialized, &[0xCD, 0xAB]);
let deserialized: DefinitelyLE = crate::from_bytes(serialized).unwrap();
assert_eq!(deserialized, input);
}
#[test]
fn test_big_endian() {
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
pub struct DefinitelyBE {
#[serde(with = "crate::fixint::be")]
x: u16,
}
let input = DefinitelyBE { x: 0xABCD };
let mut buf = [0; 32];
let serialized = crate::to_slice(&input, &mut buf).unwrap();
assert_eq!(serialized, &[0xAB, 0xCD]);
let deserialized: DefinitelyBE = crate::from_bytes(serialized).unwrap();
assert_eq!(deserialized, input);
}
}
+125
View File
@@ -0,0 +1,125 @@
#![cfg_attr(not(any(test, feature = "use-std")), no_std)]
#![warn(missing_docs)]
#![cfg_attr(not(doctest), doc = include_str!("../README.md"))]
#![cfg_attr(docsrs, feature(doc_cfg))]
pub mod accumulator;
mod de;
mod eio;
mod error;
pub mod fixint;
mod ser;
mod varint;
// Still experimental! Don't make pub pub.
pub(crate) mod max_size;
/// # Experimental Postcard Features
///
/// Items inside this module require various feature flags, and are not
/// subject to SemVer stability. Items may be removed or deprecated at
/// any point.
///
/// ## Derive
///
/// The `experimental-derive` feature enables one experimental feature:
///
/// * Max size calculation
///
/// ### Max Size Calculation
///
/// This features enables calculation of the Max serialized size of a message as
/// an associated `usize` constant called `POSTCARD_MAX_SIZE`. It also provides a
/// `#[derive(MaxSize)]` macro that can be used for calculating user types.
///
/// This is useful for determining the maximum buffer size needed when receiving
/// or sending a message that has been serialized.
///
/// NOTE: This only covers the size of "plain" flavored messages, e.g. not with COBS
/// or any other Flavors applied. The overhead for these flavors must be calculated
/// separately.
///
/// Please report any missing types, or any incorrectly calculated values.
///
/// ### Message Schema Generation
///
/// This now lives in the `postcard-schema` crate.
pub mod experimental {
/// Compile time max-serialization size calculation
#[cfg(feature = "experimental-derive")]
#[cfg_attr(docsrs, doc(cfg(feature = "experimental-derive")))]
pub mod max_size {
// NOTE: This is the trait...
pub use crate::max_size::MaxSize;
// NOTE: ...and this is the derive macro
pub use postcard_derive::MaxSize;
}
pub use crate::ser::serialized_size;
}
pub use de::deserializer::Deserializer;
pub use de::flavors as de_flavors;
pub use de::{from_bytes, from_bytes_cobs, take_from_bytes, take_from_bytes_cobs};
pub use error::{Error, Result};
pub use ser::flavors as ser_flavors;
pub use ser::{serialize_with_flavor, serializer::Serializer, to_extend, to_slice, to_slice_cobs};
#[cfg(feature = "heapless")]
pub use ser::{to_vec, to_vec_cobs};
#[cfg(any(feature = "embedded-io-04", feature = "embedded-io-06"))]
pub use ser::to_eio;
#[cfg(any(feature = "embedded-io-04", feature = "embedded-io-06"))]
pub use de::from_eio;
#[cfg(feature = "use-std")]
pub use ser::{to_io, to_stdvec, to_stdvec_cobs};
#[cfg(feature = "use-std")]
pub use de::from_io;
#[cfg(feature = "alloc")]
pub use ser::{to_allocvec, to_allocvec_cobs};
#[cfg(feature = "use-crc")]
pub use {
de::{from_bytes_crc32, take_from_bytes_crc32},
ser::to_slice_crc32,
};
#[cfg(all(feature = "use-crc", feature = "heapless"))]
pub use ser::to_vec_crc32;
#[cfg(all(feature = "use-crc", feature = "use-std"))]
pub use ser::to_stdvec_crc32;
#[cfg(all(feature = "use-crc", feature = "alloc"))]
pub use ser::to_allocvec_crc32;
#[cfg(test)]
mod test {
#[test]
fn varint_boundary_canon() {
let x = u32::MAX;
let mut buf = [0u8; 5];
let used = crate::to_slice(&x, &mut buf).unwrap();
let deser: u32 = crate::from_bytes(used).unwrap();
assert_eq!(deser, u32::MAX);
assert_eq!(used, &mut [0xFF, 0xFF, 0xFF, 0xFF, 0x0F]);
let deser: Result<u32, crate::Error> = crate::from_bytes(&[0xFF, 0xFF, 0xFF, 0xFF, 0x1F]);
assert_eq!(deser, Err(crate::Error::DeserializeBadVarint));
}
#[test]
fn signed_int128() {
let x = -19490127978232325886905073712831_i128;
let mut buf = [0u8; 32];
let used = crate::to_slice(&x, &mut buf).unwrap();
let deser: i128 = crate::from_bytes(used).unwrap();
assert_eq!(deser, x);
}
}
+354
View File
@@ -0,0 +1,354 @@
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "alloc")]
use alloc::{boxed::Box, rc::Rc};
#[cfg(all(feature = "alloc", target_has_atomic = "ptr"))]
use alloc::sync::Arc;
use crate::varint::varint_max;
use core::{
marker::PhantomData,
num::{
NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroIsize, NonZeroU128,
NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize,
},
ops::{Range, RangeFrom, RangeInclusive, RangeTo},
time::Duration,
};
/// This trait is used to enforce the maximum size required to
/// store the serialization of a given type.
pub trait MaxSize {
/// The maximum possible size that the serialization of this
/// type can have, in bytes.
const POSTCARD_MAX_SIZE: usize;
}
impl MaxSize for bool {
const POSTCARD_MAX_SIZE: usize = 1;
}
impl MaxSize for i8 {
const POSTCARD_MAX_SIZE: usize = 1;
}
impl MaxSize for i16 {
const POSTCARD_MAX_SIZE: usize = varint_max::<Self>();
}
impl MaxSize for i32 {
const POSTCARD_MAX_SIZE: usize = varint_max::<Self>();
}
impl MaxSize for i64 {
const POSTCARD_MAX_SIZE: usize = varint_max::<Self>();
}
impl MaxSize for i128 {
const POSTCARD_MAX_SIZE: usize = varint_max::<Self>();
}
impl MaxSize for isize {
const POSTCARD_MAX_SIZE: usize = varint_max::<Self>();
}
impl MaxSize for u8 {
const POSTCARD_MAX_SIZE: usize = 1;
}
impl MaxSize for u16 {
const POSTCARD_MAX_SIZE: usize = varint_max::<Self>();
}
impl MaxSize for u32 {
const POSTCARD_MAX_SIZE: usize = varint_max::<Self>();
}
impl MaxSize for u64 {
const POSTCARD_MAX_SIZE: usize = varint_max::<Self>();
}
impl MaxSize for u128 {
const POSTCARD_MAX_SIZE: usize = varint_max::<Self>();
}
impl MaxSize for usize {
const POSTCARD_MAX_SIZE: usize = varint_max::<Self>();
}
impl MaxSize for f32 {
const POSTCARD_MAX_SIZE: usize = 4;
}
impl MaxSize for f64 {
const POSTCARD_MAX_SIZE: usize = 8;
}
impl MaxSize for char {
const POSTCARD_MAX_SIZE: usize = 5;
}
impl<T: MaxSize> MaxSize for Option<T> {
const POSTCARD_MAX_SIZE: usize = T::POSTCARD_MAX_SIZE + 1;
}
impl<T: MaxSize, E: MaxSize> MaxSize for Result<T, E> {
const POSTCARD_MAX_SIZE: usize = max(T::POSTCARD_MAX_SIZE, E::POSTCARD_MAX_SIZE) + 1;
}
impl MaxSize for () {
const POSTCARD_MAX_SIZE: usize = 0;
}
impl<T: MaxSize, const N: usize> MaxSize for [T; N] {
const POSTCARD_MAX_SIZE: usize = T::POSTCARD_MAX_SIZE * N;
}
impl<T: MaxSize> MaxSize for &'_ T {
const POSTCARD_MAX_SIZE: usize = T::POSTCARD_MAX_SIZE;
}
impl<T: MaxSize> MaxSize for &'_ mut T {
const POSTCARD_MAX_SIZE: usize = T::POSTCARD_MAX_SIZE;
}
impl MaxSize for NonZeroI8 {
const POSTCARD_MAX_SIZE: usize = i8::POSTCARD_MAX_SIZE;
}
impl MaxSize for NonZeroI16 {
const POSTCARD_MAX_SIZE: usize = i16::POSTCARD_MAX_SIZE;
}
impl MaxSize for NonZeroI32 {
const POSTCARD_MAX_SIZE: usize = i32::POSTCARD_MAX_SIZE;
}
impl MaxSize for NonZeroI64 {
const POSTCARD_MAX_SIZE: usize = i64::POSTCARD_MAX_SIZE;
}
impl MaxSize for NonZeroI128 {
const POSTCARD_MAX_SIZE: usize = i128::POSTCARD_MAX_SIZE;
}
impl MaxSize for NonZeroIsize {
const POSTCARD_MAX_SIZE: usize = isize::POSTCARD_MAX_SIZE;
}
impl MaxSize for NonZeroU8 {
const POSTCARD_MAX_SIZE: usize = u8::POSTCARD_MAX_SIZE;
}
impl MaxSize for NonZeroU16 {
const POSTCARD_MAX_SIZE: usize = u16::POSTCARD_MAX_SIZE;
}
impl MaxSize for NonZeroU32 {
const POSTCARD_MAX_SIZE: usize = u32::POSTCARD_MAX_SIZE;
}
impl MaxSize for NonZeroU64 {
const POSTCARD_MAX_SIZE: usize = u64::POSTCARD_MAX_SIZE;
}
impl MaxSize for NonZeroU128 {
const POSTCARD_MAX_SIZE: usize = u128::POSTCARD_MAX_SIZE;
}
impl MaxSize for NonZeroUsize {
const POSTCARD_MAX_SIZE: usize = usize::POSTCARD_MAX_SIZE;
}
impl MaxSize for Duration {
const POSTCARD_MAX_SIZE: usize = u64::POSTCARD_MAX_SIZE + u32::POSTCARD_MAX_SIZE;
}
impl<T> MaxSize for PhantomData<T> {
const POSTCARD_MAX_SIZE: usize = 0;
}
impl<A: MaxSize> MaxSize for (A,) {
const POSTCARD_MAX_SIZE: usize = A::POSTCARD_MAX_SIZE;
}
impl<A: MaxSize, B: MaxSize> MaxSize for (A, B) {
const POSTCARD_MAX_SIZE: usize = A::POSTCARD_MAX_SIZE + B::POSTCARD_MAX_SIZE;
}
impl<A: MaxSize, B: MaxSize, C: MaxSize> MaxSize for (A, B, C) {
const POSTCARD_MAX_SIZE: usize =
A::POSTCARD_MAX_SIZE + B::POSTCARD_MAX_SIZE + C::POSTCARD_MAX_SIZE;
}
impl<A: MaxSize, B: MaxSize, C: MaxSize, D: MaxSize> MaxSize for (A, B, C, D) {
const POSTCARD_MAX_SIZE: usize =
A::POSTCARD_MAX_SIZE + B::POSTCARD_MAX_SIZE + C::POSTCARD_MAX_SIZE + D::POSTCARD_MAX_SIZE;
}
impl<A: MaxSize, B: MaxSize, C: MaxSize, D: MaxSize, E: MaxSize> MaxSize for (A, B, C, D, E) {
const POSTCARD_MAX_SIZE: usize = A::POSTCARD_MAX_SIZE
+ B::POSTCARD_MAX_SIZE
+ C::POSTCARD_MAX_SIZE
+ D::POSTCARD_MAX_SIZE
+ E::POSTCARD_MAX_SIZE;
}
impl<A: MaxSize, B: MaxSize, C: MaxSize, D: MaxSize, E: MaxSize, F: MaxSize> MaxSize
for (A, B, C, D, E, F)
{
const POSTCARD_MAX_SIZE: usize = A::POSTCARD_MAX_SIZE
+ B::POSTCARD_MAX_SIZE
+ C::POSTCARD_MAX_SIZE
+ D::POSTCARD_MAX_SIZE
+ E::POSTCARD_MAX_SIZE
+ F::POSTCARD_MAX_SIZE;
}
impl<T: MaxSize> MaxSize for Range<T> {
const POSTCARD_MAX_SIZE: usize = T::POSTCARD_MAX_SIZE * 2;
}
impl<T: MaxSize> MaxSize for RangeInclusive<T> {
const POSTCARD_MAX_SIZE: usize = T::POSTCARD_MAX_SIZE * 2;
}
impl<T: MaxSize> MaxSize for RangeFrom<T> {
const POSTCARD_MAX_SIZE: usize = T::POSTCARD_MAX_SIZE;
}
impl<T: MaxSize> MaxSize for RangeTo<T> {
const POSTCARD_MAX_SIZE: usize = T::POSTCARD_MAX_SIZE;
}
impl<T: MaxSize> MaxSize for core::num::Wrapping<T> {
const POSTCARD_MAX_SIZE: usize = T::POSTCARD_MAX_SIZE;
}
#[cfg(all(feature = "core-num-saturating", feature = "experimental-derive"))]
#[cfg_attr(docsrs, doc(cfg(feature = "core-num-saturating")))]
impl<T: MaxSize> MaxSize for core::num::Saturating<T> {
const POSTCARD_MAX_SIZE: usize = T::POSTCARD_MAX_SIZE;
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl<T: MaxSize> MaxSize for Box<T> {
const POSTCARD_MAX_SIZE: usize = T::POSTCARD_MAX_SIZE;
}
#[cfg(all(feature = "alloc", target_has_atomic = "ptr"))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "alloc", target_has_atomic = "ptr"))))]
impl<T: MaxSize> MaxSize for Arc<T> {
const POSTCARD_MAX_SIZE: usize = T::POSTCARD_MAX_SIZE;
}
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
impl<T: MaxSize> MaxSize for Rc<T> {
const POSTCARD_MAX_SIZE: usize = T::POSTCARD_MAX_SIZE;
}
#[cfg(feature = "heapless")]
#[cfg_attr(docsrs, doc(cfg(feature = "heapless")))]
impl<T: MaxSize, const N: usize> MaxSize for heapless::Vec<T, N> {
const POSTCARD_MAX_SIZE: usize = <[T; N]>::POSTCARD_MAX_SIZE + varint_size(N);
}
#[cfg(feature = "heapless")]
#[cfg_attr(docsrs, doc(cfg(feature = "heapless")))]
impl<const N: usize> MaxSize for heapless::String<N> {
const POSTCARD_MAX_SIZE: usize = <[u8; N]>::POSTCARD_MAX_SIZE + varint_size(N);
}
#[cfg(all(feature = "nalgebra-v0_33", feature = "experimental-derive"))]
#[cfg_attr(docsrs, doc(cfg(feature = "nalgebra-v0_33")))]
impl<T, const R: usize, const C: usize> MaxSize
for nalgebra_v0_33::Matrix<
T,
nalgebra_v0_33::Const<R>,
nalgebra_v0_33::Const<C>,
nalgebra_v0_33::ArrayStorage<T, R, C>,
>
where
T: MaxSize + nalgebra_v0_33::Scalar,
{
const POSTCARD_MAX_SIZE: usize = T::POSTCARD_MAX_SIZE * R * C;
}
#[cfg(all(feature = "nalgebra-v0_33", feature = "experimental-derive"))]
#[cfg_attr(docsrs, doc(cfg(feature = "nalgebra-v0_33")))]
impl<T: MaxSize> MaxSize for nalgebra_v0_33::Unit<T> {
const POSTCARD_MAX_SIZE: usize = T::POSTCARD_MAX_SIZE;
}
#[cfg(all(feature = "nalgebra-v0_33", feature = "experimental-derive"))]
#[cfg_attr(docsrs, doc(cfg(feature = "nalgebra-v0_33")))]
impl<T: MaxSize + nalgebra_v0_33::Scalar> MaxSize for nalgebra_v0_33::Quaternion<T> {
const POSTCARD_MAX_SIZE: usize = nalgebra_v0_33::Vector4::<T>::POSTCARD_MAX_SIZE;
}
#[cfg(feature = "heapless")]
const fn varint_size(max_n: usize) -> usize {
const BITS_PER_BYTE: usize = 8;
const BITS_PER_VARINT_BYTE: usize = 7;
if max_n == 0 {
return 1;
}
// How many data bits do we need for `max_n`.
let bits = core::mem::size_of::<usize>() * BITS_PER_BYTE - max_n.leading_zeros() as usize;
// We add (BITS_PER_BYTE - 1), to ensure any integer divisions
// with a remainder will always add exactly one full byte, but
// an evenly divided number of bits will be the same
let roundup_bits = bits + (BITS_PER_VARINT_BYTE - 1);
// Apply division, using normal "round down" integer division
roundup_bits / BITS_PER_VARINT_BYTE
}
const fn max(lhs: usize, rhs: usize) -> usize {
if lhs > rhs {
lhs
} else {
rhs
}
}
#[cfg(any(feature = "alloc", feature = "use-std"))]
#[cfg(test)]
mod tests {
extern crate alloc;
use super::*;
use alloc::rc::Rc;
#[cfg(target_has_atomic = "ptr")]
use alloc::sync::Arc;
#[test]
fn box_max_size() {
assert_eq!(Box::<u8>::POSTCARD_MAX_SIZE, 1);
assert_eq!(Box::<u32>::POSTCARD_MAX_SIZE, 5);
assert_eq!(Box::<(u128, [u8; 8])>::POSTCARD_MAX_SIZE, 27);
}
#[test]
#[cfg(target_has_atomic = "ptr")]
fn arc_max_size() {
assert_eq!(Arc::<u8>::POSTCARD_MAX_SIZE, 1);
assert_eq!(Arc::<u32>::POSTCARD_MAX_SIZE, 5);
assert_eq!(Arc::<(u128, [u8; 8])>::POSTCARD_MAX_SIZE, 27);
}
#[test]
fn rc_max_size() {
assert_eq!(Rc::<u8>::POSTCARD_MAX_SIZE, 1);
assert_eq!(Rc::<u32>::POSTCARD_MAX_SIZE, 5);
assert_eq!(Rc::<(u128, [u8; 8])>::POSTCARD_MAX_SIZE, 27);
}
}
+729
View File
@@ -0,0 +1,729 @@
//! # Serialization Flavors
//!
//! "Flavors" in `postcard` are used as modifiers to the serialization or deserialization
//! process. Flavors typically modify one or both of the following:
//!
//! 1. The output medium of the serialization, e.g. whether the data is serialized to a `[u8]` slice, or a `heapless::Vec`.
//! 2. The format of the serialization, such as encoding the serialized output in a COBS format, performing CRC32 checksumming while serializing, etc.
//!
//! Flavors are implemented using the [`Flavor`] trait, which acts as a "middleware" for receiving the bytes as serialized by `serde`.
//! Multiple flavors may be combined to obtain a desired combination of behavior and storage.
//! When flavors are combined, it is expected that the storage flavor (such as `Slice` or `HVec`) is the innermost flavor.
//!
//! Custom flavors may be defined by users of the `postcard` crate, however some commonly useful flavors have been provided in
//! this module. If you think your custom flavor would be useful to others, PRs adding flavors are very welcome!
//!
//! ## Usability
//!
//! Flavors may not always be convenient to use directly, as they may expose some implementation details of how the
//! inner workings of the flavor behaves. It is typical to provide a convenience method for using a flavor, to prevent
//! the user from having to specify generic parameters, setting correct initialization values, or handling the output of
//! the flavor correctly. See `postcard::to_vec()` for an example of this.
//!
//! It is recommended to use the [`serialize_with_flavor()`](../fn.serialize_with_flavor.html) method for serialization. See it's documentation for information
//! regarding usage and generic type parameters.
//!
//! ## When to use (multiple) flavors
//!
//! Combining flavors are nice for convenience, as they perform potentially multiple steps of
//! serialization at one time.
//!
//! This can often be more memory efficient, as intermediate buffers are not typically required.
//!
//! ## When NOT to use (multiple) flavors
//!
//! The downside of passing serialization through multiple steps is that it is typically slower than
//! performing each step serially. Said simply, "cobs encoding while serializing" is often slower
//! than "serialize then cobs encode", due to the ability to handle longer "runs" of data in each
//! stage. The downside is that if these stages can not be performed in-place on the buffer, you
//! will need additional buffers for each stage.
//!
//! ## Examples
//!
//! ### Using a single flavor
//!
//! In the first example, we use the `Slice` flavor, to store the serialized output into a mutable `[u8]` slice.
//! No other modification is made to the serialization process.
//!
//! ```rust
//! use postcard::{
//! serialize_with_flavor,
//! ser_flavors::Slice,
//! };
//!
//! let mut buf = [0u8; 32];
//!
//! let data: &[u8] = &[0x01, 0x00, 0x20, 0x30];
//! let buffer = &mut [0u8; 32];
//! let res = serialize_with_flavor::<[u8], Slice, &mut [u8]>(
//! data,
//! Slice::new(buffer)
//! ).unwrap();
//!
//! assert_eq!(res, &[0x04, 0x01, 0x00, 0x20, 0x30]);
//! ```
//!
//! ### Using combined flavors
//!
//! In the second example, we mix `Slice` with `Cobs`, to cobs encode the output while
//! the data is serialized. Notice how `Slice` (the storage flavor) is the innermost flavor used.
//!
//! ```rust
//! use postcard::{
//! serialize_with_flavor,
//! ser_flavors::{Cobs, Slice},
//! };
//!
//! let mut buf = [0u8; 32];
//!
//! let data: &[u8] = &[0x01, 0x00, 0x20, 0x30];
//! let buffer = &mut [0u8; 32];
//! let res = serialize_with_flavor::<[u8], Cobs<Slice>, &mut [u8]>(
//! data,
//! Cobs::try_new(Slice::new(buffer)).unwrap(),
//! ).unwrap();
//!
//! assert_eq!(res, &[0x03, 0x04, 0x01, 0x03, 0x20, 0x30, 0x00]);
//! ```
use crate::error::{Error, Result};
use cobs::{EncoderState, PushResult};
use core::marker::PhantomData;
use core::ops::Index;
use core::ops::IndexMut;
#[cfg(feature = "heapless")]
pub use heapless_vec::*;
#[cfg(feature = "use-std")]
pub use std_vec::*;
#[cfg(feature = "alloc")]
pub use alloc_vec::*;
#[cfg(feature = "alloc")]
extern crate alloc;
/// The serialization Flavor trait
///
/// This is used as the primary way to encode serialized data into some kind of buffer,
/// or modify that data in a middleware style pattern.
///
/// See the module level docs for an example of how flavors are used.
pub trait Flavor {
/// The `Output` type is what this storage "resolves" to when the serialization is complete,
/// such as a slice or a Vec of some sort.
type Output;
/// Override this method when you want to customize processing
/// multiple bytes at once, such as copying a slice to the output,
/// rather than iterating over one byte at a time.
#[inline]
fn try_extend(&mut self, data: &[u8]) -> Result<()> {
data.iter().try_for_each(|d| self.try_push(*d))
}
/// Push a single byte to be modified and/or stored.
fn try_push(&mut self, data: u8) -> Result<()>;
/// Finalize the serialization process.
fn finalize(self) -> Result<Self::Output>;
}
////////////////////////////////////////
// Slice
////////////////////////////////////////
/// The `Slice` flavor is a storage flavor, storing the serialized (or otherwise modified) bytes into a plain
/// `[u8]` slice. The `Slice` flavor resolves into a sub-slice of the original slice buffer.
pub struct Slice<'a> {
start: *mut u8,
cursor: *mut u8,
end: *mut u8,
_pl: PhantomData<&'a [u8]>,
}
impl<'a> Slice<'a> {
/// Create a new `Slice` flavor from a given backing buffer
pub fn new(buf: &'a mut [u8]) -> Self {
let ptr = buf.as_mut_ptr_range();
Slice {
start: ptr.start,
cursor: ptr.start,
end: ptr.end,
_pl: PhantomData,
}
}
}
impl<'a> Flavor for Slice<'a> {
type Output = &'a mut [u8];
#[inline(always)]
fn try_push(&mut self, b: u8) -> Result<()> {
if self.cursor == self.end {
Err(Error::SerializeBufferFull)
} else {
// SAFETY: `self.cursor` is in-bounds and won't be incremented past `self.end` as we
// have checked above.
unsafe {
self.cursor.write(b);
self.cursor = self.cursor.add(1);
}
Ok(())
}
}
#[inline(always)]
fn try_extend(&mut self, b: &[u8]) -> Result<()> {
let remain = (self.end as usize) - (self.cursor as usize);
let blen = b.len();
if blen > remain {
Err(Error::SerializeBufferFull)
} else {
// SAFETY: `self.cursor` is in-bounds for `blen` elements and won't be incremented past
// `self.end` as we have checked above.
unsafe {
core::ptr::copy_nonoverlapping(b.as_ptr(), self.cursor, blen);
self.cursor = self.cursor.add(blen);
}
Ok(())
}
}
fn finalize(self) -> Result<Self::Output> {
let used = (self.cursor as usize) - (self.start as usize);
// SAFETY: `self.cursor` is in-bounds for `used` elements
let sli = unsafe { core::slice::from_raw_parts_mut(self.start, used) };
Ok(sli)
}
}
impl Index<usize> for Slice<'_> {
type Output = u8;
fn index(&self, idx: usize) -> &u8 {
let len = (self.end as usize) - (self.start as usize);
assert!(idx < len);
// SAFETY: `self.start` is in-bounds at `idx`
unsafe { &*self.start.add(idx) }
}
}
impl IndexMut<usize> for Slice<'_> {
fn index_mut(&mut self, idx: usize) -> &mut u8 {
let len = (self.end as usize) - (self.start as usize);
assert!(idx < len);
// SAFETY: `self.start` is in-bounds at `idx`
unsafe { &mut *self.start.add(idx) }
}
}
/// Wrapper over a [`core::iter::Extend<u8>`] that implements the flavor trait
pub struct ExtendFlavor<T> {
iter: T,
}
impl<T> ExtendFlavor<T>
where
T: core::iter::Extend<u8>,
{
/// Create a new [`Self`] flavor from a given [`core::iter::Extend<u8>`]
pub fn new(iter: T) -> Self {
Self { iter }
}
}
impl<T> Flavor for ExtendFlavor<T>
where
T: core::iter::Extend<u8>,
{
type Output = T;
#[inline(always)]
fn try_push(&mut self, data: u8) -> Result<()> {
self.iter.extend([data]);
Ok(())
}
#[inline(always)]
fn try_extend(&mut self, b: &[u8]) -> Result<()> {
self.iter.extend(b.iter().copied());
Ok(())
}
fn finalize(self) -> Result<Self::Output> {
Ok(self.iter)
}
}
/// Support for the [`embedded-io`](crate::eio::embedded_io) traits
#[cfg(any(feature = "embedded-io-04", feature = "embedded-io-06"))]
pub mod eio {
use super::Flavor;
use crate::{Error, Result};
/// Wrapper over a [`embedded_io Write`](crate::eio::Write) that implements the flavor trait
pub struct WriteFlavor<T> {
writer: T,
}
impl<T> WriteFlavor<T>
where
T: crate::eio::Write,
{
/// Create a new [`Self`] flavor from a given [`embedded_io Write`](crate::eio::Write)
pub fn new(writer: T) -> Self {
Self { writer }
}
}
impl<T> Flavor for WriteFlavor<T>
where
T: crate::eio::Write,
{
type Output = T;
#[inline(always)]
fn try_push(&mut self, data: u8) -> Result<()> {
self.writer
.write_all(&[data])
.map_err(|_| Error::SerializeBufferFull)?;
Ok(())
}
#[inline(always)]
fn try_extend(&mut self, b: &[u8]) -> Result<()> {
self.writer
.write_all(b)
.map_err(|_| Error::SerializeBufferFull)?;
Ok(())
}
fn finalize(mut self) -> Result<Self::Output> {
self.writer
.flush()
.map_err(|_| Error::SerializeBufferFull)?;
Ok(self.writer)
}
}
}
/// Support for the [`std::io`] traits
#[cfg(feature = "use-std")]
pub mod io {
use super::Flavor;
use crate::{Error, Result};
/// Wrapper over a [`std::io::Write`] that implements the flavor trait
pub struct WriteFlavor<T> {
writer: T,
}
impl<T> WriteFlavor<T>
where
T: std::io::Write,
{
/// Create a new [`Self`] flavor from a given [`std::io::Write`]
pub fn new(writer: T) -> Self {
Self { writer }
}
}
impl<T> Flavor for WriteFlavor<T>
where
T: std::io::Write,
{
type Output = T;
#[inline(always)]
fn try_push(&mut self, data: u8) -> Result<()> {
self.writer
.write_all(&[data])
.map_err(|_| Error::SerializeBufferFull)?;
Ok(())
}
#[inline(always)]
fn try_extend(&mut self, b: &[u8]) -> Result<()> {
self.writer
.write_all(b)
.map_err(|_| Error::SerializeBufferFull)?;
Ok(())
}
fn finalize(mut self) -> Result<Self::Output> {
self.writer
.flush()
.map_err(|_| Error::SerializeBufferFull)?;
Ok(self.writer)
}
}
}
#[cfg(feature = "heapless")]
mod heapless_vec {
use super::Flavor;
use super::Index;
use super::IndexMut;
use crate::{Error, Result};
use heapless::Vec;
////////////////////////////////////////
// HVec
////////////////////////////////////////
/// The `HVec` flavor is a wrapper type around a `heapless::Vec`. This is a stack
/// allocated data structure, with a fixed maximum size and variable amount of contents.
#[derive(Default)]
pub struct HVec<const B: usize> {
/// the contained data buffer
vec: Vec<u8, B>,
}
impl<const B: usize> HVec<B> {
/// Create a new, currently empty, [`heapless::Vec`] to be used for storing serialized
/// output data.
pub fn new() -> Self {
Self::default()
}
}
impl<const B: usize> Flavor for HVec<B> {
type Output = Vec<u8, B>;
#[inline(always)]
fn try_extend(&mut self, data: &[u8]) -> Result<()> {
self.vec
.extend_from_slice(data)
.map_err(|_| Error::SerializeBufferFull)
}
#[inline(always)]
fn try_push(&mut self, data: u8) -> Result<()> {
self.vec.push(data).map_err(|_| Error::SerializeBufferFull)
}
fn finalize(self) -> Result<Vec<u8, B>> {
Ok(self.vec)
}
}
impl<const B: usize> Index<usize> for HVec<B> {
type Output = u8;
fn index(&self, idx: usize) -> &u8 {
&self.vec[idx]
}
}
impl<const B: usize> IndexMut<usize> for HVec<B> {
fn index_mut(&mut self, idx: usize) -> &mut u8 {
&mut self.vec[idx]
}
}
}
#[cfg(feature = "use-std")]
mod std_vec {
/// The `StdVec` flavor is a wrapper type around a `std::vec::Vec`.
///
/// This type is only available when the (non-default) `use-std` feature is active
pub type StdVec = super::alloc_vec::AllocVec;
}
#[cfg(feature = "alloc")]
mod alloc_vec {
extern crate alloc;
use super::Flavor;
use super::Index;
use super::IndexMut;
use crate::Result;
use alloc::vec::Vec;
/// The `AllocVec` flavor is a wrapper type around an [`alloc::vec::Vec`].
///
/// This type is only available when the (non-default) `alloc` feature is active
#[derive(Default)]
pub struct AllocVec {
/// The vec to be used for serialization
vec: Vec<u8>,
}
impl AllocVec {
/// Create a new, currently empty, [`alloc::vec::Vec`] to be used for storing serialized
/// output data.
pub fn new() -> Self {
Self::default()
}
}
impl Flavor for AllocVec {
type Output = Vec<u8>;
#[inline(always)]
fn try_extend(&mut self, data: &[u8]) -> Result<()> {
self.vec.extend_from_slice(data);
Ok(())
}
#[inline(always)]
fn try_push(&mut self, data: u8) -> Result<()> {
self.vec.push(data);
Ok(())
}
fn finalize(self) -> Result<Self::Output> {
Ok(self.vec)
}
}
impl Index<usize> for AllocVec {
type Output = u8;
#[inline]
fn index(&self, idx: usize) -> &u8 {
&self.vec[idx]
}
}
impl IndexMut<usize> for AllocVec {
#[inline]
fn index_mut(&mut self, idx: usize) -> &mut u8 {
&mut self.vec[idx]
}
}
}
////////////////////////////////////////////////////////////////////////////////
// Modification Flavors
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////
// COBS
////////////////////////////////////////
/// The `Cobs` flavor implements [Consistent Overhead Byte Stuffing] on
/// the serialized data. The output of this flavor includes the termination/sentinel
/// byte of `0x00`.
///
/// This protocol is useful when sending data over a serial interface without framing such as a UART
///
/// [Consistent Overhead Byte Stuffing]: https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing
pub struct Cobs<B>
where
B: Flavor + IndexMut<usize, Output = u8>,
{
flav: B,
cobs: EncoderState,
}
impl<B> Cobs<B>
where
B: Flavor + IndexMut<usize, Output = u8>,
{
/// Create a new Cobs modifier Flavor. If there is insufficient space
/// to push the leading header byte, the method will return an Error
pub fn try_new(mut bee: B) -> Result<Self> {
bee.try_push(0).map_err(|_| Error::SerializeBufferFull)?;
Ok(Self {
flav: bee,
cobs: EncoderState::default(),
})
}
}
impl<B> Flavor for Cobs<B>
where
B: Flavor + IndexMut<usize, Output = u8>,
{
type Output = <B as Flavor>::Output;
#[inline(always)]
fn try_push(&mut self, data: u8) -> Result<()> {
use PushResult::*;
match self.cobs.push(data) {
AddSingle(n) => self.flav.try_push(n),
ModifyFromStartAndSkip((idx, mval)) => {
self.flav[idx] = mval;
self.flav.try_push(0)
}
ModifyFromStartAndPushAndSkip((idx, mval, nval)) => {
self.flav[idx] = mval;
self.flav.try_push(nval)?;
self.flav.try_push(0)
}
}
}
fn finalize(mut self) -> Result<Self::Output> {
let (idx, mval) = self.cobs.finalize();
self.flav[idx] = mval;
self.flav.try_push(0)?;
self.flav.finalize()
}
}
////////////////////////////////////////
// CRC
////////////////////////////////////////
/// This Cyclic Redundancy Check flavor applies [the CRC crate's `Algorithm`](https://docs.rs/crc/latest/crc/struct.Algorithm.html) struct on
/// the serialized data.
///
/// The output of this flavor receives the CRC appended to the bytes.
/// CRCs are used for error detection when reading data back.
/// Requires the `crc` feature.
///
/// More on CRCs: <https://en.wikipedia.org/wiki/Cyclic_redundancy_check>.
#[cfg(feature = "use-crc")]
#[cfg_attr(docsrs, doc(cfg(feature = "use-crc")))]
pub mod crc {
use crc::Digest;
use crc::Width;
use serde::Serialize;
#[cfg(feature = "alloc")]
use super::alloc;
use super::Flavor;
use super::Slice;
use crate::serialize_with_flavor;
use crate::Result;
/// Manages CRC modifications as a flavor.
pub struct CrcModifier<'a, B, W>
where
B: Flavor,
W: Width,
{
flav: B,
digest: Digest<'a, W>,
}
impl<'a, B, W> CrcModifier<'a, B, W>
where
B: Flavor,
W: Width,
{
/// Create a new CRC modifier Flavor.
pub fn new(bee: B, digest: Digest<'a, W>) -> Self {
Self { flav: bee, digest }
}
}
macro_rules! impl_flavor {
($int:ty, $to_slice:ident, $to_vec:ident, $to_allocvec:ident) => {
impl<'a, B> Flavor for CrcModifier<'a, B, $int>
where
B: Flavor,
{
type Output = <B as Flavor>::Output;
#[inline(always)]
fn try_push(&mut self, data: u8) -> Result<()> {
self.digest.update(&[data]);
self.flav.try_push(data)
}
fn finalize(mut self) -> Result<Self::Output> {
let crc = self.digest.finalize();
for byte in crc.to_le_bytes() {
self.flav.try_push(byte)?;
}
self.flav.finalize()
}
}
/// Serialize a `T` to the given slice, with the resulting slice containing
/// data followed by a CRC. The CRC bytes are included in the output buffer.
///
/// When successful, this function returns the slice containing the
/// serialized and encoded message.
pub fn $to_slice<'a, T>(
value: &T,
buf: &'a mut [u8],
digest: Digest<'_, $int>,
) -> Result<&'a mut [u8]>
where
T: Serialize + ?Sized,
{
serialize_with_flavor(value, CrcModifier::new(Slice::new(buf), digest))
}
/// Serialize a `T` to a `heapless::Vec<u8>`, with the `Vec` containing
/// data followed by a CRC. The CRC bytes are included in the output `Vec`.
#[cfg(feature = "heapless")]
#[cfg_attr(docsrs, doc(cfg(feature = "heapless")))]
pub fn $to_vec<T, const B: usize>(
value: &T,
digest: Digest<'_, $int>,
) -> Result<heapless::Vec<u8, B>>
where
T: Serialize + ?Sized,
{
use super::HVec;
serialize_with_flavor(value, CrcModifier::new(HVec::default(), digest))
}
/// Serialize a `T` to a `heapless::Vec<u8>`, with the `Vec` containing
/// data followed by a CRC. The CRC bytes are included in the output `Vec`.
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub fn $to_allocvec<T>(
value: &T,
digest: Digest<'_, $int>,
) -> Result<alloc::vec::Vec<u8>>
where
T: Serialize + ?Sized,
{
use super::AllocVec;
serialize_with_flavor(value, CrcModifier::new(AllocVec::new(), digest))
}
};
}
impl_flavor!(u8, to_slice_u8, to_vec_u8, to_allocvec_u8);
impl_flavor!(u16, to_slice_u16, to_vec_u16, to_allocvec_u16);
impl_flavor!(u32, to_slice_u32, to_vec_u32, to_allocvec_u32);
impl_flavor!(u64, to_slice_u64, to_vec_u64, to_allocvec_u64);
impl_flavor!(u128, to_slice_u128, to_vec_u128, to_allocvec_u128);
}
/// The `Size` flavor is a measurement flavor, which accumulates the number of bytes needed to
/// serialize the data.
///
/// ```
/// use postcard::{serialize_with_flavor, ser_flavors};
///
/// let value = false;
/// let size = serialize_with_flavor(&value, ser_flavors::Size::default()).unwrap();
///
/// assert_eq!(size, 1);
/// ```
#[derive(Default)]
pub struct Size {
size: usize,
}
impl Flavor for Size {
type Output = usize;
#[inline(always)]
fn try_push(&mut self, _b: u8) -> Result<()> {
self.size += 1;
Ok(())
}
#[inline(always)]
fn try_extend(&mut self, b: &[u8]) -> Result<()> {
self.size += b.len();
Ok(())
}
fn finalize(self) -> Result<Self::Output> {
Ok(self.size)
}
}
+863
View File
@@ -0,0 +1,863 @@
use crate::error::{Error, Result};
use crate::ser::flavors::{Cobs, Flavor, Slice};
use serde::Serialize;
#[cfg(feature = "heapless")]
use crate::ser::flavors::HVec;
#[cfg(feature = "heapless")]
use heapless::Vec;
#[cfg(feature = "alloc")]
use crate::ser::flavors::AllocVec;
#[cfg(feature = "alloc")]
extern crate alloc;
use crate::ser::serializer::Serializer;
pub mod flavors;
pub(crate) mod serializer;
/// Serialize a `T` to the given slice, with the resulting slice containing
/// data in a serialized then COBS encoded format. The terminating sentinel
/// `0x00` byte is included in the output buffer.
///
/// When successful, this function returns the slice containing the
/// serialized and encoded message.
///
/// ## Example
///
/// ```rust
/// use postcard::to_slice_cobs;
/// let mut buf = [0u8; 32];
///
/// let used = to_slice_cobs(&false, &mut buf).unwrap();
/// assert_eq!(used, &[0x01, 0x01, 0x00]);
///
/// let used = to_slice_cobs("1", &mut buf).unwrap();
/// assert_eq!(used, &[0x03, 0x01, b'1', 0x00]);
///
/// let used = to_slice_cobs("Hi!", &mut buf).unwrap();
/// assert_eq!(used, &[0x05, 0x03, b'H', b'i', b'!', 0x00]);
///
/// let data: &[u8] = &[0x01u8, 0x00, 0x20, 0x30];
/// let used = to_slice_cobs(data, &mut buf).unwrap();
/// assert_eq!(used, &[0x03, 0x04, 0x01, 0x03, 0x20, 0x30, 0x00]);
/// ```
pub fn to_slice_cobs<'a, 'b, T>(value: &'b T, buf: &'a mut [u8]) -> Result<&'a mut [u8]>
where
T: Serialize + ?Sized,
{
serialize_with_flavor::<T, Cobs<Slice<'a>>, &'a mut [u8]>(
value,
Cobs::try_new(Slice::new(buf))?,
)
}
/// Serialize a `T` to the given slice, with the resulting slice containing
/// data in a serialized format.
///
/// When successful, this function returns the slice containing the
/// serialized message
///
/// ## Example
///
/// ```rust
/// use postcard::to_slice;
/// let mut buf = [0u8; 32];
///
/// let used = to_slice(&true, &mut buf).unwrap();
/// assert_eq!(used, &[0x01]);
///
/// let used = to_slice("Hi!", &mut buf).unwrap();
/// assert_eq!(used, &[0x03, b'H', b'i', b'!']);
///
/// // NOTE: postcard handles `&[u8]` and `&[u8; N]` differently.
/// let data: &[u8] = &[0x01u8, 0x00, 0x20, 0x30];
/// let used = to_slice(data, &mut buf).unwrap();
/// assert_eq!(used, &[0x04, 0x01, 0x00, 0x20, 0x30]);
///
/// let data: &[u8; 4] = &[0x01u8, 0x00, 0x20, 0x30];
/// let used = to_slice(data, &mut buf).unwrap();
/// assert_eq!(used, &[0x01, 0x00, 0x20, 0x30]);
/// ```
pub fn to_slice<'a, 'b, T>(value: &'b T, buf: &'a mut [u8]) -> Result<&'a mut [u8]>
where
T: Serialize + ?Sized,
{
serialize_with_flavor::<T, Slice<'a>, &'a mut [u8]>(value, Slice::new(buf))
}
/// Serialize a `T` to a `heapless::Vec<u8>`, with the `Vec` containing
/// data in a serialized then COBS encoded format. The terminating sentinel
/// `0x00` byte is included in the output `Vec`.
///
/// ## Example
///
/// ```rust
/// use postcard::to_vec_cobs;
/// use heapless::Vec;
/// use core::ops::Deref;
///
/// let ser: Vec<u8, 32> = to_vec_cobs(&false).unwrap();
/// assert_eq!(ser.deref(), &[0x01, 0x01, 0x00]);
///
/// let ser: Vec<u8, 32> = to_vec_cobs("Hi!").unwrap();
/// assert_eq!(ser.deref(), &[0x05, 0x03, b'H', b'i', b'!', 0x00]);
///
/// // NOTE: postcard handles `&[u8]` and `&[u8; N]` differently.
/// let data: &[u8] = &[0x01u8, 0x00, 0x20, 0x30];
/// let ser: Vec<u8, 32> = to_vec_cobs(data).unwrap();
/// assert_eq!(ser.deref(), &[0x03, 0x04, 0x01, 0x03, 0x20, 0x30, 0x00]);
///
/// let data: &[u8; 4] = &[0x01u8, 0x00, 0x20, 0x30];
/// let ser: Vec<u8, 32> = to_vec_cobs(data).unwrap();
/// assert_eq!(ser.deref(), &[0x02, 0x01, 0x03, 0x20, 0x30, 0x00]);
/// ```
#[cfg(feature = "heapless")]
#[cfg_attr(docsrs, doc(cfg(feature = "heapless")))]
pub fn to_vec_cobs<T, const B: usize>(value: &T) -> Result<Vec<u8, B>>
where
T: Serialize + ?Sized,
{
serialize_with_flavor::<T, Cobs<HVec<B>>, Vec<u8, B>>(value, Cobs::try_new(HVec::default())?)
}
/// Serialize a `T` to a `heapless::Vec<u8>`, with the `Vec` containing
/// data in a serialized format.
///
/// ## Example
///
/// ```rust
/// use postcard::to_vec;
/// use heapless::Vec;
/// use core::ops::Deref;
///
/// let ser: Vec<u8, 32> = to_vec(&true).unwrap();
/// assert_eq!(ser.deref(), &[0x01]);
///
/// let ser: Vec<u8, 32> = to_vec("Hi!").unwrap();
/// assert_eq!(ser.deref(), &[0x03, b'H', b'i', b'!']);
///
/// // NOTE: postcard handles `&[u8]` and `&[u8; N]` differently.
/// let data: &[u8] = &[0x01u8, 0x00, 0x20, 0x30];
/// let ser: Vec<u8, 32> = to_vec(data).unwrap();
/// assert_eq!(ser.deref(), &[0x04, 0x01, 0x00, 0x20, 0x30]);
///
/// let data: &[u8; 4] = &[0x01u8, 0x00, 0x20, 0x30];
/// let ser: Vec<u8, 32> = to_vec(data).unwrap();
/// assert_eq!(ser.deref(), &[0x01, 0x00, 0x20, 0x30]);
/// ```
#[cfg(feature = "heapless")]
#[cfg_attr(docsrs, doc(cfg(feature = "heapless")))]
pub fn to_vec<T, const B: usize>(value: &T) -> Result<Vec<u8, B>>
where
T: Serialize + ?Sized,
{
serialize_with_flavor::<T, HVec<B>, Vec<u8, B>>(value, HVec::default())
}
/// Serialize a `T` to a `std::vec::Vec<u8>`.
///
/// ## Example
///
/// ```rust
/// use postcard::to_stdvec;
///
/// let ser: Vec<u8> = to_stdvec(&true).unwrap();
/// assert_eq!(ser.as_slice(), &[0x01]);
///
/// let ser: Vec<u8> = to_stdvec("Hi!").unwrap();
/// assert_eq!(ser.as_slice(), &[0x03, b'H', b'i', b'!']);
/// ```
#[cfg(feature = "use-std")]
#[cfg_attr(docsrs, doc(cfg(feature = "use-std")))]
#[inline]
pub fn to_stdvec<T>(value: &T) -> Result<std::vec::Vec<u8>>
where
T: Serialize + ?Sized,
{
to_allocvec(value)
}
/// Serialize and COBS encode a `T` to a `std::vec::Vec<u8>`.
///
/// The terminating sentinel `0x00` byte is included in the output.
///
/// ## Example
///
/// ```rust
/// use postcard::to_stdvec_cobs;
///
/// let ser: Vec<u8> = to_stdvec_cobs(&true).unwrap();
/// assert_eq!(ser.as_slice(), &[0x02, 0x01, 0x00]);
///
/// let ser: Vec<u8> = to_stdvec_cobs("Hi!").unwrap();
/// assert_eq!(ser.as_slice(), &[0x05, 0x03, b'H', b'i', b'!', 0x00]);
/// ```
#[cfg(feature = "use-std")]
#[cfg_attr(docsrs, doc(cfg(feature = "use-std")))]
#[inline]
pub fn to_stdvec_cobs<T>(value: &T) -> Result<std::vec::Vec<u8>>
where
T: Serialize + ?Sized,
{
to_allocvec_cobs(value)
}
/// Serialize a `T` to an `alloc::vec::Vec<u8>`.
///
/// ## Example
///
/// ```rust
/// use postcard::to_allocvec;
///
/// let ser: Vec<u8> = to_allocvec(&true).unwrap();
/// assert_eq!(ser.as_slice(), &[0x01]);
///
/// let ser: Vec<u8> = to_allocvec("Hi!").unwrap();
/// assert_eq!(ser.as_slice(), &[0x03, b'H', b'i', b'!']);
/// ```
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub fn to_allocvec<T>(value: &T) -> Result<alloc::vec::Vec<u8>>
where
T: Serialize + ?Sized,
{
serialize_with_flavor::<T, AllocVec, alloc::vec::Vec<u8>>(value, AllocVec::new())
}
/// Serialize and COBS encode a `T` to an `alloc::vec::Vec<u8>`.
///
/// The terminating sentinel `0x00` byte is included in the output.
///
/// ## Example
///
/// ```rust
/// use postcard::to_allocvec_cobs;
///
/// let ser: Vec<u8> = to_allocvec_cobs(&true).unwrap();
/// assert_eq!(ser.as_slice(), &[0x02, 0x01, 0x00]);
///
/// let ser: Vec<u8> = to_allocvec_cobs("Hi!").unwrap();
/// assert_eq!(ser.as_slice(), &[0x05, 0x03, b'H', b'i', b'!', 0x00]);
/// ```
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub fn to_allocvec_cobs<T>(value: &T) -> Result<alloc::vec::Vec<u8>>
where
T: Serialize + ?Sized,
{
serialize_with_flavor::<T, Cobs<AllocVec>, alloc::vec::Vec<u8>>(
value,
Cobs::try_new(AllocVec::new())?,
)
}
/// Serialize a `T` to a [`core::iter::Extend`],
/// ## Example
///
/// ```rust
/// use postcard::to_extend;
/// let mut vec = Vec::new();
///
/// let ser = to_extend(&true, vec).unwrap();
/// let vec = to_extend("Hi!", ser).unwrap();
/// assert_eq!(&vec[0..5], &[0x01, 0x03, b'H', b'i', b'!']);
/// ```
pub fn to_extend<T, W>(value: &T, writer: W) -> Result<W>
where
T: Serialize + ?Sized,
W: core::iter::Extend<u8>,
{
serialize_with_flavor::<T, _, _>(value, flavors::ExtendFlavor::new(writer))
}
/// Serialize a `T` to an [`embedded_io Write`](crate::eio::Write),
/// ## Example
///
/// ```rust
/// use postcard::to_eio;
/// let mut buf: [u8; 32] = [0; 32];
/// let mut writer: &mut [u8] = &mut buf;
///
/// let ser = to_eio(&true, &mut writer).unwrap();
/// to_eio("Hi!", ser).unwrap();
/// assert_eq!(&buf[0..5], &[0x01, 0x03, b'H', b'i', b'!']);
/// ```
#[cfg(any(feature = "embedded-io-04", feature = "embedded-io-06"))]
pub fn to_eio<T, W>(value: &T, writer: W) -> Result<W>
where
T: Serialize + ?Sized,
W: crate::eio::Write,
{
serialize_with_flavor::<T, _, _>(value, flavors::eio::WriteFlavor::new(writer))
}
/// Serialize a `T` to a [`std::io::Write`],
/// ## Example
///
/// ```rust
/// use postcard::to_io;
/// let mut buf: [u8; 32] = [0; 32];
/// let mut writer: &mut [u8] = &mut buf;
///
/// let ser = to_io(&true, &mut writer).unwrap();
/// to_io("Hi!", ser).unwrap();
/// assert_eq!(&buf[0..5], &[0x01, 0x03, b'H', b'i', b'!']);
/// ```
#[cfg(feature = "use-std")]
pub fn to_io<T, W>(value: &T, writer: W) -> Result<W>
where
T: Serialize + ?Sized,
W: std::io::Write,
{
serialize_with_flavor::<T, _, _>(value, flavors::io::WriteFlavor::new(writer))
}
/// Conveniently serialize a `T` to the given slice, with the resulting slice containing
/// data followed by a 32-bit CRC. The CRC bytes are included in the output buffer.
///
/// When successful, this function returns the slice containing the
/// serialized and encoded message.
///
/// ## Example
///
/// ```rust
/// use crc::{Crc, CRC_32_ISCSI};
///
/// let mut buf = [0; 9];
///
/// let data: &[u8] = &[0x01, 0x00, 0x20, 0x30];
/// let crc = Crc::<u32>::new(&CRC_32_ISCSI);
/// let used = postcard::to_slice_crc32(data, &mut buf, crc.digest()).unwrap();
/// assert_eq!(used, &[0x04, 0x01, 0x00, 0x20, 0x30, 0x8E, 0xC8, 0x1A, 0x37]);
/// ```
///
/// See the `ser_flavors::crc` module for the complete set of functions.
#[cfg(feature = "use-crc")]
#[cfg_attr(docsrs, doc(cfg(feature = "use-crc")))]
#[inline]
pub fn to_slice_crc32<'a, T>(
value: &T,
buf: &'a mut [u8],
digest: crc::Digest<'_, u32>,
) -> Result<&'a mut [u8]>
where
T: Serialize + ?Sized,
{
flavors::crc::to_slice_u32(value, buf, digest)
}
/// Conveniently serialize a `T` to a `heapless::Vec<u8>`, with the `Vec` containing
/// data followed by a 32-bit CRC. The CRC bytes are included in the output `Vec`.
///
/// ## Example
///
/// ```rust
/// use crc::{Crc, CRC_32_ISCSI};
/// use heapless::Vec;
/// use core::ops::Deref;
///
/// // NOTE: postcard handles `&[u8]` and `&[u8; N]` differently.
/// let data: &[u8] = &[0x01u8, 0x00, 0x20, 0x30];
/// let crc = Crc::<u32>::new(&CRC_32_ISCSI);
/// let ser: Vec<u8, 32> = postcard::to_vec_crc32(data, crc.digest()).unwrap();
/// assert_eq!(ser.deref(), &[0x04, 0x01, 0x00, 0x20, 0x30, 0x8E, 0xC8, 0x1A, 0x37]);
///
/// let data: &[u8; 4] = &[0x01u8, 0x00, 0x20, 0x30];
/// let ser: Vec<u8, 32> = postcard::to_vec_crc32(data, crc.digest()).unwrap();
/// assert_eq!(ser.deref(), &[0x01, 0x00, 0x20, 0x30, 0xCC, 0x4B, 0x4A, 0xDA]);
/// ```
///
/// See the `ser_flavors::crc` module for the complete set of functions.
#[cfg(all(feature = "use-crc", feature = "heapless"))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "use-crc", feature = "heapless"))))]
#[inline]
pub fn to_vec_crc32<T, const B: usize>(
value: &T,
digest: crc::Digest<'_, u32>,
) -> Result<heapless::Vec<u8, B>>
where
T: Serialize + ?Sized,
{
flavors::crc::to_vec_u32(value, digest)
}
/// Conveniently serialize a `T` to a `heapless::Vec<u8>`, with the `Vec` containing
/// data followed by a 32-bit CRC. The CRC bytes are included in the output `Vec`.
///
/// ## Example
///
/// ```rust
/// use crc::{Crc, CRC_32_ISCSI};
/// use core::ops::Deref;
///
/// // NOTE: postcard handles `&[u8]` and `&[u8; N]` differently.
/// let data: &[u8] = &[0x01u8, 0x00, 0x20, 0x30];
/// let crc = Crc::<u32>::new(&CRC_32_ISCSI);
/// let ser: Vec<u8> = postcard::to_stdvec_crc32(data, crc.digest()).unwrap();
/// assert_eq!(ser.deref(), &[0x04, 0x01, 0x00, 0x20, 0x30, 0x8E, 0xC8, 0x1A, 0x37]);
///
/// let data: &[u8; 4] = &[0x01u8, 0x00, 0x20, 0x30];
/// let ser: Vec<u8> = postcard::to_stdvec_crc32(data, crc.digest()).unwrap();
/// assert_eq!(ser.deref(), &[0x01, 0x00, 0x20, 0x30, 0xCC, 0x4B, 0x4A, 0xDA]);
/// ```
///
/// See the `ser_flavors::crc` module for the complete set of functions.
#[cfg(all(feature = "use-crc", feature = "use-std"))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "use-crc", feature = "use-std"))))]
#[inline]
pub fn to_stdvec_crc32<T>(value: &T, digest: crc::Digest<'_, u32>) -> Result<std::vec::Vec<u8>>
where
T: Serialize + ?Sized,
{
flavors::crc::to_allocvec_u32(value, digest)
}
/// Conveniently serialize a `T` to a `heapless::Vec<u8>`, with the `Vec` containing
/// data followed by a 32-bit CRC. The CRC bytes are included in the output `Vec`.
///
/// ## Example
///
/// ```rust
/// use crc::{Crc, CRC_32_ISCSI};
/// use core::ops::Deref;
///
/// // NOTE: postcard handles `&[u8]` and `&[u8; N]` differently.
/// let data: &[u8] = &[0x01u8, 0x00, 0x20, 0x30];
/// let crc = Crc::<u32>::new(&CRC_32_ISCSI);
/// let ser: Vec<u8> = postcard::to_allocvec_crc32(data, crc.digest()).unwrap();
/// assert_eq!(ser.deref(), &[0x04, 0x01, 0x00, 0x20, 0x30, 0x8E, 0xC8, 0x1A, 0x37]);
///
/// let data: &[u8; 4] = &[0x01u8, 0x00, 0x20, 0x30];
/// let ser: Vec<u8> = postcard::to_allocvec_crc32(data, crc.digest()).unwrap();
/// assert_eq!(ser.deref(), &[0x01, 0x00, 0x20, 0x30, 0xCC, 0x4B, 0x4A, 0xDA]);
/// ```
///
/// See the `ser_flavors::crc` module for the complete set of functions.
#[cfg(all(feature = "use-crc", feature = "alloc"))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "use-crc", feature = "alloc"))))]
#[inline]
pub fn to_allocvec_crc32<T>(value: &T, digest: crc::Digest<'_, u32>) -> Result<alloc::vec::Vec<u8>>
where
T: Serialize + ?Sized,
{
flavors::crc::to_allocvec_u32(value, digest)
}
/// `serialize_with_flavor()` has three generic parameters, `T, F, O`.
///
/// * `T`: This is the type that is being serialized
/// * `S`: This is the Storage that is used during serialization
/// * `O`: This is the resulting storage type that is returned containing the serialized data
///
/// For more information about how Flavors work, please see the
/// [`flavors` module documentation](./flavors/index.html).
///
/// ```rust
/// use postcard::{
/// serialize_with_flavor,
/// ser_flavors::{Cobs, Slice},
/// };
///
/// let mut buf = [0u8; 32];
///
/// let data: &[u8] = &[0x01, 0x00, 0x20, 0x30];
/// let buffer = &mut [0u8; 32];
/// let res = serialize_with_flavor::<[u8], Cobs<Slice>, &mut [u8]>(
/// data,
/// Cobs::try_new(Slice::new(buffer)).unwrap(),
/// ).unwrap();
///
/// assert_eq!(res, &[0x03, 0x04, 0x01, 0x03, 0x20, 0x30, 0x00]);
/// ```
pub fn serialize_with_flavor<T, S, O>(value: &T, storage: S) -> Result<O>
where
T: Serialize + ?Sized,
S: Flavor<Output = O>,
{
let mut serializer = Serializer { output: storage };
value.serialize(&mut serializer)?;
serializer
.output
.finalize()
.map_err(|_| Error::SerializeBufferFull)
}
/// Compute the size of the postcard serialization of `T`.
pub fn serialized_size<T>(value: &T) -> Result<usize>
where
T: Serialize + ?Sized,
{
serialize_with_flavor::<T, flavors::Size, usize>(value, flavors::Size::default())
}
#[cfg(feature = "heapless")]
#[cfg(test)]
mod test {
use super::*;
use crate::max_size::MaxSize;
use crate::varint::{varint_max, varint_usize};
use core::fmt::Write;
use core::ops::{Deref, DerefMut};
use heapless::{FnvIndexMap, String};
use serde::Deserialize;
#[test]
fn ser_u8() {
let output: Vec<u8, 1> = to_vec(&0x05u8).unwrap();
assert_eq!(&[5], output.deref());
assert!(output.len() == serialized_size(&0x05u8).unwrap());
assert!(output.len() <= Vec::<u8, 1>::POSTCARD_MAX_SIZE);
}
#[test]
fn ser_u16() {
const SZ: usize = varint_max::<u16>();
let output: Vec<u8, SZ> = to_vec(&0xA5C7u16).unwrap();
assert_eq!(&[0xC7, 0xCB, 0x02], output.deref());
assert!(output.len() == serialized_size(&0xA5C7u16).unwrap());
assert!(output.len() <= Vec::<u8, SZ>::POSTCARD_MAX_SIZE);
}
#[test]
fn ser_u32() {
const SZ: usize = varint_max::<u32>();
let output: Vec<u8, SZ> = to_vec(&0xCDAB3412u32).unwrap();
assert_eq!(&[0x92, 0xE8, 0xAC, 0xED, 0x0C], output.deref());
assert!(output.len() == serialized_size(&0xCDAB3412u32).unwrap());
assert!(output.len() <= Vec::<u8, SZ>::POSTCARD_MAX_SIZE);
}
#[test]
fn ser_u64() {
const SZ: usize = varint_max::<u64>();
let output: Vec<u8, SZ> = to_vec(&0x1234_5678_90AB_CDEFu64).unwrap();
assert_eq!(
&[0xEF, 0x9B, 0xAF, 0x85, 0x89, 0xCF, 0x95, 0x9A, 0x12],
output.deref()
);
assert!(output.len() == serialized_size(&0x1234_5678_90AB_CDEFu64).unwrap());
assert!(output.len() <= Vec::<u8, SZ>::POSTCARD_MAX_SIZE);
}
#[test]
fn ser_u128() {
const SZ: usize = varint_max::<u128>();
let output: Vec<u8, SZ> = to_vec(&0x1234_5678_90AB_CDEF_1234_5678_90AB_CDEFu128).unwrap();
assert_eq!(
&[
0xEF, 0x9B, 0xAF, 0x85, 0x89, 0xCF, 0x95, 0x9A, 0x92, 0xDE, 0xB7, 0xDE, 0x8A, 0x92,
0x9E, 0xAB, 0xB4, 0x24,
],
output.deref()
);
assert!(
output.len()
== serialized_size(&0x1234_5678_90AB_CDEF_1234_5678_90AB_CDEFu128).unwrap()
);
assert!(output.len() <= Vec::<u8, SZ>::POSTCARD_MAX_SIZE);
}
#[derive(Serialize)]
struct BasicU8S {
st: u16,
ei: u8,
ote: u128,
sf: u64,
tt: u32,
}
impl MaxSize for BasicU8S {
const POSTCARD_MAX_SIZE: usize = {
u16::POSTCARD_MAX_SIZE
+ u8::POSTCARD_MAX_SIZE
+ u128::POSTCARD_MAX_SIZE
+ u64::POSTCARD_MAX_SIZE
+ u32::POSTCARD_MAX_SIZE
};
}
#[test]
fn ser_struct_unsigned() {
const SZ: usize = BasicU8S::POSTCARD_MAX_SIZE;
let input = BasicU8S {
st: 0xABCD,
ei: 0xFE,
ote: 0x1234_4321_ABCD_DCBA_1234_4321_ABCD_DCBA,
sf: 0x1234_4321_ABCD_DCBA,
tt: 0xACAC_ACAC,
};
let output: Vec<u8, SZ> = to_vec(&input).unwrap();
assert_eq!(
&[
0xCD, 0xD7, 0x02, 0xFE, 0xBA, 0xB9, 0xB7, 0xDE, 0x9A, 0xE4, 0x90, 0x9A, 0x92, 0xF4,
0xF2, 0xEE, 0xBC, 0xB5, 0xC8, 0xA1, 0xB4, 0x24, 0xBA, 0xB9, 0xB7, 0xDE, 0x9A, 0xE4,
0x90, 0x9A, 0x12, 0xAC, 0xD9, 0xB2, 0xE5, 0x0A
],
output.deref()
);
assert!(output.len() == serialized_size(&input).unwrap());
assert!(output.len() <= BasicU8S::POSTCARD_MAX_SIZE);
}
#[test]
fn ser_byte_slice() {
let input: &[u8] = &[1u8, 2, 3, 4, 5, 6, 7, 8];
let output: Vec<u8, 9> = to_vec(input).unwrap();
assert_eq!(
&[0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08],
output.deref()
);
assert!(output.len() == serialized_size(&input).unwrap());
let mut input: Vec<u8, 1024> = Vec::new();
for i in 0..1024 {
input.push((i & 0xFF) as u8).unwrap();
}
let output: Vec<u8, 2048> = to_vec(input.deref()).unwrap();
assert_eq!(&[0x80, 0x08], &output.deref()[..2]);
assert_eq!(output.len(), 1026);
for (i, val) in output.deref()[2..].iter().enumerate() {
assert_eq!((i & 0xFF) as u8, *val);
}
}
#[test]
fn ser_str() {
let input: &str = "hello, postcard!";
let output: Vec<u8, 17> = to_vec(input).unwrap();
assert_eq!(0x10, output.deref()[0]);
assert_eq!(input.as_bytes(), &output.deref()[1..]);
assert!(output.len() == serialized_size(&input).unwrap());
let mut input: String<1024> = String::new();
for _ in 0..256 {
write!(&mut input, "abcd").unwrap();
}
let output: Vec<u8, 2048> = to_vec(input.deref()).unwrap();
assert_eq!(&[0x80, 0x08], &output.deref()[..2]);
assert!(String::<1024>::POSTCARD_MAX_SIZE <= output.len());
assert_eq!(output.len(), 1026);
for ch in output.deref()[2..].chunks(4) {
assert_eq!("abcd", core::str::from_utf8(ch).unwrap());
}
}
#[test]
fn usize_varint_encode() {
let mut buf = [0; varint_max::<usize>()];
let res = varint_usize(1, &mut buf);
assert_eq!(&[1], res);
let res = varint_usize(usize::MAX, &mut buf);
//
if varint_max::<usize>() == varint_max::<u32>() {
assert_eq!(&[0xFF, 0xFF, 0xFF, 0xFF, 0x0F], res);
} else if varint_max::<usize>() == varint_max::<u64>() {
assert_eq!(
&[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01],
res
);
} else {
panic!("Update this test for 16/128 bit targets!");
}
}
#[allow(dead_code)]
#[derive(Serialize)]
enum BasicEnum {
Bib,
Bim,
Bap,
}
#[derive(Serialize)]
struct EnumStruct {
eight: u8,
sixt: u16,
}
#[derive(Serialize)]
enum DataEnum {
Bib(u16),
Bim(u64),
Bap(u8),
Kim(EnumStruct),
Chi { a: u8, b: u32 },
Sho(u16, u8),
}
#[test]
fn enums() {
let input = BasicEnum::Bim;
let output: Vec<u8, 1> = to_vec(&input).unwrap();
assert_eq!(&[0x01], output.deref());
assert!(output.len() == serialized_size(&input).unwrap());
let input = DataEnum::Bim(u64::MAX);
let output: Vec<u8, { 1 + varint_max::<u64>() }> = to_vec(&input).unwrap();
assert_eq!(
&[0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01],
output.deref()
);
assert!(output.len() == serialized_size(&input).unwrap());
let input = DataEnum::Bib(u16::MAX);
let output: Vec<u8, { 1 + varint_max::<u16>() }> = to_vec(&input).unwrap();
assert_eq!(&[0x00, 0xFF, 0xFF, 0x03], output.deref());
assert!(output.len() == serialized_size(&input).unwrap());
let input = DataEnum::Bap(u8::MAX);
let output: Vec<u8, 2> = to_vec(&input).unwrap();
assert_eq!(&[0x02, 0xFF], output.deref());
assert!(output.len() == serialized_size(&input).unwrap());
let input = DataEnum::Kim(EnumStruct {
eight: 0xF0,
sixt: 0xACAC,
});
let output: Vec<u8, 8> = to_vec(&input).unwrap();
assert_eq!(&[0x03, 0xF0, 0xAC, 0xD9, 0x02], output.deref());
assert!(output.len() == serialized_size(&input).unwrap());
let input = DataEnum::Chi {
a: 0x0F,
b: 0xC7C7C7C7,
};
let output: Vec<u8, 8> = to_vec(&input).unwrap();
assert_eq!(&[0x04, 0x0F, 0xC7, 0x8F, 0x9F, 0xBE, 0x0C], output.deref());
assert!(output.len() == serialized_size(&input).unwrap());
let input = DataEnum::Sho(0x6969, 0x07);
let output: Vec<u8, 8> = to_vec(&input).unwrap();
assert_eq!(&[0x05, 0xE9, 0xD2, 0x01, 0x07], output.deref());
assert!(output.len() == serialized_size(&input).unwrap());
}
#[test]
fn tuples() {
let input = (1u8, 10u32, "Hello!");
let output: Vec<u8, 128> = to_vec(&input).unwrap();
assert_eq!(
&[1u8, 0x0A, 0x06, b'H', b'e', b'l', b'l', b'o', b'!'],
output.deref()
);
assert!(output.len() == serialized_size(&input).unwrap());
}
#[test]
fn bytes() {
let x: &[u8; 32] = &[0u8; 32];
let output: Vec<u8, 128> = to_vec(x).unwrap();
assert_eq!(output.len(), 32);
assert!(output.len() == serialized_size(&x).unwrap());
assert!(<[u8; 32] as MaxSize>::POSTCARD_MAX_SIZE <= output.len());
let x: &[u8] = &[0u8; 32];
let output: Vec<u8, 128> = to_vec(x).unwrap();
assert_eq!(output.len(), 33);
assert!(output.len() == serialized_size(&x).unwrap());
}
#[derive(Serialize)]
pub struct NewTypeStruct(u32);
#[derive(Serialize)]
pub struct TupleStruct((u8, u16));
#[test]
fn structs() {
let input = NewTypeStruct(5);
let output: Vec<u8, 1> = to_vec(&input).unwrap();
assert_eq!(&[0x05], output.deref());
assert!(output.len() == serialized_size(&input).unwrap());
let input = TupleStruct((0xA0, 0x1234));
let output: Vec<u8, 3> = to_vec(&input).unwrap();
assert_eq!(&[0xA0, 0xB4, 0x24], output.deref());
assert!(output.len() == serialized_size(&input).unwrap());
}
#[derive(Serialize, Deserialize, Eq, PartialEq, Debug)]
struct RefStruct<'a> {
bytes: &'a [u8],
str_s: &'a str,
}
#[test]
fn ref_struct() {
let message = "hElLo";
let bytes = [0x01, 0x10, 0x02, 0x20];
let input = RefStruct {
bytes: &bytes,
str_s: message,
};
let output: Vec<u8, 11> = to_vec(&input).unwrap();
assert_eq!(
&[0x04, 0x01, 0x10, 0x02, 0x20, 0x05, b'h', b'E', b'l', b'L', b'o',],
output.deref()
);
assert!(output.len() == serialized_size(&input).unwrap());
}
#[test]
fn unit() {
let output: Vec<u8, 1> = to_vec(&()).unwrap();
assert_eq!(output.len(), 0);
assert!(output.len() == serialized_size(&()).unwrap());
}
#[test]
fn heapless_data() {
let mut input: Vec<u8, 4> = Vec::new();
input.extend_from_slice(&[0x01, 0x02, 0x03, 0x04]).unwrap();
let output: Vec<u8, 5> = to_vec(&input).unwrap();
assert_eq!(&[0x04, 0x01, 0x02, 0x03, 0x04], output.deref());
assert!(output.len() == serialized_size(&input).unwrap());
let mut input: String<8> = String::new();
write!(&mut input, "helLO!").unwrap();
let output: Vec<u8, 7> = to_vec(&input).unwrap();
assert_eq!(&[0x06, b'h', b'e', b'l', b'L', b'O', b'!'], output.deref());
assert!(output.len() == serialized_size(&input).unwrap());
let mut input: FnvIndexMap<u8, u8, 4> = FnvIndexMap::new();
input.insert(0x01, 0x05).unwrap();
input.insert(0x02, 0x06).unwrap();
input.insert(0x03, 0x07).unwrap();
input.insert(0x04, 0x08).unwrap();
let output: Vec<u8, 100> = to_vec(&input).unwrap();
assert_eq!(
&[0x04, 0x01, 0x05, 0x02, 0x06, 0x03, 0x07, 0x04, 0x08],
output.deref()
);
assert!(output.len() == serialized_size(&input).unwrap());
}
#[test]
fn cobs_test() {
let message = "hElLo";
let bytes = [0x01, 0x00, 0x02, 0x20];
let input = RefStruct {
bytes: &bytes,
str_s: message,
};
let mut output: Vec<u8, 13> = to_vec_cobs(&input).unwrap();
let sz = cobs::decode_in_place(output.deref_mut()).unwrap();
let x = crate::from_bytes::<RefStruct<'_>>(&output.deref_mut()[..sz]).unwrap();
assert_eq!(input, x);
}
}
+564
View File
@@ -0,0 +1,564 @@
use serde::{ser, Serialize};
use crate::error::{Error, Result};
use crate::ser::flavors::Flavor;
use crate::varint::*;
/// A `serde` compatible serializer, generic over "Flavors" of serializing plugins.
///
/// It should rarely be necessary to directly use this type unless you are implementing your
/// own [`SerFlavor`].
///
/// See the docs for [`SerFlavor`] for more information about "flavors" of serialization
///
/// [`SerFlavor`]: crate::ser_flavors::Flavor
pub struct Serializer<F>
where
F: Flavor,
{
/// This is the Flavor(s) that will be used to modify or store any bytes generated
/// by serialization
pub output: F,
}
impl<F: Flavor> Serializer<F> {
/// Attempt to push a variably encoded [usize] into the output data stream
#[inline]
pub(crate) fn try_push_varint_usize(&mut self, data: usize) -> Result<()> {
let mut buf = [0u8; varint_max::<usize>()];
let used_buf = varint_usize(data, &mut buf);
self.output.try_extend(used_buf)
}
/// Attempt to push a variably encoded [u128] into the output data stream
#[inline]
pub(crate) fn try_push_varint_u128(&mut self, data: u128) -> Result<()> {
let mut buf = [0u8; varint_max::<u128>()];
let used_buf = varint_u128(data, &mut buf);
self.output.try_extend(used_buf)
}
/// Attempt to push a variably encoded [u64] into the output data stream
#[inline]
pub(crate) fn try_push_varint_u64(&mut self, data: u64) -> Result<()> {
let mut buf = [0u8; varint_max::<u64>()];
let used_buf = varint_u64(data, &mut buf);
self.output.try_extend(used_buf)
}
/// Attempt to push a variably encoded [u32] into the output data stream
#[inline]
pub(crate) fn try_push_varint_u32(&mut self, data: u32) -> Result<()> {
let mut buf = [0u8; varint_max::<u32>()];
let used_buf = varint_u32(data, &mut buf);
self.output.try_extend(used_buf)
}
/// Attempt to push a variably encoded [u16] into the output data stream
#[inline]
pub(crate) fn try_push_varint_u16(&mut self, data: u16) -> Result<()> {
let mut buf = [0u8; varint_max::<u16>()];
let used_buf = varint_u16(data, &mut buf);
self.output.try_extend(used_buf)
}
}
impl<F> ser::Serializer for &mut Serializer<F>
where
F: Flavor,
{
type Ok = ();
type Error = Error;
// Associated types for keeping track of additional state while serializing
// compound data structures like sequences and maps. In this case no
// additional state is required beyond what is already stored in the
// Serializer struct.
type SerializeSeq = Self;
type SerializeTuple = Self;
type SerializeTupleStruct = Self;
type SerializeTupleVariant = Self;
type SerializeMap = Self;
type SerializeStruct = Self;
type SerializeStructVariant = Self;
#[inline]
fn is_human_readable(&self) -> bool {
false
}
#[inline]
fn serialize_bool(self, v: bool) -> Result<()> {
self.serialize_u8(if v { 1 } else { 0 })
}
#[inline]
fn serialize_i8(self, v: i8) -> Result<()> {
self.serialize_u8(v.to_le_bytes()[0])
}
#[inline]
fn serialize_i16(self, v: i16) -> Result<()> {
let zzv = zig_zag_i16(v);
self.try_push_varint_u16(zzv)
.map_err(|_| Error::SerializeBufferFull)
}
#[inline]
fn serialize_i32(self, v: i32) -> Result<()> {
let zzv = zig_zag_i32(v);
self.try_push_varint_u32(zzv)
.map_err(|_| Error::SerializeBufferFull)
}
#[inline]
fn serialize_i64(self, v: i64) -> Result<()> {
let zzv = zig_zag_i64(v);
self.try_push_varint_u64(zzv)
.map_err(|_| Error::SerializeBufferFull)
}
#[inline]
fn serialize_i128(self, v: i128) -> Result<()> {
let zzv = zig_zag_i128(v);
self.try_push_varint_u128(zzv)
.map_err(|_| Error::SerializeBufferFull)
}
#[inline]
fn serialize_u8(self, v: u8) -> Result<()> {
self.output
.try_push(v)
.map_err(|_| Error::SerializeBufferFull)
}
#[inline]
fn serialize_u16(self, v: u16) -> Result<()> {
self.try_push_varint_u16(v)
.map_err(|_| Error::SerializeBufferFull)
}
#[inline]
fn serialize_u32(self, v: u32) -> Result<()> {
self.try_push_varint_u32(v)
.map_err(|_| Error::SerializeBufferFull)
}
#[inline]
fn serialize_u64(self, v: u64) -> Result<()> {
self.try_push_varint_u64(v)
.map_err(|_| Error::SerializeBufferFull)
}
#[inline]
fn serialize_u128(self, v: u128) -> Result<()> {
self.try_push_varint_u128(v)
.map_err(|_| Error::SerializeBufferFull)
}
#[inline]
fn serialize_f32(self, v: f32) -> Result<()> {
let buf = v.to_bits().to_le_bytes();
self.output
.try_extend(&buf)
.map_err(|_| Error::SerializeBufferFull)
}
#[inline]
fn serialize_f64(self, v: f64) -> Result<()> {
let buf = v.to_bits().to_le_bytes();
self.output
.try_extend(&buf)
.map_err(|_| Error::SerializeBufferFull)
}
#[inline]
fn serialize_char(self, v: char) -> Result<()> {
let mut buf = [0u8; 4];
let strsl = v.encode_utf8(&mut buf);
strsl.serialize(self)
}
#[inline]
fn serialize_str(self, v: &str) -> Result<()> {
self.try_push_varint_usize(v.len())
.map_err(|_| Error::SerializeBufferFull)?;
self.output
.try_extend(v.as_bytes())
.map_err(|_| Error::SerializeBufferFull)?;
Ok(())
}
#[inline]
fn serialize_bytes(self, v: &[u8]) -> Result<()> {
self.try_push_varint_usize(v.len())
.map_err(|_| Error::SerializeBufferFull)?;
self.output
.try_extend(v)
.map_err(|_| Error::SerializeBufferFull)
}
#[inline]
fn serialize_none(self) -> Result<()> {
self.serialize_u8(0)
}
#[inline]
fn serialize_some<T>(self, value: &T) -> Result<()>
where
T: ?Sized + Serialize,
{
self.serialize_u8(1)?;
value.serialize(self)
}
#[inline]
fn serialize_unit(self) -> Result<()> {
Ok(())
}
#[inline]
fn serialize_unit_struct(self, _name: &'static str) -> Result<()> {
Ok(())
}
#[inline]
fn serialize_unit_variant(
self,
_name: &'static str,
variant_index: u32,
_variant: &'static str,
) -> Result<()> {
self.try_push_varint_u32(variant_index)
.map_err(|_| Error::SerializeBufferFull)
}
#[inline]
fn serialize_newtype_struct<T>(self, _name: &'static str, value: &T) -> Result<()>
where
T: ?Sized + Serialize,
{
value.serialize(self)
}
#[inline]
fn serialize_newtype_variant<T>(
self,
_name: &'static str,
variant_index: u32,
_variant: &'static str,
value: &T,
) -> Result<()>
where
T: ?Sized + Serialize,
{
self.try_push_varint_u32(variant_index)
.map_err(|_| Error::SerializeBufferFull)?;
value.serialize(self)
}
#[inline]
fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq> {
self.try_push_varint_usize(len.ok_or(Error::SerializeSeqLengthUnknown)?)
.map_err(|_| Error::SerializeBufferFull)?;
Ok(self)
}
#[inline]
fn serialize_tuple(self, _len: usize) -> Result<Self::SerializeTuple> {
Ok(self)
}
#[inline]
fn serialize_tuple_struct(
self,
_name: &'static str,
_len: usize,
) -> Result<Self::SerializeTupleStruct> {
Ok(self)
}
#[inline]
fn serialize_tuple_variant(
self,
_name: &'static str,
variant_index: u32,
_variant: &'static str,
_len: usize,
) -> Result<Self::SerializeTupleVariant> {
self.try_push_varint_u32(variant_index)
.map_err(|_| Error::SerializeBufferFull)?;
Ok(self)
}
#[inline]
fn serialize_map(self, len: Option<usize>) -> Result<Self::SerializeMap> {
self.try_push_varint_usize(len.ok_or(Error::SerializeSeqLengthUnknown)?)
.map_err(|_| Error::SerializeBufferFull)?;
Ok(self)
}
#[inline]
fn serialize_struct(self, _name: &'static str, _len: usize) -> Result<Self::SerializeStruct> {
Ok(self)
}
#[inline]
fn serialize_struct_variant(
self,
_name: &'static str,
variant_index: u32,
_variant: &'static str,
_len: usize,
) -> Result<Self::SerializeStructVariant> {
self.try_push_varint_u32(variant_index)
.map_err(|_| Error::SerializeBufferFull)?;
Ok(self)
}
#[inline]
fn collect_str<T>(self, value: &T) -> Result<Self::Ok>
where
T: core::fmt::Display + ?Sized,
{
use core::fmt::Write;
// Unfortunately, we need to know the size of the serialized data before
// we can place it into the output. In order to do this, we run the formatting
// of the output data TWICE, the first time to determine the length, the
// second time to actually format the data
//
// There are potentially other ways to do this, such as:
//
// * Reserving a fixed max size, such as 5 bytes, for the length field, and
// leaving non-canonical trailing zeroes at the end. This would work up
// to some reasonable length, but might have some portability vs max size
// tradeoffs, e.g. 64KiB if we pick 3 bytes, or 4GiB if we pick 5 bytes
// * Expose some kind of "memmove" capability to flavors, to allow us to
// format into the buffer, then "scoot over" that many times.
//
// Despite the current approaches downside in speed, it is likely flexible
// enough for the rare-ish case where formatting a Debug impl is necessary.
// This is better than the previous panicking behavior, and can be improved
// in the future.
struct CountWriter {
ct: usize,
}
impl Write for CountWriter {
fn write_str(&mut self, s: &str) -> core::result::Result<(), core::fmt::Error> {
self.ct += s.len();
Ok(())
}
}
let mut ctr = CountWriter { ct: 0 };
// This is the first pass through, where we just count the length of the
// data that we are given
write!(&mut ctr, "{value}").map_err(|_| Error::CollectStrError)?;
let len = ctr.ct;
self.try_push_varint_usize(len)
.map_err(|_| Error::SerializeBufferFull)?;
struct FmtWriter<'a, IF>
where
IF: Flavor,
{
output: &'a mut IF,
}
impl<IF> Write for FmtWriter<'_, IF>
where
IF: Flavor,
{
fn write_str(&mut self, s: &str) -> core::result::Result<(), core::fmt::Error> {
self.output
.try_extend(s.as_bytes())
.map_err(|_| core::fmt::Error)
}
}
// This second pass actually inserts the data.
let mut fw = FmtWriter {
output: &mut self.output,
};
write!(&mut fw, "{value}").map_err(|_| Error::CollectStrError)?;
Ok(())
}
}
impl<F> ser::SerializeSeq for &mut Serializer<F>
where
F: Flavor,
{
// Must match the `Ok` type of the serializer.
type Ok = ();
// Must match the `Error` type of the serializer.
type Error = Error;
// Serialize a single element of the sequence.
#[inline]
fn serialize_element<T>(&mut self, value: &T) -> Result<()>
where
T: ?Sized + Serialize,
{
value.serialize(&mut **self)
}
// Close the sequence.
#[inline]
fn end(self) -> Result<()> {
Ok(())
}
}
impl<F> ser::SerializeTuple for &mut Serializer<F>
where
F: Flavor,
{
type Ok = ();
type Error = Error;
#[inline]
fn serialize_element<T>(&mut self, value: &T) -> Result<()>
where
T: ?Sized + Serialize,
{
value.serialize(&mut **self)
}
#[inline]
fn end(self) -> Result<()> {
Ok(())
}
}
impl<F> ser::SerializeTupleStruct for &mut Serializer<F>
where
F: Flavor,
{
type Ok = ();
type Error = Error;
#[inline]
fn serialize_field<T>(&mut self, value: &T) -> Result<()>
where
T: ?Sized + Serialize,
{
value.serialize(&mut **self)
}
#[inline]
fn end(self) -> Result<()> {
Ok(())
}
}
impl<F> ser::SerializeTupleVariant for &mut Serializer<F>
where
F: Flavor,
{
type Ok = ();
type Error = Error;
#[inline]
fn serialize_field<T>(&mut self, value: &T) -> Result<()>
where
T: ?Sized + Serialize,
{
value.serialize(&mut **self)
}
#[inline]
fn end(self) -> Result<()> {
Ok(())
}
}
impl<F> ser::SerializeMap for &mut Serializer<F>
where
F: Flavor,
{
type Ok = ();
type Error = Error;
#[inline]
fn serialize_key<T>(&mut self, key: &T) -> Result<()>
where
T: ?Sized + Serialize,
{
key.serialize(&mut **self)
}
#[inline]
fn serialize_value<T>(&mut self, value: &T) -> Result<()>
where
T: ?Sized + Serialize,
{
value.serialize(&mut **self)
}
#[inline]
fn end(self) -> Result<()> {
Ok(())
}
}
impl<F> ser::SerializeStruct for &mut Serializer<F>
where
F: Flavor,
{
type Ok = ();
type Error = Error;
#[inline]
fn serialize_field<T>(&mut self, _key: &'static str, value: &T) -> Result<()>
where
T: ?Sized + Serialize,
{
value.serialize(&mut **self)
}
#[inline]
fn end(self) -> Result<()> {
Ok(())
}
}
impl<F> ser::SerializeStructVariant for &mut Serializer<F>
where
F: Flavor,
{
type Ok = ();
type Error = Error;
#[inline]
fn serialize_field<T>(&mut self, _key: &'static str, value: &T) -> Result<()>
where
T: ?Sized + Serialize,
{
value.serialize(&mut **self)
}
#[inline]
fn end(self) -> Result<()> {
Ok(())
}
}
fn zig_zag_i16(n: i16) -> u16 {
((n << 1) ^ (n >> 15)) as u16
}
fn zig_zag_i32(n: i32) -> u32 {
((n << 1) ^ (n >> 31)) as u32
}
fn zig_zag_i64(n: i64) -> u64 {
((n << 1) ^ (n >> 63)) as u64
}
fn zig_zag_i128(n: i128) -> u128 {
((n << 1) ^ (n >> 127)) as u128
}
+103
View File
@@ -0,0 +1,103 @@
/// Returns the maximum number of bytes required to encode T.
pub const fn varint_max<T: Sized>() -> usize {
const BITS_PER_BYTE: usize = 8;
const BITS_PER_VARINT_BYTE: usize = 7;
// How many data bits do we need for this type?
let bits = core::mem::size_of::<T>() * BITS_PER_BYTE;
// We add (BITS_PER_VARINT_BYTE - 1), to ensure any integer divisions
// with a remainder will always add exactly one full byte, but
// an evenly divided number of bits will be the same
let roundup_bits = bits + (BITS_PER_VARINT_BYTE - 1);
// Apply division, using normal "round down" integer division
roundup_bits / BITS_PER_VARINT_BYTE
}
/// Returns the maximum value stored in the last encoded byte.
pub const fn max_of_last_byte<T: Sized>() -> u8 {
let max_bits = core::mem::size_of::<T>() * 8;
let extra_bits = max_bits % 7;
(1 << extra_bits) - 1
}
#[inline]
pub fn varint_usize(n: usize, out: &mut [u8; varint_max::<usize>()]) -> &mut [u8] {
let mut value = n;
for i in 0..varint_max::<usize>() {
out[i] = value.to_le_bytes()[0];
if value < 128 {
return &mut out[..=i];
}
out[i] |= 0x80;
value >>= 7;
}
debug_assert_eq!(value, 0);
&mut out[..]
}
#[inline]
pub fn varint_u16(n: u16, out: &mut [u8; varint_max::<u16>()]) -> &mut [u8] {
let mut value = n;
for i in 0..varint_max::<u16>() {
out[i] = value.to_le_bytes()[0];
if value < 128 {
return &mut out[..=i];
}
out[i] |= 0x80;
value >>= 7;
}
debug_assert_eq!(value, 0);
&mut out[..]
}
#[inline]
pub fn varint_u32(n: u32, out: &mut [u8; varint_max::<u32>()]) -> &mut [u8] {
let mut value = n;
for i in 0..varint_max::<u32>() {
out[i] = value.to_le_bytes()[0];
if value < 128 {
return &mut out[..=i];
}
out[i] |= 0x80;
value >>= 7;
}
debug_assert_eq!(value, 0);
&mut out[..]
}
#[inline]
pub fn varint_u64(n: u64, out: &mut [u8; varint_max::<u64>()]) -> &mut [u8] {
let mut value = n;
for i in 0..varint_max::<u64>() {
out[i] = value.to_le_bytes()[0];
if value < 128 {
return &mut out[..=i];
}
out[i] |= 0x80;
value >>= 7;
}
debug_assert_eq!(value, 0);
&mut out[..]
}
#[inline]
pub fn varint_u128(n: u128, out: &mut [u8; varint_max::<u128>()]) -> &mut [u8] {
let mut value = n;
for i in 0..varint_max::<u128>() {
out[i] = value.to_le_bytes()[0];
if value < 128 {
return &mut out[..=i];
}
out[i] |= 0x80;
value >>= 7;
}
debug_assert_eq!(value, 0);
&mut out[..]
}
+59
View File
@@ -0,0 +1,59 @@
use postcard::accumulator::{CobsAccumulator, FeedResult};
use serde::{Deserialize, Serialize};
use std::io::Read;
// Read a "huge" serialized struct in 32 byte chunks into a 256 byte buffer and deserialize it.
#[test]
fn reader() {
let mut raw_buf = [0u8; 32];
let mut input_buf = [0u8; 256];
let mut cobs_buf: CobsAccumulator<256> = CobsAccumulator::new();
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
struct Huge {
a: u32,
b: [u32; 32],
d: u64,
}
let expected = Huge {
a: 0xabcdef00,
b: [0x01234567; 32],
d: u64::MAX,
};
let input = postcard::to_slice_cobs(&expected, &mut input_buf).unwrap();
// TODO(https://github.com/rust-lang/rust-clippy/issues/12751): Remove once fixed.
#[allow(clippy::redundant_slicing)]
let mut input = &input[..];
// Magic number from serializing struct and printing length
assert_eq!(input.len(), 145);
let mut output = None;
while let Ok(ct) = input.read(&mut raw_buf) {
// Finished reading input
if ct == 0 {
break;
}
let buf = &raw_buf[..ct];
let mut window = buf;
'cobs: while !window.is_empty() {
window = match cobs_buf.feed::<Huge>(window) {
FeedResult::Consumed => break 'cobs,
FeedResult::OverFull(new_wind) => new_wind,
FeedResult::DeserError(new_wind) => new_wind,
FeedResult::Success { data, remaining } => {
output = Some(data);
remaining
}
};
}
}
assert_eq!(output.unwrap(), expected);
}
+85
View File
@@ -0,0 +1,85 @@
#[test]
#[cfg(feature = "use-crc")]
fn test_crc() {
use crc::{Crc, CRC_32_ISCSI};
let data: &[u8] = &[0x01, 0x00, 0x20, 0x30];
let buffer = &mut [0u8; 32];
let crc = Crc::<u32>::new(&CRC_32_ISCSI);
let digest = crc.digest();
let res = postcard::to_slice_crc32(data, buffer, digest).unwrap();
assert_eq!(res, &[0x04, 0x01, 0x00, 0x20, 0x30, 0x8E, 0xC8, 0x1A, 0x37]);
let digest = crc.digest();
let res = postcard::take_from_bytes_crc32::<[u8; 5]>(res, digest).unwrap();
let expected_bytes = [0x04, 0x01, 0x00, 0x20, 0x30];
let remaining_bytes = [];
assert_eq!(res, (expected_bytes, remaining_bytes.as_slice()));
}
#[test]
#[cfg(feature = "use-crc")]
fn test_crc_8() {
use crc::{Crc, CRC_8_SMBUS};
let data: &[u8] = &[0x01, 0x00, 0x20, 0x30];
let buffer = &mut [0u8; 32];
let crc = Crc::<u8>::new(&CRC_8_SMBUS);
let digest = crc.digest();
let res = postcard::ser_flavors::crc::to_slice_u8(data, buffer, digest).unwrap();
assert_eq!(res, &[0x04, 0x01, 0x00, 0x20, 0x30, 167]);
let digest = crc.digest();
let res = postcard::de_flavors::crc::take_from_bytes_u8::<[u8; 5]>(res, digest).unwrap();
let expected_bytes = [0x04, 0x01, 0x00, 0x20, 0x30];
let remaining_bytes = [];
assert_eq!(res, (expected_bytes, remaining_bytes.as_slice()));
}
#[test]
#[cfg(feature = "use-crc")]
fn test_crc_error() {
use crc::{Crc, CRC_32_ISCSI};
let data: &[u8] = &[0x01, 0x00, 0x20, 0x30];
let buffer = &mut [0u8; 32];
let crc = Crc::<u32>::new(&CRC_32_ISCSI);
let digest = crc.digest();
let res = postcard::to_slice_crc32(data, buffer, digest).unwrap();
// intentionally corrupt the crc
let last = res.len() - 1;
res[last] = 0;
let digest = crc.digest();
let res = postcard::take_from_bytes_crc32::<[u8; 5]>(res, digest);
assert_eq!(res, Err(postcard::Error::DeserializeBadCrc));
}
#[test]
#[cfg(feature = "use-crc")]
fn test_crc_in_method() {
use crc::{Crc, CRC_32_ISCSI};
use postcard::{to_slice_crc32, Result};
use serde::Serialize;
#[derive(Debug, Serialize)]
pub struct Thing {
value: u32,
}
impl Thing {
pub fn to_bytes<'a>(&self, buf: &'a mut [u8]) -> Result<&'a mut [u8]> {
let crc = Crc::<u32>::new(&CRC_32_ISCSI);
to_slice_crc32(self, buf, crc.digest())
}
}
let buffer = &mut [0u8; 5];
let thing = Thing { value: 42 };
let slice = thing.to_bytes(buffer).unwrap();
assert_eq!(slice, &[0x2A, 0xB7, 0xF5, 0x22, 0x19]);
}
+251
View File
@@ -0,0 +1,251 @@
use core::fmt::Debug;
use core::fmt::Write;
use core::ops::Deref;
#[cfg(feature = "heapless")]
use heapless::{FnvIndexMap, String, Vec};
#[cfg(feature = "heapless")]
use postcard::to_vec;
use postcard::from_bytes;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
struct BasicU8S {
st: u16,
ei: u8,
sf: u64,
tt: u32,
}
#[allow(dead_code)]
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
enum BasicEnum {
Bib,
Bim,
Bap,
}
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
struct EnumStruct {
eight: u8,
sixt: u16,
}
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
enum DataEnum {
Bib(u16),
Bim(u64),
Bap(u8),
Kim(EnumStruct),
Chi { a: u8, b: u32 },
Sho(u16, u8),
}
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
struct NewTypeStruct(u32);
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
struct TupleStruct((u8, u16));
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
struct RefStruct<'a> {
bytes: &'a [u8],
str_s: &'a str,
}
#[cfg(feature = "heapless")]
#[test]
fn loopback() {
// Basic types
test_one((), &[]);
test_one(false, &[0x00]);
test_one(true, &[0x01]);
test_one(5u8, &[0x05]);
test_one(0xA5C7u16, &[0xC7, 0xCB, 0x02]);
test_one(0xCDAB3412u32, &[0x92, 0xE8, 0xAC, 0xED, 0x0C]);
test_one(
0x1234_5678_90AB_CDEFu64,
&[0xEF, 0x9B, 0xAF, 0x85, 0x89, 0xCF, 0x95, 0x9A, 0x12],
);
// https://github.com/jamesmunns/postcard/pull/83
test_one(32767i16, &[0xFE, 0xFF, 0x03]);
test_one(-32768i16, &[0xFF, 0xFF, 0x03]);
// chars
test_one('z', &[0x01, 0x7a]);
test_one('¢', &[0x02, 0xc2, 0xa2]);
test_one('𐍈', &[0x04, 0xF0, 0x90, 0x8D, 0x88]);
test_one('🥺', &[0x04, 0xF0, 0x9F, 0xA5, 0xBA]);
// Structs
test_one(
BasicU8S {
st: 0xABCD,
ei: 0xFE,
sf: 0x1234_4321_ABCD_DCBA,
tt: 0xACAC_ACAC,
},
&[
0xCD, 0xD7, 0x02, 0xFE, 0xBA, 0xB9, 0xB7, 0xDE, 0x9A, 0xE4, 0x90, 0x9A, 0x12, 0xAC,
0xD9, 0xB2, 0xE5, 0x0A,
],
);
// Enums!
test_one(BasicEnum::Bim, &[0x01]);
test_one(
DataEnum::Bim(u64::MAX),
&[
0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01,
],
);
test_one(DataEnum::Bib(u16::MAX), &[0x00, 0xFF, 0xFF, 0x03]);
test_one(DataEnum::Bap(u8::MAX), &[0x02, 0xFF]);
test_one(
DataEnum::Kim(EnumStruct {
eight: 0xF0,
sixt: 0xACAC,
}),
&[0x03, 0xF0, 0xAC, 0xD9, 0x02],
);
test_one(
DataEnum::Chi {
a: 0x0F,
b: 0xC7C7C7C7,
},
&[0x04, 0x0F, 0xC7, 0x8F, 0x9F, 0xBE, 0x0C],
);
test_one(DataEnum::Sho(0x6969, 0x07), &[0x05, 0xE9, 0xD2, 0x01, 0x07]);
// Tuples!
test_one((0x12u8, 0xC7A5u16), &[0x12, 0xA5, 0x8F, 0x03]);
// Structs!
test_one(NewTypeStruct(5), &[0x05]);
test_one(TupleStruct((0xA0, 0x1234)), &[0xA0, 0xB4, 0x24]);
let mut input: Vec<u8, 4> = Vec::new();
input.extend_from_slice(&[0x01, 0x02, 0x03, 0x04]).unwrap();
test_one(input, &[0x04, 0x01, 0x02, 0x03, 0x04]);
let mut input: String<8> = String::new();
write!(&mut input, "helLO!").unwrap();
test_one(input, &[0x06, b'h', b'e', b'l', b'L', b'O', b'!']);
let mut input: FnvIndexMap<u8, u8, 4> = FnvIndexMap::new();
input.insert(0x01, 0x05).unwrap();
input.insert(0x02, 0x06).unwrap();
input.insert(0x03, 0x07).unwrap();
input.insert(0x04, 0x08).unwrap();
test_one(
input,
&[0x04, 0x01, 0x05, 0x02, 0x06, 0x03, 0x07, 0x04, 0x08],
);
// `CString` (uses `serialize_bytes`/`deserialize_byte_buf`)
#[cfg(feature = "use-std")]
test_one(
std::ffi::CString::new("heLlo").unwrap(),
&[0x05, b'h', b'e', b'L', b'l', b'o'],
);
}
#[cfg(feature = "heapless")]
#[track_caller]
fn test_one<T>(data: T, ser_rep: &[u8])
where
T: Serialize + DeserializeOwned + Eq + PartialEq + Debug,
{
let serialized: Vec<u8, 2048> = to_vec(&data).unwrap();
assert_eq!(serialized.len(), ser_rep.len());
let mut x: ::std::vec::Vec<u8> = vec![];
x.extend(serialized.deref().iter().cloned());
// let bysl: &'de [u8] = serialized.deref();
assert_eq!(x, ser_rep);
{
// let deserialized: T = from_bytes(serialized.deref()).unwrap();
let deserialized: T = from_bytes(&x).unwrap();
assert_eq!(data, deserialized);
}
}
#[cfg(feature = "use-std")]
#[test]
fn std_io_loopback() {
use postcard::from_io;
use postcard::to_io;
fn test_io<T>(data: T, ser_rep: &[u8])
where
T: Serialize + DeserializeOwned + Eq + PartialEq + Debug,
{
let serialized: ::std::vec::Vec<u8> = vec![];
let ser = to_io(&data, serialized).unwrap();
assert_eq!(ser.len(), ser_rep.len());
assert_eq!(ser, ser_rep);
{
let mut buff = [0; 2048];
let x = ser.clone();
let deserialized: T = from_io((x.as_slice(), &mut buff)).unwrap().0;
assert_eq!(data, deserialized);
}
}
test_io(DataEnum::Sho(0x6969, 0x07), &[0x05, 0xE9, 0xD2, 0x01, 0x07]);
test_io(
BasicU8S {
st: 0xABCD,
ei: 0xFE,
sf: 0x1234_4321_ABCD_DCBA,
tt: 0xACAC_ACAC,
},
&[
0xCD, 0xD7, 0x02, 0xFE, 0xBA, 0xB9, 0xB7, 0xDE, 0x9A, 0xE4, 0x90, 0x9A, 0x12, 0xAC,
0xD9, 0xB2, 0xE5, 0x0A,
],
);
}
#[cfg(all(
any(feature = "embedded-io-04", feature = "embedded-io-06"),
feature = "alloc"
))]
#[test]
fn std_eio_loopback() {
use postcard::from_eio;
use postcard::to_eio;
fn test_io<T>(data: T, ser_rep: &[u8])
where
T: Serialize + DeserializeOwned + Eq + PartialEq + Debug,
{
let serialized: ::std::vec::Vec<u8> = vec![];
let ser = to_eio(&data, serialized).unwrap();
assert_eq!(ser.len(), ser_rep.len());
assert_eq!(ser, ser_rep);
{
let mut buff = [0; 2048];
let x = ser.clone();
let deserialized: T = from_eio((x.as_slice(), &mut buff)).unwrap().0;
assert_eq!(data, deserialized);
}
}
test_io(DataEnum::Sho(0x6969, 0x07), &[0x05, 0xE9, 0xD2, 0x01, 0x07]);
test_io(
BasicU8S {
st: 0xABCD,
ei: 0xFE,
sf: 0x1234_4321_ABCD_DCBA,
tt: 0xACAC_ACAC,
},
&[
0xCD, 0xD7, 0x02, 0xFE, 0xBA, 0xB9, 0xB7, 0xDE, 0x9A, 0xE4, 0x90, 0x9A, 0x12, 0xAC,
0xD9, 0xB2, 0xE5, 0x0A,
],
);
}
+101
View File
@@ -0,0 +1,101 @@
#![allow(unused_imports)]
#[cfg(feature = "experimental-derive")]
mod tests {
use postcard::experimental::max_size::MaxSize;
use postcard::to_slice;
use serde::Serialize;
#[test]
fn test_struct_max_size() {
#[derive(MaxSize)]
struct Foo {
_a: u16,
_b: Option<u8>,
}
assert_eq!(Foo::POSTCARD_MAX_SIZE, 5);
}
#[test]
fn test_enum_max_size() {
#[allow(dead_code)]
#[derive(MaxSize, Serialize)]
enum Bar {
A(u16),
B(u8),
}
assert_eq!(Bar::POSTCARD_MAX_SIZE, 4);
let mut buf = [0u8; 128];
let used = to_slice(&Bar::A(0xFFFF), &mut buf).unwrap();
assert!(
used.len() <= Bar::POSTCARD_MAX_SIZE,
"FAIL {} > {}",
used.len(),
Bar::POSTCARD_MAX_SIZE
);
#[derive(MaxSize)]
enum Baz {}
assert_eq!(Baz::POSTCARD_MAX_SIZE, 0);
}
#[test]
fn test_ref() {
#[allow(dead_code)]
#[derive(MaxSize)]
struct Foo {
a: &'static u32,
}
}
#[cfg(feature = "heapless")]
#[test]
fn test_vec_edge_cases() {
#[track_caller]
fn test_equals<const N: usize>(buf: &mut [u8]) {
let mut v = heapless::Vec::<u8, N>::new();
for _ in 0..N {
v.push(0).unwrap();
}
let serialized = postcard::to_slice(&v, buf).unwrap();
assert_eq!(heapless::Vec::<u8, N>::POSTCARD_MAX_SIZE, serialized.len());
}
let mut buf = [0; 16400];
test_equals::<1>(&mut buf);
test_equals::<2>(&mut buf);
test_equals::<127>(&mut buf);
test_equals::<128>(&mut buf);
test_equals::<129>(&mut buf);
test_equals::<16383>(&mut buf);
test_equals::<16384>(&mut buf);
test_equals::<16385>(&mut buf);
}
// #[cfg(feature = "experimental-derive")]
// #[test]
// fn test_union_max_size() {
// #[derive(postcard::MaxSize)]
// union Foo {
// a: u16,
// b: Option<u8>,
// }
// }
// #[cfg(feature = "experimental-derive")]
// #[test]
// fn test_not_implemented() {
// #[derive(postcard::MaxSize)]
// struct Foo {
// a: &'static str,
// }
// }
}