| { | ||
| "git": { | ||
| "sha1": "9652c787730e58515ce7b44fcafd2430ab424628" | ||
| "sha1": "5d70ba7576f9aafcbf02bd8acfcb9973411fb95f" | ||
| }, | ||
| "path_in_vcs": "" | ||
| } |
@@ -23,3 +23,3 @@ version: '2.1' | ||
| '__msrv__', # won't add any other toolchains, just uses what's in the docker image | ||
| '1.65.0', # minimum needed to build dev-dependencies | ||
| '1.70.0', # minimum needed to build dev-dependencies | ||
| 'stable', | ||
@@ -26,0 +26,0 @@ 'beta', |
@@ -105,5 +105,4 @@ #[macro_use] | ||
| fill(&mut v); | ||
| let mut buf = Vec::new(); | ||
| let mut buf = Vec::with_capacity(size * 2); | ||
| buf.reserve(size * 2); | ||
| b.iter(|| { | ||
@@ -110,0 +109,0 @@ buf.clear(); |
+168
-147
| # This file is automatically @generated by Cargo. | ||
| # It is not intended for manual editing. | ||
| version = 3 | ||
| [[package]] | ||
| name = "aho-corasick" | ||
| version = "1.1.2" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" | ||
| dependencies = [ | ||
| "memchr", | ||
| ] | ||
| [[package]] | ||
| name = "anes" | ||
@@ -43,9 +32,9 @@ version = "0.1.6" | ||
| name = "async-channel" | ||
| version = "2.1.1" | ||
| version = "2.2.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "1ca33f4bc4ed1babef42cad36cc1f51fa88be00420404e5b1e80ab1b18f7678c" | ||
| checksum = "f28243a43d821d11341ab73c80bed182dc015c514b951616cf79bd4af39af0c3" | ||
| dependencies = [ | ||
| "concurrent-queue", | ||
| "event-listener 4.0.3", | ||
| "event-listener-strategy", | ||
| "event-listener 5.2.0", | ||
| "event-listener-strategy 0.5.0", | ||
| "futures-core", | ||
@@ -75,5 +64,5 @@ "pin-project-lite", | ||
| dependencies = [ | ||
| "async-channel 2.1.1", | ||
| "async-channel 2.2.0", | ||
| "async-executor", | ||
| "async-io 2.2.2", | ||
| "async-io 2.3.1", | ||
| "async-lock 3.3.0", | ||
@@ -107,5 +96,5 @@ "blocking", | ||
| name = "async-io" | ||
| version = "2.2.2" | ||
| version = "2.3.1" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "6afaa937395a620e33dc6a742c593c01aced20aa376ffb0f628121198578ccc7" | ||
| checksum = "8f97ab0c5b00a7cdbe5a371b9a782ee7be1316095885c8a4ea1daf490eb0ef65" | ||
| dependencies = [ | ||
@@ -118,4 +107,4 @@ "async-lock 3.3.0", | ||
| "parking", | ||
| "polling 3.3.1", | ||
| "rustix 0.38.28", | ||
| "polling 3.4.0", | ||
| "rustix 0.38.9", | ||
| "slab", | ||
@@ -142,3 +131,3 @@ "tracing", | ||
| "event-listener 4.0.3", | ||
| "event-listener-strategy", | ||
| "event-listener-strategy 0.4.0", | ||
| "pin-project-lite", | ||
@@ -205,3 +194,3 @@ ] | ||
| name = "base64" | ||
| version = "0.21.7" | ||
| version = "0.22.0" | ||
| dependencies = [ | ||
@@ -225,5 +214,5 @@ "clap", | ||
| name = "bitflags" | ||
| version = "2.4.1" | ||
| version = "2.4.2" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" | ||
| checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" | ||
@@ -236,3 +225,3 @@ [[package]] | ||
| dependencies = [ | ||
| "async-channel 2.1.1", | ||
| "async-channel 2.2.0", | ||
| "async-lock 3.3.0", | ||
@@ -249,5 +238,5 @@ "async-task", | ||
| name = "bumpalo" | ||
| version = "3.14.0" | ||
| version = "3.15.3" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" | ||
| checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b" | ||
@@ -268,5 +257,5 @@ [[package]] | ||
| name = "ciborium" | ||
| version = "0.2.1" | ||
| version = "0.2.2" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" | ||
| checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" | ||
| dependencies = [ | ||
@@ -280,11 +269,11 @@ "ciborium-io", | ||
| name = "ciborium-io" | ||
| version = "0.2.1" | ||
| version = "0.2.2" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" | ||
| checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" | ||
| [[package]] | ||
| name = "ciborium-ll" | ||
| version = "0.2.1" | ||
| version = "0.2.2" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" | ||
| checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" | ||
| dependencies = [ | ||
@@ -405,6 +394,22 @@ "ciborium-io", | ||
| [[package]] | ||
| name = "crunchy" | ||
| version = "0.2.2" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" | ||
| [[package]] | ||
| name = "ctor" | ||
| version = "0.1.26" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" | ||
| dependencies = [ | ||
| "quote", | ||
| "syn 1.0.109", | ||
| ] | ||
| [[package]] | ||
| name = "either" | ||
| version = "1.9.0" | ||
| version = "1.10.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" | ||
| checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" | ||
@@ -439,2 +444,13 @@ [[package]] | ||
| [[package]] | ||
| name = "event-listener" | ||
| version = "5.2.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "2b5fb89194fa3cad959b833185b3063ba881dbfc7030680b314250779fb4cc91" | ||
| dependencies = [ | ||
| "concurrent-queue", | ||
| "parking", | ||
| "pin-project-lite", | ||
| ] | ||
| [[package]] | ||
| name = "event-listener-strategy" | ||
@@ -450,2 +466,12 @@ version = "0.4.0" | ||
| [[package]] | ||
| name = "event-listener-strategy" | ||
| version = "0.5.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "feedafcaa9b749175d5ac357452a9d41ea2911da598fde46ce1fe02c37751291" | ||
| dependencies = [ | ||
| "event-listener 5.2.0", | ||
| "pin-project-lite", | ||
| ] | ||
| [[package]] | ||
| name = "fastrand" | ||
@@ -549,3 +575,3 @@ version = "1.9.0" | ||
| "quote", | ||
| "syn 2.0.48", | ||
| "syn 2.0.52", | ||
| ] | ||
@@ -567,5 +593,5 @@ | ||
| name = "futures-timer" | ||
| version = "3.0.2" | ||
| version = "3.0.3" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" | ||
| checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" | ||
@@ -615,5 +641,9 @@ [[package]] | ||
| name = "half" | ||
| version = "1.8.2" | ||
| version = "2.4.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" | ||
| checksum = "b5eceaaeec696539ddaf7b333340f1af35a5aa87ae3e4f3ead0532f72affab2e" | ||
| dependencies = [ | ||
| "cfg-if", | ||
| "crunchy", | ||
| ] | ||
@@ -643,5 +673,5 @@ [[package]] | ||
| name = "hermit-abi" | ||
| version = "0.3.3" | ||
| version = "0.3.9" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" | ||
| checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" | ||
@@ -673,3 +703,3 @@ [[package]] | ||
| dependencies = [ | ||
| "hermit-abi 0.3.3", | ||
| "hermit-abi 0.3.9", | ||
| "libc", | ||
@@ -696,5 +726,5 @@ "windows-sys 0.48.0", | ||
| name = "js-sys" | ||
| version = "0.3.66" | ||
| version = "0.3.68" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" | ||
| checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" | ||
| dependencies = [ | ||
@@ -721,5 +751,5 @@ "wasm-bindgen", | ||
| name = "libc" | ||
| version = "0.2.152" | ||
| version = "0.2.153" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" | ||
| checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" | ||
@@ -734,12 +764,13 @@ [[package]] | ||
| name = "linux-raw-sys" | ||
| version = "0.4.12" | ||
| version = "0.4.13" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" | ||
| checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" | ||
| [[package]] | ||
| name = "log" | ||
| version = "0.4.20" | ||
| version = "0.4.17" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" | ||
| checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" | ||
| dependencies = [ | ||
| "cfg-if", | ||
| "value-bag", | ||
@@ -756,5 +787,5 @@ ] | ||
| name = "num-traits" | ||
| version = "0.2.17" | ||
| version = "0.2.18" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" | ||
| checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" | ||
| dependencies = [ | ||
@@ -857,5 +888,5 @@ "autocfg", | ||
| name = "polling" | ||
| version = "3.3.1" | ||
| version = "3.4.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "cf63fa624ab313c11656b4cda960bfc46c410187ad493c41f6ba2d8c1e991c9e" | ||
| checksum = "30054e72317ab98eddd8561db0f6524df3367636884b7b21b703e4b280a84a14" | ||
| dependencies = [ | ||
@@ -865,3 +896,3 @@ "cfg-if", | ||
| "pin-project-lite", | ||
| "rustix 0.38.28", | ||
| "rustix 0.38.9", | ||
| "tracing", | ||
@@ -903,5 +934,5 @@ "windows-sys 0.52.0", | ||
| name = "proc-macro2" | ||
| version = "1.0.76" | ||
| version = "1.0.78" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" | ||
| checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" | ||
| dependencies = [ | ||
@@ -952,5 +983,5 @@ "unicode-ident", | ||
| name = "rayon" | ||
| version = "1.8.0" | ||
| version = "1.9.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" | ||
| checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd" | ||
| dependencies = [ | ||
@@ -963,5 +994,5 @@ "either", | ||
| name = "rayon-core" | ||
| version = "1.12.0" | ||
| version = "1.12.1" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" | ||
| checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" | ||
| dependencies = [ | ||
@@ -974,9 +1005,6 @@ "crossbeam-deque", | ||
| name = "regex" | ||
| version = "1.10.2" | ||
| version = "1.8.4" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" | ||
| checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" | ||
| dependencies = [ | ||
| "aho-corasick", | ||
| "memchr", | ||
| "regex-automata", | ||
| "regex-syntax", | ||
@@ -986,17 +1014,6 @@ ] | ||
| [[package]] | ||
| name = "regex-automata" | ||
| version = "0.4.3" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" | ||
| dependencies = [ | ||
| "aho-corasick", | ||
| "memchr", | ||
| "regex-syntax", | ||
| ] | ||
| [[package]] | ||
| name = "regex-syntax" | ||
| version = "0.8.2" | ||
| version = "0.7.5" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" | ||
| checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" | ||
@@ -1038,3 +1055,3 @@ [[package]] | ||
| "rustc_version", | ||
| "syn 2.0.48", | ||
| "syn 2.0.52", | ||
| ] | ||
@@ -1067,11 +1084,11 @@ | ||
| name = "rustix" | ||
| version = "0.38.28" | ||
| version = "0.38.9" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" | ||
| checksum = "9bfe0f2582b4931a45d1fa608f8a8722e8b3c7ac54dd6d5f3b3212791fedef49" | ||
| dependencies = [ | ||
| "bitflags 2.4.1", | ||
| "bitflags 2.4.2", | ||
| "errno", | ||
| "libc", | ||
| "linux-raw-sys 0.4.12", | ||
| "windows-sys 0.52.0", | ||
| "linux-raw-sys 0.4.13", | ||
| "windows-sys 0.48.0", | ||
| ] | ||
@@ -1087,5 +1104,5 @@ | ||
| name = "ryu" | ||
| version = "1.0.16" | ||
| version = "1.0.17" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" | ||
| checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" | ||
@@ -1103,11 +1120,11 @@ [[package]] | ||
| name = "semver" | ||
| version = "1.0.21" | ||
| version = "1.0.22" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" | ||
| checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" | ||
| [[package]] | ||
| name = "serde" | ||
| version = "1.0.195" | ||
| version = "1.0.197" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" | ||
| checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" | ||
| dependencies = [ | ||
@@ -1119,9 +1136,9 @@ "serde_derive", | ||
| name = "serde_derive" | ||
| version = "1.0.195" | ||
| version = "1.0.197" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" | ||
| checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" | ||
| dependencies = [ | ||
| "proc-macro2", | ||
| "quote", | ||
| "syn 2.0.48", | ||
| "syn 2.0.52", | ||
| ] | ||
@@ -1131,5 +1148,5 @@ | ||
| name = "serde_json" | ||
| version = "1.0.111" | ||
| version = "1.0.114" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" | ||
| checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" | ||
| dependencies = [ | ||
@@ -1185,3 +1202,3 @@ "itoa", | ||
| "rustversion", | ||
| "syn 2.0.48", | ||
| "syn 2.0.52", | ||
| ] | ||
@@ -1202,5 +1219,5 @@ | ||
| name = "syn" | ||
| version = "2.0.48" | ||
| version = "2.0.52" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" | ||
| checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" | ||
| dependencies = [ | ||
@@ -1223,5 +1240,5 @@ "proc-macro2", | ||
| name = "textwrap" | ||
| version = "0.16.0" | ||
| version = "0.16.1" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" | ||
| checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" | ||
@@ -1262,5 +1279,9 @@ [[package]] | ||
| name = "value-bag" | ||
| version = "1.6.0" | ||
| version = "1.0.0-alpha.9" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "7cdbaf5e132e593e9fc1de6a15bbec912395b11fb9719e061cf64f804524c503" | ||
| checksum = "2209b78d1249f7e6f3293657c9779fe31ced465df091bbd433a1cf88e916ec55" | ||
| dependencies = [ | ||
| "ctor", | ||
| "version_check", | ||
| ] | ||
@@ -1281,5 +1302,5 @@ [[package]] | ||
| name = "walkdir" | ||
| version = "2.4.0" | ||
| version = "2.5.0" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" | ||
| checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" | ||
| dependencies = [ | ||
@@ -1298,5 +1319,5 @@ "same-file", | ||
| name = "wasm-bindgen" | ||
| version = "0.2.89" | ||
| version = "0.2.91" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" | ||
| checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" | ||
| dependencies = [ | ||
@@ -1309,5 +1330,5 @@ "cfg-if", | ||
| name = "wasm-bindgen-backend" | ||
| version = "0.2.89" | ||
| version = "0.2.91" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" | ||
| checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" | ||
| dependencies = [ | ||
@@ -1319,3 +1340,3 @@ "bumpalo", | ||
| "quote", | ||
| "syn 2.0.48", | ||
| "syn 2.0.52", | ||
| "wasm-bindgen-shared", | ||
@@ -1326,5 +1347,5 @@ ] | ||
| name = "wasm-bindgen-futures" | ||
| version = "0.4.39" | ||
| version = "0.4.41" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12" | ||
| checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97" | ||
| dependencies = [ | ||
@@ -1339,5 +1360,5 @@ "cfg-if", | ||
| name = "wasm-bindgen-macro" | ||
| version = "0.2.89" | ||
| version = "0.2.91" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" | ||
| checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" | ||
| dependencies = [ | ||
@@ -1350,9 +1371,9 @@ "quote", | ||
| name = "wasm-bindgen-macro-support" | ||
| version = "0.2.89" | ||
| version = "0.2.91" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" | ||
| checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" | ||
| dependencies = [ | ||
| "proc-macro2", | ||
| "quote", | ||
| "syn 2.0.48", | ||
| "syn 2.0.52", | ||
| "wasm-bindgen-backend", | ||
@@ -1364,11 +1385,11 @@ "wasm-bindgen-shared", | ||
| name = "wasm-bindgen-shared" | ||
| version = "0.2.89" | ||
| version = "0.2.91" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" | ||
| checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" | ||
| [[package]] | ||
| name = "web-sys" | ||
| version = "0.3.66" | ||
| version = "0.3.68" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" | ||
| checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" | ||
| dependencies = [ | ||
@@ -1425,3 +1446,3 @@ "js-sys", | ||
| dependencies = [ | ||
| "windows-targets 0.52.0", | ||
| "windows-targets 0.52.4", | ||
| ] | ||
@@ -1446,13 +1467,13 @@ | ||
| name = "windows-targets" | ||
| version = "0.52.0" | ||
| version = "0.52.4" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" | ||
| checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" | ||
| dependencies = [ | ||
| "windows_aarch64_gnullvm 0.52.0", | ||
| "windows_aarch64_msvc 0.52.0", | ||
| "windows_i686_gnu 0.52.0", | ||
| "windows_i686_msvc 0.52.0", | ||
| "windows_x86_64_gnu 0.52.0", | ||
| "windows_x86_64_gnullvm 0.52.0", | ||
| "windows_x86_64_msvc 0.52.0", | ||
| "windows_aarch64_gnullvm 0.52.4", | ||
| "windows_aarch64_msvc 0.52.4", | ||
| "windows_i686_gnu 0.52.4", | ||
| "windows_i686_msvc 0.52.4", | ||
| "windows_x86_64_gnu 0.52.4", | ||
| "windows_x86_64_gnullvm 0.52.4", | ||
| "windows_x86_64_msvc 0.52.4", | ||
| ] | ||
@@ -1468,5 +1489,5 @@ | ||
| name = "windows_aarch64_gnullvm" | ||
| version = "0.52.0" | ||
| version = "0.52.4" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" | ||
| checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" | ||
@@ -1481,5 +1502,5 @@ [[package]] | ||
| name = "windows_aarch64_msvc" | ||
| version = "0.52.0" | ||
| version = "0.52.4" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" | ||
| checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" | ||
@@ -1494,5 +1515,5 @@ [[package]] | ||
| name = "windows_i686_gnu" | ||
| version = "0.52.0" | ||
| version = "0.52.4" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" | ||
| checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" | ||
@@ -1507,5 +1528,5 @@ [[package]] | ||
| name = "windows_i686_msvc" | ||
| version = "0.52.0" | ||
| version = "0.52.4" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" | ||
| checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" | ||
@@ -1520,5 +1541,5 @@ [[package]] | ||
| name = "windows_x86_64_gnu" | ||
| version = "0.52.0" | ||
| version = "0.52.4" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" | ||
| checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" | ||
@@ -1533,5 +1554,5 @@ [[package]] | ||
| name = "windows_x86_64_gnullvm" | ||
| version = "0.52.0" | ||
| version = "0.52.4" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" | ||
| checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" | ||
@@ -1546,4 +1567,4 @@ [[package]] | ||
| name = "windows_x86_64_msvc" | ||
| version = "0.52.0" | ||
| version = "0.52.4" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" | ||
| checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" |
+1
-1
@@ -16,3 +16,3 @@ # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO | ||
| name = "base64" | ||
| version = "0.21.7" | ||
| version = "0.22.0" | ||
| authors = [ | ||
@@ -19,0 +19,0 @@ "Alice Maz <alice@alicemaz.com>", |
+6
-0
@@ -0,1 +1,7 @@ | ||
| # 0.22.0 | ||
| - `DecodeSliceError::OutputSliceTooSmall` is now conservative rather than precise. That is, the error will only occur if the decoded output _cannot_ fit, meaning that `Engine::decode_slice` can now be used with exactly-sized output slices. As part of this, `Engine::internal_decode` now returns `DecodeSliceError` instead of `DecodeError`, but that is not expected to affect any external callers. | ||
| - `DecodeError::InvalidLength` now refers specifically to the _number of valid symbols_ being invalid (i.e. `len % 4 == 1`), rather than just the number of input bytes. This avoids confusing scenarios when based on interpretation you could make a case for either `InvalidLength` or `InvalidByte` being appropriate. | ||
| - Decoding is somewhat faster (5-10%) | ||
| # 0.21.7 | ||
@@ -2,0 +8,0 @@ |
+60
-14
@@ -12,14 +12,16 @@ use crate::engine::{general_purpose::STANDARD, DecodeEstimate, Engine}; | ||
| /// An invalid byte was found in the input. The offset and offending byte are provided. | ||
| /// Padding characters (`=`) interspersed in the encoded form will be treated as invalid bytes. | ||
| /// | ||
| /// Padding characters (`=`) interspersed in the encoded form are invalid, as they may only | ||
| /// be present as the last 0-2 bytes of input. | ||
| /// | ||
| /// This error may also indicate that extraneous trailing input bytes are present, causing | ||
| /// otherwise valid padding to no longer be the last bytes of input. | ||
| InvalidByte(usize, u8), | ||
| /// The length of the input is invalid. | ||
| /// A typical cause of this is stray trailing whitespace or other separator bytes. | ||
| /// In the case where excess trailing bytes have produced an invalid length *and* the last byte | ||
| /// is also an invalid base64 symbol (as would be the case for whitespace, etc), `InvalidByte` | ||
| /// will be emitted instead of `InvalidLength` to make the issue easier to debug. | ||
| InvalidLength, | ||
| /// The length of the input, as measured in valid base64 symbols, is invalid. | ||
| /// There must be 2-4 symbols in the last input quad. | ||
| InvalidLength(usize), | ||
| /// The last non-padding input symbol's encoded 6 bits have nonzero bits that will be discarded. | ||
| /// This is indicative of corrupted or truncated Base64. | ||
| /// Unlike `InvalidByte`, which reports symbols that aren't in the alphabet, this error is for | ||
| /// symbols that are in the alphabet but represent nonsensical encodings. | ||
| /// Unlike [DecodeError::InvalidByte], which reports symbols that aren't in the alphabet, | ||
| /// this error is for symbols that are in the alphabet but represent nonsensical encodings. | ||
| InvalidLastSymbol(usize, u8), | ||
@@ -34,4 +36,6 @@ /// The nature of the padding was not as configured: absent or incorrect when it must be | ||
| match *self { | ||
| Self::InvalidByte(index, byte) => write!(f, "Invalid byte {}, offset {}.", byte, index), | ||
| Self::InvalidLength => write!(f, "Encoded text cannot have a 6-bit remainder."), | ||
| Self::InvalidByte(index, byte) => { | ||
| write!(f, "Invalid symbol {}, offset {}.", byte, index) | ||
| } | ||
| Self::InvalidLength(len) => write!(f, "Invalid input length: {}", len), | ||
| Self::InvalidLastSymbol(index, byte) => { | ||
@@ -53,5 +57,3 @@ write!(f, "Invalid last symbol {}, offset {}.", byte, index) | ||
| DecodeError(DecodeError), | ||
| /// The provided slice _may_ be too small. | ||
| /// | ||
| /// The check is conservative (assumes the last triplet of output bytes will all be needed). | ||
| /// The provided slice is too small. | ||
| OutputSliceTooSmall, | ||
@@ -344,1 +346,45 @@ } | ||
| } | ||
| #[allow(deprecated)] | ||
| #[cfg(test)] | ||
| mod coverage_gaming { | ||
| use super::*; | ||
| use std::error::Error; | ||
| #[test] | ||
| fn decode_error() { | ||
| let _ = format!("{:?}", DecodeError::InvalidPadding.clone()); | ||
| let _ = format!( | ||
| "{} {} {} {}", | ||
| DecodeError::InvalidByte(0, 0), | ||
| DecodeError::InvalidLength(0), | ||
| DecodeError::InvalidLastSymbol(0, 0), | ||
| DecodeError::InvalidPadding, | ||
| ); | ||
| } | ||
| #[test] | ||
| fn decode_slice_error() { | ||
| let _ = format!("{:?}", DecodeSliceError::OutputSliceTooSmall.clone()); | ||
| let _ = format!( | ||
| "{} {}", | ||
| DecodeSliceError::OutputSliceTooSmall, | ||
| DecodeSliceError::DecodeError(DecodeError::InvalidPadding) | ||
| ); | ||
| let _ = DecodeSliceError::OutputSliceTooSmall.source(); | ||
| let _ = DecodeSliceError::DecodeError(DecodeError::InvalidPadding).source(); | ||
| } | ||
| #[test] | ||
| fn deprecated_fns() { | ||
| let _ = decode(""); | ||
| let _ = decode_engine("", &crate::prelude::BASE64_STANDARD); | ||
| let _ = decode_engine_vec("", &mut Vec::new(), &crate::prelude::BASE64_STANDARD); | ||
| let _ = decode_engine_slice("", &mut [], &crate::prelude::BASE64_STANDARD); | ||
| } | ||
| #[test] | ||
| fn decoded_len_est() { | ||
| assert_eq!(3, decoded_len_estimate(4)); | ||
| } | ||
| } |
| use crate::{ | ||
| engine::{general_purpose::INVALID_VALUE, DecodeMetadata, DecodePaddingMode}, | ||
| DecodeError, PAD_BYTE, | ||
| DecodeError, DecodeSliceError, PAD_BYTE, | ||
| }; | ||
| /// Decode the last 1-8 bytes, checking for trailing set bits and padding per the provided | ||
| /// Decode the last 0-4 bytes, checking for trailing set bits and padding per the provided | ||
| /// parameters. | ||
@@ -19,13 +19,15 @@ /// | ||
| padding_mode: DecodePaddingMode, | ||
| ) -> Result<DecodeMetadata, DecodeError> { | ||
| // Decode any leftovers that aren't a complete input block of 8 bytes. | ||
| // Use a u64 as a stack-resident 8 byte buffer. | ||
| let mut leftover_bits: u64 = 0; | ||
| ) -> Result<DecodeMetadata, DecodeSliceError> { | ||
| debug_assert!((input.len() - input_index) <= 4); | ||
| // Decode any leftovers that might not be a complete input chunk of 4 bytes. | ||
| // Use a u32 as a stack-resident 4 byte buffer. | ||
| let mut morsels_in_leftover = 0; | ||
| let mut padding_bytes = 0; | ||
| let mut first_padding_index: usize = 0; | ||
| let mut padding_bytes_count = 0; | ||
| // offset from input_index | ||
| let mut first_padding_offset: usize = 0; | ||
| let mut last_symbol = 0_u8; | ||
| let start_of_leftovers = input_index; | ||
| let mut morsels = [0_u8; 4]; | ||
| for (i, &b) in input[start_of_leftovers..].iter().enumerate() { | ||
| for (leftover_index, &b) in input[input_index..].iter().enumerate() { | ||
| // '=' padding | ||
@@ -45,26 +47,18 @@ if b == PAD_BYTE { | ||
| if i % 4 < 2 { | ||
| // Check for case #2. | ||
| let bad_padding_index = start_of_leftovers | ||
| + if padding_bytes > 0 { | ||
| // If we've already seen padding, report the first padding index. | ||
| // This is to be consistent with the normal decode logic: it will report an | ||
| // error on the first padding character (since it doesn't expect to see | ||
| // anything but actual encoded data). | ||
| // This could only happen if the padding started in the previous quad since | ||
| // otherwise this case would have been hit at i % 4 == 0 if it was the same | ||
| // quad. | ||
| first_padding_index | ||
| } else { | ||
| // haven't seen padding before, just use where we are now | ||
| i | ||
| }; | ||
| return Err(DecodeError::InvalidByte(bad_padding_index, b)); | ||
| if leftover_index < 2 { | ||
| // Check for error #2. | ||
| // Either the previous byte was padding, in which case we would have already hit | ||
| // this case, or it wasn't, in which case this is the first such error. | ||
| debug_assert!( | ||
| leftover_index == 0 || (leftover_index == 1 && padding_bytes_count == 0) | ||
| ); | ||
| let bad_padding_index = input_index + leftover_index; | ||
| return Err(DecodeError::InvalidByte(bad_padding_index, b).into()); | ||
| } | ||
| if padding_bytes == 0 { | ||
| first_padding_index = i; | ||
| if padding_bytes_count == 0 { | ||
| first_padding_offset = leftover_index; | ||
| } | ||
| padding_bytes += 1; | ||
| padding_bytes_count += 1; | ||
| continue; | ||
@@ -77,7 +71,6 @@ } | ||
| // erroneous padding. | ||
| if padding_bytes > 0 { | ||
| return Err(DecodeError::InvalidByte( | ||
| start_of_leftovers + first_padding_index, | ||
| PAD_BYTE, | ||
| )); | ||
| if padding_bytes_count > 0 { | ||
| return Err( | ||
| DecodeError::InvalidByte(input_index + first_padding_offset, PAD_BYTE).into(), | ||
| ); | ||
| } | ||
@@ -89,24 +82,30 @@ | ||
| // Pack the leftovers from left to right. | ||
| let shift = 64 - (morsels_in_leftover + 1) * 6; | ||
| let morsel = decode_table[b as usize]; | ||
| if morsel == INVALID_VALUE { | ||
| return Err(DecodeError::InvalidByte(start_of_leftovers + i, b)); | ||
| return Err(DecodeError::InvalidByte(input_index + leftover_index, b).into()); | ||
| } | ||
| leftover_bits |= (morsel as u64) << shift; | ||
| morsels[morsels_in_leftover] = morsel; | ||
| morsels_in_leftover += 1; | ||
| } | ||
| // If there was 1 trailing byte, and it was valid, and we got to this point without hitting | ||
| // an invalid byte, now we can report invalid length | ||
| if !input.is_empty() && morsels_in_leftover < 2 { | ||
| return Err(DecodeError::InvalidLength(input_index + morsels_in_leftover).into()); | ||
| } | ||
| match padding_mode { | ||
| DecodePaddingMode::Indifferent => { /* everything we care about was already checked */ } | ||
| DecodePaddingMode::RequireCanonical => { | ||
| if (padding_bytes + morsels_in_leftover) % 4 != 0 { | ||
| return Err(DecodeError::InvalidPadding); | ||
| // allow empty input | ||
| if (padding_bytes_count + morsels_in_leftover) % 4 != 0 { | ||
| return Err(DecodeError::InvalidPadding.into()); | ||
| } | ||
| } | ||
| DecodePaddingMode::RequireNone => { | ||
| if padding_bytes > 0 { | ||
| if padding_bytes_count > 0 { | ||
| // check at the end to make sure we let the cases of padding that should be InvalidByte | ||
| // get hit | ||
| return Err(DecodeError::InvalidPadding); | ||
| return Err(DecodeError::InvalidPadding.into()); | ||
| } | ||
@@ -124,40 +123,35 @@ } | ||
| // error if any other bits are set. In the example of one encoded byte -> 2 symbols, | ||
| // 2 symbols can technically encode 12 bits, but the last 4 are non canonical, and | ||
| // 2 symbols can technically encode 12 bits, but the last 4 are non-canonical, and | ||
| // useless since there are no more symbols to provide the necessary 4 additional bits | ||
| // to finish the second original byte. | ||
| let leftover_bits_ready_to_append = match morsels_in_leftover { | ||
| 0 => 0, | ||
| 2 => 8, | ||
| 3 => 16, | ||
| 4 => 24, | ||
| 6 => 32, | ||
| 7 => 40, | ||
| 8 => 48, | ||
| // can also be detected as case #2 bad padding above | ||
| _ => unreachable!( | ||
| "Impossible: must only have 0 to 8 input bytes in last chunk, with no invalid lengths" | ||
| ), | ||
| }; | ||
| let leftover_bytes_to_append = morsels_in_leftover * 6 / 8; | ||
| // Put the up to 6 complete bytes as the high bytes. | ||
| // Gain a couple percent speedup from nudging these ORs to use more ILP with a two-way split. | ||
| let mut leftover_num = (u32::from(morsels[0]) << 26) | ||
| | (u32::from(morsels[1]) << 20) | ||
| | (u32::from(morsels[2]) << 14) | ||
| | (u32::from(morsels[3]) << 8); | ||
| // if there are bits set outside the bits we care about, last symbol encodes trailing bits that | ||
| // will not be included in the output | ||
| let mask = !0 >> leftover_bits_ready_to_append; | ||
| if !decode_allow_trailing_bits && (leftover_bits & mask) != 0 { | ||
| let mask = !0_u32 >> (leftover_bytes_to_append * 8); | ||
| if !decode_allow_trailing_bits && (leftover_num & mask) != 0 { | ||
| // last morsel is at `morsels_in_leftover` - 1 | ||
| return Err(DecodeError::InvalidLastSymbol( | ||
| start_of_leftovers + morsels_in_leftover - 1, | ||
| input_index + morsels_in_leftover - 1, | ||
| last_symbol, | ||
| )); | ||
| ) | ||
| .into()); | ||
| } | ||
| // TODO benchmark simply converting to big endian bytes | ||
| let mut leftover_bits_appended_to_buf = 0; | ||
| while leftover_bits_appended_to_buf < leftover_bits_ready_to_append { | ||
| // `as` simply truncates the higher bits, which is what we want here | ||
| let selected_bits = (leftover_bits >> (56 - leftover_bits_appended_to_buf)) as u8; | ||
| output[output_index] = selected_bits; | ||
| // Strangely, this approach benchmarks better than writing bytes one at a time, | ||
| // or copy_from_slice into output. | ||
| for _ in 0..leftover_bytes_to_append { | ||
| let hi_byte = (leftover_num >> 24) as u8; | ||
| leftover_num <<= 8; | ||
| *output | ||
| .get_mut(output_index) | ||
| .ok_or(DecodeSliceError::OutputSliceTooSmall)? = hi_byte; | ||
| output_index += 1; | ||
| leftover_bits_appended_to_buf += 8; | ||
| } | ||
@@ -167,4 +161,4 @@ | ||
| output_index, | ||
| if padding_bytes > 0 { | ||
| Some(input_index + first_padding_index) | ||
| if padding_bytes_count > 0 { | ||
| Some(input_index + first_padding_offset) | ||
| } else { | ||
@@ -171,0 +165,0 @@ None |
| use crate::{ | ||
| engine::{general_purpose::INVALID_VALUE, DecodeEstimate, DecodeMetadata, DecodePaddingMode}, | ||
| DecodeError, PAD_BYTE, | ||
| DecodeError, DecodeSliceError, PAD_BYTE, | ||
| }; | ||
| // decode logic operates on chunks of 8 input bytes without padding | ||
| const INPUT_CHUNK_LEN: usize = 8; | ||
| const DECODED_CHUNK_LEN: usize = 6; | ||
| // we read a u64 and write a u64, but a u64 of input only yields 6 bytes of output, so the last | ||
| // 2 bytes of any output u64 should not be counted as written to (but must be available in a | ||
| // slice). | ||
| const DECODED_CHUNK_SUFFIX: usize = 2; | ||
| // how many u64's of input to handle at a time | ||
| const CHUNKS_PER_FAST_LOOP_BLOCK: usize = 4; | ||
| const INPUT_BLOCK_LEN: usize = CHUNKS_PER_FAST_LOOP_BLOCK * INPUT_CHUNK_LEN; | ||
| // includes the trailing 2 bytes for the final u64 write | ||
| const DECODED_BLOCK_LEN: usize = | ||
| CHUNKS_PER_FAST_LOOP_BLOCK * DECODED_CHUNK_LEN + DECODED_CHUNK_SUFFIX; | ||
| #[doc(hidden)] | ||
| pub struct GeneralPurposeEstimate { | ||
| /// Total number of decode chunks, including a possibly partial last chunk | ||
| num_chunks: usize, | ||
| decoded_len_estimate: usize, | ||
| /// input len % 4 | ||
| rem: usize, | ||
| conservative_decoded_len: usize, | ||
| } | ||
@@ -33,7 +15,6 @@ | ||
| pub(crate) fn new(encoded_len: usize) -> Self { | ||
| // Formulas that won't overflow | ||
| let rem = encoded_len % 4; | ||
| Self { | ||
| num_chunks: encoded_len / INPUT_CHUNK_LEN | ||
| + (encoded_len % INPUT_CHUNK_LEN > 0) as usize, | ||
| decoded_len_estimate: (encoded_len / 4 + (encoded_len % 4 > 0) as usize) * 3, | ||
| rem, | ||
| conservative_decoded_len: (encoded_len / 4 + (rem > 0) as usize) * 3, | ||
| } | ||
@@ -45,3 +26,3 @@ } | ||
| fn decoded_len_estimate(&self) -> usize { | ||
| self.decoded_len_estimate | ||
| self.conservative_decoded_len | ||
| } | ||
@@ -63,136 +44,77 @@ } | ||
| padding_mode: DecodePaddingMode, | ||
| ) -> Result<DecodeMetadata, DecodeError> { | ||
| let remainder_len = input.len() % INPUT_CHUNK_LEN; | ||
| ) -> Result<DecodeMetadata, DecodeSliceError> { | ||
| let input_complete_nonterminal_quads_len = | ||
| complete_quads_len(input, estimate.rem, output.len(), decode_table)?; | ||
| // Because the fast decode loop writes in groups of 8 bytes (unrolled to | ||
| // CHUNKS_PER_FAST_LOOP_BLOCK times 8 bytes, where possible) and outputs 8 bytes at a time (of | ||
| // which only 6 are valid data), we need to be sure that we stop using the fast decode loop | ||
| // soon enough that there will always be 2 more bytes of valid data written after that loop. | ||
| let trailing_bytes_to_skip = match remainder_len { | ||
| // if input is a multiple of the chunk size, ignore the last chunk as it may have padding, | ||
| // and the fast decode logic cannot handle padding | ||
| 0 => INPUT_CHUNK_LEN, | ||
| // 1 and 5 trailing bytes are illegal: can't decode 6 bits of input into a byte | ||
| 1 | 5 => { | ||
| // trailing whitespace is so common that it's worth it to check the last byte to | ||
| // possibly return a better error message | ||
| if let Some(b) = input.last() { | ||
| if *b != PAD_BYTE && decode_table[*b as usize] == INVALID_VALUE { | ||
| return Err(DecodeError::InvalidByte(input.len() - 1, *b)); | ||
| } | ||
| } | ||
| const UNROLLED_INPUT_CHUNK_SIZE: usize = 32; | ||
| const UNROLLED_OUTPUT_CHUNK_SIZE: usize = UNROLLED_INPUT_CHUNK_SIZE / 4 * 3; | ||
| return Err(DecodeError::InvalidLength); | ||
| } | ||
| // This will decode to one output byte, which isn't enough to overwrite the 2 extra bytes | ||
| // written by the fast decode loop. So, we have to ignore both these 2 bytes and the | ||
| // previous chunk. | ||
| 2 => INPUT_CHUNK_LEN + 2, | ||
| // If this is 3 un-padded chars, then it would actually decode to 2 bytes. However, if this | ||
| // is an erroneous 2 chars + 1 pad char that would decode to 1 byte, then it should fail | ||
| // with an error, not panic from going past the bounds of the output slice, so we let it | ||
| // use stage 3 + 4. | ||
| 3 => INPUT_CHUNK_LEN + 3, | ||
| // This can also decode to one output byte because it may be 2 input chars + 2 padding | ||
| // chars, which would decode to 1 byte. | ||
| 4 => INPUT_CHUNK_LEN + 4, | ||
| // Everything else is a legal decode len (given that we don't require padding), and will | ||
| // decode to at least 2 bytes of output. | ||
| _ => remainder_len, | ||
| }; | ||
| let input_complete_quads_after_unrolled_chunks_len = | ||
| input_complete_nonterminal_quads_len % UNROLLED_INPUT_CHUNK_SIZE; | ||
| // rounded up to include partial chunks | ||
| let mut remaining_chunks = estimate.num_chunks; | ||
| let input_unrolled_loop_len = | ||
| input_complete_nonterminal_quads_len - input_complete_quads_after_unrolled_chunks_len; | ||
| let mut input_index = 0; | ||
| let mut output_index = 0; | ||
| // chunks of 32 bytes | ||
| for (chunk_index, chunk) in input[..input_unrolled_loop_len] | ||
| .chunks_exact(UNROLLED_INPUT_CHUNK_SIZE) | ||
| .enumerate() | ||
| { | ||
| let length_of_fast_decode_chunks = input.len().saturating_sub(trailing_bytes_to_skip); | ||
| let input_index = chunk_index * UNROLLED_INPUT_CHUNK_SIZE; | ||
| let chunk_output = &mut output[chunk_index * UNROLLED_OUTPUT_CHUNK_SIZE | ||
| ..(chunk_index + 1) * UNROLLED_OUTPUT_CHUNK_SIZE]; | ||
| // Fast loop, stage 1 | ||
| // manual unroll to CHUNKS_PER_FAST_LOOP_BLOCK of u64s to amortize slice bounds checks | ||
| if let Some(max_start_index) = length_of_fast_decode_chunks.checked_sub(INPUT_BLOCK_LEN) { | ||
| while input_index <= max_start_index { | ||
| let input_slice = &input[input_index..(input_index + INPUT_BLOCK_LEN)]; | ||
| let output_slice = &mut output[output_index..(output_index + DECODED_BLOCK_LEN)]; | ||
| decode_chunk_8( | ||
| &chunk[0..8], | ||
| input_index, | ||
| decode_table, | ||
| &mut chunk_output[0..6], | ||
| )?; | ||
| decode_chunk_8( | ||
| &chunk[8..16], | ||
| input_index + 8, | ||
| decode_table, | ||
| &mut chunk_output[6..12], | ||
| )?; | ||
| decode_chunk_8( | ||
| &chunk[16..24], | ||
| input_index + 16, | ||
| decode_table, | ||
| &mut chunk_output[12..18], | ||
| )?; | ||
| decode_chunk_8( | ||
| &chunk[24..32], | ||
| input_index + 24, | ||
| decode_table, | ||
| &mut chunk_output[18..24], | ||
| )?; | ||
| } | ||
| decode_chunk( | ||
| &input_slice[0..], | ||
| input_index, | ||
| decode_table, | ||
| &mut output_slice[0..], | ||
| )?; | ||
| decode_chunk( | ||
| &input_slice[8..], | ||
| input_index + 8, | ||
| decode_table, | ||
| &mut output_slice[6..], | ||
| )?; | ||
| decode_chunk( | ||
| &input_slice[16..], | ||
| input_index + 16, | ||
| decode_table, | ||
| &mut output_slice[12..], | ||
| )?; | ||
| decode_chunk( | ||
| &input_slice[24..], | ||
| input_index + 24, | ||
| decode_table, | ||
| &mut output_slice[18..], | ||
| )?; | ||
| // remaining quads, except for the last possibly partial one, as it may have padding | ||
| let output_unrolled_loop_len = input_unrolled_loop_len / 4 * 3; | ||
| let output_complete_quad_len = input_complete_nonterminal_quads_len / 4 * 3; | ||
| { | ||
| let output_after_unroll = &mut output[output_unrolled_loop_len..output_complete_quad_len]; | ||
| input_index += INPUT_BLOCK_LEN; | ||
| output_index += DECODED_BLOCK_LEN - DECODED_CHUNK_SUFFIX; | ||
| remaining_chunks -= CHUNKS_PER_FAST_LOOP_BLOCK; | ||
| } | ||
| } | ||
| for (chunk_index, chunk) in input | ||
| [input_unrolled_loop_len..input_complete_nonterminal_quads_len] | ||
| .chunks_exact(4) | ||
| .enumerate() | ||
| { | ||
| let chunk_output = &mut output_after_unroll[chunk_index * 3..chunk_index * 3 + 3]; | ||
| // Fast loop, stage 2 (aka still pretty fast loop) | ||
| // 8 bytes at a time for whatever we didn't do in stage 1. | ||
| if let Some(max_start_index) = length_of_fast_decode_chunks.checked_sub(INPUT_CHUNK_LEN) { | ||
| while input_index < max_start_index { | ||
| decode_chunk( | ||
| &input[input_index..(input_index + INPUT_CHUNK_LEN)], | ||
| input_index, | ||
| decode_table, | ||
| &mut output | ||
| [output_index..(output_index + DECODED_CHUNK_LEN + DECODED_CHUNK_SUFFIX)], | ||
| )?; | ||
| output_index += DECODED_CHUNK_LEN; | ||
| input_index += INPUT_CHUNK_LEN; | ||
| remaining_chunks -= 1; | ||
| } | ||
| decode_chunk_4( | ||
| chunk, | ||
| input_unrolled_loop_len + chunk_index * 4, | ||
| decode_table, | ||
| chunk_output, | ||
| )?; | ||
| } | ||
| } | ||
| // Stage 3 | ||
| // If input length was such that a chunk had to be deferred until after the fast loop | ||
| // because decoding it would have produced 2 trailing bytes that wouldn't then be | ||
| // overwritten, we decode that chunk here. This way is slower but doesn't write the 2 | ||
| // trailing bytes. | ||
| // However, we still need to avoid the last chunk (partial or complete) because it could | ||
| // have padding, so we always do 1 fewer to avoid the last chunk. | ||
| for _ in 1..remaining_chunks { | ||
| decode_chunk_precise( | ||
| &input[input_index..], | ||
| input_index, | ||
| decode_table, | ||
| &mut output[output_index..(output_index + DECODED_CHUNK_LEN)], | ||
| )?; | ||
| input_index += INPUT_CHUNK_LEN; | ||
| output_index += DECODED_CHUNK_LEN; | ||
| } | ||
| // always have one more (possibly partial) block of 8 input | ||
| debug_assert!(input.len() - input_index > 1 || input.is_empty()); | ||
| debug_assert!(input.len() - input_index <= 8); | ||
| super::decode_suffix::decode_suffix( | ||
| input, | ||
| input_index, | ||
| input_complete_nonterminal_quads_len, | ||
| output, | ||
| output_index, | ||
| output_complete_quad_len, | ||
| decode_table, | ||
@@ -204,14 +126,54 @@ decode_allow_trailing_bits, | ||
| /// Decode 8 bytes of input into 6 bytes of output. 8 bytes of output will be written, but only the | ||
| /// first 6 of those contain meaningful data. | ||
| /// Returns the length of complete quads, except for the last one, even if it is complete. | ||
| /// | ||
| /// `input` is the bytes to decode, of which the first 8 bytes will be processed. | ||
| /// Returns an error if the output len is not big enough for decoding those complete quads, or if | ||
| /// the input % 4 == 1, and that last byte is an invalid value other than a pad byte. | ||
| /// | ||
| /// - `input` is the base64 input | ||
| /// - `input_len_rem` is input len % 4 | ||
| /// - `output_len` is the length of the output slice | ||
| pub(crate) fn complete_quads_len( | ||
| input: &[u8], | ||
| input_len_rem: usize, | ||
| output_len: usize, | ||
| decode_table: &[u8; 256], | ||
| ) -> Result<usize, DecodeSliceError> { | ||
| debug_assert!(input.len() % 4 == input_len_rem); | ||
| // detect a trailing invalid byte, like a newline, as a user convenience | ||
| if input_len_rem == 1 { | ||
| let last_byte = input[input.len() - 1]; | ||
| // exclude pad bytes; might be part of padding that extends from earlier in the input | ||
| if last_byte != PAD_BYTE && decode_table[usize::from(last_byte)] == INVALID_VALUE { | ||
| return Err(DecodeError::InvalidByte(input.len() - 1, last_byte).into()); | ||
| } | ||
| }; | ||
| // skip last quad, even if it's complete, as it may have padding | ||
| let input_complete_nonterminal_quads_len = input | ||
| .len() | ||
| .saturating_sub(input_len_rem) | ||
| // if rem was 0, subtract 4 to avoid padding | ||
| .saturating_sub((input_len_rem == 0) as usize * 4); | ||
| debug_assert!( | ||
| input.is_empty() || (1..=4).contains(&(input.len() - input_complete_nonterminal_quads_len)) | ||
| ); | ||
| // check that everything except the last quad handled by decode_suffix will fit | ||
| if output_len < input_complete_nonterminal_quads_len / 4 * 3 { | ||
| return Err(DecodeSliceError::OutputSliceTooSmall); | ||
| }; | ||
| Ok(input_complete_nonterminal_quads_len) | ||
| } | ||
| /// Decode 8 bytes of input into 6 bytes of output. | ||
| /// | ||
| /// `input` is the 8 bytes to decode. | ||
| /// `index_at_start_of_input` is the offset in the overall input (used for reporting errors | ||
| /// accurately) | ||
| /// `decode_table` is the lookup table for the particular base64 alphabet. | ||
| /// `output` will have its first 8 bytes overwritten, of which only the first 6 are valid decoded | ||
| /// data. | ||
| /// `output` will have its first 6 bytes overwritten | ||
| // yes, really inline (worth 30-50% speedup) | ||
| #[inline(always)] | ||
| fn decode_chunk( | ||
| fn decode_chunk_8( | ||
| input: &[u8], | ||
@@ -222,9 +184,9 @@ index_at_start_of_input: usize, | ||
| ) -> Result<(), DecodeError> { | ||
| let morsel = decode_table[input[0] as usize]; | ||
| let morsel = decode_table[usize::from(input[0])]; | ||
| if morsel == INVALID_VALUE { | ||
| return Err(DecodeError::InvalidByte(index_at_start_of_input, input[0])); | ||
| } | ||
| let mut accum = (morsel as u64) << 58; | ||
| let mut accum = u64::from(morsel) << 58; | ||
| let morsel = decode_table[input[1] as usize]; | ||
| let morsel = decode_table[usize::from(input[1])]; | ||
| if morsel == INVALID_VALUE { | ||
@@ -236,5 +198,5 @@ return Err(DecodeError::InvalidByte( | ||
| } | ||
| accum |= (morsel as u64) << 52; | ||
| accum |= u64::from(morsel) << 52; | ||
| let morsel = decode_table[input[2] as usize]; | ||
| let morsel = decode_table[usize::from(input[2])]; | ||
| if morsel == INVALID_VALUE { | ||
@@ -246,5 +208,5 @@ return Err(DecodeError::InvalidByte( | ||
| } | ||
| accum |= (morsel as u64) << 46; | ||
| accum |= u64::from(morsel) << 46; | ||
| let morsel = decode_table[input[3] as usize]; | ||
| let morsel = decode_table[usize::from(input[3])]; | ||
| if morsel == INVALID_VALUE { | ||
@@ -256,5 +218,5 @@ return Err(DecodeError::InvalidByte( | ||
| } | ||
| accum |= (morsel as u64) << 40; | ||
| accum |= u64::from(morsel) << 40; | ||
| let morsel = decode_table[input[4] as usize]; | ||
| let morsel = decode_table[usize::from(input[4])]; | ||
| if morsel == INVALID_VALUE { | ||
@@ -266,5 +228,5 @@ return Err(DecodeError::InvalidByte( | ||
| } | ||
| accum |= (morsel as u64) << 34; | ||
| accum |= u64::from(morsel) << 34; | ||
| let morsel = decode_table[input[5] as usize]; | ||
| let morsel = decode_table[usize::from(input[5])]; | ||
| if morsel == INVALID_VALUE { | ||
@@ -276,5 +238,5 @@ return Err(DecodeError::InvalidByte( | ||
| } | ||
| accum |= (morsel as u64) << 28; | ||
| accum |= u64::from(morsel) << 28; | ||
| let morsel = decode_table[input[6] as usize]; | ||
| let morsel = decode_table[usize::from(input[6])]; | ||
| if morsel == INVALID_VALUE { | ||
@@ -286,5 +248,5 @@ return Err(DecodeError::InvalidByte( | ||
| } | ||
| accum |= (morsel as u64) << 22; | ||
| accum |= u64::from(morsel) << 22; | ||
| let morsel = decode_table[input[7] as usize]; | ||
| let morsel = decode_table[usize::from(input[7])]; | ||
| if morsel == INVALID_VALUE { | ||
@@ -296,5 +258,5 @@ return Err(DecodeError::InvalidByte( | ||
| } | ||
| accum |= (morsel as u64) << 16; | ||
| accum |= u64::from(morsel) << 16; | ||
| write_u64(output, accum); | ||
| output[..6].copy_from_slice(&accum.to_be_bytes()[..6]); | ||
@@ -304,6 +266,5 @@ Ok(()) | ||
| /// Decode an 8-byte chunk, but only write the 6 bytes actually decoded instead of including 2 | ||
| /// trailing garbage bytes. | ||
| #[inline] | ||
| fn decode_chunk_precise( | ||
| /// Like [decode_chunk_8] but for 4 bytes of input and 3 bytes of output. | ||
| #[inline(always)] | ||
| fn decode_chunk_4( | ||
| input: &[u8], | ||
@@ -314,21 +275,40 @@ index_at_start_of_input: usize, | ||
| ) -> Result<(), DecodeError> { | ||
| let mut tmp_buf = [0_u8; 8]; | ||
| let morsel = decode_table[usize::from(input[0])]; | ||
| if morsel == INVALID_VALUE { | ||
| return Err(DecodeError::InvalidByte(index_at_start_of_input, input[0])); | ||
| } | ||
| let mut accum = u32::from(morsel) << 26; | ||
| decode_chunk( | ||
| input, | ||
| index_at_start_of_input, | ||
| decode_table, | ||
| &mut tmp_buf[..], | ||
| )?; | ||
| let morsel = decode_table[usize::from(input[1])]; | ||
| if morsel == INVALID_VALUE { | ||
| return Err(DecodeError::InvalidByte( | ||
| index_at_start_of_input + 1, | ||
| input[1], | ||
| )); | ||
| } | ||
| accum |= u32::from(morsel) << 20; | ||
| output[0..6].copy_from_slice(&tmp_buf[0..6]); | ||
| let morsel = decode_table[usize::from(input[2])]; | ||
| if morsel == INVALID_VALUE { | ||
| return Err(DecodeError::InvalidByte( | ||
| index_at_start_of_input + 2, | ||
| input[2], | ||
| )); | ||
| } | ||
| accum |= u32::from(morsel) << 14; | ||
| let morsel = decode_table[usize::from(input[3])]; | ||
| if morsel == INVALID_VALUE { | ||
| return Err(DecodeError::InvalidByte( | ||
| index_at_start_of_input + 3, | ||
| input[3], | ||
| )); | ||
| } | ||
| accum |= u32::from(morsel) << 8; | ||
| output[..3].copy_from_slice(&accum.to_be_bytes()[..3]); | ||
| Ok(()) | ||
| } | ||
| #[inline] | ||
| fn write_u64(output: &mut [u8], value: u64) { | ||
| output[..8].copy_from_slice(&value.to_be_bytes()); | ||
| } | ||
| #[cfg(test)] | ||
@@ -341,7 +321,7 @@ mod tests { | ||
| #[test] | ||
| fn decode_chunk_precise_writes_only_6_bytes() { | ||
| fn decode_chunk_8_writes_only_6_bytes() { | ||
| let input = b"Zm9vYmFy"; // "foobar" | ||
| let mut output = [0_u8, 1, 2, 3, 4, 5, 6, 7]; | ||
| decode_chunk_precise(&input[..], 0, &STANDARD.decode_table, &mut output).unwrap(); | ||
| decode_chunk_8(&input[..], 0, &STANDARD.decode_table, &mut output).unwrap(); | ||
| assert_eq!(&vec![b'f', b'o', b'o', b'b', b'a', b'r', 6, 7], &output); | ||
@@ -351,8 +331,8 @@ } | ||
| #[test] | ||
| fn decode_chunk_writes_8_bytes() { | ||
| let input = b"Zm9vYmFy"; // "foobar" | ||
| let mut output = [0_u8, 1, 2, 3, 4, 5, 6, 7]; | ||
| fn decode_chunk_4_writes_only_3_bytes() { | ||
| let input = b"Zm9v"; // "foobar" | ||
| let mut output = [0_u8, 1, 2, 3]; | ||
| decode_chunk(&input[..], 0, &STANDARD.decode_table, &mut output).unwrap(); | ||
| assert_eq!(&vec![b'f', b'o', b'o', b'b', b'a', b'r', 0, 0], &output); | ||
| decode_chunk_4(&input[..], 0, &STANDARD.decode_table, &mut output).unwrap(); | ||
| assert_eq!(&vec![b'f', b'o', b'o', 3], &output); | ||
| } | ||
@@ -362,14 +342,13 @@ | ||
| fn estimate_short_lengths() { | ||
| for (range, (num_chunks, decoded_len_estimate)) in [ | ||
| (0..=0, (0, 0)), | ||
| (1..=4, (1, 3)), | ||
| (5..=8, (1, 6)), | ||
| (9..=12, (2, 9)), | ||
| (13..=16, (2, 12)), | ||
| (17..=20, (3, 15)), | ||
| for (range, decoded_len_estimate) in [ | ||
| (0..=0, 0), | ||
| (1..=4, 3), | ||
| (5..=8, 6), | ||
| (9..=12, 9), | ||
| (13..=16, 12), | ||
| (17..=20, 15), | ||
| ] { | ||
| for encoded_len in range { | ||
| let estimate = GeneralPurposeEstimate::new(encoded_len); | ||
| assert_eq!(num_chunks, estimate.num_chunks); | ||
| assert_eq!(decoded_len_estimate, estimate.decoded_len_estimate); | ||
| assert_eq!(decoded_len_estimate, estimate.decoded_len_estimate()); | ||
| } | ||
@@ -390,12 +369,7 @@ } | ||
| assert_eq!( | ||
| ((len_128 + (INPUT_CHUNK_LEN - 1) as u128) / (INPUT_CHUNK_LEN as u128)) | ||
| as usize, | ||
| estimate.num_chunks | ||
| (len_128 + 3) / 4 * 3, | ||
| estimate.conservative_decoded_len as u128 | ||
| ); | ||
| assert_eq!( | ||
| ((len_128 + 3) / 4 * 3) as usize, | ||
| estimate.decoded_len_estimate | ||
| ); | ||
| }) | ||
| } | ||
| } |
@@ -6,7 +6,7 @@ //! Provides the [GeneralPurpose] engine and associated config types. | ||
| engine::{Config, DecodeMetadata, DecodePaddingMode}, | ||
| DecodeError, | ||
| DecodeSliceError, | ||
| }; | ||
| use core::convert::TryInto; | ||
| mod decode; | ||
| pub(crate) mod decode; | ||
| pub(crate) mod decode_suffix; | ||
@@ -177,3 +177,3 @@ | ||
| estimate: Self::DecodeEstimate, | ||
| ) -> Result<DecodeMetadata, DecodeError> { | ||
| ) -> Result<DecodeMetadata, DecodeSliceError> { | ||
| decode::decode_helper( | ||
@@ -180,0 +180,0 @@ input, |
+26
-15
@@ -86,6 +86,2 @@ //! Provides the [Engine] abstraction and out of the box implementations. | ||
| /// errors unless the engine is configured otherwise. | ||
| /// | ||
| /// # Panics | ||
| /// | ||
| /// Panics if `output` is too small. | ||
| #[doc(hidden)] | ||
@@ -97,3 +93,3 @@ fn internal_decode( | ||
| decode_estimate: Self::DecodeEstimate, | ||
| ) -> Result<DecodeMetadata, DecodeError>; | ||
| ) -> Result<DecodeMetadata, DecodeSliceError>; | ||
@@ -258,3 +254,9 @@ /// Returns the config for this engine. | ||
| let bytes_written = engine | ||
| .internal_decode(input_bytes, &mut buffer, estimate)? | ||
| .internal_decode(input_bytes, &mut buffer, estimate) | ||
| .map_err(|e| match e { | ||
| DecodeSliceError::DecodeError(e) => e, | ||
| DecodeSliceError::OutputSliceTooSmall => { | ||
| unreachable!("Vec is sized conservatively") | ||
| } | ||
| })? | ||
| .decoded_len; | ||
@@ -324,3 +326,9 @@ | ||
| let bytes_written = engine | ||
| .internal_decode(input_bytes, buffer_slice, estimate)? | ||
| .internal_decode(input_bytes, buffer_slice, estimate) | ||
| .map_err(|e| match e { | ||
| DecodeSliceError::DecodeError(e) => e, | ||
| DecodeSliceError::OutputSliceTooSmall => { | ||
| unreachable!("Vec is sized conservatively") | ||
| } | ||
| })? | ||
| .decoded_len; | ||
@@ -361,11 +369,8 @@ | ||
| { | ||
| let estimate = engine.internal_decoded_len_estimate(input_bytes.len()); | ||
| if output.len() < estimate.decoded_len_estimate() { | ||
| return Err(DecodeSliceError::OutputSliceTooSmall); | ||
| } | ||
| engine | ||
| .internal_decode(input_bytes, output, estimate) | ||
| .map_err(|e| e.into()) | ||
| .internal_decode( | ||
| input_bytes, | ||
| output, | ||
| engine.internal_decoded_len_estimate(input_bytes.len()), | ||
| ) | ||
| .map(|dm| dm.decoded_len) | ||
@@ -408,2 +413,8 @@ } | ||
| .map(|dm| dm.decoded_len) | ||
| .map_err(|e| match e { | ||
| DecodeSliceError::DecodeError(e) => e, | ||
| DecodeSliceError::OutputSliceTooSmall => { | ||
| panic!("Output slice is too small") | ||
| } | ||
| }) | ||
| } | ||
@@ -410,0 +421,0 @@ |
+25
-48
@@ -7,3 +7,3 @@ use crate::{ | ||
| }, | ||
| DecodeError, PAD_BYTE, | ||
| DecodeError, DecodeSliceError, | ||
| }; | ||
@@ -115,52 +115,29 @@ use std::ops::{BitAnd, BitOr, Shl, Shr}; | ||
| estimate: Self::DecodeEstimate, | ||
| ) -> Result<DecodeMetadata, DecodeError> { | ||
| if estimate.rem == 1 { | ||
| // trailing whitespace is so common that it's worth it to check the last byte to | ||
| // possibly return a better error message | ||
| if let Some(b) = input.last() { | ||
| if *b != PAD_BYTE | ||
| && self.decode_table[*b as usize] == general_purpose::INVALID_VALUE | ||
| { | ||
| return Err(DecodeError::InvalidByte(input.len() - 1, *b)); | ||
| } | ||
| } | ||
| ) -> Result<DecodeMetadata, DecodeSliceError> { | ||
| let complete_nonterminal_quads_len = general_purpose::decode::complete_quads_len( | ||
| input, | ||
| estimate.rem, | ||
| output.len(), | ||
| &self.decode_table, | ||
| )?; | ||
| return Err(DecodeError::InvalidLength); | ||
| } | ||
| let mut input_index = 0_usize; | ||
| let mut output_index = 0_usize; | ||
| const BOTTOM_BYTE: u32 = 0xFF; | ||
| // can only use the main loop on non-trailing chunks | ||
| if input.len() > Self::DECODE_INPUT_CHUNK_SIZE { | ||
| // skip the last chunk, whether it's partial or full, since it might | ||
| // have padding, and start at the beginning of the chunk before that | ||
| let last_complete_chunk_start_index = estimate.complete_chunk_len | ||
| - if estimate.rem == 0 { | ||
| // Trailing chunk is also full chunk, so there must be at least 2 chunks, and | ||
| // this won't underflow | ||
| Self::DECODE_INPUT_CHUNK_SIZE * 2 | ||
| } else { | ||
| // Trailing chunk is partial, so it's already excluded in | ||
| // complete_chunk_len | ||
| Self::DECODE_INPUT_CHUNK_SIZE | ||
| }; | ||
| for (chunk_index, chunk) in input[..complete_nonterminal_quads_len] | ||
| .chunks_exact(4) | ||
| .enumerate() | ||
| { | ||
| let input_index = chunk_index * 4; | ||
| let output_index = chunk_index * 3; | ||
| while input_index <= last_complete_chunk_start_index { | ||
| let chunk = &input[input_index..input_index + Self::DECODE_INPUT_CHUNK_SIZE]; | ||
| let decoded_int: u32 = self.decode_byte_into_u32(input_index, chunk[0])?.shl(18) | ||
| | self | ||
| .decode_byte_into_u32(input_index + 1, chunk[1])? | ||
| .shl(12) | ||
| | self.decode_byte_into_u32(input_index + 2, chunk[2])?.shl(6) | ||
| | self.decode_byte_into_u32(input_index + 3, chunk[3])?; | ||
| let decoded_int: u32 = self.decode_byte_into_u32(input_index, chunk[0])?.shl(18) | ||
| | self | ||
| .decode_byte_into_u32(input_index + 1, chunk[1])? | ||
| .shl(12) | ||
| | self.decode_byte_into_u32(input_index + 2, chunk[2])?.shl(6) | ||
| | self.decode_byte_into_u32(input_index + 3, chunk[3])?; | ||
| output[output_index] = decoded_int.shr(16_u8).bitand(BOTTOM_BYTE) as u8; | ||
| output[output_index + 1] = decoded_int.shr(8_u8).bitand(BOTTOM_BYTE) as u8; | ||
| output[output_index + 2] = decoded_int.bitand(BOTTOM_BYTE) as u8; | ||
| input_index += Self::DECODE_INPUT_CHUNK_SIZE; | ||
| output_index += 3; | ||
| } | ||
| output[output_index] = decoded_int.shr(16_u8).bitand(BOTTOM_BYTE) as u8; | ||
| output[output_index + 1] = decoded_int.shr(8_u8).bitand(BOTTOM_BYTE) as u8; | ||
| output[output_index + 2] = decoded_int.bitand(BOTTOM_BYTE) as u8; | ||
| } | ||
@@ -170,5 +147,5 @@ | ||
| input, | ||
| input_index, | ||
| complete_nonterminal_quads_len, | ||
| output, | ||
| output_index, | ||
| complete_nonterminal_quads_len / 4 * 3, | ||
| &self.decode_table, | ||
@@ -175,0 +152,0 @@ self.config.decode_allow_trailing_bits, |
+272
-410
@@ -22,3 +22,3 @@ // rstest_reuse template functions have unused variables | ||
| tests::{assert_encode_sanity, random_alphabet, random_config}, | ||
| DecodeError, PAD_BYTE, | ||
| DecodeError, DecodeSliceError, PAD_BYTE, | ||
| }; | ||
@@ -369,22 +369,20 @@ | ||
| #[apply(all_engines)] | ||
| fn decode_detect_invalid_last_symbol_when_length_is_also_invalid<E: EngineWrapper>( | ||
| engine_wrapper: E, | ||
| ) { | ||
| let mut rng = seeded_rng(); | ||
| // check across enough lengths that it would likely cover any implementation's various internal | ||
| // small/large input division | ||
| fn decode_detect_1_valid_symbol_in_last_quad_invalid_length<E: EngineWrapper>(engine_wrapper: E) { | ||
| for len in (0_usize..256).map(|len| len * 4 + 1) { | ||
| let engine = E::random_alphabet(&mut rng, &STANDARD); | ||
| for mode in all_pad_modes() { | ||
| let mut input = vec![b'A'; len]; | ||
| let mut input = vec![b'A'; len]; | ||
| let engine = E::standard_with_pad_mode(true, mode); | ||
| // with a valid last char, it's InvalidLength | ||
| assert_eq!(Err(DecodeError::InvalidLength), engine.decode(&input)); | ||
| // after mangling the last char, it's InvalidByte | ||
| input[len - 1] = b'"'; | ||
| assert_eq!( | ||
| Err(DecodeError::InvalidByte(len - 1, b'"')), | ||
| engine.decode(&input) | ||
| ); | ||
| assert_eq!(Err(DecodeError::InvalidLength(len)), engine.decode(&input)); | ||
| // if we add padding, then the first pad byte in the quad is invalid because it should | ||
| // be the second symbol | ||
| for _ in 0..3 { | ||
| input.push(PAD_BYTE); | ||
| assert_eq!( | ||
| Err(DecodeError::InvalidByte(len, PAD_BYTE)), | ||
| engine.decode(&input) | ||
| ); | ||
| } | ||
| } | ||
| } | ||
@@ -394,2 +392,27 @@ } | ||
| #[apply(all_engines)] | ||
| fn decode_detect_1_invalid_byte_in_last_quad_invalid_byte<E: EngineWrapper>(engine_wrapper: E) { | ||
| for prefix_len in (0_usize..256).map(|len| len * 4) { | ||
| for mode in all_pad_modes() { | ||
| let mut input = vec![b'A'; prefix_len]; | ||
| input.push(b'*'); | ||
| let engine = E::standard_with_pad_mode(true, mode); | ||
| assert_eq!( | ||
| Err(DecodeError::InvalidByte(prefix_len, b'*')), | ||
| engine.decode(&input) | ||
| ); | ||
| // adding padding doesn't matter | ||
| for _ in 0..3 { | ||
| input.push(PAD_BYTE); | ||
| assert_eq!( | ||
| Err(DecodeError::InvalidByte(prefix_len, b'*')), | ||
| engine.decode(&input) | ||
| ); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| #[apply(all_engines)] | ||
| fn decode_detect_invalid_last_symbol_every_possible_two_symbols<E: EngineWrapper>( | ||
@@ -477,4 +500,6 @@ engine_wrapper: E, | ||
| let mut prefix = Vec::new(); | ||
| let mut input = Vec::new(); | ||
| for _ in 0..256 { | ||
| let mut input = prefix.clone(); | ||
| input.clear(); | ||
| input.extend_from_slice(&prefix); | ||
@@ -620,45 +645,90 @@ let mut symbols = [0_u8; 4]; | ||
| /// pad byte. | ||
| /// From this, we know padding must extend to the end of the input. | ||
| // DecoderReader pseudo-engine detects InvalidLastSymbol instead of InvalidLength because it | ||
| // can end a decode on the quad that happens to contain the start of the padding | ||
| #[apply(all_engines_except_decoder_reader)] | ||
| fn decode_padding_before_final_non_padding_char_error_invalid_byte<E: EngineWrapper>( | ||
| /// From this and [decode_padding_before_final_non_padding_char_error_invalid_byte_at_first_pad_non_canonical_padding_suffix_all_modes], | ||
| /// we know padding must extend contiguously to the end of the input. | ||
| #[apply(all_engines)] | ||
| fn decode_padding_before_final_non_padding_char_error_invalid_byte_at_first_pad_all_modes< | ||
| E: EngineWrapper, | ||
| >( | ||
| engine_wrapper: E, | ||
| ) { | ||
| // Different amounts of padding, w/ offset from end for the last non-padding char. | ||
| // Only canonical padding, so Canonical mode will work. | ||
| let suffixes = &[("AA==", 2), ("AAA=", 1), ("AAAA", 0)]; | ||
| for mode in pad_modes_allowing_padding() { | ||
| // We don't encode, so we don't care about encode padding. | ||
| let engine = E::standard_with_pad_mode(true, mode); | ||
| decode_padding_before_final_non_padding_char_error_invalid_byte_at_first_pad( | ||
| engine, | ||
| suffixes.as_slice(), | ||
| ); | ||
| } | ||
| } | ||
| /// See [decode_padding_before_final_non_padding_char_error_invalid_byte_at_first_pad_all_modes] | ||
| #[apply(all_engines)] | ||
| fn decode_padding_before_final_non_padding_char_error_invalid_byte_at_first_pad_non_canonical_padding_suffix< | ||
| E: EngineWrapper, | ||
| >( | ||
| engine_wrapper: E, | ||
| ) { | ||
| // Different amounts of padding, w/ offset from end for the last non-padding char, and | ||
| // non-canonical padding. | ||
| let suffixes = [ | ||
| ("AA==", 2), | ||
| ("AA=", 1), | ||
| ("AA", 0), | ||
| ("AAA=", 1), | ||
| ("AAA", 0), | ||
| ("AAAA", 0), | ||
| ]; | ||
| // We don't encode, so we don't care about encode padding. | ||
| // Decoding is indifferent so that we don't get caught by missing padding on the last quad | ||
| let engine = E::standard_with_pad_mode(true, DecodePaddingMode::Indifferent); | ||
| decode_padding_before_final_non_padding_char_error_invalid_byte_at_first_pad( | ||
| engine, | ||
| suffixes.as_slice(), | ||
| ) | ||
| } | ||
| fn decode_padding_before_final_non_padding_char_error_invalid_byte_at_first_pad( | ||
| engine: impl Engine, | ||
| suffixes: &[(&str, usize)], | ||
| ) { | ||
| let mut rng = seeded_rng(); | ||
| // the different amounts of proper padding, w/ offset from end for the last non-padding char | ||
| let suffixes = [("/w==", 2), ("iYu=", 1), ("zzzz", 0)]; | ||
| let prefix_quads_range = distributions::Uniform::from(0..=256); | ||
| for mode in all_pad_modes() { | ||
| // we don't encode so we don't care about encode padding | ||
| let engine = E::standard_with_pad_mode(true, mode); | ||
| for _ in 0..100_000 { | ||
| for (suffix, suffix_offset) in suffixes.iter() { | ||
| let mut s = "AAAA".repeat(prefix_quads_range.sample(&mut rng)); | ||
| s.push_str(suffix); | ||
| let mut encoded = s.into_bytes(); | ||
| for _ in 0..100_000 { | ||
| for (suffix, offset) in suffixes.iter() { | ||
| let mut s = "ABCD".repeat(prefix_quads_range.sample(&mut rng)); | ||
| s.push_str(suffix); | ||
| let mut encoded = s.into_bytes(); | ||
| // calculate a range to write padding into that leaves at least one non padding char | ||
| let last_non_padding_offset = encoded.len() - 1 - suffix_offset; | ||
| // calculate a range to write padding into that leaves at least one non padding char | ||
| let last_non_padding_offset = encoded.len() - 1 - offset; | ||
| // don't include last non padding char as it must stay not padding | ||
| let padding_end = rng.gen_range(0..last_non_padding_offset); | ||
| // don't include last non padding char as it must stay not padding | ||
| let padding_end = rng.gen_range(0..last_non_padding_offset); | ||
| // don't use more than 100 bytes of padding, but also use shorter lengths when | ||
| // padding_end is near the start of the encoded data to avoid biasing to padding | ||
| // the entire prefix on short lengths | ||
| let padding_len = rng.gen_range(1..=usize::min(100, padding_end + 1)); | ||
| let padding_start = padding_end.saturating_sub(padding_len); | ||
| // don't use more than 100 bytes of padding, but also use shorter lengths when | ||
| // padding_end is near the start of the encoded data to avoid biasing to padding | ||
| // the entire prefix on short lengths | ||
| let padding_len = rng.gen_range(1..=usize::min(100, padding_end + 1)); | ||
| let padding_start = padding_end.saturating_sub(padding_len); | ||
| encoded[padding_start..=padding_end].fill(PAD_BYTE); | ||
| encoded[padding_start..=padding_end].fill(PAD_BYTE); | ||
| assert_eq!( | ||
| Err(DecodeError::InvalidByte(padding_start, PAD_BYTE)), | ||
| engine.decode(&encoded), | ||
| ); | ||
| } | ||
| // should still have non-padding before any final padding | ||
| assert_ne!(PAD_BYTE, encoded[last_non_padding_offset]); | ||
| assert_eq!( | ||
| Err(DecodeError::InvalidByte(padding_start, PAD_BYTE)), | ||
| engine.decode(&encoded), | ||
| "len: {}, input: {}", | ||
| encoded.len(), | ||
| String::from_utf8(encoded).unwrap() | ||
| ); | ||
| } | ||
@@ -668,10 +738,7 @@ } | ||
| /// Any amount of padding before final chunk that crosses over into final chunk with 2-4 bytes = | ||
| /// Any amount of padding before final chunk that crosses over into final chunk with 1-4 bytes = | ||
| /// invalid byte at first pad byte. | ||
| /// From this and [decode_padding_starts_before_final_chunk_error_invalid_length] we know the | ||
| /// padding must start in the final chunk. | ||
| // DecoderReader pseudo-engine detects InvalidLastSymbol instead of InvalidLength because it | ||
| // can end a decode on the quad that happens to contain the start of the padding | ||
| #[apply(all_engines_except_decoder_reader)] | ||
| fn decode_padding_starts_before_final_chunk_error_invalid_byte<E: EngineWrapper>( | ||
| /// From this we know the padding must start in the final chunk. | ||
| #[apply(all_engines)] | ||
| fn decode_padding_starts_before_final_chunk_error_invalid_byte_at_first_pad<E: EngineWrapper>( | ||
| engine_wrapper: E, | ||
@@ -683,5 +750,6 @@ ) { | ||
| let prefix_quads_range = distributions::Uniform::from(1..256); | ||
| // excluding 1 since we don't care about invalid length in this test | ||
| let suffix_pad_len_range = distributions::Uniform::from(2..=4); | ||
| for mode in all_pad_modes() { | ||
| let suffix_pad_len_range = distributions::Uniform::from(1..=4); | ||
| // don't use no-padding mode, as the reader decode might decode a block that ends with | ||
| // valid padding, which should then be referenced when encountering the later invalid byte | ||
| for mode in pad_modes_allowing_padding() { | ||
| // we don't encode so we don't care about encode padding | ||
@@ -691,3 +759,4 @@ let engine = E::standard_with_pad_mode(true, mode); | ||
| let suffix_len = suffix_pad_len_range.sample(&mut rng); | ||
| let mut encoded = "ABCD" | ||
| // all 0 bits so we don't hit InvalidLastSymbol with the reader decoder | ||
| let mut encoded = "AAAA" | ||
| .repeat(prefix_quads_range.sample(&mut rng)) | ||
@@ -716,36 +785,2 @@ .into_bytes(); | ||
| /// Any amount of padding before final chunk that crosses over into final chunk with 1 byte = | ||
| /// invalid length. | ||
| /// From this we know the padding must start in the final chunk. | ||
| // DecoderReader pseudo-engine detects InvalidByte instead of InvalidLength because it starts by | ||
| // decoding only the available complete quads | ||
| #[apply(all_engines_except_decoder_reader)] | ||
| fn decode_padding_starts_before_final_chunk_error_invalid_length<E: EngineWrapper>( | ||
| engine_wrapper: E, | ||
| ) { | ||
| let mut rng = seeded_rng(); | ||
| // must have at least one prefix quad | ||
| let prefix_quads_range = distributions::Uniform::from(1..256); | ||
| for mode in all_pad_modes() { | ||
| // we don't encode so we don't care about encode padding | ||
| let engine = E::standard_with_pad_mode(true, mode); | ||
| for _ in 0..100_000 { | ||
| let mut encoded = "ABCD" | ||
| .repeat(prefix_quads_range.sample(&mut rng)) | ||
| .into_bytes(); | ||
| encoded.resize(encoded.len() + 1, PAD_BYTE); | ||
| // amount of padding must be long enough to extend back from suffix into previous | ||
| // quads | ||
| let padding_len = rng.gen_range(1 + 1..encoded.len()); | ||
| // no non-padding after padding in this test, so padding goes to the end | ||
| let padding_start = encoded.len() - padding_len; | ||
| encoded[padding_start..].fill(PAD_BYTE); | ||
| assert_eq!(Err(DecodeError::InvalidLength), engine.decode(&encoded),); | ||
| } | ||
| } | ||
| } | ||
| /// 0-1 bytes of data before any amount of padding in final chunk = invalid byte, since padding | ||
@@ -768,7 +803,4 @@ /// is not valid data (consistent with error for pad bytes in earlier chunks). | ||
| // ensure there is a suffix quad | ||
| let min_padding = usize::from(suffix_data_len == 0); | ||
| // for all possible padding lengths | ||
| for padding_len in min_padding..=(4 - suffix_data_len) { | ||
| for padding_len in 1..=(4 - suffix_data_len) { | ||
| let mut encoded = "ABCD".repeat(prefix_quad_len).into_bytes(); | ||
@@ -778,16 +810,13 @@ encoded.resize(encoded.len() + suffix_data_len, b'A'); | ||
| if suffix_data_len + padding_len == 1 { | ||
| assert_eq!(Err(DecodeError::InvalidLength), engine.decode(&encoded),); | ||
| } else { | ||
| assert_eq!( | ||
| Err(DecodeError::InvalidByte( | ||
| prefix_quad_len * 4 + suffix_data_len, | ||
| PAD_BYTE, | ||
| )), | ||
| engine.decode(&encoded), | ||
| "suffix data len {} pad len {}", | ||
| suffix_data_len, | ||
| padding_len | ||
| ); | ||
| } | ||
| assert_eq!( | ||
| Err(DecodeError::InvalidByte( | ||
| prefix_quad_len * 4 + suffix_data_len, | ||
| PAD_BYTE, | ||
| )), | ||
| engine.decode(&encoded), | ||
| "input {} suffix data len {} pad len {}", | ||
| String::from_utf8(encoded).unwrap(), | ||
| suffix_data_len, | ||
| padding_len | ||
| ); | ||
| } | ||
@@ -932,46 +961,12 @@ } | ||
| //this is a MAY in the rfc: https://tools.ietf.org/html/rfc4648#section-3.3 | ||
| // DecoderReader pseudo-engine finds the first padding, but doesn't report it as an error, | ||
| // because in the next decode it finds more padding, which is reported as InvalidByte, just | ||
| // with an offset at its position in the second decode, rather than being linked to the start | ||
| // of the padding that was first seen in the previous decode. | ||
| /// 1 trailing byte that's not padding is detected as invalid byte even though there's padding | ||
| /// in the middle of the input. This is essentially mandating the eager check for 1 trailing byte | ||
| /// to catch the \n suffix case. | ||
| // DecoderReader pseudo-engine can't handle DecodePaddingMode::RequireNone since it will decode | ||
| // a complete quad with padding in it before encountering the stray byte that makes it an invalid | ||
| // length | ||
| #[apply(all_engines_except_decoder_reader)] | ||
| fn decode_pad_byte_in_penultimate_quad_error<E: EngineWrapper>(engine_wrapper: E) { | ||
| fn decode_invalid_trailing_bytes_all_pad_modes_invalid_byte<E: EngineWrapper>(engine_wrapper: E) { | ||
| for mode in all_pad_modes() { | ||
| // we don't encode so we don't care about encode padding | ||
| let engine = E::standard_with_pad_mode(true, mode); | ||
| for num_prefix_quads in 0..256 { | ||
| // leave room for at least one pad byte in penultimate quad | ||
| for num_valid_bytes_penultimate_quad in 0..4 { | ||
| // can't have 1 or it would be invalid length | ||
| for num_pad_bytes_in_final_quad in 2..=4 { | ||
| let mut s: String = "ABCD".repeat(num_prefix_quads); | ||
| // varying amounts of padding in the penultimate quad | ||
| for _ in 0..num_valid_bytes_penultimate_quad { | ||
| s.push('A'); | ||
| } | ||
| // finish penultimate quad with padding | ||
| for _ in num_valid_bytes_penultimate_quad..4 { | ||
| s.push('='); | ||
| } | ||
| // and more padding in the final quad | ||
| for _ in 0..num_pad_bytes_in_final_quad { | ||
| s.push('='); | ||
| } | ||
| // padding should be an invalid byte before the final quad. | ||
| // Could argue that the *next* padding byte (in the next quad) is technically the first | ||
| // erroneous one, but reporting that accurately is more complex and probably nobody cares | ||
| assert_eq!( | ||
| DecodeError::InvalidByte( | ||
| num_prefix_quads * 4 + num_valid_bytes_penultimate_quad, | ||
| b'=', | ||
| ), | ||
| engine.decode(&s).unwrap_err(), | ||
| ); | ||
| } | ||
| } | ||
| } | ||
| do_invalid_trailing_byte(E::standard_with_pad_mode(true, mode), mode); | ||
| } | ||
@@ -981,48 +976,28 @@ } | ||
| #[apply(all_engines)] | ||
| fn decode_bytes_after_padding_in_final_quad_error<E: EngineWrapper>(engine_wrapper: E) { | ||
| for mode in all_pad_modes() { | ||
| // we don't encode so we don't care about encode padding | ||
| let engine = E::standard_with_pad_mode(true, mode); | ||
| for num_prefix_quads in 0..256 { | ||
| // leave at least one byte in the quad for padding | ||
| for bytes_after_padding in 1..4 { | ||
| let mut s: String = "ABCD".repeat(num_prefix_quads); | ||
| // every invalid padding position with a 3-byte final quad: 1 to 3 bytes after padding | ||
| for _ in 0..(3 - bytes_after_padding) { | ||
| s.push('A'); | ||
| } | ||
| s.push('='); | ||
| for _ in 0..bytes_after_padding { | ||
| s.push('A'); | ||
| } | ||
| // First (and only) padding byte is invalid. | ||
| assert_eq!( | ||
| DecodeError::InvalidByte( | ||
| num_prefix_quads * 4 + (3 - bytes_after_padding), | ||
| b'=' | ||
| ), | ||
| engine.decode(&s).unwrap_err() | ||
| ); | ||
| } | ||
| } | ||
| fn decode_invalid_trailing_bytes_invalid_byte<E: EngineWrapper>(engine_wrapper: E) { | ||
| // excluding no padding mode because the DecoderWrapper pseudo-engine will fail with | ||
| // InvalidPadding because it will decode the last complete quad with padding first | ||
| for mode in pad_modes_allowing_padding() { | ||
| do_invalid_trailing_byte(E::standard_with_pad_mode(true, mode), mode); | ||
| } | ||
| } | ||
| #[apply(all_engines)] | ||
| fn decode_absurd_pad_error<E: EngineWrapper>(engine_wrapper: E) { | ||
| for mode in all_pad_modes() { | ||
| // we don't encode so we don't care about encode padding | ||
| let engine = E::standard_with_pad_mode(true, mode); | ||
| fn do_invalid_trailing_byte(engine: impl Engine, mode: DecodePaddingMode) { | ||
| for last_byte in [b'*', b'\n'] { | ||
| for num_prefix_quads in 0..256 { | ||
| let mut s: String = "ABCD".repeat(num_prefix_quads); | ||
| s.push_str("==Y=Wx===pY=2U====="); | ||
| s.push_str("Cg=="); | ||
| let mut input = s.into_bytes(); | ||
| input.push(last_byte); | ||
| // first padding byte | ||
| // The case of trailing newlines is common enough to warrant a test for a good error | ||
| // message. | ||
| assert_eq!( | ||
| DecodeError::InvalidByte(num_prefix_quads * 4, b'='), | ||
| engine.decode(&s).unwrap_err() | ||
| Err(DecodeError::InvalidByte( | ||
| num_prefix_quads * 4 + 4, | ||
| last_byte | ||
| )), | ||
| engine.decode(&input), | ||
| "mode: {:?}, input: {}", | ||
| mode, | ||
| String::from_utf8(input).unwrap() | ||
| ); | ||
@@ -1033,158 +1008,18 @@ } | ||
| // DecoderReader pseudo-engine detects InvalidByte instead of InvalidLength because it starts by | ||
| // decoding only the available complete quads | ||
| #[apply(all_engines_except_decoder_reader)] | ||
| fn decode_too_much_padding_returns_error<E: EngineWrapper>(engine_wrapper: E) { | ||
| for mode in all_pad_modes() { | ||
| // we don't encode so we don't care about encode padding | ||
| let engine = E::standard_with_pad_mode(true, mode); | ||
| for num_prefix_quads in 0..256 { | ||
| // add enough padding to ensure that we'll hit all decode stages at the different lengths | ||
| for pad_bytes in 1..=64 { | ||
| let mut s: String = "ABCD".repeat(num_prefix_quads); | ||
| let padding: String = "=".repeat(pad_bytes); | ||
| s.push_str(&padding); | ||
| if pad_bytes % 4 == 1 { | ||
| assert_eq!(DecodeError::InvalidLength, engine.decode(&s).unwrap_err()); | ||
| } else { | ||
| assert_eq!( | ||
| DecodeError::InvalidByte(num_prefix_quads * 4, b'='), | ||
| engine.decode(&s).unwrap_err() | ||
| ); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| // DecoderReader pseudo-engine detects InvalidByte instead of InvalidLength because it starts by | ||
| // decoding only the available complete quads | ||
| #[apply(all_engines_except_decoder_reader)] | ||
| fn decode_padding_followed_by_non_padding_returns_error<E: EngineWrapper>(engine_wrapper: E) { | ||
| for mode in all_pad_modes() { | ||
| // we don't encode so we don't care about encode padding | ||
| let engine = E::standard_with_pad_mode(true, mode); | ||
| for num_prefix_quads in 0..256 { | ||
| for pad_bytes in 0..=32 { | ||
| let mut s: String = "ABCD".repeat(num_prefix_quads); | ||
| let padding: String = "=".repeat(pad_bytes); | ||
| s.push_str(&padding); | ||
| s.push('E'); | ||
| if pad_bytes % 4 == 0 { | ||
| assert_eq!(DecodeError::InvalidLength, engine.decode(&s).unwrap_err()); | ||
| } else { | ||
| assert_eq!( | ||
| DecodeError::InvalidByte(num_prefix_quads * 4, b'='), | ||
| engine.decode(&s).unwrap_err() | ||
| ); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| /// When there's 1 trailing byte, but it's padding, it's only InvalidByte if there isn't padding | ||
| /// earlier. | ||
| #[apply(all_engines)] | ||
| fn decode_one_char_in_final_quad_with_padding_error<E: EngineWrapper>(engine_wrapper: E) { | ||
| for mode in all_pad_modes() { | ||
| // we don't encode so we don't care about encode padding | ||
| let engine = E::standard_with_pad_mode(true, mode); | ||
| for num_prefix_quads in 0..256 { | ||
| let mut s: String = "ABCD".repeat(num_prefix_quads); | ||
| s.push_str("E="); | ||
| assert_eq!( | ||
| DecodeError::InvalidByte(num_prefix_quads * 4 + 1, b'='), | ||
| engine.decode(&s).unwrap_err() | ||
| ); | ||
| // more padding doesn't change the error | ||
| s.push('='); | ||
| assert_eq!( | ||
| DecodeError::InvalidByte(num_prefix_quads * 4 + 1, b'='), | ||
| engine.decode(&s).unwrap_err() | ||
| ); | ||
| s.push('='); | ||
| assert_eq!( | ||
| DecodeError::InvalidByte(num_prefix_quads * 4 + 1, b'='), | ||
| engine.decode(&s).unwrap_err() | ||
| ); | ||
| } | ||
| } | ||
| } | ||
| #[apply(all_engines)] | ||
| fn decode_too_few_symbols_in_final_quad_error<E: EngineWrapper>(engine_wrapper: E) { | ||
| for mode in all_pad_modes() { | ||
| // we don't encode so we don't care about encode padding | ||
| let engine = E::standard_with_pad_mode(true, mode); | ||
| for num_prefix_quads in 0..256 { | ||
| // <2 is invalid | ||
| for final_quad_symbols in 0..2 { | ||
| for padding_symbols in 0..=(4 - final_quad_symbols) { | ||
| let mut s: String = "ABCD".repeat(num_prefix_quads); | ||
| for _ in 0..final_quad_symbols { | ||
| s.push('A'); | ||
| } | ||
| for _ in 0..padding_symbols { | ||
| s.push('='); | ||
| } | ||
| match final_quad_symbols + padding_symbols { | ||
| 0 => continue, | ||
| 1 => { | ||
| assert_eq!(DecodeError::InvalidLength, engine.decode(&s).unwrap_err()); | ||
| } | ||
| _ => { | ||
| // error reported at first padding byte | ||
| assert_eq!( | ||
| DecodeError::InvalidByte( | ||
| num_prefix_quads * 4 + final_quad_symbols, | ||
| b'=', | ||
| ), | ||
| engine.decode(&s).unwrap_err() | ||
| ); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| // DecoderReader pseudo-engine can't handle DecodePaddingMode::RequireNone since it will decode | ||
| // a complete quad with padding in it before encountering the stray byte that makes it an invalid | ||
| // length | ||
| #[apply(all_engines_except_decoder_reader)] | ||
| fn decode_invalid_trailing_bytes<E: EngineWrapper>(engine_wrapper: E) { | ||
| for mode in all_pad_modes() { | ||
| do_invalid_trailing_byte(E::standard_with_pad_mode(true, mode), mode); | ||
| } | ||
| } | ||
| #[apply(all_engines)] | ||
| fn decode_invalid_trailing_bytes_all_modes<E: EngineWrapper>(engine_wrapper: E) { | ||
| fn decode_invalid_trailing_padding_as_invalid_byte_at_first_pad_byte<E: EngineWrapper>( | ||
| engine_wrapper: E, | ||
| ) { | ||
| // excluding no padding mode because the DecoderWrapper pseudo-engine will fail with | ||
| // InvalidPadding because it will decode the last complete quad with padding first | ||
| for mode in pad_modes_allowing_padding() { | ||
| do_invalid_trailing_byte(E::standard_with_pad_mode(true, mode), mode); | ||
| do_invalid_trailing_padding_as_invalid_byte_at_first_padding( | ||
| E::standard_with_pad_mode(true, mode), | ||
| mode, | ||
| ); | ||
| } | ||
| } | ||
| #[apply(all_engines)] | ||
| fn decode_invalid_trailing_padding_as_invalid_length<E: EngineWrapper>(engine_wrapper: E) { | ||
| // excluding no padding mode because the DecoderWrapper pseudo-engine will fail with | ||
| // InvalidPadding because it will decode the last complete quad with padding first | ||
| for mode in pad_modes_allowing_padding() { | ||
| do_invalid_trailing_padding_as_invalid_length(E::standard_with_pad_mode(true, mode), mode); | ||
| } | ||
| } | ||
| // DecoderReader pseudo-engine can't handle DecodePaddingMode::RequireNone since it will decode | ||
@@ -1194,44 +1029,32 @@ // a complete quad with padding in it before encountering the stray byte that makes it an invalid | ||
| #[apply(all_engines_except_decoder_reader)] | ||
| fn decode_invalid_trailing_padding_as_invalid_length_all_modes<E: EngineWrapper>( | ||
| fn decode_invalid_trailing_padding_as_invalid_byte_at_first_byte_all_modes<E: EngineWrapper>( | ||
| engine_wrapper: E, | ||
| ) { | ||
| for mode in all_pad_modes() { | ||
| do_invalid_trailing_padding_as_invalid_length(E::standard_with_pad_mode(true, mode), mode); | ||
| do_invalid_trailing_padding_as_invalid_byte_at_first_padding( | ||
| E::standard_with_pad_mode(true, mode), | ||
| mode, | ||
| ); | ||
| } | ||
| } | ||
| #[apply(all_engines)] | ||
| fn decode_wrong_length_error<E: EngineWrapper>(engine_wrapper: E) { | ||
| let engine = E::standard_with_pad_mode(true, DecodePaddingMode::Indifferent); | ||
| fn do_invalid_trailing_padding_as_invalid_byte_at_first_padding( | ||
| engine: impl Engine, | ||
| mode: DecodePaddingMode, | ||
| ) { | ||
| for num_prefix_quads in 0..256 { | ||
| // at least one token, otherwise it wouldn't be a final quad | ||
| for num_tokens_final_quad in 1..=4 { | ||
| for num_padding in 0..=(4 - num_tokens_final_quad) { | ||
| let mut s: String = "IIII".repeat(num_prefix_quads); | ||
| for _ in 0..num_tokens_final_quad { | ||
| s.push('g'); | ||
| } | ||
| for _ in 0..num_padding { | ||
| s.push('='); | ||
| } | ||
| for (suffix, pad_offset) in [("AA===", 2), ("AAA==", 3), ("AAAA=", 4)] { | ||
| let mut s: String = "ABCD".repeat(num_prefix_quads); | ||
| s.push_str(suffix); | ||
| let res = engine.decode(&s); | ||
| if num_tokens_final_quad >= 2 { | ||
| assert!(res.is_ok()); | ||
| } else if num_tokens_final_quad == 1 && num_padding > 0 { | ||
| // = is invalid if it's too early | ||
| assert_eq!( | ||
| Err(DecodeError::InvalidByte( | ||
| num_prefix_quads * 4 + num_tokens_final_quad, | ||
| 61 | ||
| )), | ||
| res | ||
| ); | ||
| } else if num_padding > 2 { | ||
| assert_eq!(Err(DecodeError::InvalidPadding), res); | ||
| } else { | ||
| assert_eq!(Err(DecodeError::InvalidLength), res); | ||
| } | ||
| } | ||
| assert_eq!( | ||
| // pad after `g`, not the last one | ||
| Err(DecodeError::InvalidByte( | ||
| num_prefix_quads * 4 + pad_offset, | ||
| PAD_BYTE | ||
| )), | ||
| engine.decode(&s), | ||
| "mode: {:?}, input: {}", | ||
| mode, | ||
| s | ||
| ); | ||
| } | ||
@@ -1266,3 +1089,2 @@ } | ||
| decode_buf.resize(input_len, 0); | ||
| // decode into the non-empty buf | ||
@@ -1272,3 +1094,12 @@ let decode_bytes_written = engine | ||
| .unwrap(); | ||
| assert_eq!(orig_data.len(), decode_bytes_written); | ||
| assert_eq!(orig_data, decode_buf); | ||
| // same for checked variant | ||
| decode_buf.clear(); | ||
| decode_buf.resize(input_len, 0); | ||
| // decode into the non-empty buf | ||
| let decode_bytes_written = engine | ||
| .decode_slice(encoded_data.as_bytes(), &mut decode_buf[..]) | ||
| .unwrap(); | ||
| assert_eq!(orig_data.len(), decode_bytes_written); | ||
@@ -1307,3 +1138,6 @@ assert_eq!(orig_data, decode_buf); | ||
| assert_eq!( | ||
| Err(DecodeError::InvalidByte(pad_position, PAD_BYTE)), | ||
| Err(DecodeSliceError::DecodeError(DecodeError::InvalidByte( | ||
| pad_position, | ||
| PAD_BYTE | ||
| ))), | ||
| decode_res | ||
@@ -1376,31 +1210,56 @@ ); | ||
| fn do_invalid_trailing_byte(engine: impl Engine, mode: DecodePaddingMode) { | ||
| for num_prefix_quads in 0..256 { | ||
| let mut s: String = "ABCD".repeat(num_prefix_quads); | ||
| s.push_str("Cg==\n"); | ||
| #[apply(all_engines)] | ||
| fn decode_slice_checked_fails_gracefully_at_all_output_lengths<E: EngineWrapper>( | ||
| engine_wrapper: E, | ||
| ) { | ||
| let mut rng = seeded_rng(); | ||
| for original_len in 0..1000 { | ||
| let mut original = vec![0; original_len]; | ||
| rng.fill(&mut original[..]); | ||
| // The case of trailing newlines is common enough to warrant a test for a good error | ||
| // message. | ||
| assert_eq!( | ||
| Err(DecodeError::InvalidByte(num_prefix_quads * 4 + 4, b'\n')), | ||
| engine.decode(&s), | ||
| "mode: {:?}, input: {}", | ||
| mode, | ||
| s | ||
| ); | ||
| } | ||
| } | ||
| for mode in all_pad_modes() { | ||
| let engine = E::standard_with_pad_mode( | ||
| match mode { | ||
| DecodePaddingMode::Indifferent | DecodePaddingMode::RequireCanonical => true, | ||
| DecodePaddingMode::RequireNone => false, | ||
| }, | ||
| mode, | ||
| ); | ||
| fn do_invalid_trailing_padding_as_invalid_length(engine: impl Engine, mode: DecodePaddingMode) { | ||
| for num_prefix_quads in 0..256 { | ||
| let mut s: String = "ABCD".repeat(num_prefix_quads); | ||
| s.push_str("Cg==="); | ||
| let encoded = engine.encode(&original); | ||
| let mut decode_buf = Vec::with_capacity(original_len); | ||
| for decode_buf_len in 0..original_len { | ||
| decode_buf.resize(decode_buf_len, 0); | ||
| assert_eq!( | ||
| DecodeSliceError::OutputSliceTooSmall, | ||
| engine | ||
| .decode_slice(&encoded, &mut decode_buf[..]) | ||
| .unwrap_err(), | ||
| "original len: {}, encoded len: {}, buf len: {}, mode: {:?}", | ||
| original_len, | ||
| encoded.len(), | ||
| decode_buf_len, | ||
| mode | ||
| ); | ||
| // internal method works the same | ||
| assert_eq!( | ||
| DecodeSliceError::OutputSliceTooSmall, | ||
| engine | ||
| .internal_decode( | ||
| encoded.as_bytes(), | ||
| &mut decode_buf[..], | ||
| engine.internal_decoded_len_estimate(encoded.len()) | ||
| ) | ||
| .unwrap_err() | ||
| ); | ||
| } | ||
| assert_eq!( | ||
| Err(DecodeError::InvalidLength), | ||
| engine.decode(&s), | ||
| "mode: {:?}, input: {}", | ||
| mode, | ||
| s | ||
| ); | ||
| decode_buf.resize(original_len, 0); | ||
| rng.fill(&mut decode_buf[..]); | ||
| assert_eq!( | ||
| original_len, | ||
| engine.decode_slice(&encoded, &mut decode_buf[..]).unwrap() | ||
| ); | ||
| assert_eq!(original, decode_buf); | ||
| } | ||
| } | ||
@@ -1569,3 +1428,3 @@ } | ||
| naive::NaiveConfig { | ||
| encode_padding: false, | ||
| encode_padding: encode_pad, | ||
| decode_allow_trailing_bits: false, | ||
@@ -1639,3 +1498,3 @@ decode_padding_mode: decode_pad_mode, | ||
| decode_estimate: Self::DecodeEstimate, | ||
| ) -> Result<DecodeMetadata, DecodeError> { | ||
| ) -> Result<DecodeMetadata, DecodeSliceError> { | ||
| let mut reader = DecoderReader::new(input, &self.engine); | ||
@@ -1659,2 +1518,5 @@ let mut buf = vec![0; input.len()]; | ||
| })?; | ||
| if output.len() < buf.len() { | ||
| return Err(DecodeSliceError::OutputSliceTooSmall); | ||
| } | ||
| output[..buf.len()].copy_from_slice(&buf); | ||
@@ -1661,0 +1523,0 @@ Ok(DecodeMetadata::new( |
+1
-2
@@ -231,4 +231,3 @@ //! Correct, fast, and configurable [base64][] decoding and encoding. Base64 | ||
| unused_results, | ||
| variant_size_differences, | ||
| warnings | ||
| variant_size_differences | ||
| )] | ||
@@ -235,0 +234,0 @@ #![forbid(unsafe_code)] |
+56
-37
@@ -1,2 +0,2 @@ | ||
| use crate::{engine::Engine, DecodeError, PAD_BYTE}; | ||
| use crate::{engine::Engine, DecodeError, DecodeSliceError, PAD_BYTE}; | ||
| use std::{cmp, fmt, io}; | ||
@@ -38,24 +38,26 @@ | ||
| // Holds b64 data read from the delegate reader. | ||
| /// Holds b64 data read from the delegate reader. | ||
| b64_buffer: [u8; BUF_SIZE], | ||
| // The start of the pending buffered data in b64_buffer. | ||
| /// The start of the pending buffered data in `b64_buffer`. | ||
| b64_offset: usize, | ||
| // The amount of buffered b64 data. | ||
| /// The amount of buffered b64 data after `b64_offset` in `b64_len`. | ||
| b64_len: usize, | ||
| // Since the caller may provide us with a buffer of size 1 or 2 that's too small to copy a | ||
| // decoded chunk in to, we have to be able to hang on to a few decoded bytes. | ||
| // Technically we only need to hold 2 bytes but then we'd need a separate temporary buffer to | ||
| // decode 3 bytes into and then juggle copying one byte into the provided read buf and the rest | ||
| // into here, which seems like a lot of complexity for 1 extra byte of storage. | ||
| decoded_buffer: [u8; DECODED_CHUNK_SIZE], | ||
| // index of start of decoded data | ||
| /// Since the caller may provide us with a buffer of size 1 or 2 that's too small to copy a | ||
| /// decoded chunk in to, we have to be able to hang on to a few decoded bytes. | ||
| /// Technically we only need to hold 2 bytes, but then we'd need a separate temporary buffer to | ||
| /// decode 3 bytes into and then juggle copying one byte into the provided read buf and the rest | ||
| /// into here, which seems like a lot of complexity for 1 extra byte of storage. | ||
| decoded_chunk_buffer: [u8; DECODED_CHUNK_SIZE], | ||
| /// Index of start of decoded data in `decoded_chunk_buffer` | ||
| decoded_offset: usize, | ||
| // length of decoded data | ||
| /// Length of decoded data after `decoded_offset` in `decoded_chunk_buffer` | ||
| decoded_len: usize, | ||
| // used to provide accurate offsets in errors | ||
| total_b64_decoded: usize, | ||
| // offset of previously seen padding, if any | ||
| /// Input length consumed so far. | ||
| /// Used to provide accurate offsets in errors | ||
| input_consumed_len: usize, | ||
| /// offset of previously seen padding, if any | ||
| padding_offset: Option<usize>, | ||
| } | ||
| // exclude b64_buffer as it's uselessly large | ||
| impl<'e, E: Engine, R: io::Read> fmt::Debug for DecoderReader<'e, E, R> { | ||
@@ -66,6 +68,6 @@ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
| .field("b64_len", &self.b64_len) | ||
| .field("decoded_buffer", &self.decoded_buffer) | ||
| .field("decoded_chunk_buffer", &self.decoded_chunk_buffer) | ||
| .field("decoded_offset", &self.decoded_offset) | ||
| .field("decoded_len", &self.decoded_len) | ||
| .field("total_b64_decoded", &self.total_b64_decoded) | ||
| .field("input_consumed_len", &self.input_consumed_len) | ||
| .field("padding_offset", &self.padding_offset) | ||
@@ -85,6 +87,6 @@ .finish() | ||
| b64_len: 0, | ||
| decoded_buffer: [0; DECODED_CHUNK_SIZE], | ||
| decoded_chunk_buffer: [0; DECODED_CHUNK_SIZE], | ||
| decoded_offset: 0, | ||
| decoded_len: 0, | ||
| total_b64_decoded: 0, | ||
| input_consumed_len: 0, | ||
| padding_offset: None, | ||
@@ -106,3 +108,3 @@ } | ||
| buf[..copy_len].copy_from_slice( | ||
| &self.decoded_buffer[self.decoded_offset..self.decoded_offset + copy_len], | ||
| &self.decoded_chunk_buffer[self.decoded_offset..self.decoded_offset + copy_len], | ||
| ); | ||
@@ -138,2 +140,6 @@ | ||
| /// Returns a Result with the number of decoded bytes written to `buf`. | ||
| /// | ||
| /// # Panics | ||
| /// | ||
| /// panics if `buf` is too small | ||
| fn decode_to_buf(&mut self, b64_len_to_decode: usize, buf: &mut [u8]) -> io::Result<usize> { | ||
@@ -152,18 +158,31 @@ debug_assert!(self.b64_len >= b64_len_to_decode); | ||
| ) | ||
| .map_err(|e| match e { | ||
| DecodeError::InvalidByte(offset, byte) => { | ||
| // This can be incorrect, but not in a way that probably matters to anyone: | ||
| // if there was padding handled in a previous decode, and we are now getting | ||
| // InvalidByte due to more padding, we should arguably report InvalidByte with | ||
| // PAD_BYTE at the original padding position (`self.padding_offset`), but we | ||
| // don't have a good way to tie those two cases together, so instead we | ||
| // just report the invalid byte as if the previous padding, and its possibly | ||
| // related downgrade to a now invalid byte, didn't happen. | ||
| DecodeError::InvalidByte(self.total_b64_decoded + offset, byte) | ||
| .map_err(|dse| match dse { | ||
| DecodeSliceError::DecodeError(de) => { | ||
| match de { | ||
| DecodeError::InvalidByte(offset, byte) => { | ||
| match (byte, self.padding_offset) { | ||
| // if there was padding in a previous block of decoding that happened to | ||
| // be correct, and we now find more padding that happens to be incorrect, | ||
| // to be consistent with non-reader decodes, record the error at the first | ||
| // padding | ||
| (PAD_BYTE, Some(first_pad_offset)) => { | ||
| DecodeError::InvalidByte(first_pad_offset, PAD_BYTE) | ||
| } | ||
| _ => { | ||
| DecodeError::InvalidByte(self.input_consumed_len + offset, byte) | ||
| } | ||
| } | ||
| } | ||
| DecodeError::InvalidLength(len) => { | ||
| DecodeError::InvalidLength(self.input_consumed_len + len) | ||
| } | ||
| DecodeError::InvalidLastSymbol(offset, byte) => { | ||
| DecodeError::InvalidLastSymbol(self.input_consumed_len + offset, byte) | ||
| } | ||
| DecodeError::InvalidPadding => DecodeError::InvalidPadding, | ||
| } | ||
| } | ||
| DecodeError::InvalidLength => DecodeError::InvalidLength, | ||
| DecodeError::InvalidLastSymbol(offset, byte) => { | ||
| DecodeError::InvalidLastSymbol(self.total_b64_decoded + offset, byte) | ||
| DecodeSliceError::OutputSliceTooSmall => { | ||
| unreachable!("buf is sized correctly in calling code") | ||
| } | ||
| DecodeError::InvalidPadding => DecodeError::InvalidPadding, | ||
| }) | ||
@@ -185,4 +204,4 @@ .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; | ||
| .padding_offset | ||
| .map(|offset| self.total_b64_decoded + offset)); | ||
| self.total_b64_decoded += b64_len_to_decode; | ||
| .map(|offset| self.input_consumed_len + offset)); | ||
| self.input_consumed_len += b64_len_to_decode; | ||
| self.b64_offset += b64_len_to_decode; | ||
@@ -293,3 +312,3 @@ self.b64_len -= b64_len_to_decode; | ||
| let decoded = self.decode_to_buf(to_decode, &mut decoded_chunk[..])?; | ||
| self.decoded_buffer[..decoded].copy_from_slice(&decoded_chunk[..decoded]); | ||
| self.decoded_chunk_buffer[..decoded].copy_from_slice(&decoded_chunk[..decoded]); | ||
@@ -296,0 +315,0 @@ self.decoded_offset = 0; |
Sorry, the diff of this file is not supported yet