Bump zeroidc dependencies (#1847)

openidconnect -> 2.5
base64 -> 0.21
url -> 2.3
bytes -> 1.3
This commit is contained in:
Grant Limberg 2023-01-12 13:24:58 -08:00 committed by GitHub
parent 67a7534c21
commit a59626c971
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
347 changed files with 52263 additions and 5360 deletions

138
zeroidc/Cargo.lock generated
View File

@ -34,6 +34,12 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]]
name = "base64"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.3.2" version = "1.3.2"
@ -57,9 +63,9 @@ checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.1.0" version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c"
[[package]] [[package]]
name = "cbindgen" name = "cbindgen"
@ -114,7 +120,7 @@ dependencies = [
"ansi_term", "ansi_term",
"atty", "atty",
"bitflags", "bitflags",
"strsim", "strsim 0.8.0",
"textwrap", "textwrap",
"unicode-width", "unicode-width",
"vec_map", "vec_map",
@ -155,6 +161,41 @@ dependencies = [
"typenum", "typenum",
] ]
[[package]]
name = "darling"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim 0.10.0",
"syn",
]
[[package]]
name = "darling_macro"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
dependencies = [
"darling_core",
"quote",
"syn",
]
[[package]] [[package]]
name = "digest" name = "digest"
version = "0.10.3" version = "0.10.3"
@ -213,11 +254,10 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]] [[package]]
name = "form_urlencoded" name = "form_urlencoded"
version = "1.0.1" version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
dependencies = [ dependencies = [
"matches",
"percent-encoding", "percent-encoding",
] ]
@ -416,12 +456,17 @@ dependencies = [
] ]
[[package]] [[package]]
name = "idna" name = "ident_case"
version = "0.2.3" version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
dependencies = [ dependencies = [
"matches",
"unicode-bidi", "unicode-bidi",
"unicode-normalization", "unicode-normalization",
] ]
@ -480,7 +525,7 @@ name = "jwt"
version = "0.16.0" version = "0.16.0"
source = "git+https://github.com/glimberg/rust-jwt#61a9291fdeec747c6edf14f4fa0caf235136c168" source = "git+https://github.com/glimberg/rust-jwt#61a9291fdeec747c6edf14f4fa0caf235136c168"
dependencies = [ dependencies = [
"base64", "base64 0.13.0",
"crypto-common", "crypto-common",
"digest", "digest",
"hmac", "hmac",
@ -510,12 +555,6 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "matches"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.5.0" version = "2.5.0"
@ -609,11 +648,11 @@ dependencies = [
[[package]] [[package]]
name = "oauth2" name = "oauth2"
version = "4.2.0" version = "4.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3bd7d544f02ae0fa9e06137962703d043870d7ad6e6d44786d6a5f20679b2c9" checksum = "eeaf26a72311c087f8c5ba617c96fac67a5c04f430e716ac8d8ab2de62e23368"
dependencies = [ dependencies = [
"base64", "base64 0.13.0",
"chrono", "chrono",
"getrandom", "getrandom",
"http", "http",
@ -635,11 +674,11 @@ checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225"
[[package]] [[package]]
name = "openidconnect" name = "openidconnect"
version = "2.3.1" version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32f73e47a1766acd7bedd605742a1a2651c111f34ed3e0be117d8651432d509c" checksum = "32a0f47b0f1499d08c4a8480c963d49c5ec77f4249c2b6869780979415f45809"
dependencies = [ dependencies = [
"base64", "base64 0.13.0",
"chrono", "chrono",
"http", "http",
"itertools", "itertools",
@ -653,6 +692,9 @@ dependencies = [
"serde_derive", "serde_derive",
"serde_json", "serde_json",
"serde_path_to_error", "serde_path_to_error",
"serde_plain",
"serde_with",
"subtle",
"thiserror", "thiserror",
"url", "url",
] ]
@ -713,9 +755,9 @@ dependencies = [
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.1.0" version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
[[package]] [[package]]
name = "pin-project-lite" name = "pin-project-lite"
@ -813,7 +855,7 @@ version = "0.11.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46a1f7aa4f35e5e8b4160449f51afc758f0ce6454315a9fa7d0d113e958c41eb" checksum = "46a1f7aa4f35e5e8b4160449f51afc758f0ce6454315a9fa7d0d113e958c41eb"
dependencies = [ dependencies = [
"base64", "base64 0.13.0",
"bytes", "bytes",
"encoding_rs", "encoding_rs",
"futures-core", "futures-core",
@ -947,6 +989,15 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "serde_plain"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6018081315db179d0ce57b1fe4b62a12a0028c9cf9bbef868c9cf477b3c34ae"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "serde_urlencoded" name = "serde_urlencoded"
version = "0.7.1" version = "0.7.1"
@ -959,6 +1010,28 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "serde_with"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff"
dependencies = [
"serde",
"serde_with_macros",
]
[[package]]
name = "serde_with_macros"
version = "1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "sha2" name = "sha2"
version = "0.10.2" version = "0.10.2"
@ -998,6 +1071,12 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]] [[package]]
name = "subtle" name = "subtle"
version = "2.4.1" version = "2.4.1"
@ -1213,13 +1292,12 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]] [[package]]
name = "url" name = "url"
version = "2.2.2" version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643"
dependencies = [ dependencies = [
"form_urlencoded", "form_urlencoded",
"idna", "idna",
"matches",
"percent-encoding", "percent-encoding",
"serde", "serde",
] ]
@ -1475,7 +1553,7 @@ dependencies = [
name = "zeroidc" name = "zeroidc"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"base64", "base64 0.21.0",
"bytes", "bytes",
"cbindgen", "cbindgen",
"jwt", "jwt",

View File

@ -9,14 +9,14 @@ publish = false
crate-type = ["staticlib","rlib"] crate-type = ["staticlib","rlib"]
[dependencies] [dependencies]
openidconnect = { version = "2.2", default-features = false, features = ["reqwest", "native-tls", "accept-rfc3339-timestamps"] } openidconnect = { version = "2.5", default-features = false, features = ["reqwest", "native-tls", "accept-rfc3339-timestamps"] }
base64 = "0.13" base64 = "0.21"
url = "2.2" url = "2.3"
reqwest = "0.11" reqwest = "0.11"
jwt = {version = "0.16", git = "https://github.com/glimberg/rust-jwt"} jwt = {version = "0.16", git = "https://github.com/glimberg/rust-jwt"}
serde = "1.0" serde = "1.0"
time = { version = "0.3", features = ["formatting"] } time = { version = "0.3", features = ["formatting"] }
bytes = "1.1" bytes = "1.3"
thiserror = "1" thiserror = "1"
tokio = { version = ">=1.24" } tokio = { version = ">=1.24" }

View File

@ -0,0 +1 @@
{"files":{"Cargo.lock":"ba3f757ac955932ea298d293ce44dfb25e064ffac93e429c13bd684a686d2ec1","Cargo.toml":"3402755896ee085ef905b9ff4d2ed25040e4fd53e254353f1bb044252404072b","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"0dd882e53de11566d50f8e8e2d5a651bcf3fabee4987d70f306233cf39094ba7","README.md":"2810098d290f3df719e6f41ffca38bb954d0fe62d4e56905a9a2436c4784bebf","RELEASE-NOTES.md":"5aaccee22ad349c72531bdd6c68b233bccc8c1eb23dce2cf5e05ae4498a15a1a","benches/benchmarks.rs":"bc1f603c5aa87627a93eee71eaed64fbd767d013051bac00ea265c16fecb30b9","examples/base64.rs":"f397b8726df41fce0793a8c6ebe95d4651aa37ed746da305032f1e99d9c37235","examples/make_tables.rs":"392f51b3edb1b5a2c62b823337c7785a6b8535f39f09283b1913a5c68fb186bf","icon_CLion.svg":"cffa044ba75cb998ee3306991dc4a3755ec2f39ab95ddd4b74bc21988389020f","src/chunked_encoder.rs":"fba5ea5f1204b9bf11291ec3483bcb23d330101eb2f6635e33cd63e4de13b653","src/decode.rs":"fa18535da4c549bdb29e4c4f074387645467384f88a1ecdb72c31828bd930ee3","src/display.rs":"55f9cd05ba037b77689e2558fa174055fad5273959d1edce3a2289f94244fd5d","src/encode.rs":"8a0a6b71581b4c52c2844111a3611cf73522533452a27f5ef8d09eaa73594e2e","src/lib.rs":"c7b904fac8706bc4758c2763e7a43dc1edd99ed5641ac2355957f6aeff91eece","src/read/decoder.rs":"9a7b65e165f7aed6b007bf7436ac9ba9b03d3b03e9d5a1e16691874e21509ced","src/read/decoder_tests.rs":"aacb7624c33ed6b90e068ff9af6095c839b4088060b4c406c08dce25ce837f6d","src/read/mod.rs":"e0b714eda02d16b1ffa6f78fd09b2f963e01c881b1f7c17b39db4e904be5e746","src/tables.rs":"73ce100fd3f4917ec1e8d9277ff0b956cc2636b33145f101a7cf1a5a8b7bacc1","src/tests.rs":"202ddced9cf52205182c6202e583c4c4f929b9d225fd9d1ebdbfd389cb2df0ba","src/write/encoder.rs":"afabacf7fa54f2ec9b1fe4987de818d368d7470ade0396649743a11c81dba28e","src/write/encoder_string_writer.rs":"3f9109585babe048230659f64973cb1633bbb2ed9de255177336260226127b81","src/write/encoder_tests.rs":"381d7c2871407157c36e909c928307ac0389b3d4504fb80607134e94ac59e68f","src/write/mod.rs":"1503b9457e4f5d2895b24136c3af893f0b7ce18dfe4de1096fc5d17f8d78e99b","tests/decode.rs":"da2cbd49b84e0d8b1d8a52136ba3d97cfb248920a45f9955db1e5bc5367218ce","tests/encode.rs":"5efb6904c36c6f899a05078e5c9be756fc58af1ee9940edfa8dea1ee53675364","tests/helpers.rs":"a76015e4a4e8f98213bdbaa592cd9574ccdc95a28e1b1f835a2753e09fa6037f","tests/tests.rs":"05753e5f1d4a6c75015a5342f9b5dc3073c00bdfe0a829a962f8723321c75549"},"package":"904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"}

826
zeroidc/vendor/base64-0.13.0/Cargo.lock generated vendored Normal file
View File

@ -0,0 +1,826 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "ansi_term"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"hermit-abi 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "autocfg"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "autocfg"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "base64"
version = "0.13.0"
dependencies = [
"criterion 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"structopt 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "bstr"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-automata 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bumpalo"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "byteorder"
version = "1.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cast"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "clap"
version = "2.33.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"vec_map 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "cloudabi"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "criterion"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
"cast 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.33.1 (registry+https://github.com/rust-lang/crates.io-index)",
"criterion-plot 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
"csv 1.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"itertools 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
"oorandom 11.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"plotters 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)",
"rayon 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.57 (registry+https://github.com/rust-lang/crates.io-index)",
"tinytemplate 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "criterion-plot"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cast 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"itertools 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crossbeam-deque"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crossbeam-epoch 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crossbeam-epoch"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"memoffset 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
"scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crossbeam-queue"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crossbeam-utils"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "csv"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bstr 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
"csv-core 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"itoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"ryu 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "csv-core"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "either"
version = "1.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "heck"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "hermit-abi"
version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "itertools"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "itoa"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "js-sys"
version = "0.3.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"wasm-bindgen 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "libc"
version = "0.2.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "log"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "maybe-uninit"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "memchr"
version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "memoffset"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num-traits"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num_cpus"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"hermit-abi 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "oorandom"
version = "11.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "plotters"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"js-sys 0.3.44 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
"web-sys 0.3.44 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro-error-attr 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)",
"version_check 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"version_check 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "proc-macro2"
version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "quote"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand_chacha"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand_core"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand_core"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "rand_hc"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand_isaac"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand_jitter"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand_os"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand_pcg"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand_xorshift"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rayon"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
"either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
"rayon-core 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rayon-core"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-queue 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rdrand"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "regex"
version = "1.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"regex-syntax 0.6.18 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "regex-automata"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "regex-syntax"
version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "rustc_version"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ryu"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "semver"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "semver-parser"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "serde"
version = "1.0.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "serde_derive"
version = "1.0.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "serde_json"
version = "1.0.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"itoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"ryu 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "structopt"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"clap 2.33.1 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"structopt-derive 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "structopt-derive"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro-error 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "syn"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "tinytemplate"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.57 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "unicode-segmentation"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-width"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-xid"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "version_check"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "walkdir"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen-macro 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bumpalo 3.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen-shared 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen-macro-support 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen-backend 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen-shared 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "web-sys"
version = "0.3.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"js-sys 0.3.44 (registry+https://github.com/rust-lang/crates.io-index)",
"wasm-bindgen 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[metadata]
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
"checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
"checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
"checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
"checksum bstr 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "31accafdb70df7871592c058eca3985b71104e15ac32f64706022c58867da931"
"checksum bumpalo 3.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
"checksum byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
"checksum cast 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b9434b9a5aa1450faa3f9cb14ea0e8c53bb5d2b3c1bfd1ab4fc03e9f33fbfb0"
"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
"checksum clap 2.33.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129"
"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
"checksum criterion 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "63f696897c88b57f4ffe3c69d8e1a0613c7d0e6c4833363c8560fbde9c47b966"
"checksum criterion-plot 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e022feadec601fba1649cfa83586381a4ad31c6bf3a9ab7d408118b05dd9889d"
"checksum crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285"
"checksum crossbeam-epoch 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace"
"checksum crossbeam-queue 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570"
"checksum crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
"checksum csv 1.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "00affe7f6ab566df61b4be3ce8cf16bc2576bca0963ceb0955e45d514bf9a279"
"checksum csv-core 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90"
"checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3"
"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
"checksum hermit-abi 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "3deed196b6e7f9e44a2ae8d94225d80302d81208b1bb673fd21fe634645c85a9"
"checksum itertools 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
"checksum itoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
"checksum js-sys 0.3.44 (registry+https://github.com/rust-lang/crates.io-index)" = "85a7e2c92a4804dd459b86c339278d0fe87cf93757fae222c3fa3ae75458bc73"
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
"checksum libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)" = "a2f02823cf78b754822df5f7f268fb59822e7296276d3e069d8e8cb26a14bd10"
"checksum log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
"checksum maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
"checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
"checksum memoffset 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "c198b026e1bbf08a937e94c6c60f9ec4a2267f5b0d2eec9c1b21b061ce2be55f"
"checksum num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611"
"checksum num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
"checksum oorandom 11.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a170cebd8021a008ea92e4db85a72f80b35df514ec664b296fdcbb654eac0b2c"
"checksum plotters 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)" = "0d1685fbe7beba33de0330629da9d955ac75bd54f33d7b79f9a895590124f6bb"
"checksum proc-macro-error 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
"checksum proc-macro-error-attr 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
"checksum proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)" = "04f5f085b5d71e2188cb8271e5da0161ad52c3f227a661a3c135fdf28e258b12"
"checksum quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
"checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca"
"checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef"
"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
"checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
"checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4"
"checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08"
"checksum rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b"
"checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
"checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44"
"checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c"
"checksum rayon 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "62f02856753d04e03e26929f820d0a0a337ebe71f849801eea335d464b349080"
"checksum rayon-core 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e92e15d89083484e11353891f1af602cc661426deb9564c298b270c726973280"
"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
"checksum regex 1.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6"
"checksum regex-automata 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4"
"checksum regex-syntax 0.6.18 (registry+https://github.com/rust-lang/crates.io-index)" = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8"
"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
"checksum ryu 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
"checksum same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
"checksum scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
"checksum serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)" = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3"
"checksum serde_derive 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)" = "2a0be94b04690fbaed37cddffc5c134bf537c8e3329d53e982fe04c374978f8e"
"checksum serde_json 1.0.57 (registry+https://github.com/rust-lang/crates.io-index)" = "164eacbdb13512ec2745fb09d51fd5b22b0d65ed294a1dcf7285a360c80a675c"
"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
"checksum structopt 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "6cc388d94ffabf39b5ed5fadddc40147cb21e605f53db6f8f36a625d27489ac5"
"checksum structopt-derive 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "5e2513111825077552a6751dfad9e11ce0fba07d7276a3943a037d7e93e64c5f"
"checksum syn 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)" = "4cdb98bcb1f9d81d07b536179c269ea15999b5d14ea958196413869445bb5250"
"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
"checksum tinytemplate 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6d3dc76004a03cec1c5932bca4cdc2e39aaa798e3f82363dd94f9adf6098c12f"
"checksum unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0"
"checksum unicode-width 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
"checksum unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
"checksum vec_map 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
"checksum version_check 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
"checksum walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d"
"checksum wasm-bindgen 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)" = "f0563a9a4b071746dd5aedbc3a28c6fe9be4586fb3fbadb67c400d4f53c6b16c"
"checksum wasm-bindgen-backend 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)" = "bc71e4c5efa60fb9e74160e89b93353bc24059999c0ae0fb03affc39770310b0"
"checksum wasm-bindgen-macro 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)" = "97c57cefa5fa80e2ba15641578b44d36e7a64279bc5ed43c6dbaf329457a2ed2"
"checksum wasm-bindgen-macro-support 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)" = "841a6d1c35c6f596ccea1f82504a192a60378f64b3bb0261904ad8f2f5657556"
"checksum wasm-bindgen-shared 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)" = "93b162580e34310e5931c4b792560108b10fd14d64915d7fff8ff00180e70092"
"checksum web-sys 0.3.44 (registry+https://github.com/rust-lang/crates.io-index)" = "dda38f4e5ca63eda02c059d243aa25b5f35ab98451e518c51612cd0f1bd19a47"
"checksum winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
"checksum winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

43
zeroidc/vendor/base64-0.13.0/Cargo.toml vendored Normal file
View File

@ -0,0 +1,43 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies
#
# If you believe there's an error in this file please file an
# issue against the rust-lang/cargo repository. If you're
# editing this file be aware that the upstream Cargo.toml
# will likely look very different (and much more reasonable)
[package]
edition = "2018"
name = "base64"
version = "0.13.0"
authors = ["Alice Maz <alice@alicemaz.com>", "Marshall Pierce <marshall@mpierce.org>"]
description = "encodes and decodes base64 as bytes or utf8"
documentation = "https://docs.rs/base64"
readme = "README.md"
keywords = ["base64", "utf8", "encode", "decode", "no_std"]
categories = ["encoding"]
license = "MIT/Apache-2.0"
repository = "https://github.com/marshallpierce/rust-base64"
[profile.bench]
debug = true
[[bench]]
name = "benchmarks"
harness = false
[dev-dependencies.criterion]
version = "=0.3.2"
[dev-dependencies.rand]
version = "0.6.1"
[dev-dependencies.structopt]
version = "0.3"
[features]
alloc = []
default = ["std"]
std = []

View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 Alice Maz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

114
zeroidc/vendor/base64-0.13.0/README.md vendored Normal file
View File

@ -0,0 +1,114 @@
[base64](https://crates.io/crates/base64)
===
[![](https://img.shields.io/crates/v/base64.svg)](https://crates.io/crates/base64) [![Docs](https://docs.rs/base64/badge.svg)](https://docs.rs/base64) [![Build](https://travis-ci.org/marshallpierce/rust-base64.svg?branch=master)](https://travis-ci.org/marshallpierce/rust-base64) [![codecov](https://codecov.io/gh/marshallpierce/rust-base64/branch/master/graph/badge.svg)](https://codecov.io/gh/marshallpierce/rust-base64) [![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/)
<a href="https://www.jetbrains.com/?from=rust-base64"><img src="/icon_CLion.svg" height="40px"/></a>
Made with CLion. Thanks to JetBrains for supporting open source!
It's base64. What more could anyone want?
This library's goals are to be *correct* and *fast*. It's thoroughly tested and widely used. It exposes functionality at multiple levels of abstraction so you can choose the level of convenience vs performance that you want, e.g. `decode_config_slice` decodes into an existing `&mut [u8]` and is pretty fast (2.6GiB/s for a 3 KiB input), whereas `decode_config` allocates a new `Vec<u8>` and returns it, which might be more convenient in some cases, but is slower (although still fast enough for almost any purpose) at 2.1 GiB/s.
Example
---
```rust
extern crate base64;
use base64::{encode, decode};
fn main() {
let a = b"hello world";
let b = "aGVsbG8gd29ybGQ=";
assert_eq!(encode(a), b);
assert_eq!(a, &decode(b).unwrap()[..]);
}
```
See the [docs](https://docs.rs/base64) for all the details.
Rust version compatibility
---
The minimum required Rust version is 1.34.0.
Developing
---
Benchmarks are in `benches/`. Running them requires nightly rust, but `rustup` makes it easy:
```bash
rustup run nightly cargo bench
```
Decoding is aided by some pre-calculated tables, which are generated by:
```bash
cargo run --example make_tables > src/tables.rs.tmp && mv src/tables.rs.tmp src/tables.rs
```
no_std
---
This crate supports no_std. By default the crate targets std via the `std` feature. You can deactivate the `default-features` to target core instead. In that case you lose out on all the functionality revolving around `std::io`, `std::error::Error` and heap allocations. There is an additional `alloc` feature that you can activate to bring back the support for heap allocations.
Profiling
---
On Linux, you can use [perf](https://perf.wiki.kernel.org/index.php/Main_Page) for profiling. Then compile the benchmarks with `rustup nightly run cargo bench --no-run`.
Run the benchmark binary with `perf` (shown here filtering to one particular benchmark, which will make the results easier to read). `perf` is only available to the root user on most systems as it fiddles with event counters in your CPU, so use `sudo`. We need to run the actual benchmark binary, hence the path into `target`. You can see the actual full path with `rustup run nightly cargo bench -v`; it will print out the commands it runs. If you use the exact path that `bench` outputs, make sure you get the one that's for the benchmarks, not the tests. You may also want to `cargo clean` so you have only one `benchmarks-` binary (they tend to accumulate).
```bash
sudo perf record target/release/deps/benchmarks-* --bench decode_10mib_reuse
```
Then analyze the results, again with perf:
```bash
sudo perf annotate -l
```
You'll see a bunch of interleaved rust source and assembly like this. The section with `lib.rs:327` is telling us that 4.02% of samples saw the `movzbl` aka bit shift as the active instruction. However, this percentage is not as exact as it seems due to a phenomenon called *skid*. Basically, a consequence of how fancy modern CPUs are is that this sort of instruction profiling is inherently inaccurate, especially in branch-heavy code.
```text
lib.rs:322 0.70 : 10698: mov %rdi,%rax
2.82 : 1069b: shr $0x38,%rax
: if morsel == decode_tables::INVALID_VALUE {
: bad_byte_index = input_index;
: break;
: };
: accum = (morsel as u64) << 58;
lib.rs:327 4.02 : 1069f: movzbl (%r9,%rax,1),%r15d
: // fast loop of 8 bytes at a time
: while input_index < length_of_full_chunks {
: let mut accum: u64;
:
: let input_chunk = BigEndian::read_u64(&input_bytes[input_index..(input_index + 8)]);
: morsel = decode_table[(input_chunk >> 56) as usize];
lib.rs:322 3.68 : 106a4: cmp $0xff,%r15
: if morsel == decode_tables::INVALID_VALUE {
0.00 : 106ab: je 1090e <base64::decode_config_buf::hbf68a45fefa299c1+0x46e>
```
Fuzzing
---
This uses [cargo-fuzz](https://github.com/rust-fuzz/cargo-fuzz). See `fuzz/fuzzers` for the available fuzzing scripts. To run, use an invocation like these:
```bash
cargo +nightly fuzz run roundtrip
cargo +nightly fuzz run roundtrip_no_pad
cargo +nightly fuzz run roundtrip_random_config -- -max_len=10240
cargo +nightly fuzz run decode_random
```
License
---
This project is dual-licensed under MIT and Apache 2.0.

View File

@ -0,0 +1,105 @@
# 0.13.0
- Config methods are const
- Added `EncoderStringWriter` to allow encoding directly to a String
- `EncoderWriter` now owns its delegate writer rather than keeping a reference to it (though refs still work)
- As a consequence, it is now possible to extract the delegate writer from an `EncoderWriter` via `finish()`, which returns `Result<W>` instead of `Result<()>`. If you were calling `finish()` explicitly, you will now need to use `let _ = foo.finish()` instead of just `foo.finish()` to avoid a warning about the unused value.
- When decoding input that has both an invalid length and an invalid symbol as the last byte, `InvalidByte` will be emitted instead of `InvalidLength` to make the problem more obvious.
# 0.12.2
- Add `BinHex` alphabet
# 0.12.1
- Add `Bcrypt` alphabet
# 0.12.0
- A `Read` implementation (`DecoderReader`) to let users transparently decoded data from a b64 input source
- IMAP's modified b64 alphabet
- Relaxed type restrictions to just `AsRef<[ut8]>` for main `encode*`/`decode*` functions
- A minor performance improvement in encoding
# 0.11.0
- Minimum rust version 1.34.0
- `no_std` is now supported via the two new features `alloc` and `std`.
# 0.10.1
- Minimum rust version 1.27.2
- Fix bug in streaming encoding ([#90](https://github.com/marshallpierce/rust-base64/pull/90)): if the underlying writer didn't write all the bytes given to it, the remaining bytes would not be retried later. See the docs on `EncoderWriter::write`.
- Make it configurable whether or not to return an error when decoding detects excess trailing bits.
# 0.10.0
- Remove line wrapping. Line wrapping was never a great conceptual fit in this library, and other features (streaming encoding, etc) either couldn't support it or could support only special cases of it with a great increase in complexity. Line wrapping has been pulled out into a [line-wrap](https://crates.io/crates/line-wrap) crate, so it's still available if you need it.
- `Base64Display` creation no longer uses a `Result` because it can't fail, which means its helper methods for common
configs that `unwrap()` for you are no longer needed
- Add a streaming encoder `Write` impl to transparently base64 as you write.
- Remove the remaining `unsafe` code.
- Remove whitespace stripping to simplify `no_std` support. No out of the box configs use it, and it's trivial to do yourself if needed: `filter(|b| !b" \n\t\r\x0b\x0c".contains(b)`.
- Detect invalid trailing symbols when decoding and return an error rather than silently ignoring them.
# 0.9.3
- Update safemem
# 0.9.2
- Derive `Clone` for `DecodeError`.
# 0.9.1
- Add support for `crypt(3)`'s base64 variant.
# 0.9.0
- `decode_config_slice` function for no-allocation decoding, analogous to `encode_config_slice`
- Decode performance optimization
# 0.8.0
- `encode_config_slice` function for no-allocation encoding
# 0.7.0
- `STANDARD_NO_PAD` config
- `Base64Display` heap-free wrapper for use in format strings, etc
# 0.6.0
- Decode performance improvements
- Use `unsafe` in fewer places
- Added fuzzers
# 0.5.2
- Avoid usize overflow when calculating length
- Better line wrapping performance
# 0.5.1
- Temporarily disable line wrapping
- Add Apache 2.0 license
# 0.5.0
- MIME support, including configurable line endings and line wrapping
- Removed `decode_ws`
- Renamed `Base64Error` to `DecodeError`
# 0.4.1
- Allow decoding a `AsRef<[u8]>` instead of just a `&str`
# 0.4.0
- Configurable padding
- Encode performance improvements
# 0.3.0
- Added encode/decode functions that do not allocate their own storage
- Decode performance improvements
- Extraneous padding bytes are no longer ignored. Now, an error will be returned.

View File

@ -0,0 +1,210 @@
extern crate base64;
#[macro_use]
extern crate criterion;
extern crate rand;
use base64::display;
use base64::{
decode, decode_config_buf, decode_config_slice, encode, encode_config_buf, encode_config_slice,
write, Config,
};
use criterion::{black_box, Bencher, Criterion, ParameterizedBenchmark, Throughput};
use rand::{FromEntropy, Rng};
use std::io::{self, Read, Write};
const TEST_CONFIG: Config = base64::STANDARD;
fn do_decode_bench(b: &mut Bencher, &size: &usize) {
let mut v: Vec<u8> = Vec::with_capacity(size * 3 / 4);
fill(&mut v);
let encoded = encode(&v);
b.iter(|| {
let orig = decode(&encoded);
black_box(&orig);
});
}
fn do_decode_bench_reuse_buf(b: &mut Bencher, &size: &usize) {
let mut v: Vec<u8> = Vec::with_capacity(size * 3 / 4);
fill(&mut v);
let encoded = encode(&v);
let mut buf = Vec::new();
b.iter(|| {
decode_config_buf(&encoded, TEST_CONFIG, &mut buf).unwrap();
black_box(&buf);
buf.clear();
});
}
fn do_decode_bench_slice(b: &mut Bencher, &size: &usize) {
let mut v: Vec<u8> = Vec::with_capacity(size * 3 / 4);
fill(&mut v);
let encoded = encode(&v);
let mut buf = Vec::new();
buf.resize(size, 0);
b.iter(|| {
decode_config_slice(&encoded, TEST_CONFIG, &mut buf).unwrap();
black_box(&buf);
});
}
fn do_decode_bench_stream(b: &mut Bencher, &size: &usize) {
let mut v: Vec<u8> = Vec::with_capacity(size * 3 / 4);
fill(&mut v);
let encoded = encode(&v);
let mut buf = Vec::new();
buf.resize(size, 0);
buf.truncate(0);
b.iter(|| {
let mut cursor = io::Cursor::new(&encoded[..]);
let mut decoder = base64::read::DecoderReader::new(&mut cursor, TEST_CONFIG);
decoder.read_to_end(&mut buf).unwrap();
buf.clear();
black_box(&buf);
});
}
fn do_encode_bench(b: &mut Bencher, &size: &usize) {
let mut v: Vec<u8> = Vec::with_capacity(size);
fill(&mut v);
b.iter(|| {
let e = encode(&v);
black_box(&e);
});
}
fn do_encode_bench_display(b: &mut Bencher, &size: &usize) {
let mut v: Vec<u8> = Vec::with_capacity(size);
fill(&mut v);
b.iter(|| {
let e = format!("{}", display::Base64Display::with_config(&v, TEST_CONFIG));
black_box(&e);
});
}
fn do_encode_bench_reuse_buf(b: &mut Bencher, &size: &usize) {
let mut v: Vec<u8> = Vec::with_capacity(size);
fill(&mut v);
let mut buf = String::new();
b.iter(|| {
encode_config_buf(&v, TEST_CONFIG, &mut buf);
buf.clear();
});
}
fn do_encode_bench_slice(b: &mut Bencher, &size: &usize) {
let mut v: Vec<u8> = Vec::with_capacity(size);
fill(&mut v);
let mut buf = Vec::new();
// conservative estimate of encoded size
buf.resize(v.len() * 2, 0);
b.iter(|| {
encode_config_slice(&v, TEST_CONFIG, &mut buf);
});
}
fn do_encode_bench_stream(b: &mut Bencher, &size: &usize) {
let mut v: Vec<u8> = Vec::with_capacity(size);
fill(&mut v);
let mut buf = Vec::new();
buf.reserve(size * 2);
b.iter(|| {
buf.clear();
let mut stream_enc = write::EncoderWriter::new(&mut buf, TEST_CONFIG);
stream_enc.write_all(&v).unwrap();
stream_enc.flush().unwrap();
});
}
fn do_encode_bench_string_stream(b: &mut Bencher, &size: &usize) {
let mut v: Vec<u8> = Vec::with_capacity(size);
fill(&mut v);
b.iter(|| {
let mut stream_enc = write::EncoderStringWriter::new(TEST_CONFIG);
stream_enc.write_all(&v).unwrap();
stream_enc.flush().unwrap();
let _ = stream_enc.into_inner();
});
}
fn do_encode_bench_string_reuse_buf_stream(b: &mut Bencher, &size: &usize) {
let mut v: Vec<u8> = Vec::with_capacity(size);
fill(&mut v);
let mut buf = String::new();
b.iter(|| {
buf.clear();
let mut stream_enc = write::EncoderStringWriter::from(&mut buf, TEST_CONFIG);
stream_enc.write_all(&v).unwrap();
stream_enc.flush().unwrap();
let _ = stream_enc.into_inner();
});
}
fn fill(v: &mut Vec<u8>) {
let cap = v.capacity();
// weak randomness is plenty; we just want to not be completely friendly to the branch predictor
let mut r = rand::rngs::SmallRng::from_entropy();
while v.len() < cap {
v.push(r.gen::<u8>());
}
}
const BYTE_SIZES: [usize; 5] = [3, 50, 100, 500, 3 * 1024];
// Benchmarks over these byte sizes take longer so we will run fewer samples to
// keep the benchmark runtime reasonable.
const LARGE_BYTE_SIZES: [usize; 3] = [3 * 1024 * 1024, 10 * 1024 * 1024, 30 * 1024 * 1024];
fn encode_benchmarks(byte_sizes: &[usize]) -> ParameterizedBenchmark<usize> {
ParameterizedBenchmark::new("encode", do_encode_bench, byte_sizes.iter().cloned())
.warm_up_time(std::time::Duration::from_millis(500))
.measurement_time(std::time::Duration::from_secs(3))
.throughput(|s| Throughput::Bytes(*s as u64))
.with_function("encode_display", do_encode_bench_display)
.with_function("encode_reuse_buf", do_encode_bench_reuse_buf)
.with_function("encode_slice", do_encode_bench_slice)
.with_function("encode_reuse_buf_stream", do_encode_bench_stream)
.with_function("encode_string_stream", do_encode_bench_string_stream)
.with_function(
"encode_string_reuse_buf_stream",
do_encode_bench_string_reuse_buf_stream,
)
}
fn decode_benchmarks(byte_sizes: &[usize]) -> ParameterizedBenchmark<usize> {
ParameterizedBenchmark::new("decode", do_decode_bench, byte_sizes.iter().cloned())
.warm_up_time(std::time::Duration::from_millis(500))
.measurement_time(std::time::Duration::from_secs(3))
.throughput(|s| Throughput::Bytes(*s as u64))
.with_function("decode_reuse_buf", do_decode_bench_reuse_buf)
.with_function("decode_slice", do_decode_bench_slice)
.with_function("decode_stream", do_decode_bench_stream)
}
fn bench(c: &mut Criterion) {
c.bench("bench_small_input", encode_benchmarks(&BYTE_SIZES[..]));
c.bench(
"bench_large_input",
encode_benchmarks(&LARGE_BYTE_SIZES[..]).sample_size(10),
);
c.bench("bench_small_input", decode_benchmarks(&BYTE_SIZES[..]));
c.bench(
"bench_large_input",
decode_benchmarks(&LARGE_BYTE_SIZES[..]).sample_size(10),
);
}
criterion_group!(benches, bench);
criterion_main!(benches);

View File

@ -0,0 +1,89 @@
use std::fs::File;
use std::io::{self, Read};
use std::path::PathBuf;
use std::process;
use std::str::FromStr;
use base64::{read, write};
use structopt::StructOpt;
#[derive(Debug, StructOpt)]
enum CharacterSet {
Standard,
UrlSafe,
}
impl Default for CharacterSet {
fn default() -> Self {
CharacterSet::Standard
}
}
impl Into<base64::Config> for CharacterSet {
fn into(self) -> base64::Config {
match self {
CharacterSet::Standard => base64::STANDARD,
CharacterSet::UrlSafe => base64::URL_SAFE,
}
}
}
impl FromStr for CharacterSet {
type Err = String;
fn from_str(s: &str) -> Result<CharacterSet, String> {
match s {
"standard" => Ok(CharacterSet::Standard),
"urlsafe" => Ok(CharacterSet::UrlSafe),
_ => Err(format!("charset '{}' unrecognized", s)),
}
}
}
/// Base64 encode or decode FILE (or standard input), to standard output.
#[derive(Debug, StructOpt)]
struct Opt {
/// decode data
#[structopt(short = "d", long = "decode")]
decode: bool,
/// The character set to choose. Defaults to the standard base64 character set.
/// Supported character sets include "standard" and "urlsafe".
#[structopt(long = "charset")]
charset: Option<CharacterSet>,
/// The file to encode/decode.
#[structopt(parse(from_os_str))]
file: Option<PathBuf>,
}
fn main() {
let opt = Opt::from_args();
let stdin;
let mut input: Box<dyn Read> = match opt.file {
None => {
stdin = io::stdin();
Box::new(stdin.lock())
}
Some(ref f) if f.as_os_str() == "-" => {
stdin = io::stdin();
Box::new(stdin.lock())
}
Some(f) => Box::new(File::open(f).unwrap()),
};
let config = opt.charset.unwrap_or_default().into();
let stdout = io::stdout();
let mut stdout = stdout.lock();
let r = if opt.decode {
let mut decoder = read::DecoderReader::new(&mut input, config);
io::copy(&mut decoder, &mut stdout)
} else {
let mut encoder = write::EncoderWriter::new(&mut stdout, config);
io::copy(&mut input, &mut encoder)
};
if let Err(e) = r {
eprintln!(
"Base64 {} failed with {}",
if opt.decode { "decode" } else { "encode" },
e
);
process::exit(1);
}
}

View File

@ -0,0 +1,34 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 128 128">
<defs>
<linearGradient id="linear-gradient" x1="40.69" y1="-676.56" x2="83.48" y2="-676.56" gradientTransform="matrix(1, 0, 0, -1, 0, -648.86)" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#ed358c"/>
<stop offset="0.16" stop-color="#e9388c"/>
<stop offset="0.3" stop-color="#de418c"/>
<stop offset="0.43" stop-color="#cc508c"/>
<stop offset="0.57" stop-color="#b2658d"/>
<stop offset="0.7" stop-color="#90808d"/>
<stop offset="0.83" stop-color="#67a18e"/>
<stop offset="0.95" stop-color="#37c78f"/>
<stop offset="1" stop-color="#22d88f"/>
</linearGradient>
<linearGradient id="linear-gradient-2" x1="32.58" y1="-665.27" x2="13.76" y2="-791.59" gradientTransform="matrix(1, 0, 0, -1, 0, -648.86)" gradientUnits="userSpaceOnUse">
<stop offset="0.09" stop-color="#22d88f"/>
<stop offset="0.9" stop-color="#029de0"/>
</linearGradient>
<linearGradient id="linear-gradient-3" x1="116.68" y1="-660.66" x2="-12.09" y2="-796.66" xlink:href="#linear-gradient-2"/>
<linearGradient id="linear-gradient-4" x1="73.35" y1="-739.1" x2="122.29" y2="-746.06" xlink:href="#linear-gradient-2"/>
</defs>
<title>icon_CLion</title>
<g>
<polygon points="49.2 51.8 40.6 55.4 48.4 0 77.8 16.2 49.2 51.8" fill="url(#linear-gradient)"/>
<polygon points="44.6 76.8 48.8 0 11.8 23.2 0 94 44.6 76.8" fill="url(#linear-gradient-2)"/>
<polygon points="125.4 38.4 109 4.8 77.8 16.2 55 41.4 0 94 41.6 124.4 93.6 77.2 125.4 38.4" fill="url(#linear-gradient-3)"/>
<polygon points="53.8 54.6 46.6 98.4 75.8 121 107.8 128 128 82.4 53.8 54.6" fill="url(#linear-gradient-4)"/>
</g>
<g>
<rect x="24" y="24" width="80" height="80"/>
<rect x="31.6" y="89" width="30" height="5" fill="#fff"/>
<path d="M31,51.2h0A16.83,16.83,0,0,1,48.2,34c6.2,0,10,2,13,5.2l-4.6,5.4c-2.6-2.4-5.2-3.8-8.4-3.8-5.6,0-9.6,4.6-9.6,10.4h0c0,5.6,4,10.4,9.6,10.4,3.8,0,6.2-1.6,8.8-3.8l4.6,4.6c-3.4,3.6-7.2,6-13.6,6A17,17,0,0,1,31,51.2" fill="#fff"/>
<path d="M66.6,34.4H74v27H88.4v6.2H66.6V34.4Z" fill="#fff"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,247 @@
use crate::{
encode::{add_padding, encode_to_slice},
Config,
};
#[cfg(any(feature = "alloc", feature = "std", test))]
use alloc::string::String;
use core::cmp;
#[cfg(any(feature = "alloc", feature = "std", test))]
use core::str;
/// The output mechanism for ChunkedEncoder's encoded bytes.
pub trait Sink {
type Error;
/// Handle a chunk of encoded base64 data (as UTF-8 bytes)
fn write_encoded_bytes(&mut self, encoded: &[u8]) -> Result<(), Self::Error>;
}
const BUF_SIZE: usize = 1024;
/// A base64 encoder that emits encoded bytes in chunks without heap allocation.
pub struct ChunkedEncoder {
config: Config,
max_input_chunk_len: usize,
}
impl ChunkedEncoder {
pub fn new(config: Config) -> ChunkedEncoder {
ChunkedEncoder {
config,
max_input_chunk_len: max_input_length(BUF_SIZE, config),
}
}
pub fn encode<S: Sink>(&self, bytes: &[u8], sink: &mut S) -> Result<(), S::Error> {
let mut encode_buf: [u8; BUF_SIZE] = [0; BUF_SIZE];
let encode_table = self.config.char_set.encode_table();
let mut input_index = 0;
while input_index < bytes.len() {
// either the full input chunk size, or it's the last iteration
let input_chunk_len = cmp::min(self.max_input_chunk_len, bytes.len() - input_index);
let chunk = &bytes[input_index..(input_index + input_chunk_len)];
let mut b64_bytes_written = encode_to_slice(chunk, &mut encode_buf, encode_table);
input_index += input_chunk_len;
let more_input_left = input_index < bytes.len();
if self.config.pad && !more_input_left {
// no more input, add padding if needed. Buffer will have room because
// max_input_length leaves room for it.
b64_bytes_written += add_padding(bytes.len(), &mut encode_buf[b64_bytes_written..]);
}
sink.write_encoded_bytes(&encode_buf[0..b64_bytes_written])?;
}
Ok(())
}
}
/// Calculate the longest input that can be encoded for the given output buffer size.
///
/// If the config requires padding, two bytes of buffer space will be set aside so that the last
/// chunk of input can be encoded safely.
///
/// The input length will always be a multiple of 3 so that no encoding state has to be carried over
/// between chunks.
fn max_input_length(encoded_buf_len: usize, config: Config) -> usize {
let effective_buf_len = if config.pad {
// make room for padding
encoded_buf_len
.checked_sub(2)
.expect("Don't use a tiny buffer")
} else {
encoded_buf_len
};
// No padding, so just normal base64 expansion.
(effective_buf_len / 4) * 3
}
// A really simple sink that just appends to a string
#[cfg(any(feature = "alloc", feature = "std", test))]
pub(crate) struct StringSink<'a> {
string: &'a mut String,
}
#[cfg(any(feature = "alloc", feature = "std", test))]
impl<'a> StringSink<'a> {
pub(crate) fn new(s: &mut String) -> StringSink {
StringSink { string: s }
}
}
#[cfg(any(feature = "alloc", feature = "std", test))]
impl<'a> Sink for StringSink<'a> {
type Error = ();
fn write_encoded_bytes(&mut self, s: &[u8]) -> Result<(), Self::Error> {
self.string.push_str(str::from_utf8(s).unwrap());
Ok(())
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use crate::{encode_config_buf, tests::random_config, CharacterSet, STANDARD};
use rand::{
distributions::{Distribution, Uniform},
FromEntropy, Rng,
};
#[test]
fn chunked_encode_empty() {
assert_eq!("", chunked_encode_str(&[], STANDARD));
}
#[test]
fn chunked_encode_intermediate_fast_loop() {
// > 8 bytes input, will enter the pretty fast loop
assert_eq!(
"Zm9vYmFyYmF6cXV4",
chunked_encode_str(b"foobarbazqux", STANDARD)
);
}
#[test]
fn chunked_encode_fast_loop() {
// > 32 bytes input, will enter the uber fast loop
assert_eq!(
"Zm9vYmFyYmF6cXV4cXV1eGNvcmdlZ3JhdWx0Z2FycGx5eg==",
chunked_encode_str(b"foobarbazquxquuxcorgegraultgarplyz", STANDARD)
);
}
#[test]
fn chunked_encode_slow_loop_only() {
// < 8 bytes input, slow loop only
assert_eq!("Zm9vYmFy", chunked_encode_str(b"foobar", STANDARD));
}
#[test]
fn chunked_encode_matches_normal_encode_random_string_sink() {
let helper = StringSinkTestHelper;
chunked_encode_matches_normal_encode_random(&helper);
}
#[test]
fn max_input_length_no_pad() {
let config = config_with_pad(false);
assert_eq!(768, max_input_length(1024, config));
}
#[test]
fn max_input_length_with_pad_decrements_one_triple() {
let config = config_with_pad(true);
assert_eq!(765, max_input_length(1024, config));
}
#[test]
fn max_input_length_with_pad_one_byte_short() {
let config = config_with_pad(true);
assert_eq!(765, max_input_length(1025, config));
}
#[test]
fn max_input_length_with_pad_fits_exactly() {
let config = config_with_pad(true);
assert_eq!(768, max_input_length(1026, config));
}
#[test]
fn max_input_length_cant_use_extra_single_encoded_byte() {
let config = Config::new(crate::CharacterSet::Standard, false);
assert_eq!(300, max_input_length(401, config));
}
pub fn chunked_encode_matches_normal_encode_random<S: SinkTestHelper>(sink_test_helper: &S) {
let mut input_buf: Vec<u8> = Vec::new();
let mut output_buf = String::new();
let mut rng = rand::rngs::SmallRng::from_entropy();
let input_len_range = Uniform::new(1, 10_000);
for _ in 0..5_000 {
input_buf.clear();
output_buf.clear();
let buf_len = input_len_range.sample(&mut rng);
for _ in 0..buf_len {
input_buf.push(rng.gen());
}
let config = random_config(&mut rng);
let chunk_encoded_string = sink_test_helper.encode_to_string(config, &input_buf);
encode_config_buf(&input_buf, config, &mut output_buf);
assert_eq!(
output_buf, chunk_encoded_string,
"input len={}, config: pad={}",
buf_len, config.pad
);
}
}
fn chunked_encode_str(bytes: &[u8], config: Config) -> String {
let mut s = String::new();
{
let mut sink = StringSink::new(&mut s);
let encoder = ChunkedEncoder::new(config);
encoder.encode(bytes, &mut sink).unwrap();
}
return s;
}
fn config_with_pad(pad: bool) -> Config {
Config::new(CharacterSet::Standard, pad)
}
// An abstraction around sinks so that we can have tests that easily to any sink implementation
pub trait SinkTestHelper {
fn encode_to_string(&self, config: Config, bytes: &[u8]) -> String;
}
struct StringSinkTestHelper;
impl SinkTestHelper for StringSinkTestHelper {
fn encode_to_string(&self, config: Config, bytes: &[u8]) -> String {
let encoder = ChunkedEncoder::new(config);
let mut s = String::new();
{
let mut sink = StringSink::new(&mut s);
encoder.encode(bytes, &mut sink).unwrap();
}
s
}
}
}

View File

@ -0,0 +1,873 @@
use crate::{tables, Config, PAD_BYTE};
#[cfg(any(feature = "alloc", feature = "std", test))]
use crate::STANDARD;
#[cfg(any(feature = "alloc", feature = "std", test))]
use alloc::vec::Vec;
use core::fmt;
#[cfg(any(feature = "std", test))]
use std::error;
// 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;
/// Errors that can occur while decoding.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum DecodeError {
/// An invalid byte was found in the input. The offset and offending byte are provided.
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 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.
InvalidLastSymbol(usize, u8),
}
impl fmt::Display for DecodeError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
DecodeError::InvalidByte(index, byte) => {
write!(f, "Invalid byte {}, offset {}.", byte, index)
}
DecodeError::InvalidLength => write!(f, "Encoded text cannot have a 6-bit remainder."),
DecodeError::InvalidLastSymbol(index, byte) => {
write!(f, "Invalid last symbol {}, offset {}.", byte, index)
}
}
}
}
#[cfg(any(feature = "std", test))]
impl error::Error for DecodeError {
fn description(&self) -> &str {
match *self {
DecodeError::InvalidByte(_, _) => "invalid byte",
DecodeError::InvalidLength => "invalid length",
DecodeError::InvalidLastSymbol(_, _) => "invalid last symbol",
}
}
fn cause(&self) -> Option<&dyn error::Error> {
None
}
}
///Decode from string reference as octets.
///Returns a Result containing a Vec<u8>.
///Convenience `decode_config(input, base64::STANDARD);`.
///
///# Example
///
///```rust
///extern crate base64;
///
///fn main() {
/// let bytes = base64::decode("aGVsbG8gd29ybGQ=").unwrap();
/// println!("{:?}", bytes);
///}
///```
#[cfg(any(feature = "alloc", feature = "std", test))]
pub fn decode<T: AsRef<[u8]>>(input: T) -> Result<Vec<u8>, DecodeError> {
decode_config(input, STANDARD)
}
///Decode from string reference as octets.
///Returns a Result containing a Vec<u8>.
///
///# Example
///
///```rust
///extern crate base64;
///
///fn main() {
/// let bytes = base64::decode_config("aGVsbG8gd29ybGR+Cg==", base64::STANDARD).unwrap();
/// println!("{:?}", bytes);
///
/// let bytes_url = base64::decode_config("aGVsbG8gaW50ZXJuZXR-Cg==", base64::URL_SAFE).unwrap();
/// println!("{:?}", bytes_url);
///}
///```
#[cfg(any(feature = "alloc", feature = "std", test))]
pub fn decode_config<T: AsRef<[u8]>>(input: T, config: Config) -> Result<Vec<u8>, DecodeError> {
let mut buffer = Vec::<u8>::with_capacity(input.as_ref().len() * 4 / 3);
decode_config_buf(input, config, &mut buffer).map(|_| buffer)
}
///Decode from string reference as octets.
///Writes into the supplied buffer to avoid allocation.
///Returns a Result containing an empty tuple, aka ().
///
///# Example
///
///```rust
///extern crate base64;
///
///fn main() {
/// let mut buffer = Vec::<u8>::new();
/// base64::decode_config_buf("aGVsbG8gd29ybGR+Cg==", base64::STANDARD, &mut buffer).unwrap();
/// println!("{:?}", buffer);
///
/// buffer.clear();
///
/// base64::decode_config_buf("aGVsbG8gaW50ZXJuZXR-Cg==", base64::URL_SAFE, &mut buffer)
/// .unwrap();
/// println!("{:?}", buffer);
///}
///```
#[cfg(any(feature = "alloc", feature = "std", test))]
pub fn decode_config_buf<T: AsRef<[u8]>>(
input: T,
config: Config,
buffer: &mut Vec<u8>,
) -> Result<(), DecodeError> {
let input_bytes = input.as_ref();
let starting_output_len = buffer.len();
let num_chunks = num_chunks(input_bytes);
let decoded_len_estimate = num_chunks
.checked_mul(DECODED_CHUNK_LEN)
.and_then(|p| p.checked_add(starting_output_len))
.expect("Overflow when calculating output buffer length");
buffer.resize(decoded_len_estimate, 0);
let bytes_written;
{
let buffer_slice = &mut buffer.as_mut_slice()[starting_output_len..];
bytes_written = decode_helper(input_bytes, num_chunks, config, buffer_slice)?;
}
buffer.truncate(starting_output_len + bytes_written);
Ok(())
}
/// Decode the input into the provided output slice.
///
/// This will not write any bytes past exactly what is decoded (no stray garbage bytes at the end).
///
/// If you don't know ahead of time what the decoded length should be, size your buffer with a
/// conservative estimate for the decoded length of an input: 3 bytes of output for every 4 bytes of
/// input, rounded up, or in other words `(input_len + 3) / 4 * 3`.
///
/// If the slice is not large enough, this will panic.
pub fn decode_config_slice<T: AsRef<[u8]>>(
input: T,
config: Config,
output: &mut [u8],
) -> Result<usize, DecodeError> {
let input_bytes = input.as_ref();
decode_helper(input_bytes, num_chunks(input_bytes), config, output)
}
/// Return the number of input chunks (including a possibly partial final chunk) in the input
fn num_chunks(input: &[u8]) -> usize {
input
.len()
.checked_add(INPUT_CHUNK_LEN - 1)
.expect("Overflow when calculating number of chunks in input")
/ INPUT_CHUNK_LEN
}
/// Helper to avoid duplicating num_chunks calculation, which is costly on short inputs.
/// Returns the number of bytes written, or an error.
// We're on the fragile edge of compiler heuristics here. If this is not inlined, slow. If this is
// inlined(always), a different slow. plain ol' inline makes the benchmarks happiest at the moment,
// but this is fragile and the best setting changes with only minor code modifications.
#[inline]
fn decode_helper(
input: &[u8],
num_chunks: usize,
config: Config,
output: &mut [u8],
) -> Result<usize, DecodeError> {
let char_set = config.char_set;
let decode_table = char_set.decode_table();
let remainder_len = input.len() % INPUT_CHUNK_LEN;
// 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] == tables::INVALID_VALUE {
return Err(DecodeError::InvalidByte(input.len() - 1, *b));
}
}
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 unpadded 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,
};
// rounded up to include partial chunks
let mut remaining_chunks = num_chunks;
let mut input_index = 0;
let mut output_index = 0;
{
let length_of_fast_decode_chunks = input.len().saturating_sub(trailing_bytes_to_skip);
// 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(
&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..],
)?;
input_index += INPUT_BLOCK_LEN;
output_index += DECODED_BLOCK_LEN - DECODED_CHUNK_SUFFIX;
remaining_chunks -= CHUNKS_PER_FAST_LOOP_BLOCK;
}
}
// 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;
}
}
}
// 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);
// Stage 4
// Finally, 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;
let mut morsels_in_leftover = 0;
let mut padding_bytes = 0;
let mut first_padding_index: usize = 0;
let mut last_symbol = 0_u8;
let start_of_leftovers = input_index;
for (i, b) in input[start_of_leftovers..].iter().enumerate() {
// '=' padding
if *b == PAD_BYTE {
// There can be bad padding in a few ways:
// 1 - Padding with non-padding characters after it
// 2 - Padding after zero or one non-padding characters before it
// in the current quad.
// 3 - More than two characters of padding. If 3 or 4 padding chars
// are in the same quad, that implies it will be caught by #2.
// If it spreads from one quad to another, it will be caught by
// #2 in the second quad.
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 faster logic above: it will report an
// error on the first padding character (since it doesn't expect to see
// anything but actual encoded data).
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 padding_bytes == 0 {
first_padding_index = i;
}
padding_bytes += 1;
continue;
}
// Check for case #1.
// To make '=' handling consistent with the main loop, don't allow
// non-suffix '=' in trailing chunk either. Report error as first
// erroneous padding.
if padding_bytes > 0 {
return Err(DecodeError::InvalidByte(
start_of_leftovers + first_padding_index,
PAD_BYTE,
));
}
last_symbol = *b;
// can use up to 8 * 6 = 48 bits of the u64, if last chunk has no padding.
// To minimize shifts, pack the leftovers from left to right.
let shift = 64 - (morsels_in_leftover + 1) * 6;
// tables are all 256 elements, lookup with a u8 index always succeeds
let morsel = decode_table[*b as usize];
if morsel == tables::INVALID_VALUE {
return Err(DecodeError::InvalidByte(start_of_leftovers + i, *b));
}
leftover_bits |= (morsel as u64) << shift;
morsels_in_leftover += 1;
}
let leftover_bits_ready_to_append = match morsels_in_leftover {
0 => 0,
2 => 8,
3 => 16,
4 => 24,
6 => 32,
7 => 40,
8 => 48,
_ => unreachable!(
"Impossible: must only have 0 to 8 input bytes in last chunk, with no invalid lengths"
),
};
// 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 !config.decode_allow_trailing_bits && (leftover_bits & mask) != 0 {
// last morsel is at `morsels_in_leftover` - 1
return Err(DecodeError::InvalidLastSymbol(
start_of_leftovers + morsels_in_leftover - 1,
last_symbol,
));
}
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;
output_index += 1;
leftover_bits_appended_to_buf += 8;
}
Ok(output_index)
}
#[inline]
fn write_u64(output: &mut [u8], value: u64) {
output[..8].copy_from_slice(&value.to_be_bytes());
}
/// 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.
///
/// `input` is the bytes to decode, of which the first 8 bytes will be processed.
/// `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.
// yes, really inline (worth 30-50% speedup)
#[inline(always)]
fn decode_chunk(
input: &[u8],
index_at_start_of_input: usize,
decode_table: &[u8; 256],
output: &mut [u8],
) -> Result<(), DecodeError> {
let mut accum: u64;
let morsel = decode_table[input[0] as usize];
if morsel == tables::INVALID_VALUE {
return Err(DecodeError::InvalidByte(index_at_start_of_input, input[0]));
}
accum = (morsel as u64) << 58;
let morsel = decode_table[input[1] as usize];
if morsel == tables::INVALID_VALUE {
return Err(DecodeError::InvalidByte(
index_at_start_of_input + 1,
input[1],
));
}
accum |= (morsel as u64) << 52;
let morsel = decode_table[input[2] as usize];
if morsel == tables::INVALID_VALUE {
return Err(DecodeError::InvalidByte(
index_at_start_of_input + 2,
input[2],
));
}
accum |= (morsel as u64) << 46;
let morsel = decode_table[input[3] as usize];
if morsel == tables::INVALID_VALUE {
return Err(DecodeError::InvalidByte(
index_at_start_of_input + 3,
input[3],
));
}
accum |= (morsel as u64) << 40;
let morsel = decode_table[input[4] as usize];
if morsel == tables::INVALID_VALUE {
return Err(DecodeError::InvalidByte(
index_at_start_of_input + 4,
input[4],
));
}
accum |= (morsel as u64) << 34;
let morsel = decode_table[input[5] as usize];
if morsel == tables::INVALID_VALUE {
return Err(DecodeError::InvalidByte(
index_at_start_of_input + 5,
input[5],
));
}
accum |= (morsel as u64) << 28;
let morsel = decode_table[input[6] as usize];
if morsel == tables::INVALID_VALUE {
return Err(DecodeError::InvalidByte(
index_at_start_of_input + 6,
input[6],
));
}
accum |= (morsel as u64) << 22;
let morsel = decode_table[input[7] as usize];
if morsel == tables::INVALID_VALUE {
return Err(DecodeError::InvalidByte(
index_at_start_of_input + 7,
input[7],
));
}
accum |= (morsel as u64) << 16;
write_u64(output, accum);
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(
input: &[u8],
index_at_start_of_input: usize,
decode_table: &[u8; 256],
output: &mut [u8],
) -> Result<(), DecodeError> {
let mut tmp_buf = [0_u8; 8];
decode_chunk(
input,
index_at_start_of_input,
decode_table,
&mut tmp_buf[..],
)?;
output[0..6].copy_from_slice(&tmp_buf[0..6]);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
encode::encode_config_buf,
encode::encode_config_slice,
tests::{assert_encode_sanity, random_config},
};
use rand::{
distributions::{Distribution, Uniform},
FromEntropy, Rng,
};
#[test]
fn decode_chunk_precise_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, tables::STANDARD_DECODE, &mut output).unwrap();
assert_eq!(&vec![b'f', b'o', b'o', b'b', b'a', b'r', 6, 7], &output);
}
#[test]
fn decode_chunk_writes_8_bytes() {
let input = b"Zm9vYmFy"; // "foobar"
let mut output = [0_u8, 1, 2, 3, 4, 5, 6, 7];
decode_chunk(&input[..], 0, tables::STANDARD_DECODE, &mut output).unwrap();
assert_eq!(&vec![b'f', b'o', b'o', b'b', b'a', b'r', 0, 0], &output);
}
#[test]
fn decode_into_nonempty_vec_doesnt_clobber_existing_prefix() {
let mut orig_data = Vec::new();
let mut encoded_data = String::new();
let mut decoded_with_prefix = Vec::new();
let mut decoded_without_prefix = Vec::new();
let mut prefix = Vec::new();
let prefix_len_range = Uniform::new(0, 1000);
let input_len_range = Uniform::new(0, 1000);
let mut rng = rand::rngs::SmallRng::from_entropy();
for _ in 0..10_000 {
orig_data.clear();
encoded_data.clear();
decoded_with_prefix.clear();
decoded_without_prefix.clear();
prefix.clear();
let input_len = input_len_range.sample(&mut rng);
for _ in 0..input_len {
orig_data.push(rng.gen());
}
let config = random_config(&mut rng);
encode_config_buf(&orig_data, config, &mut encoded_data);
assert_encode_sanity(&encoded_data, config, input_len);
let prefix_len = prefix_len_range.sample(&mut rng);
// fill the buf with a prefix
for _ in 0..prefix_len {
prefix.push(rng.gen());
}
decoded_with_prefix.resize(prefix_len, 0);
decoded_with_prefix.copy_from_slice(&prefix);
// decode into the non-empty buf
decode_config_buf(&encoded_data, config, &mut decoded_with_prefix).unwrap();
// also decode into the empty buf
decode_config_buf(&encoded_data, config, &mut decoded_without_prefix).unwrap();
assert_eq!(
prefix_len + decoded_without_prefix.len(),
decoded_with_prefix.len()
);
assert_eq!(orig_data, decoded_without_prefix);
// append plain decode onto prefix
prefix.append(&mut decoded_without_prefix);
assert_eq!(prefix, decoded_with_prefix);
}
}
#[test]
fn decode_into_slice_doesnt_clobber_existing_prefix_or_suffix() {
let mut orig_data = Vec::new();
let mut encoded_data = String::new();
let mut decode_buf = Vec::new();
let mut decode_buf_copy: Vec<u8> = Vec::new();
let input_len_range = Uniform::new(0, 1000);
let mut rng = rand::rngs::SmallRng::from_entropy();
for _ in 0..10_000 {
orig_data.clear();
encoded_data.clear();
decode_buf.clear();
decode_buf_copy.clear();
let input_len = input_len_range.sample(&mut rng);
for _ in 0..input_len {
orig_data.push(rng.gen());
}
let config = random_config(&mut rng);
encode_config_buf(&orig_data, config, &mut encoded_data);
assert_encode_sanity(&encoded_data, config, input_len);
// fill the buffer with random garbage, long enough to have some room before and after
for _ in 0..5000 {
decode_buf.push(rng.gen());
}
// keep a copy for later comparison
decode_buf_copy.extend(decode_buf.iter());
let offset = 1000;
// decode into the non-empty buf
let decode_bytes_written =
decode_config_slice(&encoded_data, config, &mut decode_buf[offset..]).unwrap();
assert_eq!(orig_data.len(), decode_bytes_written);
assert_eq!(
orig_data,
&decode_buf[offset..(offset + decode_bytes_written)]
);
assert_eq!(&decode_buf_copy[0..offset], &decode_buf[0..offset]);
assert_eq!(
&decode_buf_copy[offset + decode_bytes_written..],
&decode_buf[offset + decode_bytes_written..]
);
}
}
#[test]
fn decode_into_slice_fits_in_precisely_sized_slice() {
let mut orig_data = Vec::new();
let mut encoded_data = String::new();
let mut decode_buf = Vec::new();
let input_len_range = Uniform::new(0, 1000);
let mut rng = rand::rngs::SmallRng::from_entropy();
for _ in 0..10_000 {
orig_data.clear();
encoded_data.clear();
decode_buf.clear();
let input_len = input_len_range.sample(&mut rng);
for _ in 0..input_len {
orig_data.push(rng.gen());
}
let config = random_config(&mut rng);
encode_config_buf(&orig_data, config, &mut encoded_data);
assert_encode_sanity(&encoded_data, config, input_len);
decode_buf.resize(input_len, 0);
// decode into the non-empty buf
let decode_bytes_written =
decode_config_slice(&encoded_data, config, &mut decode_buf[..]).unwrap();
assert_eq!(orig_data.len(), decode_bytes_written);
assert_eq!(orig_data, decode_buf);
}
}
#[test]
fn detect_invalid_last_symbol_two_bytes() {
let decode =
|input, forgiving| decode_config(input, STANDARD.decode_allow_trailing_bits(forgiving));
// example from https://github.com/marshallpierce/rust-base64/issues/75
assert!(decode("iYU=", false).is_ok());
// trailing 01
assert_eq!(
Err(DecodeError::InvalidLastSymbol(2, b'V')),
decode("iYV=", false)
);
assert_eq!(Ok(vec![137, 133]), decode("iYV=", true));
// trailing 10
assert_eq!(
Err(DecodeError::InvalidLastSymbol(2, b'W')),
decode("iYW=", false)
);
assert_eq!(Ok(vec![137, 133]), decode("iYV=", true));
// trailing 11
assert_eq!(
Err(DecodeError::InvalidLastSymbol(2, b'X')),
decode("iYX=", false)
);
assert_eq!(Ok(vec![137, 133]), decode("iYV=", true));
// also works when there are 2 quads in the last block
assert_eq!(
Err(DecodeError::InvalidLastSymbol(6, b'X')),
decode("AAAAiYX=", false)
);
assert_eq!(Ok(vec![0, 0, 0, 137, 133]), decode("AAAAiYX=", true));
}
#[test]
fn detect_invalid_last_symbol_one_byte() {
// 0xFF -> "/w==", so all letters > w, 0-9, and '+', '/' should get InvalidLastSymbol
assert!(decode("/w==").is_ok());
// trailing 01
assert_eq!(Err(DecodeError::InvalidLastSymbol(1, b'x')), decode("/x=="));
assert_eq!(Err(DecodeError::InvalidLastSymbol(1, b'z')), decode("/z=="));
assert_eq!(Err(DecodeError::InvalidLastSymbol(1, b'0')), decode("/0=="));
assert_eq!(Err(DecodeError::InvalidLastSymbol(1, b'9')), decode("/9=="));
assert_eq!(Err(DecodeError::InvalidLastSymbol(1, b'+')), decode("/+=="));
assert_eq!(Err(DecodeError::InvalidLastSymbol(1, b'/')), decode("//=="));
// also works when there are 2 quads in the last block
assert_eq!(
Err(DecodeError::InvalidLastSymbol(5, b'x')),
decode("AAAA/x==")
);
}
#[test]
fn detect_invalid_last_symbol_every_possible_three_symbols() {
let mut base64_to_bytes = ::std::collections::HashMap::new();
let mut bytes = [0_u8; 2];
for b1 in 0_u16..256 {
bytes[0] = b1 as u8;
for b2 in 0_u16..256 {
bytes[1] = b2 as u8;
let mut b64 = vec![0_u8; 4];
assert_eq!(4, encode_config_slice(&bytes, STANDARD, &mut b64[..]));
let mut v = ::std::vec::Vec::with_capacity(2);
v.extend_from_slice(&bytes[..]);
assert!(base64_to_bytes.insert(b64, v).is_none());
}
}
// every possible combination of symbols must either decode to 2 bytes or get InvalidLastSymbol
let mut symbols = [0_u8; 4];
for &s1 in STANDARD.char_set.encode_table().iter() {
symbols[0] = s1;
for &s2 in STANDARD.char_set.encode_table().iter() {
symbols[1] = s2;
for &s3 in STANDARD.char_set.encode_table().iter() {
symbols[2] = s3;
symbols[3] = PAD_BYTE;
match base64_to_bytes.get(&symbols[..]) {
Some(bytes) => {
assert_eq!(Ok(bytes.to_vec()), decode_config(&symbols, STANDARD))
}
None => assert_eq!(
Err(DecodeError::InvalidLastSymbol(2, s3)),
decode_config(&symbols[..], STANDARD)
),
}
}
}
}
}
#[test]
fn detect_invalid_last_symbol_every_possible_two_symbols() {
let mut base64_to_bytes = ::std::collections::HashMap::new();
for b in 0_u16..256 {
let mut b64 = vec![0_u8; 4];
assert_eq!(4, encode_config_slice(&[b as u8], STANDARD, &mut b64[..]));
let mut v = ::std::vec::Vec::with_capacity(1);
v.push(b as u8);
assert!(base64_to_bytes.insert(b64, v).is_none());
}
// every possible combination of symbols must either decode to 1 byte or get InvalidLastSymbol
let mut symbols = [0_u8; 4];
for &s1 in STANDARD.char_set.encode_table().iter() {
symbols[0] = s1;
for &s2 in STANDARD.char_set.encode_table().iter() {
symbols[1] = s2;
symbols[2] = PAD_BYTE;
symbols[3] = PAD_BYTE;
match base64_to_bytes.get(&symbols[..]) {
Some(bytes) => {
assert_eq!(Ok(bytes.to_vec()), decode_config(&symbols, STANDARD))
}
None => assert_eq!(
Err(DecodeError::InvalidLastSymbol(1, s2)),
decode_config(&symbols[..], STANDARD)
),
}
}
}
}
}

View File

@ -0,0 +1,88 @@
//! Enables base64'd output anywhere you might use a `Display` implementation, like a format string.
//!
//! ```
//! use base64::display::Base64Display;
//!
//! let data = vec![0x0, 0x1, 0x2, 0x3];
//! let wrapper = Base64Display::with_config(&data, base64::STANDARD);
//!
//! assert_eq!("base64: AAECAw==", format!("base64: {}", wrapper));
//! ```
use super::chunked_encoder::ChunkedEncoder;
use super::Config;
use core::fmt::{Display, Formatter};
use core::{fmt, str};
/// A convenience wrapper for base64'ing bytes into a format string without heap allocation.
pub struct Base64Display<'a> {
bytes: &'a [u8],
chunked_encoder: ChunkedEncoder,
}
impl<'a> Base64Display<'a> {
/// Create a `Base64Display` with the provided config.
pub fn with_config(bytes: &[u8], config: Config) -> Base64Display {
Base64Display {
bytes,
chunked_encoder: ChunkedEncoder::new(config),
}
}
}
impl<'a> Display for Base64Display<'a> {
fn fmt(&self, formatter: &mut Formatter) -> Result<(), fmt::Error> {
let mut sink = FormatterSink { f: formatter };
self.chunked_encoder.encode(self.bytes, &mut sink)
}
}
struct FormatterSink<'a, 'b: 'a> {
f: &'a mut Formatter<'b>,
}
impl<'a, 'b: 'a> super::chunked_encoder::Sink for FormatterSink<'a, 'b> {
type Error = fmt::Error;
fn write_encoded_bytes(&mut self, encoded: &[u8]) -> Result<(), Self::Error> {
// Avoid unsafe. If max performance is needed, write your own display wrapper that uses
// unsafe here to gain about 10-15%.
self.f
.write_str(str::from_utf8(encoded).expect("base64 data was not utf8"))
}
}
#[cfg(test)]
mod tests {
use super::super::chunked_encoder::tests::{
chunked_encode_matches_normal_encode_random, SinkTestHelper,
};
use super::super::*;
use super::*;
#[test]
fn basic_display() {
assert_eq!(
"~$Zm9vYmFy#*",
format!("~${}#*", Base64Display::with_config(b"foobar", STANDARD))
);
assert_eq!(
"~$Zm9vYmFyZg==#*",
format!("~${}#*", Base64Display::with_config(b"foobarf", STANDARD))
);
}
#[test]
fn display_encode_matches_normal_encode() {
let helper = DisplaySinkTestHelper;
chunked_encode_matches_normal_encode_random(&helper);
}
struct DisplaySinkTestHelper;
impl SinkTestHelper for DisplaySinkTestHelper {
fn encode_to_string(&self, config: Config, bytes: &[u8]) -> String {
format!("{}", Base64Display::with_config(bytes, config))
}
}
}

View File

@ -0,0 +1,675 @@
use crate::{Config, PAD_BYTE};
#[cfg(any(feature = "alloc", feature = "std", test))]
use crate::{chunked_encoder, STANDARD};
#[cfg(any(feature = "alloc", feature = "std", test))]
use alloc::{string::String, vec};
use core::convert::TryInto;
///Encode arbitrary octets as base64.
///Returns a String.
///Convenience for `encode_config(input, base64::STANDARD);`.
///
///# Example
///
///```rust
///extern crate base64;
///
///fn main() {
/// let b64 = base64::encode(b"hello world");
/// println!("{}", b64);
///}
///```
#[cfg(any(feature = "alloc", feature = "std", test))]
pub fn encode<T: AsRef<[u8]>>(input: T) -> String {
encode_config(input, STANDARD)
}
///Encode arbitrary octets as base64.
///Returns a String.
///
///# Example
///
///```rust
///extern crate base64;
///
///fn main() {
/// let b64 = base64::encode_config(b"hello world~", base64::STANDARD);
/// println!("{}", b64);
///
/// let b64_url = base64::encode_config(b"hello internet~", base64::URL_SAFE);
/// println!("{}", b64_url);
///}
///```
#[cfg(any(feature = "alloc", feature = "std", test))]
pub fn encode_config<T: AsRef<[u8]>>(input: T, config: Config) -> String {
let mut buf = match encoded_size(input.as_ref().len(), config) {
Some(n) => vec![0; n],
None => panic!("integer overflow when calculating buffer size"),
};
encode_with_padding(input.as_ref(), config, buf.len(), &mut buf[..]);
String::from_utf8(buf).expect("Invalid UTF8")
}
///Encode arbitrary octets as base64.
///Writes into the supplied output buffer, which will grow the buffer if needed.
///
///# Example
///
///```rust
///extern crate base64;
///
///fn main() {
/// let mut buf = String::new();
/// base64::encode_config_buf(b"hello world~", base64::STANDARD, &mut buf);
/// println!("{}", buf);
///
/// buf.clear();
/// base64::encode_config_buf(b"hello internet~", base64::URL_SAFE, &mut buf);
/// println!("{}", buf);
///}
///```
#[cfg(any(feature = "alloc", feature = "std", test))]
pub fn encode_config_buf<T: AsRef<[u8]>>(input: T, config: Config, buf: &mut String) {
let input_bytes = input.as_ref();
{
let mut sink = chunked_encoder::StringSink::new(buf);
let encoder = chunked_encoder::ChunkedEncoder::new(config);
encoder
.encode(input_bytes, &mut sink)
.expect("Writing to a String shouldn't fail")
}
}
/// Encode arbitrary octets as base64.
/// Writes into the supplied output buffer.
///
/// This is useful if you wish to avoid allocation entirely (e.g. encoding into a stack-resident
/// or statically-allocated buffer).
///
/// # Panics
///
/// If `output` is too small to hold the encoded version of `input`, a panic will result.
///
/// # Example
///
/// ```rust
/// extern crate base64;
///
/// fn main() {
/// let s = b"hello internet!";
/// let mut buf = Vec::new();
/// // make sure we'll have a slice big enough for base64 + padding
/// buf.resize(s.len() * 4 / 3 + 4, 0);
///
/// let bytes_written = base64::encode_config_slice(s,
/// base64::STANDARD, &mut buf);
///
/// // shorten our vec down to just what was written
/// buf.resize(bytes_written, 0);
///
/// assert_eq!(s, base64::decode(&buf).unwrap().as_slice());
/// }
/// ```
pub fn encode_config_slice<T: AsRef<[u8]>>(input: T, config: Config, output: &mut [u8]) -> usize {
let input_bytes = input.as_ref();
let encoded_size = encoded_size(input_bytes.len(), config)
.expect("usize overflow when calculating buffer size");
let mut b64_output = &mut output[0..encoded_size];
encode_with_padding(&input_bytes, config, encoded_size, &mut b64_output);
encoded_size
}
/// B64-encode and pad (if configured).
///
/// This helper exists to avoid recalculating encoded_size, which is relatively expensive on short
/// inputs.
///
/// `encoded_size` is the encoded size calculated for `input`.
///
/// `output` must be of size `encoded_size`.
///
/// All bytes in `output` will be written to since it is exactly the size of the output.
fn encode_with_padding(input: &[u8], config: Config, encoded_size: usize, output: &mut [u8]) {
debug_assert_eq!(encoded_size, output.len());
let b64_bytes_written = encode_to_slice(input, output, config.char_set.encode_table());
let padding_bytes = if config.pad {
add_padding(input.len(), &mut output[b64_bytes_written..])
} else {
0
};
let encoded_bytes = b64_bytes_written
.checked_add(padding_bytes)
.expect("usize overflow when calculating b64 length");
debug_assert_eq!(encoded_size, encoded_bytes);
}
#[inline]
fn read_u64(s: &[u8]) -> u64 {
u64::from_be_bytes(s[..8].try_into().unwrap())
}
/// Encode input bytes to utf8 base64 bytes. Does not pad.
/// `output` must be long enough to hold the encoded `input` without padding.
/// Returns the number of bytes written.
#[inline]
pub fn encode_to_slice(input: &[u8], output: &mut [u8], encode_table: &[u8; 64]) -> usize {
let mut input_index: usize = 0;
const BLOCKS_PER_FAST_LOOP: usize = 4;
const LOW_SIX_BITS: u64 = 0x3F;
// we read 8 bytes at a time (u64) but only actually consume 6 of those bytes. Thus, we need
// 2 trailing bytes to be available to read..
let last_fast_index = input.len().saturating_sub(BLOCKS_PER_FAST_LOOP * 6 + 2);
let mut output_index = 0;
if last_fast_index > 0 {
while input_index <= last_fast_index {
// Major performance wins from letting the optimizer do the bounds check once, mostly
// on the output side
let input_chunk = &input[input_index..(input_index + (BLOCKS_PER_FAST_LOOP * 6 + 2))];
let output_chunk = &mut output[output_index..(output_index + BLOCKS_PER_FAST_LOOP * 8)];
// Hand-unrolling for 32 vs 16 or 8 bytes produces yields performance about equivalent
// to unsafe pointer code on a Xeon E5-1650v3. 64 byte unrolling was slightly better for
// large inputs but significantly worse for 50-byte input, unsurprisingly. I suspect
// that it's a not uncommon use case to encode smallish chunks of data (e.g. a 64-byte
// SHA-512 digest), so it would be nice if that fit in the unrolled loop at least once.
// Plus, single-digit percentage performance differences might well be quite different
// on different hardware.
let input_u64 = read_u64(&input_chunk[0..]);
output_chunk[0] = encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize];
output_chunk[1] = encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize];
output_chunk[2] = encode_table[((input_u64 >> 46) & LOW_SIX_BITS) as usize];
output_chunk[3] = encode_table[((input_u64 >> 40) & LOW_SIX_BITS) as usize];
output_chunk[4] = encode_table[((input_u64 >> 34) & LOW_SIX_BITS) as usize];
output_chunk[5] = encode_table[((input_u64 >> 28) & LOW_SIX_BITS) as usize];
output_chunk[6] = encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize];
output_chunk[7] = encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize];
let input_u64 = read_u64(&input_chunk[6..]);
output_chunk[8] = encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize];
output_chunk[9] = encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize];
output_chunk[10] = encode_table[((input_u64 >> 46) & LOW_SIX_BITS) as usize];
output_chunk[11] = encode_table[((input_u64 >> 40) & LOW_SIX_BITS) as usize];
output_chunk[12] = encode_table[((input_u64 >> 34) & LOW_SIX_BITS) as usize];
output_chunk[13] = encode_table[((input_u64 >> 28) & LOW_SIX_BITS) as usize];
output_chunk[14] = encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize];
output_chunk[15] = encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize];
let input_u64 = read_u64(&input_chunk[12..]);
output_chunk[16] = encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize];
output_chunk[17] = encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize];
output_chunk[18] = encode_table[((input_u64 >> 46) & LOW_SIX_BITS) as usize];
output_chunk[19] = encode_table[((input_u64 >> 40) & LOW_SIX_BITS) as usize];
output_chunk[20] = encode_table[((input_u64 >> 34) & LOW_SIX_BITS) as usize];
output_chunk[21] = encode_table[((input_u64 >> 28) & LOW_SIX_BITS) as usize];
output_chunk[22] = encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize];
output_chunk[23] = encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize];
let input_u64 = read_u64(&input_chunk[18..]);
output_chunk[24] = encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize];
output_chunk[25] = encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize];
output_chunk[26] = encode_table[((input_u64 >> 46) & LOW_SIX_BITS) as usize];
output_chunk[27] = encode_table[((input_u64 >> 40) & LOW_SIX_BITS) as usize];
output_chunk[28] = encode_table[((input_u64 >> 34) & LOW_SIX_BITS) as usize];
output_chunk[29] = encode_table[((input_u64 >> 28) & LOW_SIX_BITS) as usize];
output_chunk[30] = encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize];
output_chunk[31] = encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize];
output_index += BLOCKS_PER_FAST_LOOP * 8;
input_index += BLOCKS_PER_FAST_LOOP * 6;
}
}
// Encode what's left after the fast loop.
const LOW_SIX_BITS_U8: u8 = 0x3F;
let rem = input.len() % 3;
let start_of_rem = input.len() - rem;
// start at the first index not handled by fast loop, which may be 0.
while input_index < start_of_rem {
let input_chunk = &input[input_index..(input_index + 3)];
let output_chunk = &mut output[output_index..(output_index + 4)];
output_chunk[0] = encode_table[(input_chunk[0] >> 2) as usize];
output_chunk[1] =
encode_table[((input_chunk[0] << 4 | input_chunk[1] >> 4) & LOW_SIX_BITS_U8) as usize];
output_chunk[2] =
encode_table[((input_chunk[1] << 2 | input_chunk[2] >> 6) & LOW_SIX_BITS_U8) as usize];
output_chunk[3] = encode_table[(input_chunk[2] & LOW_SIX_BITS_U8) as usize];
input_index += 3;
output_index += 4;
}
if rem == 2 {
output[output_index] = encode_table[(input[start_of_rem] >> 2) as usize];
output[output_index + 1] = encode_table[((input[start_of_rem] << 4
| input[start_of_rem + 1] >> 4)
& LOW_SIX_BITS_U8) as usize];
output[output_index + 2] =
encode_table[((input[start_of_rem + 1] << 2) & LOW_SIX_BITS_U8) as usize];
output_index += 3;
} else if rem == 1 {
output[output_index] = encode_table[(input[start_of_rem] >> 2) as usize];
output[output_index + 1] =
encode_table[((input[start_of_rem] << 4) & LOW_SIX_BITS_U8) as usize];
output_index += 2;
}
output_index
}
/// calculate the base64 encoded string size, including padding if appropriate
pub fn encoded_size(bytes_len: usize, config: Config) -> Option<usize> {
let rem = bytes_len % 3;
let complete_input_chunks = bytes_len / 3;
let complete_chunk_output = complete_input_chunks.checked_mul(4);
if rem > 0 {
if config.pad {
complete_chunk_output.and_then(|c| c.checked_add(4))
} else {
let encoded_rem = match rem {
1 => 2,
2 => 3,
_ => unreachable!("Impossible remainder"),
};
complete_chunk_output.and_then(|c| c.checked_add(encoded_rem))
}
} else {
complete_chunk_output
}
}
/// Write padding characters.
/// `output` is the slice where padding should be written, of length at least 2.
///
/// Returns the number of padding bytes written.
pub fn add_padding(input_len: usize, output: &mut [u8]) -> usize {
let rem = input_len % 3;
let mut bytes_written = 0;
for _ in 0..((3 - rem) % 3) {
output[bytes_written] = PAD_BYTE;
bytes_written += 1;
}
bytes_written
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
decode::decode_config_buf,
tests::{assert_encode_sanity, random_config},
Config, STANDARD, URL_SAFE_NO_PAD,
};
use rand::{
distributions::{Distribution, Uniform},
FromEntropy, Rng,
};
use std;
use std::str;
#[test]
fn encoded_size_correct_standard() {
assert_encoded_length(0, 0, STANDARD);
assert_encoded_length(1, 4, STANDARD);
assert_encoded_length(2, 4, STANDARD);
assert_encoded_length(3, 4, STANDARD);
assert_encoded_length(4, 8, STANDARD);
assert_encoded_length(5, 8, STANDARD);
assert_encoded_length(6, 8, STANDARD);
assert_encoded_length(7, 12, STANDARD);
assert_encoded_length(8, 12, STANDARD);
assert_encoded_length(9, 12, STANDARD);
assert_encoded_length(54, 72, STANDARD);
assert_encoded_length(55, 76, STANDARD);
assert_encoded_length(56, 76, STANDARD);
assert_encoded_length(57, 76, STANDARD);
assert_encoded_length(58, 80, STANDARD);
}
#[test]
fn encoded_size_correct_no_pad() {
assert_encoded_length(0, 0, URL_SAFE_NO_PAD);
assert_encoded_length(1, 2, URL_SAFE_NO_PAD);
assert_encoded_length(2, 3, URL_SAFE_NO_PAD);
assert_encoded_length(3, 4, URL_SAFE_NO_PAD);
assert_encoded_length(4, 6, URL_SAFE_NO_PAD);
assert_encoded_length(5, 7, URL_SAFE_NO_PAD);
assert_encoded_length(6, 8, URL_SAFE_NO_PAD);
assert_encoded_length(7, 10, URL_SAFE_NO_PAD);
assert_encoded_length(8, 11, URL_SAFE_NO_PAD);
assert_encoded_length(9, 12, URL_SAFE_NO_PAD);
assert_encoded_length(54, 72, URL_SAFE_NO_PAD);
assert_encoded_length(55, 74, URL_SAFE_NO_PAD);
assert_encoded_length(56, 75, URL_SAFE_NO_PAD);
assert_encoded_length(57, 76, URL_SAFE_NO_PAD);
assert_encoded_length(58, 78, URL_SAFE_NO_PAD);
}
#[test]
fn encoded_size_overflow() {
assert_eq!(None, encoded_size(std::usize::MAX, STANDARD));
}
#[test]
fn encode_config_buf_into_nonempty_buffer_doesnt_clobber_prefix() {
let mut orig_data = Vec::new();
let mut prefix = String::new();
let mut encoded_data_no_prefix = String::new();
let mut encoded_data_with_prefix = String::new();
let mut decoded = Vec::new();
let prefix_len_range = Uniform::new(0, 1000);
let input_len_range = Uniform::new(0, 1000);
let mut rng = rand::rngs::SmallRng::from_entropy();
for _ in 0..10_000 {
orig_data.clear();
prefix.clear();
encoded_data_no_prefix.clear();
encoded_data_with_prefix.clear();
decoded.clear();
let input_len = input_len_range.sample(&mut rng);
for _ in 0..input_len {
orig_data.push(rng.gen());
}
let prefix_len = prefix_len_range.sample(&mut rng);
for _ in 0..prefix_len {
// getting convenient random single-byte printable chars that aren't base64 is
// annoying
prefix.push('#');
}
encoded_data_with_prefix.push_str(&prefix);
let config = random_config(&mut rng);
encode_config_buf(&orig_data, config, &mut encoded_data_no_prefix);
encode_config_buf(&orig_data, config, &mut encoded_data_with_prefix);
assert_eq!(
encoded_data_no_prefix.len() + prefix_len,
encoded_data_with_prefix.len()
);
assert_encode_sanity(&encoded_data_no_prefix, config, input_len);
assert_encode_sanity(&encoded_data_with_prefix[prefix_len..], config, input_len);
// append plain encode onto prefix
prefix.push_str(&mut encoded_data_no_prefix);
assert_eq!(prefix, encoded_data_with_prefix);
decode_config_buf(&encoded_data_no_prefix, config, &mut decoded).unwrap();
assert_eq!(orig_data, decoded);
}
}
#[test]
fn encode_config_slice_into_nonempty_buffer_doesnt_clobber_suffix() {
let mut orig_data = Vec::new();
let mut encoded_data = Vec::new();
let mut encoded_data_original_state = Vec::new();
let mut decoded = Vec::new();
let input_len_range = Uniform::new(0, 1000);
let mut rng = rand::rngs::SmallRng::from_entropy();
for _ in 0..10_000 {
orig_data.clear();
encoded_data.clear();
encoded_data_original_state.clear();
decoded.clear();
let input_len = input_len_range.sample(&mut rng);
for _ in 0..input_len {
orig_data.push(rng.gen());
}
// plenty of existing garbage in the encoded buffer
for _ in 0..10 * input_len {
encoded_data.push(rng.gen());
}
encoded_data_original_state.extend_from_slice(&encoded_data);
let config = random_config(&mut rng);
let encoded_size = encoded_size(input_len, config).unwrap();
assert_eq!(
encoded_size,
encode_config_slice(&orig_data, config, &mut encoded_data)
);
assert_encode_sanity(
std::str::from_utf8(&encoded_data[0..encoded_size]).unwrap(),
config,
input_len,
);
assert_eq!(
&encoded_data[encoded_size..],
&encoded_data_original_state[encoded_size..]
);
decode_config_buf(&encoded_data[0..encoded_size], config, &mut decoded).unwrap();
assert_eq!(orig_data, decoded);
}
}
#[test]
fn encode_config_slice_fits_into_precisely_sized_slice() {
let mut orig_data = Vec::new();
let mut encoded_data = Vec::new();
let mut decoded = Vec::new();
let input_len_range = Uniform::new(0, 1000);
let mut rng = rand::rngs::SmallRng::from_entropy();
for _ in 0..10_000 {
orig_data.clear();
encoded_data.clear();
decoded.clear();
let input_len = input_len_range.sample(&mut rng);
for _ in 0..input_len {
orig_data.push(rng.gen());
}
let config = random_config(&mut rng);
let encoded_size = encoded_size(input_len, config).unwrap();
encoded_data.resize(encoded_size, 0);
assert_eq!(
encoded_size,
encode_config_slice(&orig_data, config, &mut encoded_data)
);
assert_encode_sanity(
std::str::from_utf8(&encoded_data[0..encoded_size]).unwrap(),
config,
input_len,
);
decode_config_buf(&encoded_data[0..encoded_size], config, &mut decoded).unwrap();
assert_eq!(orig_data, decoded);
}
}
#[test]
fn encode_to_slice_random_valid_utf8() {
let mut input = Vec::new();
let mut output = Vec::new();
let input_len_range = Uniform::new(0, 1000);
let mut rng = rand::rngs::SmallRng::from_entropy();
for _ in 0..10_000 {
input.clear();
output.clear();
let input_len = input_len_range.sample(&mut rng);
for _ in 0..input_len {
input.push(rng.gen());
}
let config = random_config(&mut rng);
// fill up the output buffer with garbage
let encoded_size = encoded_size(input_len, config).unwrap();
for _ in 0..encoded_size {
output.push(rng.gen());
}
let orig_output_buf = output.to_vec();
let bytes_written =
encode_to_slice(&input, &mut output, config.char_set.encode_table());
// make sure the part beyond bytes_written is the same garbage it was before
assert_eq!(orig_output_buf[bytes_written..], output[bytes_written..]);
// make sure the encoded bytes are UTF-8
let _ = str::from_utf8(&output[0..bytes_written]).unwrap();
}
}
#[test]
fn encode_with_padding_random_valid_utf8() {
let mut input = Vec::new();
let mut output = Vec::new();
let input_len_range = Uniform::new(0, 1000);
let mut rng = rand::rngs::SmallRng::from_entropy();
for _ in 0..10_000 {
input.clear();
output.clear();
let input_len = input_len_range.sample(&mut rng);
for _ in 0..input_len {
input.push(rng.gen());
}
let config = random_config(&mut rng);
// fill up the output buffer with garbage
let encoded_size = encoded_size(input_len, config).unwrap();
for _ in 0..encoded_size + 1000 {
output.push(rng.gen());
}
let orig_output_buf = output.to_vec();
encode_with_padding(&input, config, encoded_size, &mut output[0..encoded_size]);
// make sure the part beyond b64 is the same garbage it was before
assert_eq!(orig_output_buf[encoded_size..], output[encoded_size..]);
// make sure the encoded bytes are UTF-8
let _ = str::from_utf8(&output[0..encoded_size]).unwrap();
}
}
#[test]
fn add_padding_random_valid_utf8() {
let mut output = Vec::new();
let mut rng = rand::rngs::SmallRng::from_entropy();
// cover our bases for length % 3
for input_len in 0..10 {
output.clear();
// fill output with random
for _ in 0..10 {
output.push(rng.gen());
}
let orig_output_buf = output.to_vec();
let bytes_written = add_padding(input_len, &mut output);
// make sure the part beyond bytes_written is the same garbage it was before
assert_eq!(orig_output_buf[bytes_written..], output[bytes_written..]);
// make sure the encoded bytes are UTF-8
let _ = str::from_utf8(&output[0..bytes_written]).unwrap();
}
}
fn assert_encoded_length(input_len: usize, encoded_len: usize, config: Config) {
assert_eq!(encoded_len, encoded_size(input_len, config).unwrap());
let mut bytes: Vec<u8> = Vec::new();
let mut rng = rand::rngs::SmallRng::from_entropy();
for _ in 0..input_len {
bytes.push(rng.gen());
}
let encoded = encode_config(&bytes, config);
assert_encode_sanity(&encoded, config, input_len);
assert_eq!(encoded_len, encoded.len());
}
#[test]
fn encode_imap() {
assert_eq!(
encode_config(b"\xFB\xFF", crate::IMAP_MUTF7),
encode_config(b"\xFB\xFF", crate::STANDARD_NO_PAD).replace("/", ",")
);
}
}

245
zeroidc/vendor/base64-0.13.0/src/lib.rs vendored Normal file
View File

@ -0,0 +1,245 @@
//! # Configs
//!
//! There isn't just one type of Base64; that would be too simple. You need to choose a character
//! set (standard, URL-safe, etc) and padding suffix (yes/no).
//! The `Config` struct encapsulates this info. There are some common configs included: `STANDARD`,
//! `URL_SAFE`, etc. You can also make your own `Config` if needed.
//!
//! The functions that don't have `config` in the name (e.g. `encode()` and `decode()`) use the
//! `STANDARD` config .
//!
//! The functions that write to a slice (the ones that end in `_slice`) are generally the fastest
//! because they don't need to resize anything. If it fits in your workflow and you care about
//! performance, keep using the same buffer (growing as need be) and use the `_slice` methods for
//! the best performance.
//!
//! # Encoding
//!
//! Several different encoding functions are available to you depending on your desire for
//! convenience vs performance.
//!
//! | Function | Output | Allocates |
//! | ----------------------- | ---------------------------- | ------------------------------ |
//! | `encode` | Returns a new `String` | Always |
//! | `encode_config` | Returns a new `String` | Always |
//! | `encode_config_buf` | Appends to provided `String` | Only if `String` needs to grow |
//! | `encode_config_slice` | Writes to provided `&[u8]` | Never |
//!
//! All of the encoding functions that take a `Config` will pad as per the config.
//!
//! # Decoding
//!
//! Just as for encoding, there are different decoding functions available.
//!
//! | Function | Output | Allocates |
//! | ----------------------- | ----------------------------- | ------------------------------ |
//! | `decode` | Returns a new `Vec<u8>` | Always |
//! | `decode_config` | Returns a new `Vec<u8>` | Always |
//! | `decode_config_buf` | Appends to provided `Vec<u8>` | Only if `Vec` needs to grow |
//! | `decode_config_slice` | Writes to provided `&[u8]` | Never |
//!
//! Unlike encoding, where all possible input is valid, decoding can fail (see `DecodeError`).
//!
//! Input can be invalid because it has invalid characters or invalid padding. (No padding at all is
//! valid, but excess padding is not.) Whitespace in the input is invalid.
//!
//! # `Read` and `Write`
//!
//! To map a `Read` of b64 bytes to the decoded bytes, wrap a reader (file, network socket, etc)
//! with `base64::read::DecoderReader`. To write raw bytes and have them b64 encoded on the fly,
//! wrap a writer with `base64::write::EncoderWriter`. There is some performance overhead (15% or
//! so) because of the necessary buffer shuffling -- still fast enough that almost nobody cares.
//! Also, these implementations do not heap allocate.
//!
//! # Panics
//!
//! If length calculations result in overflowing `usize`, a panic will result.
//!
//! The `_slice` flavors of encode or decode will panic if the provided output slice is too small,
#![cfg_attr(feature = "cargo-clippy", allow(clippy::cast_lossless))]
#![deny(
missing_docs,
trivial_casts,
trivial_numeric_casts,
unused_extern_crates,
unused_import_braces,
unused_results,
variant_size_differences,
warnings
)]
#![forbid(unsafe_code)]
#![cfg_attr(not(any(feature = "std", test)), no_std)]
#[cfg(all(feature = "alloc", not(any(feature = "std", test))))]
extern crate alloc;
#[cfg(any(feature = "std", test))]
extern crate std as alloc;
mod chunked_encoder;
pub mod display;
#[cfg(any(feature = "std", test))]
pub mod read;
mod tables;
#[cfg(any(feature = "std", test))]
pub mod write;
mod encode;
pub use crate::encode::encode_config_slice;
#[cfg(any(feature = "alloc", feature = "std", test))]
pub use crate::encode::{encode, encode_config, encode_config_buf};
mod decode;
#[cfg(any(feature = "alloc", feature = "std", test))]
pub use crate::decode::{decode, decode_config, decode_config_buf};
pub use crate::decode::{decode_config_slice, DecodeError};
#[cfg(test)]
mod tests;
/// Available encoding character sets
#[derive(Clone, Copy, Debug)]
pub enum CharacterSet {
/// The standard character set (uses `+` and `/`).
///
/// See [RFC 3548](https://tools.ietf.org/html/rfc3548#section-3).
Standard,
/// The URL safe character set (uses `-` and `_`).
///
/// See [RFC 3548](https://tools.ietf.org/html/rfc3548#section-4).
UrlSafe,
/// The `crypt(3)` character set (uses `./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`).
///
/// Not standardized, but folk wisdom on the net asserts that this alphabet is what crypt uses.
Crypt,
/// The bcrypt character set (uses `./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789`).
Bcrypt,
/// The character set used in IMAP-modified UTF-7 (uses `+` and `,`).
///
/// See [RFC 3501](https://tools.ietf.org/html/rfc3501#section-5.1.3)
ImapMutf7,
/// The character set used in BinHex 4.0 files.
///
/// See [BinHex 4.0 Definition](http://files.stairways.com/other/binhex-40-specs-info.txt)
BinHex,
}
impl CharacterSet {
fn encode_table(self) -> &'static [u8; 64] {
match self {
CharacterSet::Standard => tables::STANDARD_ENCODE,
CharacterSet::UrlSafe => tables::URL_SAFE_ENCODE,
CharacterSet::Crypt => tables::CRYPT_ENCODE,
CharacterSet::Bcrypt => tables::BCRYPT_ENCODE,
CharacterSet::ImapMutf7 => tables::IMAP_MUTF7_ENCODE,
CharacterSet::BinHex => tables::BINHEX_ENCODE,
}
}
fn decode_table(self) -> &'static [u8; 256] {
match self {
CharacterSet::Standard => tables::STANDARD_DECODE,
CharacterSet::UrlSafe => tables::URL_SAFE_DECODE,
CharacterSet::Crypt => tables::CRYPT_DECODE,
CharacterSet::Bcrypt => tables::BCRYPT_DECODE,
CharacterSet::ImapMutf7 => tables::IMAP_MUTF7_DECODE,
CharacterSet::BinHex => tables::BINHEX_DECODE,
}
}
}
/// Contains configuration parameters for base64 encoding
#[derive(Clone, Copy, Debug)]
pub struct Config {
/// Character set to use
char_set: CharacterSet,
/// True to pad output with `=` characters
pad: bool,
/// True to ignore excess nonzero bits in the last few symbols, otherwise an error is returned.
decode_allow_trailing_bits: bool,
}
impl Config {
/// Create a new `Config`.
pub const fn new(char_set: CharacterSet, pad: bool) -> Config {
Config {
char_set,
pad,
decode_allow_trailing_bits: false,
}
}
/// Sets whether to pad output with `=` characters.
pub const fn pad(self, pad: bool) -> Config {
Config { pad, ..self }
}
/// Sets whether to emit errors for nonzero trailing bits.
///
/// This is useful when implementing
/// [forgiving-base64 decode](https://infra.spec.whatwg.org/#forgiving-base64-decode).
pub const fn decode_allow_trailing_bits(self, allow: bool) -> Config {
Config {
decode_allow_trailing_bits: allow,
..self
}
}
}
/// Standard character set with padding.
pub const STANDARD: Config = Config {
char_set: CharacterSet::Standard,
pad: true,
decode_allow_trailing_bits: false,
};
/// Standard character set without padding.
pub const STANDARD_NO_PAD: Config = Config {
char_set: CharacterSet::Standard,
pad: false,
decode_allow_trailing_bits: false,
};
/// URL-safe character set with padding
pub const URL_SAFE: Config = Config {
char_set: CharacterSet::UrlSafe,
pad: true,
decode_allow_trailing_bits: false,
};
/// URL-safe character set without padding
pub const URL_SAFE_NO_PAD: Config = Config {
char_set: CharacterSet::UrlSafe,
pad: false,
decode_allow_trailing_bits: false,
};
/// As per `crypt(3)` requirements
pub const CRYPT: Config = Config {
char_set: CharacterSet::Crypt,
pad: false,
decode_allow_trailing_bits: false,
};
/// Bcrypt character set
pub const BCRYPT: Config = Config {
char_set: CharacterSet::Bcrypt,
pad: false,
decode_allow_trailing_bits: false,
};
/// IMAP modified UTF-7 requirements
pub const IMAP_MUTF7: Config = Config {
char_set: CharacterSet::ImapMutf7,
pad: false,
decode_allow_trailing_bits: false,
};
/// BinHex character set
pub const BINHEX: Config = Config {
char_set: CharacterSet::BinHex,
pad: false,
decode_allow_trailing_bits: false,
};
const PAD_BYTE: u8 = b'=';

View File

@ -0,0 +1,282 @@
use crate::{decode_config_slice, Config, DecodeError};
use std::io::Read;
use std::{cmp, fmt, io};
// This should be large, but it has to fit on the stack.
pub(crate) const BUF_SIZE: usize = 1024;
// 4 bytes of base64 data encode 3 bytes of raw data (modulo padding).
const BASE64_CHUNK_SIZE: usize = 4;
const DECODED_CHUNK_SIZE: usize = 3;
/// A `Read` implementation that decodes base64 data read from an underlying reader.
///
/// # Examples
///
/// ```
/// use std::io::Read;
/// use std::io::Cursor;
///
/// // use a cursor as the simplest possible `Read` -- in real code this is probably a file, etc.
/// let mut wrapped_reader = Cursor::new(b"YXNkZg==");
/// let mut decoder = base64::read::DecoderReader::new(
/// &mut wrapped_reader, base64::STANDARD);
///
/// // handle errors as you normally would
/// let mut result = Vec::new();
/// decoder.read_to_end(&mut result).unwrap();
///
/// assert_eq!(b"asdf", &result[..]);
///
/// ```
pub struct DecoderReader<'a, R: 'a + io::Read> {
config: Config,
/// Where b64 data is read from
r: &'a mut R,
// Holds b64 data read from the delegate reader.
b64_buffer: [u8; BUF_SIZE],
// The start of the pending buffered data in b64_buffer.
b64_offset: usize,
// The amount of buffered b64 data.
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; 3],
// index of start of decoded data
decoded_offset: usize,
// length of decoded data
decoded_len: usize,
// used to provide accurate offsets in errors
total_b64_decoded: usize,
}
impl<'a, R: io::Read> fmt::Debug for DecoderReader<'a, R> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("DecoderReader")
.field("config", &self.config)
.field("b64_offset", &self.b64_offset)
.field("b64_len", &self.b64_len)
.field("decoded_buffer", &self.decoded_buffer)
.field("decoded_offset", &self.decoded_offset)
.field("decoded_len", &self.decoded_len)
.field("total_b64_decoded", &self.total_b64_decoded)
.finish()
}
}
impl<'a, R: io::Read> DecoderReader<'a, R> {
/// Create a new decoder that will read from the provided reader `r`.
pub fn new(r: &'a mut R, config: Config) -> Self {
DecoderReader {
config,
r,
b64_buffer: [0; BUF_SIZE],
b64_offset: 0,
b64_len: 0,
decoded_buffer: [0; DECODED_CHUNK_SIZE],
decoded_offset: 0,
decoded_len: 0,
total_b64_decoded: 0,
}
}
/// Write as much as possible of the decoded buffer into the target buffer.
/// Must only be called when there is something to write and space to write into.
/// Returns a Result with the number of (decoded) bytes copied.
fn flush_decoded_buf(&mut self, buf: &mut [u8]) -> io::Result<usize> {
debug_assert!(self.decoded_len > 0);
debug_assert!(buf.len() > 0);
let copy_len = cmp::min(self.decoded_len, buf.len());
debug_assert!(copy_len > 0);
debug_assert!(copy_len <= self.decoded_len);
buf[..copy_len].copy_from_slice(
&self.decoded_buffer[self.decoded_offset..self.decoded_offset + copy_len],
);
self.decoded_offset += copy_len;
self.decoded_len -= copy_len;
debug_assert!(self.decoded_len < DECODED_CHUNK_SIZE);
Ok(copy_len)
}
/// Read into the remaining space in the buffer after the current contents.
/// Must only be called when there is space to read into in the buffer.
/// Returns the number of bytes read.
fn read_from_delegate(&mut self) -> io::Result<usize> {
debug_assert!(self.b64_offset + self.b64_len < BUF_SIZE);
let read = self
.r
.read(&mut self.b64_buffer[self.b64_offset + self.b64_len..])?;
self.b64_len += read;
debug_assert!(self.b64_offset + self.b64_len <= BUF_SIZE);
return Ok(read);
}
/// Decode the requested number of bytes from the b64 buffer into the provided buffer. It's the
/// caller's responsibility to choose the number of b64 bytes to decode correctly.
///
/// Returns a Result with the number of decoded bytes written to `buf`.
fn decode_to_buf(&mut self, num_bytes: usize, buf: &mut [u8]) -> io::Result<usize> {
debug_assert!(self.b64_len >= num_bytes);
debug_assert!(self.b64_offset + self.b64_len <= BUF_SIZE);
debug_assert!(buf.len() > 0);
let decoded = decode_config_slice(
&self.b64_buffer[self.b64_offset..self.b64_offset + num_bytes],
self.config,
&mut buf[..],
)
.map_err(|e| match e {
DecodeError::InvalidByte(offset, byte) => {
DecodeError::InvalidByte(self.total_b64_decoded + offset, byte)
}
DecodeError::InvalidLength => DecodeError::InvalidLength,
DecodeError::InvalidLastSymbol(offset, byte) => {
DecodeError::InvalidLastSymbol(self.total_b64_decoded + offset, byte)
}
})
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
self.total_b64_decoded += num_bytes;
self.b64_offset += num_bytes;
self.b64_len -= num_bytes;
debug_assert!(self.b64_offset + self.b64_len <= BUF_SIZE);
Ok(decoded)
}
}
impl<'a, R: Read> Read for DecoderReader<'a, R> {
/// Decode input from the wrapped reader.
///
/// Under non-error circumstances, this returns `Ok` with the value being the number of bytes
/// written in `buf`.
///
/// Where possible, this function buffers base64 to minimize the number of read() calls to the
/// delegate reader.
///
/// # Errors
///
/// Any errors emitted by the delegate reader are returned. Decoding errors due to invalid
/// base64 are also possible, and will have `io::ErrorKind::InvalidData`.
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if buf.len() == 0 {
return Ok(0);
}
// offset == BUF_SIZE when we copied it all last time
debug_assert!(self.b64_offset <= BUF_SIZE);
debug_assert!(self.b64_offset + self.b64_len <= BUF_SIZE);
debug_assert!(if self.b64_offset == BUF_SIZE {
self.b64_len == 0
} else {
self.b64_len <= BUF_SIZE
});
debug_assert!(if self.decoded_len == 0 {
// can be = when we were able to copy the complete chunk
self.decoded_offset <= DECODED_CHUNK_SIZE
} else {
self.decoded_offset < DECODED_CHUNK_SIZE
});
// We shouldn't ever decode into here when we can't immediately write at least one byte into
// the provided buf, so the effective length should only be 3 momentarily between when we
// decode and when we copy into the target buffer.
debug_assert!(self.decoded_len < DECODED_CHUNK_SIZE);
debug_assert!(self.decoded_len + self.decoded_offset <= DECODED_CHUNK_SIZE);
if self.decoded_len > 0 {
// we have a few leftover decoded bytes; flush that rather than pull in more b64
self.flush_decoded_buf(buf)
} else {
let mut at_eof = false;
while self.b64_len < BASE64_CHUNK_SIZE {
// Work around lack of copy_within, which is only present in 1.37
// Copy any bytes we have to the start of the buffer.
// We know we have < 1 chunk, so we can use a tiny tmp buffer.
let mut memmove_buf = [0_u8; BASE64_CHUNK_SIZE];
memmove_buf[..self.b64_len].copy_from_slice(
&self.b64_buffer[self.b64_offset..self.b64_offset + self.b64_len],
);
self.b64_buffer[0..self.b64_len].copy_from_slice(&memmove_buf[..self.b64_len]);
self.b64_offset = 0;
// then fill in more data
let read = self.read_from_delegate()?;
if read == 0 {
// we never pass in an empty buf, so 0 => we've hit EOF
at_eof = true;
break;
}
}
if self.b64_len == 0 {
debug_assert!(at_eof);
// we must be at EOF, and we have no data left to decode
return Ok(0);
};
debug_assert!(if at_eof {
// if we are at eof, we may not have a complete chunk
self.b64_len > 0
} else {
// otherwise, we must have at least one chunk
self.b64_len >= BASE64_CHUNK_SIZE
});
debug_assert_eq!(0, self.decoded_len);
if buf.len() < DECODED_CHUNK_SIZE {
// caller requested an annoyingly short read
// have to write to a tmp buf first to avoid double mutable borrow
let mut decoded_chunk = [0_u8; DECODED_CHUNK_SIZE];
// if we are at eof, could have less than BASE64_CHUNK_SIZE, in which case we have
// to assume that these last few tokens are, in fact, valid (i.e. must be 2-4 b64
// tokens, not 1, since 1 token can't decode to 1 byte).
let to_decode = cmp::min(self.b64_len, BASE64_CHUNK_SIZE);
let decoded = self.decode_to_buf(to_decode, &mut decoded_chunk[..])?;
self.decoded_buffer[..decoded].copy_from_slice(&decoded_chunk[..decoded]);
self.decoded_offset = 0;
self.decoded_len = decoded;
// can be less than 3 on last block due to padding
debug_assert!(decoded <= 3);
self.flush_decoded_buf(buf)
} else {
let b64_bytes_that_can_decode_into_buf = (buf.len() / DECODED_CHUNK_SIZE)
.checked_mul(BASE64_CHUNK_SIZE)
.expect("too many chunks");
debug_assert!(b64_bytes_that_can_decode_into_buf >= BASE64_CHUNK_SIZE);
let b64_bytes_available_to_decode = if at_eof {
self.b64_len
} else {
// only use complete chunks
self.b64_len - self.b64_len % 4
};
let actual_decode_len = cmp::min(
b64_bytes_that_can_decode_into_buf,
b64_bytes_available_to_decode,
);
self.decode_to_buf(actual_decode_len, buf)
}
}
}
}

View File

@ -0,0 +1,335 @@
use std::io::{self, Read};
use rand::{Rng, RngCore};
use std::{cmp, iter};
use super::decoder::{DecoderReader, BUF_SIZE};
use crate::encode::encode_config_buf;
use crate::tests::random_config;
use crate::{decode_config_buf, DecodeError, STANDARD};
#[test]
fn simple() {
let tests: &[(&[u8], &[u8])] = &[
(&b"0"[..], &b"MA=="[..]),
(b"01", b"MDE="),
(b"012", b"MDEy"),
(b"0123", b"MDEyMw=="),
(b"01234", b"MDEyMzQ="),
(b"012345", b"MDEyMzQ1"),
(b"0123456", b"MDEyMzQ1Ng=="),
(b"01234567", b"MDEyMzQ1Njc="),
(b"012345678", b"MDEyMzQ1Njc4"),
(b"0123456789", b"MDEyMzQ1Njc4OQ=="),
][..];
for (text_expected, base64data) in tests.iter() {
// Read n bytes at a time.
for n in 1..base64data.len() + 1 {
let mut wrapped_reader = io::Cursor::new(base64data);
let mut decoder = DecoderReader::new(&mut wrapped_reader, STANDARD);
// handle errors as you normally would
let mut text_got = Vec::new();
let mut buffer = vec![0u8; n];
while let Ok(read) = decoder.read(&mut buffer[..]) {
if read == 0 {
break;
}
text_got.extend_from_slice(&buffer[..read]);
}
assert_eq!(
text_got,
*text_expected,
"\nGot: {}\nExpected: {}",
String::from_utf8_lossy(&text_got[..]),
String::from_utf8_lossy(text_expected)
);
}
}
}
// Make sure we error out on trailing junk.
#[test]
fn trailing_junk() {
let tests: &[&[u8]] = &[&b"MDEyMzQ1Njc4*!@#$%^&"[..], b"MDEyMzQ1Njc4OQ== "][..];
for base64data in tests.iter() {
// Read n bytes at a time.
for n in 1..base64data.len() + 1 {
let mut wrapped_reader = io::Cursor::new(base64data);
let mut decoder = DecoderReader::new(&mut wrapped_reader, STANDARD);
// handle errors as you normally would
let mut buffer = vec![0u8; n];
let mut saw_error = false;
loop {
match decoder.read(&mut buffer[..]) {
Err(_) => {
saw_error = true;
break;
}
Ok(read) if read == 0 => break,
Ok(_) => (),
}
}
assert!(saw_error);
}
}
}
#[test]
fn handles_short_read_from_delegate() {
let mut rng = rand::thread_rng();
let mut bytes = Vec::new();
let mut b64 = String::new();
let mut decoded = Vec::new();
for _ in 0..10_000 {
bytes.clear();
b64.clear();
decoded.clear();
let size = rng.gen_range(0, 10 * BUF_SIZE);
bytes.extend(iter::repeat(0).take(size));
bytes.truncate(size);
rng.fill_bytes(&mut bytes[..size]);
assert_eq!(size, bytes.len());
let config = random_config(&mut rng);
encode_config_buf(&bytes[..], config, &mut b64);
let mut wrapped_reader = io::Cursor::new(b64.as_bytes());
let mut short_reader = RandomShortRead {
delegate: &mut wrapped_reader,
rng: &mut rng,
};
let mut decoder = DecoderReader::new(&mut short_reader, config);
let decoded_len = decoder.read_to_end(&mut decoded).unwrap();
assert_eq!(size, decoded_len);
assert_eq!(&bytes[..], &decoded[..]);
}
}
#[test]
fn read_in_short_increments() {
let mut rng = rand::thread_rng();
let mut bytes = Vec::new();
let mut b64 = String::new();
let mut decoded = Vec::new();
for _ in 0..10_000 {
bytes.clear();
b64.clear();
decoded.clear();
let size = rng.gen_range(0, 10 * BUF_SIZE);
bytes.extend(iter::repeat(0).take(size));
// leave room to play around with larger buffers
decoded.extend(iter::repeat(0).take(size * 3));
rng.fill_bytes(&mut bytes[..]);
assert_eq!(size, bytes.len());
let config = random_config(&mut rng);
encode_config_buf(&bytes[..], config, &mut b64);
let mut wrapped_reader = io::Cursor::new(&b64[..]);
let mut decoder = DecoderReader::new(&mut wrapped_reader, config);
consume_with_short_reads_and_validate(&mut rng, &bytes[..], &mut decoded, &mut decoder);
}
}
#[test]
fn read_in_short_increments_with_short_delegate_reads() {
let mut rng = rand::thread_rng();
let mut bytes = Vec::new();
let mut b64 = String::new();
let mut decoded = Vec::new();
for _ in 0..10_000 {
bytes.clear();
b64.clear();
decoded.clear();
let size = rng.gen_range(0, 10 * BUF_SIZE);
bytes.extend(iter::repeat(0).take(size));
// leave room to play around with larger buffers
decoded.extend(iter::repeat(0).take(size * 3));
rng.fill_bytes(&mut bytes[..]);
assert_eq!(size, bytes.len());
let config = random_config(&mut rng);
encode_config_buf(&bytes[..], config, &mut b64);
let mut base_reader = io::Cursor::new(&b64[..]);
let mut decoder = DecoderReader::new(&mut base_reader, config);
let mut short_reader = RandomShortRead {
delegate: &mut decoder,
rng: &mut rand::thread_rng(),
};
consume_with_short_reads_and_validate(&mut rng, &bytes[..], &mut decoded, &mut short_reader)
}
}
#[test]
fn reports_invalid_last_symbol_correctly() {
let mut rng = rand::thread_rng();
let mut bytes = Vec::new();
let mut b64 = String::new();
let mut b64_bytes = Vec::new();
let mut decoded = Vec::new();
let mut bulk_decoded = Vec::new();
for _ in 0..1_000 {
bytes.clear();
b64.clear();
b64_bytes.clear();
let size = rng.gen_range(1, 10 * BUF_SIZE);
bytes.extend(iter::repeat(0).take(size));
decoded.extend(iter::repeat(0).take(size));
rng.fill_bytes(&mut bytes[..]);
assert_eq!(size, bytes.len());
let mut config = random_config(&mut rng);
// changing padding will cause invalid padding errors when we twiddle the last byte
config.pad = false;
encode_config_buf(&bytes[..], config, &mut b64);
b64_bytes.extend(b64.bytes());
assert_eq!(b64_bytes.len(), b64.len());
// change the last character to every possible symbol. Should behave the same as bulk
// decoding whether invalid or valid.
for &s1 in config.char_set.encode_table().iter() {
decoded.clear();
bulk_decoded.clear();
// replace the last
*b64_bytes.last_mut().unwrap() = s1;
let bulk_res = decode_config_buf(&b64_bytes[..], config, &mut bulk_decoded);
let mut wrapped_reader = io::Cursor::new(&b64_bytes[..]);
let mut decoder = DecoderReader::new(&mut wrapped_reader, config);
let stream_res = decoder.read_to_end(&mut decoded).map(|_| ()).map_err(|e| {
e.into_inner()
.and_then(|e| e.downcast::<DecodeError>().ok())
});
assert_eq!(bulk_res.map_err(|e| Some(Box::new(e))), stream_res);
}
}
}
#[test]
fn reports_invalid_byte_correctly() {
let mut rng = rand::thread_rng();
let mut bytes = Vec::new();
let mut b64 = String::new();
let mut decoded = Vec::new();
for _ in 0..10_000 {
bytes.clear();
b64.clear();
decoded.clear();
let size = rng.gen_range(1, 10 * BUF_SIZE);
bytes.extend(iter::repeat(0).take(size));
rng.fill_bytes(&mut bytes[..size]);
assert_eq!(size, bytes.len());
let config = random_config(&mut rng);
encode_config_buf(&bytes[..], config, &mut b64);
// replace one byte, somewhere, with '*', which is invalid
let bad_byte_pos = rng.gen_range(0, &b64.len());
let mut b64_bytes = b64.bytes().collect::<Vec<u8>>();
b64_bytes[bad_byte_pos] = b'*';
let mut wrapped_reader = io::Cursor::new(b64_bytes.clone());
let mut decoder = DecoderReader::new(&mut wrapped_reader, config);
// some gymnastics to avoid double-moving the io::Error, which is not Copy
let read_decode_err = decoder
.read_to_end(&mut decoded)
.map_err(|e| {
let kind = e.kind();
let inner = e
.into_inner()
.and_then(|e| e.downcast::<DecodeError>().ok());
inner.map(|i| (*i, kind))
})
.err()
.and_then(|o| o);
let mut bulk_buf = Vec::new();
let bulk_decode_err = decode_config_buf(&b64_bytes[..], config, &mut bulk_buf).err();
// it's tricky to predict where the invalid data's offset will be since if it's in the last
// chunk it will be reported at the first padding location because it's treated as invalid
// padding. So, we just check that it's the same as it is for decoding all at once.
assert_eq!(
bulk_decode_err.map(|e| (e, io::ErrorKind::InvalidData)),
read_decode_err
);
}
}
fn consume_with_short_reads_and_validate<R: Read>(
rng: &mut rand::rngs::ThreadRng,
expected_bytes: &[u8],
decoded: &mut Vec<u8>,
short_reader: &mut R,
) -> () {
let mut total_read = 0_usize;
loop {
assert!(
total_read <= expected_bytes.len(),
"tr {} size {}",
total_read,
expected_bytes.len()
);
if total_read == expected_bytes.len() {
assert_eq!(expected_bytes, &decoded[..total_read]);
// should be done
assert_eq!(0, short_reader.read(&mut decoded[..]).unwrap());
// didn't write anything
assert_eq!(expected_bytes, &decoded[..total_read]);
break;
}
let decode_len = rng.gen_range(1, cmp::max(2, expected_bytes.len() * 2));
let read = short_reader
.read(&mut decoded[total_read..total_read + decode_len])
.unwrap();
total_read += read;
}
}
/// Limits how many bytes a reader will provide in each read call.
/// Useful for shaking out code that may work fine only with typical input sources that always fill
/// the buffer.
struct RandomShortRead<'a, 'b, R: io::Read, N: rand::Rng> {
delegate: &'b mut R,
rng: &'a mut N,
}
impl<'a, 'b, R: io::Read, N: rand::Rng> io::Read for RandomShortRead<'a, 'b, R, N> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, io::Error> {
// avoid 0 since it means EOF for non-empty buffers
let effective_len = cmp::min(self.rng.gen_range(1, 20), buf.len());
self.delegate.read(&mut buf[..effective_len])
}
}

View File

@ -0,0 +1,6 @@
//! Implementations of `io::Read` to transparently decode base64.
mod decoder;
pub use self::decoder::DecoderReader;
#[cfg(test)]
mod decoder_tests;

View File

@ -0,0 +1,81 @@
use crate::{decode_config, encode::encoded_size, encode_config_buf, CharacterSet, Config};
use std::str;
use rand::{
distributions::{Distribution, Uniform},
seq::SliceRandom,
FromEntropy, Rng,
};
#[test]
fn roundtrip_random_config_short() {
// exercise the slower encode/decode routines that operate on shorter buffers more vigorously
roundtrip_random_config(Uniform::new(0, 50), 10_000);
}
#[test]
fn roundtrip_random_config_long() {
roundtrip_random_config(Uniform::new(0, 1000), 10_000);
}
pub fn assert_encode_sanity(encoded: &str, config: Config, input_len: usize) {
let input_rem = input_len % 3;
let expected_padding_len = if input_rem > 0 {
if config.pad {
3 - input_rem
} else {
0
}
} else {
0
};
let expected_encoded_len = encoded_size(input_len, config).unwrap();
assert_eq!(expected_encoded_len, encoded.len());
let padding_len = encoded.chars().filter(|&c| c == '=').count();
assert_eq!(expected_padding_len, padding_len);
let _ = str::from_utf8(encoded.as_bytes()).expect("Base64 should be valid utf8");
}
fn roundtrip_random_config(input_len_range: Uniform<usize>, iterations: u32) {
let mut input_buf: Vec<u8> = Vec::new();
let mut encoded_buf = String::new();
let mut rng = rand::rngs::SmallRng::from_entropy();
for _ in 0..iterations {
input_buf.clear();
encoded_buf.clear();
let input_len = input_len_range.sample(&mut rng);
let config = random_config(&mut rng);
for _ in 0..input_len {
input_buf.push(rng.gen());
}
encode_config_buf(&input_buf, config, &mut encoded_buf);
assert_encode_sanity(&encoded_buf, config, input_len);
assert_eq!(input_buf, decode_config(&encoded_buf, config).unwrap());
}
}
pub fn random_config<R: Rng>(rng: &mut R) -> Config {
const CHARSETS: &[CharacterSet] = &[
CharacterSet::UrlSafe,
CharacterSet::Standard,
CharacterSet::Crypt,
CharacterSet::ImapMutf7,
CharacterSet::BinHex,
];
let charset = *CHARSETS.choose(rng).unwrap();
Config::new(charset, rng.gen())
}

View File

@ -0,0 +1,381 @@
use crate::encode::encode_to_slice;
use crate::{encode_config_slice, Config};
use std::{
cmp, fmt,
io::{ErrorKind, Result, Write},
};
pub(crate) const BUF_SIZE: usize = 1024;
/// The most bytes whose encoding will fit in `BUF_SIZE`
const MAX_INPUT_LEN: usize = BUF_SIZE / 4 * 3;
// 3 bytes of input = 4 bytes of base64, always (because we don't allow line wrapping)
const MIN_ENCODE_CHUNK_SIZE: usize = 3;
/// A `Write` implementation that base64 encodes data before delegating to the wrapped writer.
///
/// Because base64 has special handling for the end of the input data (padding, etc), there's a
/// `finish()` method on this type that encodes any leftover input bytes and adds padding if
/// appropriate. It's called automatically when deallocated (see the `Drop` implementation), but
/// any error that occurs when invoking the underlying writer will be suppressed. If you want to
/// handle such errors, call `finish()` yourself.
///
/// # Examples
///
/// ```
/// use std::io::Write;
///
/// // use a vec as the simplest possible `Write` -- in real code this is probably a file, etc.
/// let mut enc = base64::write::EncoderWriter::new(Vec::new(), base64::STANDARD);
///
/// // handle errors as you normally would
/// enc.write_all(b"asdf").unwrap();
///
/// // could leave this out to be called by Drop, if you don't care
/// // about handling errors or getting the delegate writer back
/// let delegate = enc.finish().unwrap();
///
/// // base64 was written to the writer
/// assert_eq!(b"YXNkZg==", &delegate[..]);
///
/// ```
///
/// # Panics
///
/// Calling `write()` (or related methods) or `finish()` after `finish()` has completed without
/// error is invalid and will panic.
///
/// # Errors
///
/// Base64 encoding itself does not generate errors, but errors from the wrapped writer will be
/// returned as per the contract of `Write`.
///
/// # Performance
///
/// It has some minor performance loss compared to encoding slices (a couple percent).
/// It does not do any heap allocation.
pub struct EncoderWriter<W: Write> {
config: Config,
/// Where encoded data is written to. It's an Option as it's None immediately before Drop is
/// called so that finish() can return the underlying writer. None implies that finish() has
/// been called successfully.
delegate: Option<W>,
/// Holds a partial chunk, if any, after the last `write()`, so that we may then fill the chunk
/// with the next `write()`, encode it, then proceed with the rest of the input normally.
extra_input: [u8; MIN_ENCODE_CHUNK_SIZE],
/// How much of `extra` is occupied, in `[0, MIN_ENCODE_CHUNK_SIZE]`.
extra_input_occupied_len: usize,
/// Buffer to encode into. May hold leftover encoded bytes from a previous write call that the underlying writer
/// did not write last time.
output: [u8; BUF_SIZE],
/// How much of `output` is occupied with encoded data that couldn't be written last time
output_occupied_len: usize,
/// panic safety: don't write again in destructor if writer panicked while we were writing to it
panicked: bool,
}
impl<W: Write> fmt::Debug for EncoderWriter<W> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"extra_input: {:?} extra_input_occupied_len:{:?} output[..5]: {:?} output_occupied_len: {:?}",
self.extra_input,
self.extra_input_occupied_len,
&self.output[0..5],
self.output_occupied_len
)
}
}
impl<W: Write> EncoderWriter<W> {
/// Create a new encoder that will write to the provided delegate writer `w`.
pub fn new(w: W, config: Config) -> EncoderWriter<W> {
EncoderWriter {
config,
delegate: Some(w),
extra_input: [0u8; MIN_ENCODE_CHUNK_SIZE],
extra_input_occupied_len: 0,
output: [0u8; BUF_SIZE],
output_occupied_len: 0,
panicked: false,
}
}
/// Encode all remaining buffered data and write it, including any trailing incomplete input
/// triples and associated padding.
///
/// Once this succeeds, no further writes or calls to this method are allowed.
///
/// This may write to the delegate writer multiple times if the delegate writer does not accept
/// all input provided to its `write` each invocation.
///
/// If you don't care about error handling, it is not necessary to call this function, as the
/// equivalent finalization is done by the Drop impl.
///
/// Returns the writer that this was constructed around.
///
/// # Errors
///
/// The first error that is not of `ErrorKind::Interrupted` will be returned.
pub fn finish(&mut self) -> Result<W> {
// If we could consume self in finish(), we wouldn't have to worry about this case, but
// finish() is retryable in the face of I/O errors, so we can't consume here.
if self.delegate.is_none() {
panic!("Encoder has already had finish() called")
};
self.write_final_leftovers()?;
let writer = self.delegate.take().expect("Writer must be present");
Ok(writer)
}
/// Write any remaining buffered data to the delegate writer.
fn write_final_leftovers(&mut self) -> Result<()> {
if self.delegate.is_none() {
// finish() has already successfully called this, and we are now in drop() with a None
// writer, so just no-op
return Ok(());
}
self.write_all_encoded_output()?;
if self.extra_input_occupied_len > 0 {
let encoded_len = encode_config_slice(
&self.extra_input[..self.extra_input_occupied_len],
self.config,
&mut self.output[..],
);
self.output_occupied_len = encoded_len;
self.write_all_encoded_output()?;
// write succeeded, do not write the encoding of extra again if finish() is retried
self.extra_input_occupied_len = 0;
}
Ok(())
}
/// Write as much of the encoded output to the delegate writer as it will accept, and store the
/// leftovers to be attempted at the next write() call. Updates `self.output_occupied_len`.
///
/// # Errors
///
/// Errors from the delegate writer are returned. In the case of an error,
/// `self.output_occupied_len` will not be updated, as errors from `write` are specified to mean
/// that no write took place.
fn write_to_delegate(&mut self, current_output_len: usize) -> Result<()> {
self.panicked = true;
let res = self
.delegate
.as_mut()
.expect("Writer must be present")
.write(&self.output[..current_output_len]);
self.panicked = false;
res.map(|consumed| {
debug_assert!(consumed <= current_output_len);
if consumed < current_output_len {
self.output_occupied_len = current_output_len.checked_sub(consumed).unwrap();
// If we're blocking on I/O, the minor inefficiency of copying bytes to the
// start of the buffer is the least of our concerns...
// Rotate moves more than we need to, but copy_within isn't stabilized yet.
self.output.rotate_left(consumed);
} else {
self.output_occupied_len = 0;
}
})
}
/// Write all buffered encoded output. If this returns `Ok`, `self.output_occupied_len` is `0`.
///
/// This is basically write_all for the remaining buffered data but without the undesirable
/// abort-on-`Ok(0)` behavior.
///
/// # Errors
///
/// Any error emitted by the delegate writer abort the write loop and is returned, unless it's
/// `Interrupted`, in which case the error is ignored and writes will continue.
fn write_all_encoded_output(&mut self) -> Result<()> {
while self.output_occupied_len > 0 {
let remaining_len = self.output_occupied_len;
match self.write_to_delegate(remaining_len) {
// try again on interrupts ala write_all
Err(ref e) if e.kind() == ErrorKind::Interrupted => {}
// other errors return
Err(e) => return Err(e),
// success no-ops because remaining length is already updated
Ok(_) => {}
};
}
debug_assert_eq!(0, self.output_occupied_len);
Ok(())
}
}
impl<W: Write> Write for EncoderWriter<W> {
/// Encode input and then write to the delegate writer.
///
/// Under non-error circumstances, this returns `Ok` with the value being the number of bytes
/// of `input` consumed. The value may be `0`, which interacts poorly with `write_all`, which
/// interprets `Ok(0)` as an error, despite it being allowed by the contract of `write`. See
/// https://github.com/rust-lang/rust/issues/56889 for more on that.
///
/// If the previous call to `write` provided more (encoded) data than the delegate writer could
/// accept in a single call to its `write`, the remaining data is buffered. As long as buffered
/// data is present, subsequent calls to `write` will try to write the remaining buffered data
/// to the delegate and return either `Ok(0)` -- and therefore not consume any of `input` -- or
/// an error.
///
/// # Errors
///
/// Any errors emitted by the delegate writer are returned.
fn write(&mut self, input: &[u8]) -> Result<usize> {
if self.delegate.is_none() {
panic!("Cannot write more after calling finish()");
}
if input.is_empty() {
return Ok(0);
}
// The contract of `Write::write` places some constraints on this implementation:
// - a call to `write()` represents at most one call to a wrapped `Write`, so we can't
// iterate over the input and encode multiple chunks.
// - Errors mean that "no bytes were written to this writer", so we need to reset the
// internal state to what it was before the error occurred
// before reading any input, write any leftover encoded output from last time
if self.output_occupied_len > 0 {
let current_len = self.output_occupied_len;
return self
.write_to_delegate(current_len)
// did not read any input
.map(|_| 0);
}
debug_assert_eq!(0, self.output_occupied_len);
// how many bytes, if any, were read into `extra` to create a triple to encode
let mut extra_input_read_len = 0;
let mut input = input;
let orig_extra_len = self.extra_input_occupied_len;
let mut encoded_size = 0;
// always a multiple of MIN_ENCODE_CHUNK_SIZE
let mut max_input_len = MAX_INPUT_LEN;
// process leftover un-encoded input from last write
if self.extra_input_occupied_len > 0 {
debug_assert!(self.extra_input_occupied_len < 3);
if input.len() + self.extra_input_occupied_len >= MIN_ENCODE_CHUNK_SIZE {
// Fill up `extra`, encode that into `output`, and consume as much of the rest of
// `input` as possible.
// We could write just the encoding of `extra` by itself but then we'd have to
// return after writing only 4 bytes, which is inefficient if the underlying writer
// would make a syscall.
extra_input_read_len = MIN_ENCODE_CHUNK_SIZE - self.extra_input_occupied_len;
debug_assert!(extra_input_read_len > 0);
// overwrite only bytes that weren't already used. If we need to rollback extra_len
// (when the subsequent write errors), the old leading bytes will still be there.
self.extra_input[self.extra_input_occupied_len..MIN_ENCODE_CHUNK_SIZE]
.copy_from_slice(&input[0..extra_input_read_len]);
let len = encode_to_slice(
&self.extra_input[0..MIN_ENCODE_CHUNK_SIZE],
&mut self.output[..],
self.config.char_set.encode_table(),
);
debug_assert_eq!(4, len);
input = &input[extra_input_read_len..];
// consider extra to be used up, since we encoded it
self.extra_input_occupied_len = 0;
// don't clobber where we just encoded to
encoded_size = 4;
// and don't read more than can be encoded
max_input_len = MAX_INPUT_LEN - MIN_ENCODE_CHUNK_SIZE;
// fall through to normal encoding
} else {
// `extra` and `input` are non empty, but `|extra| + |input| < 3`, so there must be
// 1 byte in each.
debug_assert_eq!(1, input.len());
debug_assert_eq!(1, self.extra_input_occupied_len);
self.extra_input[self.extra_input_occupied_len] = input[0];
self.extra_input_occupied_len += 1;
return Ok(1);
};
} else if input.len() < MIN_ENCODE_CHUNK_SIZE {
// `extra` is empty, and `input` fits inside it
self.extra_input[0..input.len()].copy_from_slice(input);
self.extra_input_occupied_len = input.len();
return Ok(input.len());
};
// either 0 or 1 complete chunks encoded from extra
debug_assert!(encoded_size == 0 || encoded_size == 4);
debug_assert!(
// didn't encode extra input
MAX_INPUT_LEN == max_input_len
// encoded one triple
|| MAX_INPUT_LEN == max_input_len + MIN_ENCODE_CHUNK_SIZE
);
// encode complete triples only
let input_complete_chunks_len = input.len() - (input.len() % MIN_ENCODE_CHUNK_SIZE);
let input_chunks_to_encode_len = cmp::min(input_complete_chunks_len, max_input_len);
debug_assert_eq!(0, max_input_len % MIN_ENCODE_CHUNK_SIZE);
debug_assert_eq!(0, input_chunks_to_encode_len % MIN_ENCODE_CHUNK_SIZE);
encoded_size += encode_to_slice(
&input[..(input_chunks_to_encode_len)],
&mut self.output[encoded_size..],
self.config.char_set.encode_table(),
);
// not updating `self.output_occupied_len` here because if the below write fails, it should
// "never take place" -- the buffer contents we encoded are ignored and perhaps retried
// later, if the consumer chooses.
self.write_to_delegate(encoded_size)
// no matter whether we wrote the full encoded buffer or not, we consumed the same
// input
.map(|_| extra_input_read_len + input_chunks_to_encode_len)
.map_err(|e| {
// in case we filled and encoded `extra`, reset extra_len
self.extra_input_occupied_len = orig_extra_len;
e
})
}
/// Because this is usually treated as OK to call multiple times, it will *not* flush any
/// incomplete chunks of input or write padding.
/// # Errors
///
/// The first error that is not of [`ErrorKind::Interrupted`] will be returned.
fn flush(&mut self) -> Result<()> {
self.write_all_encoded_output()?;
self.delegate
.as_mut()
.expect("Writer must be present")
.flush()
}
}
impl<W: Write> Drop for EncoderWriter<W> {
fn drop(&mut self) {
if !self.panicked {
// like `BufWriter`, ignore errors during drop
let _ = self.write_final_leftovers();
}
}
}

View File

@ -0,0 +1,176 @@
use super::encoder::EncoderWriter;
use crate::Config;
use std::io;
use std::io::Write;
/// A `Write` implementation that base64-encodes data using the provided config and accumulates the
/// resulting base64 in memory, which is then exposed as a String via `into_inner()`.
///
/// # Examples
///
/// Buffer base64 in a new String:
///
/// ```
/// use std::io::Write;
///
/// let mut enc = base64::write::EncoderStringWriter::new(base64::STANDARD);
///
/// enc.write_all(b"asdf").unwrap();
///
/// // get the resulting String
/// let b64_string = enc.into_inner();
///
/// assert_eq!("YXNkZg==", &b64_string);
/// ```
///
/// Or, append to an existing String:
///
/// ```
/// use std::io::Write;
///
/// let mut buf = String::from("base64: ");
///
/// let mut enc = base64::write::EncoderStringWriter::from(&mut buf, base64::STANDARD);
///
/// enc.write_all(b"asdf").unwrap();
///
/// // release the &mut reference on buf
/// let _ = enc.into_inner();
///
/// assert_eq!("base64: YXNkZg==", &buf);
/// ```
///
/// # Panics
///
/// Calling `write()` (or related methods) or `finish()` after `finish()` has completed without
/// error is invalid and will panic.
///
/// # Performance
///
/// Because it has to validate that the base64 is UTF-8, it is about 80% as fast as writing plain
/// bytes to a `io::Write`.
pub struct EncoderStringWriter<S: StrConsumer> {
encoder: EncoderWriter<Utf8SingleCodeUnitWriter<S>>,
}
impl<S: StrConsumer> EncoderStringWriter<S> {
/// Create a EncoderStringWriter that will append to the provided `StrConsumer`.
pub fn from(str_consumer: S, config: Config) -> Self {
EncoderStringWriter {
encoder: EncoderWriter::new(Utf8SingleCodeUnitWriter { str_consumer }, config),
}
}
/// Encode all remaining buffered data, including any trailing incomplete input triples and
/// associated padding.
///
/// Once this succeeds, no further writes or calls to this method are allowed.
///
/// Returns the base64-encoded form of the accumulated written data.
pub fn into_inner(mut self) -> S {
self.encoder
.finish()
.expect("Writing to a Vec<u8> should never fail")
.str_consumer
}
}
impl EncoderStringWriter<String> {
/// Create a EncoderStringWriter that will encode into a new String with the provided config.
pub fn new(config: Config) -> Self {
EncoderStringWriter::from(String::new(), config)
}
}
impl<S: StrConsumer> Write for EncoderStringWriter<S> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.encoder.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.encoder.flush()
}
}
/// An abstraction around consuming `str`s produced by base64 encoding.
pub trait StrConsumer {
/// Consume the base64 encoded data in `buf`
fn consume(&mut self, buf: &str);
}
/// As for io::Write, `StrConsumer` is implemented automatically for `&mut S`.
impl<S: StrConsumer + ?Sized> StrConsumer for &mut S {
fn consume(&mut self, buf: &str) {
(**self).consume(buf)
}
}
/// Pushes the str onto the end of the String
impl StrConsumer for String {
fn consume(&mut self, buf: &str) {
self.push_str(buf)
}
}
/// A `Write` that only can handle bytes that are valid single-byte UTF-8 code units.
///
/// This is safe because we only use it when writing base64, which is always valid UTF-8.
struct Utf8SingleCodeUnitWriter<S: StrConsumer> {
str_consumer: S,
}
impl<S: StrConsumer> io::Write for Utf8SingleCodeUnitWriter<S> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
// Because we expect all input to be valid utf-8 individual bytes, we can encode any buffer
// length
let s = std::str::from_utf8(buf).expect("Input must be valid UTF-8");
self.str_consumer.consume(s);
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
// no op
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::encode_config_buf;
use crate::tests::random_config;
use crate::write::encoder_string_writer::EncoderStringWriter;
use rand::Rng;
use std::io::Write;
#[test]
fn every_possible_split_of_input() {
let mut rng = rand::thread_rng();
let mut orig_data = Vec::<u8>::new();
let mut normal_encoded = String::new();
let size = 5_000;
for i in 0..size {
orig_data.clear();
normal_encoded.clear();
for _ in 0..size {
orig_data.push(rng.gen());
}
let config = random_config(&mut rng);
encode_config_buf(&orig_data, config, &mut normal_encoded);
let mut stream_encoder = EncoderStringWriter::new(config);
// Write the first i bytes, then the rest
stream_encoder.write_all(&orig_data[0..i]).unwrap();
stream_encoder.write_all(&orig_data[i..]).unwrap();
let stream_encoded = stream_encoder.into_inner();
assert_eq!(normal_encoded, stream_encoded);
}
}
}

View File

@ -0,0 +1,568 @@
use super::EncoderWriter;
use crate::tests::random_config;
use crate::{encode_config, encode_config_buf, STANDARD_NO_PAD, URL_SAFE};
use std::io::{Cursor, Write};
use std::{cmp, io, str};
use rand::Rng;
#[test]
fn encode_three_bytes() {
let mut c = Cursor::new(Vec::new());
{
let mut enc = EncoderWriter::new(&mut c, URL_SAFE);
let sz = enc.write(b"abc").unwrap();
assert_eq!(sz, 3);
}
assert_eq!(&c.get_ref()[..], encode_config("abc", URL_SAFE).as_bytes());
}
#[test]
fn encode_nine_bytes_two_writes() {
let mut c = Cursor::new(Vec::new());
{
let mut enc = EncoderWriter::new(&mut c, URL_SAFE);
let sz = enc.write(b"abcdef").unwrap();
assert_eq!(sz, 6);
let sz = enc.write(b"ghi").unwrap();
assert_eq!(sz, 3);
}
assert_eq!(
&c.get_ref()[..],
encode_config("abcdefghi", URL_SAFE).as_bytes()
);
}
#[test]
fn encode_one_then_two_bytes() {
let mut c = Cursor::new(Vec::new());
{
let mut enc = EncoderWriter::new(&mut c, URL_SAFE);
let sz = enc.write(b"a").unwrap();
assert_eq!(sz, 1);
let sz = enc.write(b"bc").unwrap();
assert_eq!(sz, 2);
}
assert_eq!(&c.get_ref()[..], encode_config("abc", URL_SAFE).as_bytes());
}
#[test]
fn encode_one_then_five_bytes() {
let mut c = Cursor::new(Vec::new());
{
let mut enc = EncoderWriter::new(&mut c, URL_SAFE);
let sz = enc.write(b"a").unwrap();
assert_eq!(sz, 1);
let sz = enc.write(b"bcdef").unwrap();
assert_eq!(sz, 5);
}
assert_eq!(
&c.get_ref()[..],
encode_config("abcdef", URL_SAFE).as_bytes()
);
}
#[test]
fn encode_1_2_3_bytes() {
let mut c = Cursor::new(Vec::new());
{
let mut enc = EncoderWriter::new(&mut c, URL_SAFE);
let sz = enc.write(b"a").unwrap();
assert_eq!(sz, 1);
let sz = enc.write(b"bc").unwrap();
assert_eq!(sz, 2);
let sz = enc.write(b"def").unwrap();
assert_eq!(sz, 3);
}
assert_eq!(
&c.get_ref()[..],
encode_config("abcdef", URL_SAFE).as_bytes()
);
}
#[test]
fn encode_with_padding() {
let mut c = Cursor::new(Vec::new());
{
let mut enc = EncoderWriter::new(&mut c, URL_SAFE);
enc.write_all(b"abcd").unwrap();
enc.flush().unwrap();
}
assert_eq!(&c.get_ref()[..], encode_config("abcd", URL_SAFE).as_bytes());
}
#[test]
fn encode_with_padding_multiple_writes() {
let mut c = Cursor::new(Vec::new());
{
let mut enc = EncoderWriter::new(&mut c, URL_SAFE);
assert_eq!(1, enc.write(b"a").unwrap());
assert_eq!(2, enc.write(b"bc").unwrap());
assert_eq!(3, enc.write(b"def").unwrap());
assert_eq!(1, enc.write(b"g").unwrap());
enc.flush().unwrap();
}
assert_eq!(
&c.get_ref()[..],
encode_config("abcdefg", URL_SAFE).as_bytes()
);
}
#[test]
fn finish_writes_extra_byte() {
let mut c = Cursor::new(Vec::new());
{
let mut enc = EncoderWriter::new(&mut c, URL_SAFE);
assert_eq!(6, enc.write(b"abcdef").unwrap());
// will be in extra
assert_eq!(1, enc.write(b"g").unwrap());
// 1 trailing byte = 2 encoded chars
let _ = enc.finish().unwrap();
}
assert_eq!(
&c.get_ref()[..],
encode_config("abcdefg", URL_SAFE).as_bytes()
);
}
#[test]
fn write_partial_chunk_encodes_partial_chunk() {
let mut c = Cursor::new(Vec::new());
{
let mut enc = EncoderWriter::new(&mut c, STANDARD_NO_PAD);
// nothing encoded yet
assert_eq!(2, enc.write(b"ab").unwrap());
// encoded here
let _ = enc.finish().unwrap();
}
assert_eq!(
&c.get_ref()[..],
encode_config("ab", STANDARD_NO_PAD).as_bytes()
);
assert_eq!(3, c.get_ref().len());
}
#[test]
fn write_1_chunk_encodes_complete_chunk() {
let mut c = Cursor::new(Vec::new());
{
let mut enc = EncoderWriter::new(&mut c, STANDARD_NO_PAD);
assert_eq!(3, enc.write(b"abc").unwrap());
let _ = enc.finish().unwrap();
}
assert_eq!(
&c.get_ref()[..],
encode_config("abc", STANDARD_NO_PAD).as_bytes()
);
assert_eq!(4, c.get_ref().len());
}
#[test]
fn write_1_chunk_and_partial_encodes_only_complete_chunk() {
let mut c = Cursor::new(Vec::new());
{
let mut enc = EncoderWriter::new(&mut c, STANDARD_NO_PAD);
// "d" not written
assert_eq!(3, enc.write(b"abcd").unwrap());
let _ = enc.finish().unwrap();
}
assert_eq!(
&c.get_ref()[..],
encode_config("abc", STANDARD_NO_PAD).as_bytes()
);
assert_eq!(4, c.get_ref().len());
}
#[test]
fn write_2_partials_to_exactly_complete_chunk_encodes_complete_chunk() {
let mut c = Cursor::new(Vec::new());
{
let mut enc = EncoderWriter::new(&mut c, STANDARD_NO_PAD);
assert_eq!(1, enc.write(b"a").unwrap());
assert_eq!(2, enc.write(b"bc").unwrap());
let _ = enc.finish().unwrap();
}
assert_eq!(
&c.get_ref()[..],
encode_config("abc", STANDARD_NO_PAD).as_bytes()
);
assert_eq!(4, c.get_ref().len());
}
#[test]
fn write_partial_then_enough_to_complete_chunk_but_not_complete_another_chunk_encodes_complete_chunk_without_consuming_remaining(
) {
let mut c = Cursor::new(Vec::new());
{
let mut enc = EncoderWriter::new(&mut c, STANDARD_NO_PAD);
assert_eq!(1, enc.write(b"a").unwrap());
// doesn't consume "d"
assert_eq!(2, enc.write(b"bcd").unwrap());
let _ = enc.finish().unwrap();
}
assert_eq!(
&c.get_ref()[..],
encode_config("abc", STANDARD_NO_PAD).as_bytes()
);
assert_eq!(4, c.get_ref().len());
}
#[test]
fn write_partial_then_enough_to_complete_chunk_and_another_chunk_encodes_complete_chunks() {
let mut c = Cursor::new(Vec::new());
{
let mut enc = EncoderWriter::new(&mut c, STANDARD_NO_PAD);
assert_eq!(1, enc.write(b"a").unwrap());
// completes partial chunk, and another chunk
assert_eq!(5, enc.write(b"bcdef").unwrap());
let _ = enc.finish().unwrap();
}
assert_eq!(
&c.get_ref()[..],
encode_config("abcdef", STANDARD_NO_PAD).as_bytes()
);
assert_eq!(8, c.get_ref().len());
}
#[test]
fn write_partial_then_enough_to_complete_chunk_and_another_chunk_and_another_partial_chunk_encodes_only_complete_chunks(
) {
let mut c = Cursor::new(Vec::new());
{
let mut enc = EncoderWriter::new(&mut c, STANDARD_NO_PAD);
assert_eq!(1, enc.write(b"a").unwrap());
// completes partial chunk, and another chunk, with one more partial chunk that's not
// consumed
assert_eq!(5, enc.write(b"bcdefe").unwrap());
let _ = enc.finish().unwrap();
}
assert_eq!(
&c.get_ref()[..],
encode_config("abcdef", STANDARD_NO_PAD).as_bytes()
);
assert_eq!(8, c.get_ref().len());
}
#[test]
fn drop_calls_finish_for_you() {
let mut c = Cursor::new(Vec::new());
{
let mut enc = EncoderWriter::new(&mut c, STANDARD_NO_PAD);
assert_eq!(1, enc.write(b"a").unwrap());
}
assert_eq!(
&c.get_ref()[..],
encode_config("a", STANDARD_NO_PAD).as_bytes()
);
assert_eq!(2, c.get_ref().len());
}
#[test]
fn every_possible_split_of_input() {
let mut rng = rand::thread_rng();
let mut orig_data = Vec::<u8>::new();
let mut stream_encoded = Vec::<u8>::new();
let mut normal_encoded = String::new();
let size = 5_000;
for i in 0..size {
orig_data.clear();
stream_encoded.clear();
normal_encoded.clear();
for _ in 0..size {
orig_data.push(rng.gen());
}
let config = random_config(&mut rng);
encode_config_buf(&orig_data, config, &mut normal_encoded);
{
let mut stream_encoder = EncoderWriter::new(&mut stream_encoded, config);
// Write the first i bytes, then the rest
stream_encoder.write_all(&orig_data[0..i]).unwrap();
stream_encoder.write_all(&orig_data[i..]).unwrap();
}
assert_eq!(normal_encoded, str::from_utf8(&stream_encoded).unwrap());
}
}
#[test]
fn encode_random_config_matches_normal_encode_reasonable_input_len() {
// choose up to 2 * buf size, so ~half the time it'll use a full buffer
do_encode_random_config_matches_normal_encode(super::encoder::BUF_SIZE * 2)
}
#[test]
fn encode_random_config_matches_normal_encode_tiny_input_len() {
do_encode_random_config_matches_normal_encode(10)
}
#[test]
fn retrying_writes_that_error_with_interrupted_works() {
let mut rng = rand::thread_rng();
let mut orig_data = Vec::<u8>::new();
let mut stream_encoded = Vec::<u8>::new();
let mut normal_encoded = String::new();
for _ in 0..1_000 {
orig_data.clear();
stream_encoded.clear();
normal_encoded.clear();
let orig_len: usize = rng.gen_range(100, 20_000);
for _ in 0..orig_len {
orig_data.push(rng.gen());
}
// encode the normal way
let config = random_config(&mut rng);
encode_config_buf(&orig_data, config, &mut normal_encoded);
// encode via the stream encoder
{
let mut interrupt_rng = rand::thread_rng();
let mut interrupting_writer = InterruptingWriter {
w: &mut stream_encoded,
rng: &mut interrupt_rng,
fraction: 0.8,
};
let mut stream_encoder = EncoderWriter::new(&mut interrupting_writer, config);
let mut bytes_consumed = 0;
while bytes_consumed < orig_len {
// use short inputs since we want to use `extra` a lot as that's what needs rollback
// when errors occur
let input_len: usize = cmp::min(rng.gen_range(0, 10), orig_len - bytes_consumed);
retry_interrupted_write_all(
&mut stream_encoder,
&orig_data[bytes_consumed..bytes_consumed + input_len],
)
.unwrap();
bytes_consumed += input_len;
}
loop {
let res = stream_encoder.finish();
match res {
Ok(_) => break,
Err(e) => match e.kind() {
io::ErrorKind::Interrupted => continue,
_ => Err(e).unwrap(), // bail
},
}
}
assert_eq!(orig_len, bytes_consumed);
}
assert_eq!(normal_encoded, str::from_utf8(&stream_encoded).unwrap());
}
}
#[test]
fn writes_that_only_write_part_of_input_and_sometimes_interrupt_produce_correct_encoded_data() {
let mut rng = rand::thread_rng();
let mut orig_data = Vec::<u8>::new();
let mut stream_encoded = Vec::<u8>::new();
let mut normal_encoded = String::new();
for _ in 0..1_000 {
orig_data.clear();
stream_encoded.clear();
normal_encoded.clear();
let orig_len: usize = rng.gen_range(100, 20_000);
for _ in 0..orig_len {
orig_data.push(rng.gen());
}
// encode the normal way
let config = random_config(&mut rng);
encode_config_buf(&orig_data, config, &mut normal_encoded);
// encode via the stream encoder
{
let mut partial_rng = rand::thread_rng();
let mut partial_writer = PartialInterruptingWriter {
w: &mut stream_encoded,
rng: &mut partial_rng,
full_input_fraction: 0.1,
no_interrupt_fraction: 0.1,
};
let mut stream_encoder = EncoderWriter::new(&mut partial_writer, config);
let mut bytes_consumed = 0;
while bytes_consumed < orig_len {
// use at most medium-length inputs to exercise retry logic more aggressively
let input_len: usize = cmp::min(rng.gen_range(0, 100), orig_len - bytes_consumed);
let res =
stream_encoder.write(&orig_data[bytes_consumed..bytes_consumed + input_len]);
// retry on interrupt
match res {
Ok(len) => bytes_consumed += len,
Err(e) => match e.kind() {
io::ErrorKind::Interrupted => continue,
_ => {
panic!("should not see other errors");
}
},
}
}
let _ = stream_encoder.finish().unwrap();
assert_eq!(orig_len, bytes_consumed);
}
assert_eq!(normal_encoded, str::from_utf8(&stream_encoded).unwrap());
}
}
/// Retry writes until all the data is written or an error that isn't Interrupted is returned.
fn retry_interrupted_write_all<W: Write>(w: &mut W, buf: &[u8]) -> io::Result<()> {
let mut bytes_consumed = 0;
while bytes_consumed < buf.len() {
let res = w.write(&buf[bytes_consumed..]);
match res {
Ok(len) => bytes_consumed += len,
Err(e) => match e.kind() {
io::ErrorKind::Interrupted => continue,
_ => return Err(e),
},
}
}
Ok(())
}
fn do_encode_random_config_matches_normal_encode(max_input_len: usize) {
let mut rng = rand::thread_rng();
let mut orig_data = Vec::<u8>::new();
let mut stream_encoded = Vec::<u8>::new();
let mut normal_encoded = String::new();
for _ in 0..1_000 {
orig_data.clear();
stream_encoded.clear();
normal_encoded.clear();
let orig_len: usize = rng.gen_range(100, 20_000);
for _ in 0..orig_len {
orig_data.push(rng.gen());
}
// encode the normal way
let config = random_config(&mut rng);
encode_config_buf(&orig_data, config, &mut normal_encoded);
// encode via the stream encoder
{
let mut stream_encoder = EncoderWriter::new(&mut stream_encoded, config);
let mut bytes_consumed = 0;
while bytes_consumed < orig_len {
let input_len: usize =
cmp::min(rng.gen_range(0, max_input_len), orig_len - bytes_consumed);
// write a little bit of the data
stream_encoder
.write_all(&orig_data[bytes_consumed..bytes_consumed + input_len])
.unwrap();
bytes_consumed += input_len;
}
let _ = stream_encoder.finish().unwrap();
assert_eq!(orig_len, bytes_consumed);
}
assert_eq!(normal_encoded, str::from_utf8(&stream_encoded).unwrap());
}
}
/// A `Write` implementation that returns Interrupted some fraction of the time, randomly.
struct InterruptingWriter<'a, W: 'a + Write, R: 'a + Rng> {
w: &'a mut W,
rng: &'a mut R,
/// In [0, 1]. If a random number in [0, 1] is `<= threshold`, `Write` methods will return
/// an `Interrupted` error
fraction: f64,
}
impl<'a, W: Write, R: Rng> Write for InterruptingWriter<'a, W, R> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
if self.rng.gen_range(0.0, 1.0) <= self.fraction {
return Err(io::Error::new(io::ErrorKind::Interrupted, "interrupted"));
}
self.w.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
if self.rng.gen_range(0.0, 1.0) <= self.fraction {
return Err(io::Error::new(io::ErrorKind::Interrupted, "interrupted"));
}
self.w.flush()
}
}
/// A `Write` implementation that sometimes will only write part of its input.
struct PartialInterruptingWriter<'a, W: 'a + Write, R: 'a + Rng> {
w: &'a mut W,
rng: &'a mut R,
/// In [0, 1]. If a random number in [0, 1] is `<= threshold`, `write()` will write all its
/// input. Otherwise, it will write a random substring
full_input_fraction: f64,
no_interrupt_fraction: f64,
}
impl<'a, W: Write, R: Rng> Write for PartialInterruptingWriter<'a, W, R> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
if self.rng.gen_range(0.0, 1.0) > self.no_interrupt_fraction {
return Err(io::Error::new(io::ErrorKind::Interrupted, "interrupted"));
}
if self.rng.gen_range(0.0, 1.0) <= self.full_input_fraction || buf.len() == 0 {
// pass through the buf untouched
self.w.write(buf)
} else {
// only use a prefix of it
self.w
.write(&buf[0..(self.rng.gen_range(0, buf.len() - 1))])
}
}
fn flush(&mut self) -> io::Result<()> {
self.w.flush()
}
}

View File

@ -0,0 +1,8 @@
//! Implementations of `io::Write` to transparently handle base64.
mod encoder;
mod encoder_string_writer;
pub use self::encoder::EncoderWriter;
pub use self::encoder_string_writer::EncoderStringWriter;
#[cfg(test)]
mod encoder_tests;

View File

@ -0,0 +1,105 @@
extern crate base64;
use base64::*;
fn compare_encode(expected: &str, target: &[u8]) {
assert_eq!(expected, encode(target));
}
#[test]
fn encode_rfc4648_0() {
compare_encode("", b"");
}
#[test]
fn encode_rfc4648_1() {
compare_encode("Zg==", b"f");
}
#[test]
fn encode_rfc4648_2() {
compare_encode("Zm8=", b"fo");
}
#[test]
fn encode_rfc4648_3() {
compare_encode("Zm9v", b"foo");
}
#[test]
fn encode_rfc4648_4() {
compare_encode("Zm9vYg==", b"foob");
}
#[test]
fn encode_rfc4648_5() {
compare_encode("Zm9vYmE=", b"fooba");
}
#[test]
fn encode_rfc4648_6() {
compare_encode("Zm9vYmFy", b"foobar");
}
#[test]
fn encode_all_ascii() {
let mut ascii = Vec::<u8>::with_capacity(128);
for i in 0..128 {
ascii.push(i);
}
compare_encode(
"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7P\
D0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn8\
=",
&ascii,
);
}
#[test]
fn encode_all_bytes() {
let mut bytes = Vec::<u8>::with_capacity(256);
for i in 0..255 {
bytes.push(i);
}
bytes.push(255); //bug with "overflowing" ranges?
compare_encode(
"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7P\
D0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn\
+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6\
/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==",
&bytes,
);
}
#[test]
fn encode_all_bytes_url() {
let mut bytes = Vec::<u8>::with_capacity(256);
for i in 0..255 {
bytes.push(i);
}
bytes.push(255); //bug with "overflowing" ranges?
assert_eq!(
"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0\
-P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn\
-AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq\
-wsbKztLW2t7i5uru8vb6_wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t_g4eLj5OXm5-jp6uvs7e7v8PHy\
8_T19vf4-fr7_P3-_w==",
encode_config(&bytes, URL_SAFE)
);
}
#[test]
fn encode_url_safe_without_padding() {
let encoded = encode_config(b"alice", URL_SAFE_NO_PAD);
assert_eq!(&encoded, "YWxpY2U");
assert_eq!(
String::from_utf8(decode(&encoded).unwrap()).unwrap(),
"alice"
);
}

View File

@ -0,0 +1,194 @@
extern crate base64;
extern crate rand;
use rand::{FromEntropy, Rng};
use base64::*;
mod helpers;
use self::helpers::*;
// generate random contents of the specified length and test encode/decode roundtrip
fn roundtrip_random(
byte_buf: &mut Vec<u8>,
str_buf: &mut String,
config: Config,
byte_len: usize,
approx_values_per_byte: u8,
max_rounds: u64,
) {
// let the short ones be short but don't let it get too crazy large
let num_rounds = calculate_number_of_rounds(byte_len, approx_values_per_byte, max_rounds);
let mut r = rand::rngs::SmallRng::from_entropy();
let mut decode_buf = Vec::new();
for _ in 0..num_rounds {
byte_buf.clear();
str_buf.clear();
decode_buf.clear();
while byte_buf.len() < byte_len {
byte_buf.push(r.gen::<u8>());
}
encode_config_buf(&byte_buf, config, str_buf);
decode_config_buf(&str_buf, config, &mut decode_buf).unwrap();
assert_eq!(byte_buf, &decode_buf);
}
}
fn calculate_number_of_rounds(byte_len: usize, approx_values_per_byte: u8, max: u64) -> u64 {
// don't overflow
let mut prod = approx_values_per_byte as u64;
for _ in 0..byte_len {
if prod > max {
return max;
}
prod = prod.saturating_mul(prod);
}
prod
}
fn no_pad_config() -> Config {
Config::new(CharacterSet::Standard, false)
}
#[test]
fn roundtrip_random_short_standard() {
let mut byte_buf: Vec<u8> = Vec::new();
let mut str_buf = String::new();
for input_len in 0..40 {
roundtrip_random(&mut byte_buf, &mut str_buf, STANDARD, input_len, 4, 10000);
}
}
#[test]
fn roundtrip_random_with_fast_loop_standard() {
let mut byte_buf: Vec<u8> = Vec::new();
let mut str_buf = String::new();
for input_len in 40..100 {
roundtrip_random(&mut byte_buf, &mut str_buf, STANDARD, input_len, 4, 1000);
}
}
#[test]
fn roundtrip_random_short_no_padding() {
let mut byte_buf: Vec<u8> = Vec::new();
let mut str_buf = String::new();
for input_len in 0..40 {
roundtrip_random(
&mut byte_buf,
&mut str_buf,
no_pad_config(),
input_len,
4,
10000,
);
}
}
#[test]
fn roundtrip_random_no_padding() {
let mut byte_buf: Vec<u8> = Vec::new();
let mut str_buf = String::new();
for input_len in 40..100 {
roundtrip_random(
&mut byte_buf,
&mut str_buf,
no_pad_config(),
input_len,
4,
1000,
);
}
}
#[test]
fn roundtrip_decode_trailing_10_bytes() {
// This is a special case because we decode 8 byte blocks of input at a time as much as we can,
// ideally unrolled to 32 bytes at a time, in stages 1 and 2. Since we also write a u64's worth
// of bytes (8) to the output, we always write 2 garbage bytes that then will be overwritten by
// the NEXT block. However, if the next block only contains 2 bytes, it will decode to 1 byte,
// and therefore be too short to cover up the trailing 2 garbage bytes. Thus, we have stage 3
// to handle that case.
for num_quads in 0..25 {
let mut s: String = std::iter::repeat("ABCD").take(num_quads).collect();
s.push_str("EFGHIJKLZg");
let decoded = decode(&s).unwrap();
assert_eq!(num_quads * 3 + 7, decoded.len());
assert_eq!(s, encode_config(&decoded, STANDARD_NO_PAD));
}
}
#[test]
fn display_wrapper_matches_normal_encode() {
let mut bytes = Vec::<u8>::with_capacity(256);
for i in 0..255 {
bytes.push(i);
}
bytes.push(255);
assert_eq!(
encode(&bytes),
format!(
"{}",
base64::display::Base64Display::with_config(&bytes, STANDARD)
)
);
}
#[test]
fn because_we_can() {
compare_decode("alice", "YWxpY2U=");
compare_decode("alice", &encode(b"alice"));
compare_decode("alice", &encode(&decode(&encode(b"alice")).unwrap()));
}
#[test]
fn encode_config_slice_can_use_inline_buffer() {
let mut buf: [u8; 22] = [0; 22];
let mut larger_buf: [u8; 24] = [0; 24];
let mut input: [u8; 16] = [0; 16];
let mut rng = rand::rngs::SmallRng::from_entropy();
for elt in &mut input {
*elt = rng.gen();
}
assert_eq!(22, encode_config_slice(&input, STANDARD_NO_PAD, &mut buf));
let decoded = decode_config(&buf, STANDARD_NO_PAD).unwrap();
assert_eq!(decoded, input);
// let's try it again with padding
assert_eq!(24, encode_config_slice(&input, STANDARD, &mut larger_buf));
let decoded = decode_config(&buf, STANDARD).unwrap();
assert_eq!(decoded, input);
}
#[test]
#[should_panic(expected = "index 24 out of range for slice of length 22")]
fn encode_config_slice_panics_when_buffer_too_small() {
let mut buf: [u8; 22] = [0; 22];
let mut input: [u8; 16] = [0; 16];
let mut rng = rand::rngs::SmallRng::from_entropy();
for elt in &mut input {
*elt = rng.gen();
}
encode_config_slice(&input, STANDARD, &mut buf);
}

View File

@ -1 +1 @@
{"files":{"Cargo.lock":"ba3f757ac955932ea298d293ce44dfb25e064ffac93e429c13bd684a686d2ec1","Cargo.toml":"3402755896ee085ef905b9ff4d2ed25040e4fd53e254353f1bb044252404072b","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"0dd882e53de11566d50f8e8e2d5a651bcf3fabee4987d70f306233cf39094ba7","README.md":"2810098d290f3df719e6f41ffca38bb954d0fe62d4e56905a9a2436c4784bebf","RELEASE-NOTES.md":"5aaccee22ad349c72531bdd6c68b233bccc8c1eb23dce2cf5e05ae4498a15a1a","benches/benchmarks.rs":"bc1f603c5aa87627a93eee71eaed64fbd767d013051bac00ea265c16fecb30b9","examples/base64.rs":"f397b8726df41fce0793a8c6ebe95d4651aa37ed746da305032f1e99d9c37235","examples/make_tables.rs":"392f51b3edb1b5a2c62b823337c7785a6b8535f39f09283b1913a5c68fb186bf","icon_CLion.svg":"cffa044ba75cb998ee3306991dc4a3755ec2f39ab95ddd4b74bc21988389020f","src/chunked_encoder.rs":"fba5ea5f1204b9bf11291ec3483bcb23d330101eb2f6635e33cd63e4de13b653","src/decode.rs":"fa18535da4c549bdb29e4c4f074387645467384f88a1ecdb72c31828bd930ee3","src/display.rs":"55f9cd05ba037b77689e2558fa174055fad5273959d1edce3a2289f94244fd5d","src/encode.rs":"8a0a6b71581b4c52c2844111a3611cf73522533452a27f5ef8d09eaa73594e2e","src/lib.rs":"c7b904fac8706bc4758c2763e7a43dc1edd99ed5641ac2355957f6aeff91eece","src/read/decoder.rs":"9a7b65e165f7aed6b007bf7436ac9ba9b03d3b03e9d5a1e16691874e21509ced","src/read/decoder_tests.rs":"aacb7624c33ed6b90e068ff9af6095c839b4088060b4c406c08dce25ce837f6d","src/read/mod.rs":"e0b714eda02d16b1ffa6f78fd09b2f963e01c881b1f7c17b39db4e904be5e746","src/tables.rs":"73ce100fd3f4917ec1e8d9277ff0b956cc2636b33145f101a7cf1a5a8b7bacc1","src/tests.rs":"202ddced9cf52205182c6202e583c4c4f929b9d225fd9d1ebdbfd389cb2df0ba","src/write/encoder.rs":"afabacf7fa54f2ec9b1fe4987de818d368d7470ade0396649743a11c81dba28e","src/write/encoder_string_writer.rs":"3f9109585babe048230659f64973cb1633bbb2ed9de255177336260226127b81","src/write/encoder_tests.rs":"381d7c2871407157c36e909c928307ac0389b3d4504fb80607134e94ac59e68f","src/write/mod.rs":"1503b9457e4f5d2895b24136c3af893f0b7ce18dfe4de1096fc5d17f8d78e99b","tests/decode.rs":"da2cbd49b84e0d8b1d8a52136ba3d97cfb248920a45f9955db1e5bc5367218ce","tests/encode.rs":"5efb6904c36c6f899a05078e5c9be756fc58af1ee9940edfa8dea1ee53675364","tests/helpers.rs":"a76015e4a4e8f98213bdbaa592cd9574ccdc95a28e1b1f835a2753e09fa6037f","tests/tests.rs":"05753e5f1d4a6c75015a5342f9b5dc3073c00bdfe0a829a962f8723321c75549"},"package":"904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"} {"files":{"Cargo.lock":"ee9a902629f1a6cc9308158590fc298c628f323383c4fb9a5ab9fd51011b268e","Cargo.toml":"37ffe4d4bdbd21f5a1cc78596abf6e704e4131dbec830fcd6c8bec33d4caf76b","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"0dd882e53de11566d50f8e8e2d5a651bcf3fabee4987d70f306233cf39094ba7","README.md":"99e61de0bafd6985761b596f9aa970dee5b4d0cfbfc05cd6565b5ffa139cb34f","RELEASE-NOTES.md":"c8b9e21adecb3a89928cdfbe55d184cc234e0e6cf8f61fb8ebab48982b3a6f9c","benches/benchmarks.rs":"faf63c3d83fe1568927288cfcc7f9bd4bd15c6b531450c53d2c064386fc5c652","clippy.toml":"ee3dedc35eb156cbfbe836692846cd492f59acc7443dd43cc6d71f087d80051c","examples/base64.rs":"8c48673029aeeb1e06a2ecfd237acf8ef24349990e97f6d2c4d0fa2af36c94b3","icon_CLion.svg":"cffa044ba75cb998ee3306991dc4a3755ec2f39ab95ddd4b74bc21988389020f","src/alphabet.rs":"420b5e23da0702c401489c53721696c5d5f69631f4ca9462f4c5ef3bdc77114e","src/chunked_encoder.rs":"4dfad5b47da1c35deaaa6ed2bb1efa51d98d6d9a7ca85a37ef4a02dfa846e723","src/decode.rs":"c293bf40a821795909a458aa8d7e76005a46e6953eed7ea284da1334e117ae74","src/display.rs":"31bf3e19274a0b80dd8948a81ea535944f756ef5b88736124c940f5fe1e8c71c","src/encode.rs":"34c800de1576f425cc48fa7ed9486d7c925cf7215dfc0634d2349c97b5199595","src/engine/general_purpose/decode.rs":"be237ac84b6a1deafd335896959302b7cf9337868cd718ebf7d249ccdc43a84f","src/engine/general_purpose/decode_suffix.rs":"797729cc1d56e9786f65e765cc5bb9ab2799f9140db4b412b919512fd7542355","src/engine/general_purpose/mod.rs":"2c6fbe61fae32800d30be5dc5bde429b8a07a5f027d0d2d1227a24ed13b1b461","src/engine/mod.rs":"7cd78bb317074a6e5439e272e4943d59d6bd47b149ed76b52e6f75b45909ce52","src/engine/naive.rs":"dcebcc41a0f4a156dd516ae89824748f5a4eedeabfe8d92f6f5bd3a6d5ceb5fb","src/engine/tests.rs":"4a8ff2ab7700b49e5b33606a93af04a5f93b18ca48e760ab6ced6337ba3a4847","src/lib.rs":"b4699408a9356f88fd8a3aeffae97e54e7a249afe5d919ecf9d4092d1c8efde1","src/prelude.rs":"f82fcf5e31921060929f9e10efb2868ba7339b085ee76fc5e7077f6030fbb2cc","src/read/decoder.rs":"65f03af1f4eb8d466a9a800dc6414678195b4ac6c579cd747b5632eee219f5a4","src/read/decoder_tests.rs":"ebf40a5722a58dbe74f013a4163ab20f5ce42ceb4beaefede07562079d596604","src/read/mod.rs":"e0b714eda02d16b1ffa6f78fd09b2f963e01c881b1f7c17b39db4e904be5e746","src/tests.rs":"90cb9f8a1ccb7c4ddc4f8618208e0031fc97e0df0e5aa466d6a5cf45d25967d8","src/write/encoder.rs":"c889c853249220fe2ddaeb77ee6e2ee2945f7db88cd6658ef89ff71b81255ea8","src/write/encoder_string_writer.rs":"ac3702b1a846fd0664e78d2dd82c939073ca00577d3201a8f1fbe17a9bf85e70","src/write/encoder_tests.rs":"39572f11fdf63af47f13bb58be280221322c669504a1b4a30a9181fe465e0f90","src/write/mod.rs":"73cd98dadc9d712b3fefd9449d97e825e097397441b90588e0051e4d3b0911b9","tests/encode.rs":"072f079f0250d542ff964e8e72b7d13e2336fbee7395367ff737e140c38ac459","tests/tests.rs":"78efcf0dc4bb6ae52f7a91fcad89e44e4dce578224c36b4e6c1c306459be8500"},"package":"a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"}

875
zeroidc/vendor/base64/Cargo.lock generated vendored

File diff suppressed because it is too large Load Diff

View File

@ -3,39 +3,60 @@
# When uploading crates to the registry Cargo will automatically # When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility # "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies # with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies # to registry (e.g., crates.io) dependencies.
# #
# If you believe there's an error in this file please file an # If you are reading this file be aware that the original Cargo.toml
# issue against the rust-lang/cargo repository. If you're # will likely look very different (and much more reasonable).
# editing this file be aware that the upstream Cargo.toml # See Cargo.toml.orig for the original contents.
# will likely look very different (and much more reasonable)
[package] [package]
edition = "2018" edition = "2021"
rust-version = "1.57.0"
name = "base64" name = "base64"
version = "0.13.0" version = "0.21.0"
authors = ["Alice Maz <alice@alicemaz.com>", "Marshall Pierce <marshall@mpierce.org>"] authors = [
"Alice Maz <alice@alicemaz.com>",
"Marshall Pierce <marshall@mpierce.org>",
]
description = "encodes and decodes base64 as bytes or utf8" description = "encodes and decodes base64 as bytes or utf8"
documentation = "https://docs.rs/base64" documentation = "https://docs.rs/base64"
readme = "README.md" readme = "README.md"
keywords = ["base64", "utf8", "encode", "decode", "no_std"] keywords = [
"base64",
"utf8",
"encode",
"decode",
"no_std",
]
categories = ["encoding"] categories = ["encoding"]
license = "MIT/Apache-2.0" license = "MIT OR Apache-2.0"
repository = "https://github.com/marshallpierce/rust-base64" repository = "https://github.com/marshallpierce/rust-base64"
[profile.bench] [profile.bench]
debug = true debug = true
[profile.test]
opt-level = 3
[[bench]] [[bench]]
name = "benchmarks" name = "benchmarks"
harness = false harness = false
[dev-dependencies.criterion] [dev-dependencies.criterion]
version = "=0.3.2" version = "0.4.0"
[dev-dependencies.rand] [dev-dependencies.rand]
version = "0.6.1" version = "0.8.5"
features = ["small_rng"]
[dev-dependencies.rstest]
version = "0.12.0"
[dev-dependencies.rstest_reuse]
version = "0.3.0"
[dev-dependencies.structopt] [dev-dependencies.structopt]
version = "0.3" version = "0.3.26"
[features] [features]
alloc = [] alloc = []

View File

@ -1,7 +1,6 @@
[base64](https://crates.io/crates/base64) # [base64](https://crates.io/crates/base64)
===
[![](https://img.shields.io/crates/v/base64.svg)](https://crates.io/crates/base64) [![Docs](https://docs.rs/base64/badge.svg)](https://docs.rs/base64) [![Build](https://travis-ci.org/marshallpierce/rust-base64.svg?branch=master)](https://travis-ci.org/marshallpierce/rust-base64) [![codecov](https://codecov.io/gh/marshallpierce/rust-base64/branch/master/graph/badge.svg)](https://codecov.io/gh/marshallpierce/rust-base64) [![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/) [![](https://img.shields.io/crates/v/base64.svg)](https://crates.io/crates/base64) [![Docs](https://docs.rs/base64/badge.svg)](https://docs.rs/base64) [![CircleCI](https://circleci.com/gh/marshallpierce/rust-base64/tree/master.svg?style=shield)](https://circleci.com/gh/marshallpierce/rust-base64/tree/master) [![codecov](https://codecov.io/gh/marshallpierce/rust-base64/branch/master/graph/badge.svg)](https://codecov.io/gh/marshallpierce/rust-base64) [![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/)
<a href="https://www.jetbrains.com/?from=rust-base64"><img src="/icon_CLion.svg" height="40px"/></a> <a href="https://www.jetbrains.com/?from=rust-base64"><img src="/icon_CLion.svg" height="40px"/></a>
@ -9,34 +8,73 @@ Made with CLion. Thanks to JetBrains for supporting open source!
It's base64. What more could anyone want? It's base64. What more could anyone want?
This library's goals are to be *correct* and *fast*. It's thoroughly tested and widely used. It exposes functionality at multiple levels of abstraction so you can choose the level of convenience vs performance that you want, e.g. `decode_config_slice` decodes into an existing `&mut [u8]` and is pretty fast (2.6GiB/s for a 3 KiB input), whereas `decode_config` allocates a new `Vec<u8>` and returns it, which might be more convenient in some cases, but is slower (although still fast enough for almost any purpose) at 2.1 GiB/s. This library's goals are to be *correct* and *fast*. It's thoroughly tested and widely used. It exposes functionality at
multiple levels of abstraction so you can choose the level of convenience vs performance that you want,
Example e.g. `decode_engine_slice` decodes into an existing `&mut [u8]` and is pretty fast (2.6GiB/s for a 3 KiB input),
--- whereas `decode_engine` allocates a new `Vec<u8>` and returns it, which might be more convenient in some cases, but is
slower (although still fast enough for almost any purpose) at 2.1 GiB/s.
```rust
extern crate base64;
use base64::{encode, decode};
fn main() {
let a = b"hello world";
let b = "aGVsbG8gd29ybGQ=";
assert_eq!(encode(a), b);
assert_eq!(a, &decode(b).unwrap()[..]);
}
```
See the [docs](https://docs.rs/base64) for all the details. See the [docs](https://docs.rs/base64) for all the details.
Rust version compatibility ## FAQ
---
The minimum required Rust version is 1.34.0. ### I need to decode base64 with whitespace/null bytes/other random things interspersed in it. What should I do?
Developing Remove non-base64 characters from your input before decoding.
---
If you have a `Vec` of base64, [retain](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.retain) can be used to
strip out whatever you need removed.
If you have a `Read` (e.g. reading a file or network socket), there are various approaches.
- Use [iter_read](https://crates.io/crates/iter-read) together with `Read`'s `bytes()` to filter out unwanted bytes.
- Implement `Read` with a `read()` impl that delegates to your actual `Read`, and then drops any bytes you don't want.
### I need to line-wrap base64, e.g. for MIME/PEM.
[line-wrap](https://crates.io/crates/line-wrap) does just that.
### I want canonical base64 encoding/decoding.
First, don't do this. You should no more expect Base64 to be canonical than you should expect compression algorithms to
produce canonical output across all usage in the wild (hint: they don't).
However, [people are drawn to their own destruction like moths to a flame](https://eprint.iacr.org/2022/361), so here we
are.
There are two opportunities for non-canonical encoding (and thus, detection of the same during decoding): the final bits
of the last encoded token in two or three token suffixes, and the `=` token used to inflate the suffix to a full four
tokens.
The trailing bits issue is unavoidable: with 6 bits available in each encoded token, 1 input byte takes 2 tokens,
with the second one having some bits unused. Same for two input bytes: 16 bits, but 3 tokens have 18 bits. Unless we
decide to stop shipping whole bytes around, we're stuck with those extra bits that a sneaky or buggy encoder might set
to 1 instead of 0.
The `=` pad bytes, on the other hand, are entirely a self-own by the Base64 standard. They do not affect decoding other
than to provide an opportunity to say "that padding is incorrect". Exabytes of storage and transfer have no doubt been
wasted on pointless `=` bytes. Somehow we all seem to be quite comfortable with, say, hex-encoded data just stopping
when it's done rather than requiring a confirmation that the author of the encoder could count to four. Anyway, there
are two ways to make pad bytes predictable: require canonical padding to the next multiple of four bytes as per the RFC,
or, if you control all producers and consumers, save a few bytes by requiring no padding (especially applicable to the
url-safe alphabet).
All `Engine` implementations must at a minimum support treating non-canonical padding of both types as an error, and
optionally may allow other behaviors.
## Rust version compatibility
The minimum supported Rust version is 1.57.0.
# Contributing
Contributions are very welcome. However, because this library is used widely, and in security-sensitive contexts, all
PRs will be carefully scrutinized. Beyond that, this sort of low level library simply needs to be 100% correct. Nobody
wants to chase bugs in encoding of any sort.
All this means that it takes me a fair amount of time to review each PR, so it might take quite a while to carve out the
free time to give each PR the attention it deserves. I will get to everyone eventually!
## Developing
Benchmarks are in `benches/`. Running them requires nightly rust, but `rustup` makes it easy: Benchmarks are in `benches/`. Running them requires nightly rust, but `rustup` makes it easy:
@ -44,23 +82,24 @@ Benchmarks are in `benches/`. Running them requires nightly rust, but `rustup` m
rustup run nightly cargo bench rustup run nightly cargo bench
``` ```
Decoding is aided by some pre-calculated tables, which are generated by: ## no_std
```bash This crate supports no_std. By default the crate targets std via the `std` feature. You can deactivate
cargo run --example make_tables > src/tables.rs.tmp && mv src/tables.rs.tmp src/tables.rs the `default-features` to target `core` instead. In that case you lose out on all the functionality revolving
``` around `std::io`, `std::error::Error`, and heap allocations. There is an additional `alloc` feature that you can activate
to bring back the support for heap allocations.
no_std ## Profiling
---
This crate supports no_std. By default the crate targets std via the `std` feature. You can deactivate the `default-features` to target core instead. In that case you lose out on all the functionality revolving around `std::io`, `std::error::Error` and heap allocations. There is an additional `alloc` feature that you can activate to bring back the support for heap allocations. On Linux, you can use [perf](https://perf.wiki.kernel.org/index.php/Main_Page) for profiling. Then compile the
benchmarks with `rustup nightly run cargo bench --no-run`.
Profiling Run the benchmark binary with `perf` (shown here filtering to one particular benchmark, which will make the results
--- easier to read). `perf` is only available to the root user on most systems as it fiddles with event counters in your
CPU, so use `sudo`. We need to run the actual benchmark binary, hence the path into `target`. You can see the actual
On Linux, you can use [perf](https://perf.wiki.kernel.org/index.php/Main_Page) for profiling. Then compile the benchmarks with `rustup nightly run cargo bench --no-run`. full path with `rustup run nightly cargo bench -v`; it will print out the commands it runs. If you use the exact path
that `bench` outputs, make sure you get the one that's for the benchmarks, not the tests. You may also want
Run the benchmark binary with `perf` (shown here filtering to one particular benchmark, which will make the results easier to read). `perf` is only available to the root user on most systems as it fiddles with event counters in your CPU, so use `sudo`. We need to run the actual benchmark binary, hence the path into `target`. You can see the actual full path with `rustup run nightly cargo bench -v`; it will print out the commands it runs. If you use the exact path that `bench` outputs, make sure you get the one that's for the benchmarks, not the tests. You may also want to `cargo clean` so you have only one `benchmarks-` binary (they tend to accumulate). to `cargo clean` so you have only one `benchmarks-` binary (they tend to accumulate).
```bash ```bash
sudo perf record target/release/deps/benchmarks-* --bench decode_10mib_reuse sudo perf record target/release/deps/benchmarks-* --bench decode_10mib_reuse
@ -72,7 +111,10 @@ Then analyze the results, again with perf:
sudo perf annotate -l sudo perf annotate -l
``` ```
You'll see a bunch of interleaved rust source and assembly like this. The section with `lib.rs:327` is telling us that 4.02% of samples saw the `movzbl` aka bit shift as the active instruction. However, this percentage is not as exact as it seems due to a phenomenon called *skid*. Basically, a consequence of how fancy modern CPUs are is that this sort of instruction profiling is inherently inaccurate, especially in branch-heavy code. You'll see a bunch of interleaved rust source and assembly like this. The section with `lib.rs:327` is telling us that
4.02% of samples saw the `movzbl` aka bit shift as the active instruction. However, this percentage is not as exact as
it seems due to a phenomenon called *skid*. Basically, a consequence of how fancy modern CPUs are is that this sort of
instruction profiling is inherently inaccurate, especially in branch-heavy code.
```text ```text
lib.rs:322 0.70 : 10698: mov %rdi,%rax lib.rs:322 0.70 : 10698: mov %rdi,%rax
@ -94,11 +136,10 @@ You'll see a bunch of interleaved rust source and assembly like this. The sectio
0.00 : 106ab: je 1090e <base64::decode_config_buf::hbf68a45fefa299c1+0x46e> 0.00 : 106ab: je 1090e <base64::decode_config_buf::hbf68a45fefa299c1+0x46e>
``` ```
## Fuzzing
Fuzzing This uses [cargo-fuzz](https://github.com/rust-fuzz/cargo-fuzz). See `fuzz/fuzzers` for the available fuzzing scripts.
--- To run, use an invocation like these:
This uses [cargo-fuzz](https://github.com/rust-fuzz/cargo-fuzz). See `fuzz/fuzzers` for the available fuzzing scripts. To run, use an invocation like these:
```bash ```bash
cargo +nightly fuzz run roundtrip cargo +nightly fuzz run roundtrip
@ -107,8 +148,7 @@ cargo +nightly fuzz run roundtrip_random_config -- -max_len=10240
cargo +nightly fuzz run decode_random cargo +nightly fuzz run decode_random
``` ```
## License
License
---
This project is dual-licensed under MIT and Apache 2.0. This project is dual-licensed under MIT and Apache 2.0.

View File

@ -1,10 +1,122 @@
# 0.21.0
(not yet released)
## Migration
### Functions
| < 0.20 function | 0.21 equivalent |
|-------------------------|-------------------------------------------------------------------------------------|
| `encode()` | `engine::general_purpose::STANDARD.encode()` or `prelude::BASE64_STANDARD.encode()` |
| `encode_config()` | `engine.encode()` |
| `encode_config_buf()` | `engine.encode_string()` |
| `encode_config_slice()` | `engine.encode_slice()` |
| `decode()` | `engine::general_purpose::STANDARD.decode()` or `prelude::BASE64_STANDARD.decode()` |
| `decode_config()` | `engine.decode()` |
| `decode_config_buf()` | `engine.decode_vec()` |
| `decode_config_slice()` | `engine.decode_slice()` |
The short-lived 0.20 functions were the 0.13 functions with `config` replaced with `engine`.
### Padding
If applicable, use the preset engines `engine::STANDARD`, `engine::STANDARD_NO_PAD`, `engine::URL_SAFE`,
or `engine::URL_SAFE_NO_PAD`.
The `NO_PAD` ones require that padding is absent when decoding, and the others require that
canonical padding is present .
If you need the < 0.20 behavior that did not care about padding, or want to recreate < 0.20.0's predefined `Config`s
precisely, see the following table.
| 0.13.1 Config | 0.20.0+ alphabet | `encode_padding` | `decode_padding_mode` |
|-----------------|------------------|------------------|-----------------------|
| STANDARD | STANDARD | true | Indifferent |
| STANDARD_NO_PAD | STANDARD | false | Indifferent |
| URL_SAFE | URL_SAFE | true | Indifferent |
| URL_SAFE_NO_PAD | URL_SAFE | false | Indifferent |
# 0.21.0-rc.1
- Restore the ability to decode into a slice of precisely the correct length with `Engine.decode_slice_unchecked`.
- Add `Engine` as a `pub use` in `prelude`.
# 0.21.0-beta.2
## Breaking changes
- Re-exports of preconfigured engines in `engine` are removed in favor of `base64::prelude::...` that are better suited to those who wish to `use` the entire path to a name.
# 0.21.0-beta.1
## Breaking changes
- `FastPortable` was only meant to be an interim name, and shouldn't have shipped in 0.20. It is now `GeneralPurpose` to
make its intended usage more clear.
- `GeneralPurpose` and its config are now `pub use`'d in the `engine` module for convenience.
- Change a few `from()` functions to be `new()`. `from()` causes confusing compiler errors because of confusion
with `From::from`, and is a little misleading because some of those invocations are not very cheap as one would
usually expect from a `from` call.
- `encode*` and `decode*` top level functions are now methods on `Engine`.
- `DEFAULT_ENGINE` was replaced by `engine::general_purpose::STANDARD`
- Predefined engine consts `engine::general_purpose::{STANDARD, STANDARD_NO_PAD, URL_SAFE, URL_SAFE_NO_PAD}`
- These are `pub use`d into `engine` as well
- The `*_slice` decode/encode functions now return an error instead of panicking when the output slice is too small
- As part of this, there isn't now a public way to decode into a slice _exactly_ the size needed for inputs that
aren't multiples of 4 tokens. If adding up to 2 bytes to always be a multiple of 3 bytes for the decode buffer is
a problem, file an issue.
## Other changes
- `decoded_len_estimate()` is provided to make it easy to size decode buffers correctly.
# 0.20.0
## Breaking changes
- Update MSRV to 1.57.0
- Decoding can now either ignore padding, require correct padding, or require no padding. The default is to require
correct padding.
- The `NO_PAD` config now requires that padding be absent when decoding.
## 0.20.0-alpha.1
### Breaking changes
- Extended the `Config` concept into the `Engine` abstraction, allowing the user to pick different encoding / decoding
implementations.
- What was formerly the only algorithm is now the `FastPortable` engine, so named because it's portable (works on
any CPU) and relatively fast.
- This opens the door to a portable constant-time
implementation ([#153](https://github.com/marshallpierce/rust-base64/pull/153),
presumably `ConstantTimePortable`?) for security-sensitive applications that need side-channel resistance, and
CPU-specific SIMD implementations for more speed.
- Standard base64 per the RFC is available via `DEFAULT_ENGINE`. To use different alphabets or other settings (
padding, etc), create your own engine instance.
- `CharacterSet` is now `Alphabet` (per the RFC), and allows creating custom alphabets. The corresponding tables that
were previously code-generated are now built dynamically.
- Since there are already multiple breaking changes, various functions are renamed to be more consistent and
discoverable.
- MSRV is now 1.47.0 to allow various things to use `const fn`.
- `DecoderReader` now owns its inner reader, and can expose it via `into_inner()`. For symmetry, `EncoderWriter` can do
the same with its writer.
- `encoded_len` is now public so you can size encode buffers precisely.
# 0.13.1
- More precise decode buffer sizing, avoiding unnecessary allocation in `decode_config`.
# 0.13.0 # 0.13.0
- Config methods are const - Config methods are const
- Added `EncoderStringWriter` to allow encoding directly to a String - Added `EncoderStringWriter` to allow encoding directly to a String
- `EncoderWriter` now owns its delegate writer rather than keeping a reference to it (though refs still work) - `EncoderWriter` now owns its delegate writer rather than keeping a reference to it (though refs still work)
- As a consequence, it is now possible to extract the delegate writer from an `EncoderWriter` via `finish()`, which returns `Result<W>` instead of `Result<()>`. If you were calling `finish()` explicitly, you will now need to use `let _ = foo.finish()` instead of just `foo.finish()` to avoid a warning about the unused value. - As a consequence, it is now possible to extract the delegate writer from an `EncoderWriter` via `finish()`, which
- When decoding input that has both an invalid length and an invalid symbol as the last byte, `InvalidByte` will be emitted instead of `InvalidLength` to make the problem more obvious. returns `Result<W>` instead of `Result<()>`. If you were calling `finish()` explicitly, you will now need to
use `let _ = foo.finish()` instead of just `foo.finish()` to avoid a warning about the unused value.
- When decoding input that has both an invalid length and an invalid symbol as the last byte, `InvalidByte` will be
emitted instead of `InvalidLength` to make the problem more obvious.
# 0.12.2 # 0.12.2
@ -22,23 +134,31 @@
- A minor performance improvement in encoding - A minor performance improvement in encoding
# 0.11.0 # 0.11.0
- Minimum rust version 1.34.0 - Minimum rust version 1.34.0
- `no_std` is now supported via the two new features `alloc` and `std`. - `no_std` is now supported via the two new features `alloc` and `std`.
# 0.10.1 # 0.10.1
- Minimum rust version 1.27.2 - Minimum rust version 1.27.2
- Fix bug in streaming encoding ([#90](https://github.com/marshallpierce/rust-base64/pull/90)): if the underlying writer didn't write all the bytes given to it, the remaining bytes would not be retried later. See the docs on `EncoderWriter::write`. - Fix bug in streaming encoding ([#90](https://github.com/marshallpierce/rust-base64/pull/90)): if the underlying writer
didn't write all the bytes given to it, the remaining bytes would not be retried later. See the docs
on `EncoderWriter::write`.
- Make it configurable whether or not to return an error when decoding detects excess trailing bits. - Make it configurable whether or not to return an error when decoding detects excess trailing bits.
# 0.10.0 # 0.10.0
- Remove line wrapping. Line wrapping was never a great conceptual fit in this library, and other features (streaming encoding, etc) either couldn't support it or could support only special cases of it with a great increase in complexity. Line wrapping has been pulled out into a [line-wrap](https://crates.io/crates/line-wrap) crate, so it's still available if you need it. - Remove line wrapping. Line wrapping was never a great conceptual fit in this library, and other features (streaming
- `Base64Display` creation no longer uses a `Result` because it can't fail, which means its helper methods for common encoding, etc) either couldn't support it or could support only special cases of it with a great increase in
configs that `unwrap()` for you are no longer needed complexity. Line wrapping has been pulled out into a [line-wrap](https://crates.io/crates/line-wrap) crate, so it's
still available if you need it.
- `Base64Display` creation no longer uses a `Result` because it can't fail, which means its helper methods for
common
configs that `unwrap()` for you are no longer needed
- Add a streaming encoder `Write` impl to transparently base64 as you write. - Add a streaming encoder `Write` impl to transparently base64 as you write.
- Remove the remaining `unsafe` code. - Remove the remaining `unsafe` code.
- Remove whitespace stripping to simplify `no_std` support. No out of the box configs use it, and it's trivial to do yourself if needed: `filter(|b| !b" \n\t\r\x0b\x0c".contains(b)`. - Remove whitespace stripping to simplify `no_std` support. No out of the box configs use it, and it's trivial to do
yourself if needed: `filter(|b| !b" \n\t\r\x0b\x0c".contains(b)`.
- Detect invalid trailing symbols when decoding and return an error rather than silently ignoring them. - Detect invalid trailing symbols when decoding and return an error rather than silently ignoring them.
# 0.9.3 # 0.9.3

View File

@ -1,27 +1,22 @@
extern crate base64;
#[macro_use] #[macro_use]
extern crate criterion; extern crate criterion;
extern crate rand;
use base64::display;
use base64::{ use base64::{
decode, decode_config_buf, decode_config_slice, encode, encode_config_buf, encode_config_slice, display,
write, Config, engine::{general_purpose::STANDARD, Engine},
write,
}; };
use criterion::{black_box, Bencher, BenchmarkId, Criterion, Throughput};
use criterion::{black_box, Bencher, Criterion, ParameterizedBenchmark, Throughput}; use rand::{Rng, SeedableRng};
use rand::{FromEntropy, Rng};
use std::io::{self, Read, Write}; use std::io::{self, Read, Write};
const TEST_CONFIG: Config = base64::STANDARD;
fn do_decode_bench(b: &mut Bencher, &size: &usize) { fn do_decode_bench(b: &mut Bencher, &size: &usize) {
let mut v: Vec<u8> = Vec::with_capacity(size * 3 / 4); let mut v: Vec<u8> = Vec::with_capacity(size * 3 / 4);
fill(&mut v); fill(&mut v);
let encoded = encode(&v); let encoded = STANDARD.encode(&v);
b.iter(|| { b.iter(|| {
let orig = decode(&encoded); let orig = STANDARD.decode(&encoded);
black_box(&orig); black_box(&orig);
}); });
} }
@ -29,11 +24,11 @@ fn do_decode_bench(b: &mut Bencher, &size: &usize) {
fn do_decode_bench_reuse_buf(b: &mut Bencher, &size: &usize) { fn do_decode_bench_reuse_buf(b: &mut Bencher, &size: &usize) {
let mut v: Vec<u8> = Vec::with_capacity(size * 3 / 4); let mut v: Vec<u8> = Vec::with_capacity(size * 3 / 4);
fill(&mut v); fill(&mut v);
let encoded = encode(&v); let encoded = STANDARD.encode(&v);
let mut buf = Vec::new(); let mut buf = Vec::new();
b.iter(|| { b.iter(|| {
decode_config_buf(&encoded, TEST_CONFIG, &mut buf).unwrap(); STANDARD.decode_vec(&encoded, &mut buf).unwrap();
black_box(&buf); black_box(&buf);
buf.clear(); buf.clear();
}); });
@ -42,12 +37,12 @@ fn do_decode_bench_reuse_buf(b: &mut Bencher, &size: &usize) {
fn do_decode_bench_slice(b: &mut Bencher, &size: &usize) { fn do_decode_bench_slice(b: &mut Bencher, &size: &usize) {
let mut v: Vec<u8> = Vec::with_capacity(size * 3 / 4); let mut v: Vec<u8> = Vec::with_capacity(size * 3 / 4);
fill(&mut v); fill(&mut v);
let encoded = encode(&v); let encoded = STANDARD.encode(&v);
let mut buf = Vec::new(); let mut buf = Vec::new();
buf.resize(size, 0); buf.resize(size, 0);
b.iter(|| { b.iter(|| {
decode_config_slice(&encoded, TEST_CONFIG, &mut buf).unwrap(); STANDARD.decode_slice(&encoded, &mut buf).unwrap();
black_box(&buf); black_box(&buf);
}); });
} }
@ -55,7 +50,7 @@ fn do_decode_bench_slice(b: &mut Bencher, &size: &usize) {
fn do_decode_bench_stream(b: &mut Bencher, &size: &usize) { fn do_decode_bench_stream(b: &mut Bencher, &size: &usize) {
let mut v: Vec<u8> = Vec::with_capacity(size * 3 / 4); let mut v: Vec<u8> = Vec::with_capacity(size * 3 / 4);
fill(&mut v); fill(&mut v);
let encoded = encode(&v); let encoded = STANDARD.encode(&v);
let mut buf = Vec::new(); let mut buf = Vec::new();
buf.resize(size, 0); buf.resize(size, 0);
@ -63,7 +58,7 @@ fn do_decode_bench_stream(b: &mut Bencher, &size: &usize) {
b.iter(|| { b.iter(|| {
let mut cursor = io::Cursor::new(&encoded[..]); let mut cursor = io::Cursor::new(&encoded[..]);
let mut decoder = base64::read::DecoderReader::new(&mut cursor, TEST_CONFIG); let mut decoder = base64::read::DecoderReader::new(&mut cursor, &STANDARD);
decoder.read_to_end(&mut buf).unwrap(); decoder.read_to_end(&mut buf).unwrap();
buf.clear(); buf.clear();
black_box(&buf); black_box(&buf);
@ -74,7 +69,7 @@ fn do_encode_bench(b: &mut Bencher, &size: &usize) {
let mut v: Vec<u8> = Vec::with_capacity(size); let mut v: Vec<u8> = Vec::with_capacity(size);
fill(&mut v); fill(&mut v);
b.iter(|| { b.iter(|| {
let e = encode(&v); let e = STANDARD.encode(&v);
black_box(&e); black_box(&e);
}); });
} }
@ -83,7 +78,7 @@ fn do_encode_bench_display(b: &mut Bencher, &size: &usize) {
let mut v: Vec<u8> = Vec::with_capacity(size); let mut v: Vec<u8> = Vec::with_capacity(size);
fill(&mut v); fill(&mut v);
b.iter(|| { b.iter(|| {
let e = format!("{}", display::Base64Display::with_config(&v, TEST_CONFIG)); let e = format!("{}", display::Base64Display::new(&v, &STANDARD));
black_box(&e); black_box(&e);
}); });
} }
@ -93,7 +88,7 @@ fn do_encode_bench_reuse_buf(b: &mut Bencher, &size: &usize) {
fill(&mut v); fill(&mut v);
let mut buf = String::new(); let mut buf = String::new();
b.iter(|| { b.iter(|| {
encode_config_buf(&v, TEST_CONFIG, &mut buf); STANDARD.encode_string(&v, &mut buf);
buf.clear(); buf.clear();
}); });
} }
@ -104,9 +99,7 @@ fn do_encode_bench_slice(b: &mut Bencher, &size: &usize) {
let mut buf = Vec::new(); let mut buf = Vec::new();
// conservative estimate of encoded size // conservative estimate of encoded size
buf.resize(v.len() * 2, 0); buf.resize(v.len() * 2, 0);
b.iter(|| { b.iter(|| STANDARD.encode_slice(&v, &mut buf).unwrap());
encode_config_slice(&v, TEST_CONFIG, &mut buf);
});
} }
fn do_encode_bench_stream(b: &mut Bencher, &size: &usize) { fn do_encode_bench_stream(b: &mut Bencher, &size: &usize) {
@ -117,7 +110,7 @@ fn do_encode_bench_stream(b: &mut Bencher, &size: &usize) {
buf.reserve(size * 2); buf.reserve(size * 2);
b.iter(|| { b.iter(|| {
buf.clear(); buf.clear();
let mut stream_enc = write::EncoderWriter::new(&mut buf, TEST_CONFIG); let mut stream_enc = write::EncoderWriter::new(&mut buf, &STANDARD);
stream_enc.write_all(&v).unwrap(); stream_enc.write_all(&v).unwrap();
stream_enc.flush().unwrap(); stream_enc.flush().unwrap();
}); });
@ -128,7 +121,7 @@ fn do_encode_bench_string_stream(b: &mut Bencher, &size: &usize) {
fill(&mut v); fill(&mut v);
b.iter(|| { b.iter(|| {
let mut stream_enc = write::EncoderStringWriter::new(TEST_CONFIG); let mut stream_enc = write::EncoderStringWriter::new(&STANDARD);
stream_enc.write_all(&v).unwrap(); stream_enc.write_all(&v).unwrap();
stream_enc.flush().unwrap(); stream_enc.flush().unwrap();
let _ = stream_enc.into_inner(); let _ = stream_enc.into_inner();
@ -142,7 +135,7 @@ fn do_encode_bench_string_reuse_buf_stream(b: &mut Bencher, &size: &usize) {
let mut buf = String::new(); let mut buf = String::new();
b.iter(|| { b.iter(|| {
buf.clear(); buf.clear();
let mut stream_enc = write::EncoderStringWriter::from(&mut buf, TEST_CONFIG); let mut stream_enc = write::EncoderStringWriter::from_consumer(&mut buf, &STANDARD);
stream_enc.write_all(&v).unwrap(); stream_enc.write_all(&v).unwrap();
stream_enc.flush().unwrap(); stream_enc.flush().unwrap();
let _ = stream_enc.into_inner(); let _ = stream_enc.into_inner();
@ -164,46 +157,85 @@ const BYTE_SIZES: [usize; 5] = [3, 50, 100, 500, 3 * 1024];
// keep the benchmark runtime reasonable. // keep the benchmark runtime reasonable.
const LARGE_BYTE_SIZES: [usize; 3] = [3 * 1024 * 1024, 10 * 1024 * 1024, 30 * 1024 * 1024]; const LARGE_BYTE_SIZES: [usize; 3] = [3 * 1024 * 1024, 10 * 1024 * 1024, 30 * 1024 * 1024];
fn encode_benchmarks(byte_sizes: &[usize]) -> ParameterizedBenchmark<usize> { fn encode_benchmarks(c: &mut Criterion, label: &str, byte_sizes: &[usize]) {
ParameterizedBenchmark::new("encode", do_encode_bench, byte_sizes.iter().cloned()) let mut group = c.benchmark_group(label);
group
.warm_up_time(std::time::Duration::from_millis(500)) .warm_up_time(std::time::Duration::from_millis(500))
.measurement_time(std::time::Duration::from_secs(3)) .measurement_time(std::time::Duration::from_secs(3));
.throughput(|s| Throughput::Bytes(*s as u64))
.with_function("encode_display", do_encode_bench_display) for size in byte_sizes {
.with_function("encode_reuse_buf", do_encode_bench_reuse_buf) group
.with_function("encode_slice", do_encode_bench_slice) .throughput(Throughput::Bytes(*size as u64))
.with_function("encode_reuse_buf_stream", do_encode_bench_stream) .bench_with_input(BenchmarkId::new("encode", size), size, do_encode_bench)
.with_function("encode_string_stream", do_encode_bench_string_stream) .bench_with_input(
.with_function( BenchmarkId::new("encode_display", size),
"encode_string_reuse_buf_stream", size,
do_encode_bench_string_reuse_buf_stream, do_encode_bench_display,
) )
.bench_with_input(
BenchmarkId::new("encode_reuse_buf", size),
size,
do_encode_bench_reuse_buf,
)
.bench_with_input(
BenchmarkId::new("encode_slice", size),
size,
do_encode_bench_slice,
)
.bench_with_input(
BenchmarkId::new("encode_reuse_buf_stream", size),
size,
do_encode_bench_stream,
)
.bench_with_input(
BenchmarkId::new("encode_string_stream", size),
size,
do_encode_bench_string_stream,
)
.bench_with_input(
BenchmarkId::new("encode_string_reuse_buf_stream", size),
size,
do_encode_bench_string_reuse_buf_stream,
);
}
group.finish();
} }
fn decode_benchmarks(byte_sizes: &[usize]) -> ParameterizedBenchmark<usize> { fn decode_benchmarks(c: &mut Criterion, label: &str, byte_sizes: &[usize]) {
ParameterizedBenchmark::new("decode", do_decode_bench, byte_sizes.iter().cloned()) let mut group = c.benchmark_group(label);
.warm_up_time(std::time::Duration::from_millis(500))
.measurement_time(std::time::Duration::from_secs(3)) for size in byte_sizes {
.throughput(|s| Throughput::Bytes(*s as u64)) group
.with_function("decode_reuse_buf", do_decode_bench_reuse_buf) .warm_up_time(std::time::Duration::from_millis(500))
.with_function("decode_slice", do_decode_bench_slice) .measurement_time(std::time::Duration::from_secs(3))
.with_function("decode_stream", do_decode_bench_stream) .throughput(Throughput::Bytes(*size as u64))
.bench_with_input(BenchmarkId::new("decode", size), size, do_decode_bench)
.bench_with_input(
BenchmarkId::new("decode_reuse_buf", size),
size,
do_decode_bench_reuse_buf,
)
.bench_with_input(
BenchmarkId::new("decode_slice", size),
size,
do_decode_bench_slice,
)
.bench_with_input(
BenchmarkId::new("decode_stream", size),
size,
do_decode_bench_stream,
);
}
group.finish();
} }
fn bench(c: &mut Criterion) { fn bench(c: &mut Criterion) {
c.bench("bench_small_input", encode_benchmarks(&BYTE_SIZES[..])); encode_benchmarks(c, "encode_small_input", &BYTE_SIZES[..]);
encode_benchmarks(c, "encode_large_input", &LARGE_BYTE_SIZES[..]);
c.bench( decode_benchmarks(c, "decode_small_input", &BYTE_SIZES[..]);
"bench_large_input", decode_benchmarks(c, "decode_large_input", &LARGE_BYTE_SIZES[..]);
encode_benchmarks(&LARGE_BYTE_SIZES[..]).sample_size(10),
);
c.bench("bench_small_input", decode_benchmarks(&BYTE_SIZES[..]));
c.bench(
"bench_large_input",
decode_benchmarks(&LARGE_BYTE_SIZES[..]).sample_size(10),
);
} }
criterion_group!(benches, bench); criterion_group!(benches, bench);

1
zeroidc/vendor/base64/clippy.toml vendored Normal file
View File

@ -0,0 +1 @@
msrv = "1.57.0"

View File

@ -4,37 +4,28 @@ use std::path::PathBuf;
use std::process; use std::process;
use std::str::FromStr; use std::str::FromStr;
use base64::{read, write}; use base64::{alphabet, engine, read, write};
use structopt::StructOpt; use structopt::StructOpt;
#[derive(Debug, StructOpt)] #[derive(Debug, StructOpt)]
enum CharacterSet { enum Alphabet {
Standard, Standard,
UrlSafe, UrlSafe,
} }
impl Default for CharacterSet { impl Default for Alphabet {
fn default() -> Self { fn default() -> Self {
CharacterSet::Standard Self::Standard
} }
} }
impl Into<base64::Config> for CharacterSet { impl FromStr for Alphabet {
fn into(self) -> base64::Config {
match self {
CharacterSet::Standard => base64::STANDARD,
CharacterSet::UrlSafe => base64::URL_SAFE,
}
}
}
impl FromStr for CharacterSet {
type Err = String; type Err = String;
fn from_str(s: &str) -> Result<CharacterSet, String> { fn from_str(s: &str) -> Result<Self, String> {
match s { match s {
"standard" => Ok(CharacterSet::Standard), "standard" => Ok(Self::Standard),
"urlsafe" => Ok(CharacterSet::UrlSafe), "urlsafe" => Ok(Self::UrlSafe),
_ => Err(format!("charset '{}' unrecognized", s)), _ => Err(format!("alphabet '{}' unrecognized", s)),
} }
} }
} }
@ -45,10 +36,10 @@ struct Opt {
/// decode data /// decode data
#[structopt(short = "d", long = "decode")] #[structopt(short = "d", long = "decode")]
decode: bool, decode: bool,
/// The character set to choose. Defaults to the standard base64 character set. /// The alphabet to choose. Defaults to the standard base64 alphabet.
/// Supported character sets include "standard" and "urlsafe". /// Supported alphabets include "standard" and "urlsafe".
#[structopt(long = "charset")] #[structopt(long = "alphabet")]
charset: Option<CharacterSet>, alphabet: Option<Alphabet>,
/// The file to encode/decode. /// The file to encode/decode.
#[structopt(parse(from_os_str))] #[structopt(parse(from_os_str))]
file: Option<PathBuf>, file: Option<PathBuf>,
@ -68,14 +59,23 @@ fn main() {
} }
Some(f) => Box::new(File::open(f).unwrap()), Some(f) => Box::new(File::open(f).unwrap()),
}; };
let config = opt.charset.unwrap_or_default().into();
let alphabet = opt.alphabet.unwrap_or_default();
let engine = engine::GeneralPurpose::new(
&match alphabet {
Alphabet::Standard => alphabet::STANDARD,
Alphabet::UrlSafe => alphabet::URL_SAFE,
},
engine::general_purpose::PAD,
);
let stdout = io::stdout(); let stdout = io::stdout();
let mut stdout = stdout.lock(); let mut stdout = stdout.lock();
let r = if opt.decode { let r = if opt.decode {
let mut decoder = read::DecoderReader::new(&mut input, config); let mut decoder = read::DecoderReader::new(&mut input, &engine);
io::copy(&mut decoder, &mut stdout) io::copy(&mut decoder, &mut stdout)
} else { } else {
let mut encoder = write::EncoderWriter::new(&mut stdout, config); let mut encoder = write::EncoderWriter::new(&mut stdout, &engine);
io::copy(&mut input, &mut encoder) io::copy(&mut input, &mut encoder)
}; };
if let Err(e) = r { if let Err(e) = r {

241
zeroidc/vendor/base64/src/alphabet.rs vendored Normal file
View File

@ -0,0 +1,241 @@
//! Provides [Alphabet] and constants for alphabets commonly used in the wild.
use crate::PAD_BYTE;
use core::fmt;
#[cfg(any(feature = "std", test))]
use std::error;
const ALPHABET_SIZE: usize = 64;
/// An alphabet defines the 64 ASCII characters (symbols) used for base64.
///
/// Common alphabets are provided as constants, and custom alphabets
/// can be made via `from_str` or the `TryFrom<str>` implementation.
///
/// ```
/// let custom = base64::alphabet::Alphabet::new("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/").unwrap();
///
/// let engine = base64::engine::GeneralPurpose::new(
/// &custom,
/// base64::engine::general_purpose::PAD);
/// ```
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Alphabet {
pub(crate) symbols: [u8; ALPHABET_SIZE],
}
impl Alphabet {
/// Performs no checks so that it can be const.
/// Used only for known-valid strings.
const fn from_str_unchecked(alphabet: &str) -> Self {
let mut symbols = [0_u8; ALPHABET_SIZE];
let source_bytes = alphabet.as_bytes();
// a way to copy that's allowed in const fn
let mut index = 0;
while index < ALPHABET_SIZE {
symbols[index] = source_bytes[index];
index += 1;
}
Self { symbols }
}
/// Create an `Alphabet` from a string of 64 unique printable ASCII bytes.
///
/// The `=` byte is not allowed as it is used for padding.
pub const fn new(alphabet: &str) -> Result<Self, ParseAlphabetError> {
let bytes = alphabet.as_bytes();
if bytes.len() != ALPHABET_SIZE {
return Err(ParseAlphabetError::InvalidLength);
}
{
let mut index = 0;
while index < ALPHABET_SIZE {
let byte = bytes[index];
// must be ascii printable. 127 (DEL) is commonly considered printable
// for some reason but clearly unsuitable for base64.
if !(byte >= 32_u8 && byte <= 126_u8) {
return Err(ParseAlphabetError::UnprintableByte(byte));
}
// = is assumed to be padding, so cannot be used as a symbol
if byte == PAD_BYTE {
return Err(ParseAlphabetError::ReservedByte(byte));
}
// Check for duplicates while staying within what const allows.
// It's n^2, but only over 64 hot bytes, and only once, so it's likely in the single digit
// microsecond range.
let mut probe_index = 0;
while probe_index < ALPHABET_SIZE {
if probe_index == index {
probe_index += 1;
continue;
}
let probe_byte = bytes[probe_index];
if byte == probe_byte {
return Err(ParseAlphabetError::DuplicatedByte(byte));
}
probe_index += 1;
}
index += 1;
}
}
Ok(Self::from_str_unchecked(alphabet))
}
}
impl TryFrom<&str> for Alphabet {
type Error = ParseAlphabetError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Self::new(value)
}
}
/// Possible errors when constructing an [Alphabet] from a `str`.
#[derive(Debug, Eq, PartialEq)]
pub enum ParseAlphabetError {
/// Alphabets must be 64 ASCII bytes
InvalidLength,
/// All bytes must be unique
DuplicatedByte(u8),
/// All bytes must be printable (in the range `[32, 126]`).
UnprintableByte(u8),
/// `=` cannot be used
ReservedByte(u8),
}
impl fmt::Display for ParseAlphabetError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidLength => write!(f, "Invalid length - must be 64 bytes"),
Self::DuplicatedByte(b) => write!(f, "Duplicated byte: {:#04x}", b),
Self::UnprintableByte(b) => write!(f, "Unprintable byte: {:#04x}", b),
Self::ReservedByte(b) => write!(f, "Reserved byte: {:#04x}", b),
}
}
}
#[cfg(any(feature = "std", test))]
impl error::Error for ParseAlphabetError {}
/// The standard alphabet (uses `+` and `/`).
///
/// See [RFC 3548](https://tools.ietf.org/html/rfc3548#section-3).
pub const STANDARD: Alphabet = Alphabet::from_str_unchecked(
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
);
/// The URL safe alphabet (uses `-` and `_`).
///
/// See [RFC 3548](https://tools.ietf.org/html/rfc3548#section-4).
pub const URL_SAFE: Alphabet = Alphabet::from_str_unchecked(
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",
);
/// The `crypt(3)` alphabet (uses `.` and `/` as the first two values).
///
/// Not standardized, but folk wisdom on the net asserts that this alphabet is what crypt uses.
pub const CRYPT: Alphabet = Alphabet::from_str_unchecked(
"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
);
/// The bcrypt alphabet.
pub const BCRYPT: Alphabet = Alphabet::from_str_unchecked(
"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
);
/// The alphabet used in IMAP-modified UTF-7 (uses `+` and `,`).
///
/// See [RFC 3501](https://tools.ietf.org/html/rfc3501#section-5.1.3)
pub const IMAP_MUTF7: Alphabet = Alphabet::from_str_unchecked(
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,",
);
/// The alphabet used in BinHex 4.0 files.
///
/// See [BinHex 4.0 Definition](http://files.stairways.com/other/binhex-40-specs-info.txt)
pub const BIN_HEX: Alphabet = Alphabet::from_str_unchecked(
"!\"#$%&'()*+,-0123456789@ABCDEFGHIJKLMNPQRSTUVXYZ[`abcdehijklmpqr",
);
#[cfg(test)]
mod tests {
use crate::alphabet::*;
use std::convert::TryFrom as _;
#[test]
fn detects_duplicate_start() {
assert_eq!(
ParseAlphabetError::DuplicatedByte(b'A'),
Alphabet::new("AACDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")
.unwrap_err()
);
}
#[test]
fn detects_duplicate_end() {
assert_eq!(
ParseAlphabetError::DuplicatedByte(b'/'),
Alphabet::new("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789//")
.unwrap_err()
);
}
#[test]
fn detects_duplicate_middle() {
assert_eq!(
ParseAlphabetError::DuplicatedByte(b'Z'),
Alphabet::new("ABCDEFGHIJKLMNOPQRSTUVWXYZZbcdefghijklmnopqrstuvwxyz0123456789+/")
.unwrap_err()
);
}
#[test]
fn detects_length() {
assert_eq!(
ParseAlphabetError::InvalidLength,
Alphabet::new(
"xxxxxxxxxABCDEFGHIJKLMNOPQRSTUVWXYZZbcdefghijklmnopqrstuvwxyz0123456789+/",
)
.unwrap_err()
);
}
#[test]
fn detects_padding() {
assert_eq!(
ParseAlphabetError::ReservedByte(b'='),
Alphabet::new("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+=")
.unwrap_err()
);
}
#[test]
fn detects_unprintable() {
// form feed
assert_eq!(
ParseAlphabetError::UnprintableByte(0xc),
Alphabet::new("\x0cBCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")
.unwrap_err()
);
}
#[test]
fn same_as_unchecked() {
assert_eq!(
STANDARD,
Alphabet::try_from("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")
.unwrap()
);
}
}

View File

@ -1,13 +1,12 @@
use crate::{
encode::{add_padding, encode_to_slice},
Config,
};
#[cfg(any(feature = "alloc", feature = "std", test))] #[cfg(any(feature = "alloc", feature = "std", test))]
use alloc::string::String; use alloc::string::String;
use core::cmp; use core::cmp;
#[cfg(any(feature = "alloc", feature = "std", test))] #[cfg(any(feature = "alloc", feature = "std", test))]
use core::str; use core::str;
use crate::encode::add_padding;
use crate::engine::{Config, Engine};
/// The output mechanism for ChunkedEncoder's encoded bytes. /// The output mechanism for ChunkedEncoder's encoded bytes.
pub trait Sink { pub trait Sink {
type Error; type Error;
@ -19,23 +18,21 @@ pub trait Sink {
const BUF_SIZE: usize = 1024; const BUF_SIZE: usize = 1024;
/// A base64 encoder that emits encoded bytes in chunks without heap allocation. /// A base64 encoder that emits encoded bytes in chunks without heap allocation.
pub struct ChunkedEncoder { pub struct ChunkedEncoder<'e, E: Engine + ?Sized> {
config: Config, engine: &'e E,
max_input_chunk_len: usize, max_input_chunk_len: usize,
} }
impl ChunkedEncoder { impl<'e, E: Engine + ?Sized> ChunkedEncoder<'e, E> {
pub fn new(config: Config) -> ChunkedEncoder { pub fn new(engine: &'e E) -> ChunkedEncoder<'e, E> {
ChunkedEncoder { ChunkedEncoder {
config, engine,
max_input_chunk_len: max_input_length(BUF_SIZE, config), max_input_chunk_len: max_input_length(BUF_SIZE, engine.config().encode_padding()),
} }
} }
pub fn encode<S: Sink>(&self, bytes: &[u8], sink: &mut S) -> Result<(), S::Error> { pub fn encode<S: Sink>(&self, bytes: &[u8], sink: &mut S) -> Result<(), S::Error> {
let mut encode_buf: [u8; BUF_SIZE] = [0; BUF_SIZE]; let mut encode_buf: [u8; BUF_SIZE] = [0; BUF_SIZE];
let encode_table = self.config.char_set.encode_table();
let mut input_index = 0; let mut input_index = 0;
while input_index < bytes.len() { while input_index < bytes.len() {
@ -44,12 +41,12 @@ impl ChunkedEncoder {
let chunk = &bytes[input_index..(input_index + input_chunk_len)]; let chunk = &bytes[input_index..(input_index + input_chunk_len)];
let mut b64_bytes_written = encode_to_slice(chunk, &mut encode_buf, encode_table); let mut b64_bytes_written = self.engine.internal_encode(chunk, &mut encode_buf);
input_index += input_chunk_len; input_index += input_chunk_len;
let more_input_left = input_index < bytes.len(); let more_input_left = input_index < bytes.len();
if self.config.pad && !more_input_left { if self.engine.config().encode_padding() && !more_input_left {
// no more input, add padding if needed. Buffer will have room because // no more input, add padding if needed. Buffer will have room because
// max_input_length leaves room for it. // max_input_length leaves room for it.
b64_bytes_written += add_padding(bytes.len(), &mut encode_buf[b64_bytes_written..]); b64_bytes_written += add_padding(bytes.len(), &mut encode_buf[b64_bytes_written..]);
@ -69,8 +66,8 @@ impl ChunkedEncoder {
/// ///
/// The input length will always be a multiple of 3 so that no encoding state has to be carried over /// The input length will always be a multiple of 3 so that no encoding state has to be carried over
/// between chunks. /// between chunks.
fn max_input_length(encoded_buf_len: usize, config: Config) -> usize { fn max_input_length(encoded_buf_len: usize, padded: bool) -> usize {
let effective_buf_len = if config.pad { let effective_buf_len = if padded {
// make room for padding // make room for padding
encoded_buf_len encoded_buf_len
.checked_sub(2) .checked_sub(2)
@ -109,26 +106,28 @@ impl<'a> Sink for StringSink<'a> {
#[cfg(test)] #[cfg(test)]
pub mod tests { pub mod tests {
use super::*;
use crate::{encode_config_buf, tests::random_config, CharacterSet, STANDARD};
use rand::{ use rand::{
distributions::{Distribution, Uniform}, distributions::{Distribution, Uniform},
FromEntropy, Rng, Rng, SeedableRng,
}; };
use crate::{
alphabet::STANDARD,
engine::general_purpose::{GeneralPurpose, GeneralPurposeConfig, PAD},
tests::random_engine,
};
use super::*;
#[test] #[test]
fn chunked_encode_empty() { fn chunked_encode_empty() {
assert_eq!("", chunked_encode_str(&[], STANDARD)); assert_eq!("", chunked_encode_str(&[], PAD));
} }
#[test] #[test]
fn chunked_encode_intermediate_fast_loop() { fn chunked_encode_intermediate_fast_loop() {
// > 8 bytes input, will enter the pretty fast loop // > 8 bytes input, will enter the pretty fast loop
assert_eq!( assert_eq!("Zm9vYmFyYmF6cXV4", chunked_encode_str(b"foobarbazqux", PAD));
"Zm9vYmFyYmF6cXV4",
chunked_encode_str(b"foobarbazqux", STANDARD)
);
} }
#[test] #[test]
@ -136,14 +135,14 @@ pub mod tests {
// > 32 bytes input, will enter the uber fast loop // > 32 bytes input, will enter the uber fast loop
assert_eq!( assert_eq!(
"Zm9vYmFyYmF6cXV4cXV1eGNvcmdlZ3JhdWx0Z2FycGx5eg==", "Zm9vYmFyYmF6cXV4cXV1eGNvcmdlZ3JhdWx0Z2FycGx5eg==",
chunked_encode_str(b"foobarbazquxquuxcorgegraultgarplyz", STANDARD) chunked_encode_str(b"foobarbazquxquuxcorgegraultgarplyz", PAD)
); );
} }
#[test] #[test]
fn chunked_encode_slow_loop_only() { fn chunked_encode_slow_loop_only() {
// < 8 bytes input, slow loop only // < 8 bytes input, slow loop only
assert_eq!("Zm9vYmFy", chunked_encode_str(b"foobar", STANDARD)); assert_eq!("Zm9vYmFy", chunked_encode_str(b"foobar", PAD));
} }
#[test] #[test]
@ -154,32 +153,27 @@ pub mod tests {
#[test] #[test]
fn max_input_length_no_pad() { fn max_input_length_no_pad() {
let config = config_with_pad(false); assert_eq!(768, max_input_length(1024, false));
assert_eq!(768, max_input_length(1024, config));
} }
#[test] #[test]
fn max_input_length_with_pad_decrements_one_triple() { fn max_input_length_with_pad_decrements_one_triple() {
let config = config_with_pad(true); assert_eq!(765, max_input_length(1024, true));
assert_eq!(765, max_input_length(1024, config));
} }
#[test] #[test]
fn max_input_length_with_pad_one_byte_short() { fn max_input_length_with_pad_one_byte_short() {
let config = config_with_pad(true); assert_eq!(765, max_input_length(1025, true));
assert_eq!(765, max_input_length(1025, config));
} }
#[test] #[test]
fn max_input_length_with_pad_fits_exactly() { fn max_input_length_with_pad_fits_exactly() {
let config = config_with_pad(true); assert_eq!(768, max_input_length(1026, true));
assert_eq!(768, max_input_length(1026, config));
} }
#[test] #[test]
fn max_input_length_cant_use_extra_single_encoded_byte() { fn max_input_length_cant_use_extra_single_encoded_byte() {
let config = Config::new(crate::CharacterSet::Standard, false); assert_eq!(300, max_input_length(401, false));
assert_eq!(300, max_input_length(401, config));
} }
pub fn chunked_encode_matches_normal_encode_random<S: SinkTestHelper>(sink_test_helper: &S) { pub fn chunked_encode_matches_normal_encode_random<S: SinkTestHelper>(sink_test_helper: &S) {
@ -197,49 +191,39 @@ pub mod tests {
input_buf.push(rng.gen()); input_buf.push(rng.gen());
} }
let config = random_config(&mut rng); let engine = random_engine(&mut rng);
let chunk_encoded_string = sink_test_helper.encode_to_string(config, &input_buf); let chunk_encoded_string = sink_test_helper.encode_to_string(&engine, &input_buf);
encode_config_buf(&input_buf, config, &mut output_buf); engine.encode_string(&input_buf, &mut output_buf);
assert_eq!( assert_eq!(output_buf, chunk_encoded_string, "input len={}", buf_len);
output_buf, chunk_encoded_string,
"input len={}, config: pad={}",
buf_len, config.pad
);
} }
} }
fn chunked_encode_str(bytes: &[u8], config: Config) -> String { fn chunked_encode_str(bytes: &[u8], config: GeneralPurposeConfig) -> String {
let mut s = String::new(); let mut s = String::new();
{
let mut sink = StringSink::new(&mut s);
let encoder = ChunkedEncoder::new(config);
encoder.encode(bytes, &mut sink).unwrap();
}
return s; let mut sink = StringSink::new(&mut s);
} let engine = GeneralPurpose::new(&STANDARD, config);
let encoder = ChunkedEncoder::new(&engine);
encoder.encode(bytes, &mut sink).unwrap();
fn config_with_pad(pad: bool) -> Config { s
Config::new(CharacterSet::Standard, pad)
} }
// An abstraction around sinks so that we can have tests that easily to any sink implementation // An abstraction around sinks so that we can have tests that easily to any sink implementation
pub trait SinkTestHelper { pub trait SinkTestHelper {
fn encode_to_string(&self, config: Config, bytes: &[u8]) -> String; fn encode_to_string<E: Engine>(&self, engine: &E, bytes: &[u8]) -> String;
} }
struct StringSinkTestHelper; struct StringSinkTestHelper;
impl SinkTestHelper for StringSinkTestHelper { impl SinkTestHelper for StringSinkTestHelper {
fn encode_to_string(&self, config: Config, bytes: &[u8]) -> String { fn encode_to_string<E: Engine>(&self, engine: &E, bytes: &[u8]) -> String {
let encoder = ChunkedEncoder::new(config); let encoder = ChunkedEncoder::new(engine);
let mut s = String::new(); let mut s = String::new();
{ let mut sink = StringSink::new(&mut s);
let mut sink = StringSink::new(&mut s); encoder.encode(bytes, &mut sink).unwrap();
encoder.encode(bytes, &mut sink).unwrap();
}
s s
} }

View File

@ -1,32 +1,15 @@
use crate::{tables, Config, PAD_BYTE}; use crate::engine::{general_purpose::STANDARD, DecodeEstimate, Engine};
#[cfg(any(feature = "alloc", feature = "std", test))]
use crate::STANDARD;
#[cfg(any(feature = "alloc", feature = "std", test))] #[cfg(any(feature = "alloc", feature = "std", test))]
use alloc::vec::Vec; use alloc::vec::Vec;
use core::fmt; use core::fmt;
#[cfg(any(feature = "std", test))] #[cfg(any(feature = "std", test))]
use std::error; use std::error;
// 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;
/// Errors that can occur while decoding. /// Errors that can occur while decoding.
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub enum DecodeError { pub enum DecodeError {
/// An invalid byte was found in the input. The offset and offending byte are provided. /// 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.
InvalidByte(usize, u8), InvalidByte(usize, u8),
/// The length of the input is invalid. /// The length of the input is invalid.
/// A typical cause of this is stray trailing whitespace or other separator bytes. /// A typical cause of this is stray trailing whitespace or other separator bytes.
@ -36,560 +19,159 @@ pub enum DecodeError {
InvalidLength, InvalidLength,
/// The last non-padding input symbol's encoded 6 bits have nonzero bits that will be discarded. /// 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. /// This is indicative of corrupted or truncated Base64.
/// Unlike InvalidByte, which reports symbols that aren't in the alphabet, this error is for /// 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. /// symbols that are in the alphabet but represent nonsensical encodings.
InvalidLastSymbol(usize, u8), InvalidLastSymbol(usize, u8),
/// The nature of the padding was not as configured: absent or incorrect when it must be
/// canonical, or present when it must be absent, etc.
InvalidPadding,
} }
impl fmt::Display for DecodeError { impl fmt::Display for DecodeError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self { match *self {
DecodeError::InvalidByte(index, byte) => { Self::InvalidByte(index, byte) => write!(f, "Invalid byte {}, offset {}.", byte, index),
write!(f, "Invalid byte {}, offset {}.", byte, index) Self::InvalidLength => write!(f, "Encoded text cannot have a 6-bit remainder."),
} Self::InvalidLastSymbol(index, byte) => {
DecodeError::InvalidLength => write!(f, "Encoded text cannot have a 6-bit remainder."),
DecodeError::InvalidLastSymbol(index, byte) => {
write!(f, "Invalid last symbol {}, offset {}.", byte, index) write!(f, "Invalid last symbol {}, offset {}.", byte, index)
} }
Self::InvalidPadding => write!(f, "Invalid padding"),
} }
} }
} }
#[cfg(any(feature = "std", test))] #[cfg(any(feature = "std", test))]
impl error::Error for DecodeError { impl error::Error for DecodeError {
fn description(&self) -> &str {
match *self {
DecodeError::InvalidByte(_, _) => "invalid byte",
DecodeError::InvalidLength => "invalid length",
DecodeError::InvalidLastSymbol(_, _) => "invalid last symbol",
}
}
fn cause(&self) -> Option<&dyn error::Error> { fn cause(&self) -> Option<&dyn error::Error> {
None None
} }
} }
///Decode from string reference as octets. /// Errors that can occur while decoding into a slice.
///Returns a Result containing a Vec<u8>. #[derive(Clone, Debug, PartialEq, Eq)]
///Convenience `decode_config(input, base64::STANDARD);`. pub enum DecodeSliceError {
/// A [DecodeError] occurred
DecodeError(DecodeError),
/// The provided slice _may_ be too small.
///
/// The check is conservative (assumes the last triplet of output bytes will all be needed).
OutputSliceTooSmall,
}
impl fmt::Display for DecodeSliceError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::DecodeError(e) => write!(f, "DecodeError: {}", e),
Self::OutputSliceTooSmall => write!(f, "Output slice too small"),
}
}
}
#[cfg(any(feature = "std", test))]
impl error::Error for DecodeSliceError {
fn cause(&self) -> Option<&dyn error::Error> {
match self {
DecodeSliceError::DecodeError(e) => Some(e),
DecodeSliceError::OutputSliceTooSmall => None,
}
}
}
impl From<DecodeError> for DecodeSliceError {
fn from(e: DecodeError) -> Self {
DecodeSliceError::DecodeError(e)
}
}
/// Decode base64 using the [`STANDARD` engine](STANDARD).
/// ///
///# Example /// See [Engine::decode].
/// #[deprecated(since = "0.21.0", note = "Use Engine::decode")]
///```rust
///extern crate base64;
///
///fn main() {
/// let bytes = base64::decode("aGVsbG8gd29ybGQ=").unwrap();
/// println!("{:?}", bytes);
///}
///```
#[cfg(any(feature = "alloc", feature = "std", test))] #[cfg(any(feature = "alloc", feature = "std", test))]
pub fn decode<T: AsRef<[u8]>>(input: T) -> Result<Vec<u8>, DecodeError> { pub fn decode<T: AsRef<[u8]>>(input: T) -> Result<Vec<u8>, DecodeError> {
decode_config(input, STANDARD) STANDARD.decode(input)
} }
///Decode from string reference as octets. /// Decode from string reference as octets using the specified [Engine].
///Returns a Result containing a Vec<u8>.
/// ///
///# Example /// See [Engine::decode].
/// ///Returns a `Result` containing a `Vec<u8>`.
///```rust #[deprecated(since = "0.21.0", note = "Use Engine::decode")]
///extern crate base64;
///
///fn main() {
/// let bytes = base64::decode_config("aGVsbG8gd29ybGR+Cg==", base64::STANDARD).unwrap();
/// println!("{:?}", bytes);
///
/// let bytes_url = base64::decode_config("aGVsbG8gaW50ZXJuZXR-Cg==", base64::URL_SAFE).unwrap();
/// println!("{:?}", bytes_url);
///}
///```
#[cfg(any(feature = "alloc", feature = "std", test))] #[cfg(any(feature = "alloc", feature = "std", test))]
pub fn decode_config<T: AsRef<[u8]>>(input: T, config: Config) -> Result<Vec<u8>, DecodeError> { pub fn decode_engine<E: Engine, T: AsRef<[u8]>>(
let mut buffer = Vec::<u8>::with_capacity(input.as_ref().len() * 4 / 3); input: T,
engine: &E,
decode_config_buf(input, config, &mut buffer).map(|_| buffer) ) -> Result<Vec<u8>, DecodeError> {
} engine.decode(input)
}
///Decode from string reference as octets.
///Writes into the supplied buffer to avoid allocation. /// Decode from string reference as octets.
///Returns a Result containing an empty tuple, aka (). ///
/// /// See [Engine::decode_vec].
///# Example #[cfg(any(feature = "alloc", feature = "std", test))]
/// #[deprecated(since = "0.21.0", note = "Use Engine::decode_vec")]
///```rust pub fn decode_engine_vec<E: Engine, T: AsRef<[u8]>>(
///extern crate base64;
///
///fn main() {
/// let mut buffer = Vec::<u8>::new();
/// base64::decode_config_buf("aGVsbG8gd29ybGR+Cg==", base64::STANDARD, &mut buffer).unwrap();
/// println!("{:?}", buffer);
///
/// buffer.clear();
///
/// base64::decode_config_buf("aGVsbG8gaW50ZXJuZXR-Cg==", base64::URL_SAFE, &mut buffer)
/// .unwrap();
/// println!("{:?}", buffer);
///}
///```
#[cfg(any(feature = "alloc", feature = "std", test))]
pub fn decode_config_buf<T: AsRef<[u8]>>(
input: T, input: T,
config: Config,
buffer: &mut Vec<u8>, buffer: &mut Vec<u8>,
engine: &E,
) -> Result<(), DecodeError> { ) -> Result<(), DecodeError> {
let input_bytes = input.as_ref(); engine.decode_vec(input, buffer)
let starting_output_len = buffer.len();
let num_chunks = num_chunks(input_bytes);
let decoded_len_estimate = num_chunks
.checked_mul(DECODED_CHUNK_LEN)
.and_then(|p| p.checked_add(starting_output_len))
.expect("Overflow when calculating output buffer length");
buffer.resize(decoded_len_estimate, 0);
let bytes_written;
{
let buffer_slice = &mut buffer.as_mut_slice()[starting_output_len..];
bytes_written = decode_helper(input_bytes, num_chunks, config, buffer_slice)?;
}
buffer.truncate(starting_output_len + bytes_written);
Ok(())
} }
/// Decode the input into the provided output slice. /// Decode the input into the provided output slice.
/// ///
/// This will not write any bytes past exactly what is decoded (no stray garbage bytes at the end). /// See [Engine::decode_slice].
/// #[deprecated(since = "0.21.0", note = "Use Engine::decode_slice")]
/// If you don't know ahead of time what the decoded length should be, size your buffer with a pub fn decode_engine_slice<E: Engine, T: AsRef<[u8]>>(
/// conservative estimate for the decoded length of an input: 3 bytes of output for every 4 bytes of
/// input, rounded up, or in other words `(input_len + 3) / 4 * 3`.
///
/// If the slice is not large enough, this will panic.
pub fn decode_config_slice<T: AsRef<[u8]>>(
input: T, input: T,
config: Config,
output: &mut [u8], output: &mut [u8],
) -> Result<usize, DecodeError> { engine: &E,
let input_bytes = input.as_ref(); ) -> Result<usize, DecodeSliceError> {
engine.decode_slice(input, output)
decode_helper(input_bytes, num_chunks(input_bytes), config, output)
} }
/// Return the number of input chunks (including a possibly partial final chunk) in the input /// Returns a conservative estimate of the decoded size of `encoded_len` base64 symbols (rounded up
fn num_chunks(input: &[u8]) -> usize { /// to the next group of 3 decoded bytes).
input
.len()
.checked_add(INPUT_CHUNK_LEN - 1)
.expect("Overflow when calculating number of chunks in input")
/ INPUT_CHUNK_LEN
}
/// Helper to avoid duplicating num_chunks calculation, which is costly on short inputs.
/// Returns the number of bytes written, or an error.
// We're on the fragile edge of compiler heuristics here. If this is not inlined, slow. If this is
// inlined(always), a different slow. plain ol' inline makes the benchmarks happiest at the moment,
// but this is fragile and the best setting changes with only minor code modifications.
#[inline]
fn decode_helper(
input: &[u8],
num_chunks: usize,
config: Config,
output: &mut [u8],
) -> Result<usize, DecodeError> {
let char_set = config.char_set;
let decode_table = char_set.decode_table();
let remainder_len = input.len() % INPUT_CHUNK_LEN;
// 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] == tables::INVALID_VALUE {
return Err(DecodeError::InvalidByte(input.len() - 1, *b));
}
}
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 unpadded 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,
};
// rounded up to include partial chunks
let mut remaining_chunks = num_chunks;
let mut input_index = 0;
let mut output_index = 0;
{
let length_of_fast_decode_chunks = input.len().saturating_sub(trailing_bytes_to_skip);
// 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(
&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..],
)?;
input_index += INPUT_BLOCK_LEN;
output_index += DECODED_BLOCK_LEN - DECODED_CHUNK_SUFFIX;
remaining_chunks -= CHUNKS_PER_FAST_LOOP_BLOCK;
}
}
// 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;
}
}
}
// 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);
// Stage 4
// Finally, 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;
let mut morsels_in_leftover = 0;
let mut padding_bytes = 0;
let mut first_padding_index: usize = 0;
let mut last_symbol = 0_u8;
let start_of_leftovers = input_index;
for (i, b) in input[start_of_leftovers..].iter().enumerate() {
// '=' padding
if *b == PAD_BYTE {
// There can be bad padding in a few ways:
// 1 - Padding with non-padding characters after it
// 2 - Padding after zero or one non-padding characters before it
// in the current quad.
// 3 - More than two characters of padding. If 3 or 4 padding chars
// are in the same quad, that implies it will be caught by #2.
// If it spreads from one quad to another, it will be caught by
// #2 in the second quad.
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 faster logic above: it will report an
// error on the first padding character (since it doesn't expect to see
// anything but actual encoded data).
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 padding_bytes == 0 {
first_padding_index = i;
}
padding_bytes += 1;
continue;
}
// Check for case #1.
// To make '=' handling consistent with the main loop, don't allow
// non-suffix '=' in trailing chunk either. Report error as first
// erroneous padding.
if padding_bytes > 0 {
return Err(DecodeError::InvalidByte(
start_of_leftovers + first_padding_index,
PAD_BYTE,
));
}
last_symbol = *b;
// can use up to 8 * 6 = 48 bits of the u64, if last chunk has no padding.
// To minimize shifts, pack the leftovers from left to right.
let shift = 64 - (morsels_in_leftover + 1) * 6;
// tables are all 256 elements, lookup with a u8 index always succeeds
let morsel = decode_table[*b as usize];
if morsel == tables::INVALID_VALUE {
return Err(DecodeError::InvalidByte(start_of_leftovers + i, *b));
}
leftover_bits |= (morsel as u64) << shift;
morsels_in_leftover += 1;
}
let leftover_bits_ready_to_append = match morsels_in_leftover {
0 => 0,
2 => 8,
3 => 16,
4 => 24,
6 => 32,
7 => 40,
8 => 48,
_ => unreachable!(
"Impossible: must only have 0 to 8 input bytes in last chunk, with no invalid lengths"
),
};
// 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 !config.decode_allow_trailing_bits && (leftover_bits & mask) != 0 {
// last morsel is at `morsels_in_leftover` - 1
return Err(DecodeError::InvalidLastSymbol(
start_of_leftovers + morsels_in_leftover - 1,
last_symbol,
));
}
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;
output_index += 1;
leftover_bits_appended_to_buf += 8;
}
Ok(output_index)
}
#[inline]
fn write_u64(output: &mut [u8], value: u64) {
output[..8].copy_from_slice(&value.to_be_bytes());
}
/// 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.
/// ///
/// `input` is the bytes to decode, of which the first 8 bytes will be processed. /// The resulting length will be a safe choice for the size of a decode buffer, but may have up to
/// `index_at_start_of_input` is the offset in the overall input (used for reporting errors /// 2 trailing bytes that won't end up being needed.
/// accurately) ///
/// `decode_table` is the lookup table for the particular base64 alphabet. /// # Examples
/// `output` will have its first 8 bytes overwritten, of which only the first 6 are valid decoded ///
/// data. /// ```
// yes, really inline (worth 30-50% speedup) /// use base64::decoded_len_estimate;
#[inline(always)] ///
fn decode_chunk( /// assert_eq!(3, decoded_len_estimate(1));
input: &[u8], /// assert_eq!(3, decoded_len_estimate(2));
index_at_start_of_input: usize, /// assert_eq!(3, decoded_len_estimate(3));
decode_table: &[u8; 256], /// assert_eq!(3, decoded_len_estimate(4));
output: &mut [u8], /// // start of the next quad of encoded symbols
) -> Result<(), DecodeError> { /// assert_eq!(6, decoded_len_estimate(5));
let mut accum: u64; /// ```
///
let morsel = decode_table[input[0] as usize]; /// # Panics
if morsel == tables::INVALID_VALUE { ///
return Err(DecodeError::InvalidByte(index_at_start_of_input, input[0])); /// Panics if decoded length estimation overflows.
} /// This would happen for sizes within a few bytes of the maximum value of `usize`.
accum = (morsel as u64) << 58; pub fn decoded_len_estimate(encoded_len: usize) -> usize {
STANDARD
let morsel = decode_table[input[1] as usize]; .internal_decoded_len_estimate(encoded_len)
if morsel == tables::INVALID_VALUE { .decoded_len_estimate()
return Err(DecodeError::InvalidByte(
index_at_start_of_input + 1,
input[1],
));
}
accum |= (morsel as u64) << 52;
let morsel = decode_table[input[2] as usize];
if morsel == tables::INVALID_VALUE {
return Err(DecodeError::InvalidByte(
index_at_start_of_input + 2,
input[2],
));
}
accum |= (morsel as u64) << 46;
let morsel = decode_table[input[3] as usize];
if morsel == tables::INVALID_VALUE {
return Err(DecodeError::InvalidByte(
index_at_start_of_input + 3,
input[3],
));
}
accum |= (morsel as u64) << 40;
let morsel = decode_table[input[4] as usize];
if morsel == tables::INVALID_VALUE {
return Err(DecodeError::InvalidByte(
index_at_start_of_input + 4,
input[4],
));
}
accum |= (morsel as u64) << 34;
let morsel = decode_table[input[5] as usize];
if morsel == tables::INVALID_VALUE {
return Err(DecodeError::InvalidByte(
index_at_start_of_input + 5,
input[5],
));
}
accum |= (morsel as u64) << 28;
let morsel = decode_table[input[6] as usize];
if morsel == tables::INVALID_VALUE {
return Err(DecodeError::InvalidByte(
index_at_start_of_input + 6,
input[6],
));
}
accum |= (morsel as u64) << 22;
let morsel = decode_table[input[7] as usize];
if morsel == tables::INVALID_VALUE {
return Err(DecodeError::InvalidByte(
index_at_start_of_input + 7,
input[7],
));
}
accum |= (morsel as u64) << 16;
write_u64(output, accum);
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(
input: &[u8],
index_at_start_of_input: usize,
decode_table: &[u8; 256],
output: &mut [u8],
) -> Result<(), DecodeError> {
let mut tmp_buf = [0_u8; 8];
decode_chunk(
input,
index_at_start_of_input,
decode_table,
&mut tmp_buf[..],
)?;
output[0..6].copy_from_slice(&tmp_buf[0..6]);
Ok(())
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::{ use crate::{
encode::encode_config_buf, alphabet,
encode::encode_config_slice, engine::{general_purpose, Config, GeneralPurpose},
tests::{assert_encode_sanity, random_config}, tests::{assert_encode_sanity, random_engine},
}; };
use rand::{ use rand::{
distributions::{Distribution, Uniform}, distributions::{Distribution, Uniform},
FromEntropy, Rng, Rng, SeedableRng,
}; };
#[test]
fn decode_chunk_precise_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, tables::STANDARD_DECODE, &mut output).unwrap();
assert_eq!(&vec![b'f', b'o', b'o', b'b', b'a', b'r', 6, 7], &output);
}
#[test]
fn decode_chunk_writes_8_bytes() {
let input = b"Zm9vYmFy"; // "foobar"
let mut output = [0_u8, 1, 2, 3, 4, 5, 6, 7];
decode_chunk(&input[..], 0, tables::STANDARD_DECODE, &mut output).unwrap();
assert_eq!(&vec![b'f', b'o', b'o', b'b', b'a', b'r', 0, 0], &output);
}
#[test] #[test]
fn decode_into_nonempty_vec_doesnt_clobber_existing_prefix() { fn decode_into_nonempty_vec_doesnt_clobber_existing_prefix() {
let mut orig_data = Vec::new(); let mut orig_data = Vec::new();
@ -616,9 +198,9 @@ mod tests {
orig_data.push(rng.gen()); orig_data.push(rng.gen());
} }
let config = random_config(&mut rng); let engine = random_engine(&mut rng);
encode_config_buf(&orig_data, config, &mut encoded_data); engine.encode_string(&orig_data, &mut encoded_data);
assert_encode_sanity(&encoded_data, config, input_len); assert_encode_sanity(&encoded_data, engine.config().encode_padding(), input_len);
let prefix_len = prefix_len_range.sample(&mut rng); let prefix_len = prefix_len_range.sample(&mut rng);
@ -631,9 +213,13 @@ mod tests {
decoded_with_prefix.copy_from_slice(&prefix); decoded_with_prefix.copy_from_slice(&prefix);
// decode into the non-empty buf // decode into the non-empty buf
decode_config_buf(&encoded_data, config, &mut decoded_with_prefix).unwrap(); engine
.decode_vec(&encoded_data, &mut decoded_with_prefix)
.unwrap();
// also decode into the empty buf // also decode into the empty buf
decode_config_buf(&encoded_data, config, &mut decoded_without_prefix).unwrap(); engine
.decode_vec(&encoded_data, &mut decoded_without_prefix)
.unwrap();
assert_eq!( assert_eq!(
prefix_len + decoded_without_prefix.len(), prefix_len + decoded_without_prefix.len(),
@ -649,7 +235,66 @@ mod tests {
} }
#[test] #[test]
fn decode_into_slice_doesnt_clobber_existing_prefix_or_suffix() { fn decode_slice_doesnt_clobber_existing_prefix_or_suffix() {
do_decode_slice_doesnt_clobber_existing_prefix_or_suffix(|e, input, output| {
e.decode_slice(input, output).unwrap()
})
}
#[test]
fn decode_slice_unchecked_doesnt_clobber_existing_prefix_or_suffix() {
do_decode_slice_doesnt_clobber_existing_prefix_or_suffix(|e, input, output| {
e.decode_slice_unchecked(input, output).unwrap()
})
}
#[test]
fn decode_engine_estimation_works_for_various_lengths() {
let engine = GeneralPurpose::new(&alphabet::STANDARD, general_purpose::NO_PAD);
for num_prefix_quads in 0..100 {
for suffix in &["AA", "AAA", "AAAA"] {
let mut prefix = "AAAA".repeat(num_prefix_quads);
prefix.push_str(suffix);
// make sure no overflow (and thus a panic) occurs
let res = engine.decode(prefix);
assert!(res.is_ok());
}
}
}
#[test]
fn decode_slice_output_length_errors() {
for num_quads in 1..100 {
let input = "AAAA".repeat(num_quads);
let mut vec = vec![0; (num_quads - 1) * 3];
assert_eq!(
DecodeSliceError::OutputSliceTooSmall,
STANDARD.decode_slice(&input, &mut vec).unwrap_err()
);
vec.push(0);
assert_eq!(
DecodeSliceError::OutputSliceTooSmall,
STANDARD.decode_slice(&input, &mut vec).unwrap_err()
);
vec.push(0);
assert_eq!(
DecodeSliceError::OutputSliceTooSmall,
STANDARD.decode_slice(&input, &mut vec).unwrap_err()
);
vec.push(0);
// now it works
assert_eq!(
num_quads * 3,
STANDARD.decode_slice(&input, &mut vec).unwrap()
);
}
}
fn do_decode_slice_doesnt_clobber_existing_prefix_or_suffix<
F: Fn(&GeneralPurpose, &[u8], &mut [u8]) -> usize,
>(
call_decode: F,
) {
let mut orig_data = Vec::new(); let mut orig_data = Vec::new();
let mut encoded_data = String::new(); let mut encoded_data = String::new();
let mut decode_buf = Vec::new(); let mut decode_buf = Vec::new();
@ -671,9 +316,9 @@ mod tests {
orig_data.push(rng.gen()); orig_data.push(rng.gen());
} }
let config = random_config(&mut rng); let engine = random_engine(&mut rng);
encode_config_buf(&orig_data, config, &mut encoded_data); engine.encode_string(&orig_data, &mut encoded_data);
assert_encode_sanity(&encoded_data, config, input_len); assert_encode_sanity(&encoded_data, engine.config().encode_padding(), input_len);
// fill the buffer with random garbage, long enough to have some room before and after // fill the buffer with random garbage, long enough to have some room before and after
for _ in 0..5000 { for _ in 0..5000 {
@ -687,7 +332,7 @@ mod tests {
// decode into the non-empty buf // decode into the non-empty buf
let decode_bytes_written = let decode_bytes_written =
decode_config_slice(&encoded_data, config, &mut decode_buf[offset..]).unwrap(); call_decode(&engine, encoded_data.as_bytes(), &mut decode_buf[offset..]);
assert_eq!(orig_data.len(), decode_bytes_written); assert_eq!(orig_data.len(), decode_bytes_written);
assert_eq!( assert_eq!(
@ -701,173 +346,4 @@ mod tests {
); );
} }
} }
#[test]
fn decode_into_slice_fits_in_precisely_sized_slice() {
let mut orig_data = Vec::new();
let mut encoded_data = String::new();
let mut decode_buf = Vec::new();
let input_len_range = Uniform::new(0, 1000);
let mut rng = rand::rngs::SmallRng::from_entropy();
for _ in 0..10_000 {
orig_data.clear();
encoded_data.clear();
decode_buf.clear();
let input_len = input_len_range.sample(&mut rng);
for _ in 0..input_len {
orig_data.push(rng.gen());
}
let config = random_config(&mut rng);
encode_config_buf(&orig_data, config, &mut encoded_data);
assert_encode_sanity(&encoded_data, config, input_len);
decode_buf.resize(input_len, 0);
// decode into the non-empty buf
let decode_bytes_written =
decode_config_slice(&encoded_data, config, &mut decode_buf[..]).unwrap();
assert_eq!(orig_data.len(), decode_bytes_written);
assert_eq!(orig_data, decode_buf);
}
}
#[test]
fn detect_invalid_last_symbol_two_bytes() {
let decode =
|input, forgiving| decode_config(input, STANDARD.decode_allow_trailing_bits(forgiving));
// example from https://github.com/marshallpierce/rust-base64/issues/75
assert!(decode("iYU=", false).is_ok());
// trailing 01
assert_eq!(
Err(DecodeError::InvalidLastSymbol(2, b'V')),
decode("iYV=", false)
);
assert_eq!(Ok(vec![137, 133]), decode("iYV=", true));
// trailing 10
assert_eq!(
Err(DecodeError::InvalidLastSymbol(2, b'W')),
decode("iYW=", false)
);
assert_eq!(Ok(vec![137, 133]), decode("iYV=", true));
// trailing 11
assert_eq!(
Err(DecodeError::InvalidLastSymbol(2, b'X')),
decode("iYX=", false)
);
assert_eq!(Ok(vec![137, 133]), decode("iYV=", true));
// also works when there are 2 quads in the last block
assert_eq!(
Err(DecodeError::InvalidLastSymbol(6, b'X')),
decode("AAAAiYX=", false)
);
assert_eq!(Ok(vec![0, 0, 0, 137, 133]), decode("AAAAiYX=", true));
}
#[test]
fn detect_invalid_last_symbol_one_byte() {
// 0xFF -> "/w==", so all letters > w, 0-9, and '+', '/' should get InvalidLastSymbol
assert!(decode("/w==").is_ok());
// trailing 01
assert_eq!(Err(DecodeError::InvalidLastSymbol(1, b'x')), decode("/x=="));
assert_eq!(Err(DecodeError::InvalidLastSymbol(1, b'z')), decode("/z=="));
assert_eq!(Err(DecodeError::InvalidLastSymbol(1, b'0')), decode("/0=="));
assert_eq!(Err(DecodeError::InvalidLastSymbol(1, b'9')), decode("/9=="));
assert_eq!(Err(DecodeError::InvalidLastSymbol(1, b'+')), decode("/+=="));
assert_eq!(Err(DecodeError::InvalidLastSymbol(1, b'/')), decode("//=="));
// also works when there are 2 quads in the last block
assert_eq!(
Err(DecodeError::InvalidLastSymbol(5, b'x')),
decode("AAAA/x==")
);
}
#[test]
fn detect_invalid_last_symbol_every_possible_three_symbols() {
let mut base64_to_bytes = ::std::collections::HashMap::new();
let mut bytes = [0_u8; 2];
for b1 in 0_u16..256 {
bytes[0] = b1 as u8;
for b2 in 0_u16..256 {
bytes[1] = b2 as u8;
let mut b64 = vec![0_u8; 4];
assert_eq!(4, encode_config_slice(&bytes, STANDARD, &mut b64[..]));
let mut v = ::std::vec::Vec::with_capacity(2);
v.extend_from_slice(&bytes[..]);
assert!(base64_to_bytes.insert(b64, v).is_none());
}
}
// every possible combination of symbols must either decode to 2 bytes or get InvalidLastSymbol
let mut symbols = [0_u8; 4];
for &s1 in STANDARD.char_set.encode_table().iter() {
symbols[0] = s1;
for &s2 in STANDARD.char_set.encode_table().iter() {
symbols[1] = s2;
for &s3 in STANDARD.char_set.encode_table().iter() {
symbols[2] = s3;
symbols[3] = PAD_BYTE;
match base64_to_bytes.get(&symbols[..]) {
Some(bytes) => {
assert_eq!(Ok(bytes.to_vec()), decode_config(&symbols, STANDARD))
}
None => assert_eq!(
Err(DecodeError::InvalidLastSymbol(2, s3)),
decode_config(&symbols[..], STANDARD)
),
}
}
}
}
}
#[test]
fn detect_invalid_last_symbol_every_possible_two_symbols() {
let mut base64_to_bytes = ::std::collections::HashMap::new();
for b in 0_u16..256 {
let mut b64 = vec![0_u8; 4];
assert_eq!(4, encode_config_slice(&[b as u8], STANDARD, &mut b64[..]));
let mut v = ::std::vec::Vec::with_capacity(1);
v.push(b as u8);
assert!(base64_to_bytes.insert(b64, v).is_none());
}
// every possible combination of symbols must either decode to 1 byte or get InvalidLastSymbol
let mut symbols = [0_u8; 4];
for &s1 in STANDARD.char_set.encode_table().iter() {
symbols[0] = s1;
for &s2 in STANDARD.char_set.encode_table().iter() {
symbols[1] = s2;
symbols[2] = PAD_BYTE;
symbols[3] = PAD_BYTE;
match base64_to_bytes.get(&symbols[..]) {
Some(bytes) => {
assert_eq!(Ok(bytes.to_vec()), decode_config(&symbols, STANDARD))
}
None => assert_eq!(
Err(DecodeError::InvalidLastSymbol(1, s2)),
decode_config(&symbols[..], STANDARD)
),
}
}
}
}
} }

View File

@ -1,36 +1,36 @@
//! Enables base64'd output anywhere you might use a `Display` implementation, like a format string. //! Enables base64'd output anywhere you might use a `Display` implementation, like a format string.
//! //!
//! ``` //! ```
//! use base64::display::Base64Display; //! use base64::{display::Base64Display, engine::general_purpose::STANDARD};
//! //!
//! let data = vec![0x0, 0x1, 0x2, 0x3]; //! let data = vec![0x0, 0x1, 0x2, 0x3];
//! let wrapper = Base64Display::with_config(&data, base64::STANDARD); //! let wrapper = Base64Display::new(&data, &STANDARD);
//! //!
//! assert_eq!("base64: AAECAw==", format!("base64: {}", wrapper)); //! assert_eq!("base64: AAECAw==", format!("base64: {}", wrapper));
//! ``` //! ```
use super::chunked_encoder::ChunkedEncoder; use super::chunked_encoder::ChunkedEncoder;
use super::Config; use crate::engine::Engine;
use core::fmt::{Display, Formatter}; use core::fmt::{Display, Formatter};
use core::{fmt, str}; use core::{fmt, str};
/// A convenience wrapper for base64'ing bytes into a format string without heap allocation. /// A convenience wrapper for base64'ing bytes into a format string without heap allocation.
pub struct Base64Display<'a> { pub struct Base64Display<'a, 'e, E: Engine> {
bytes: &'a [u8], bytes: &'a [u8],
chunked_encoder: ChunkedEncoder, chunked_encoder: ChunkedEncoder<'e, E>,
} }
impl<'a> Base64Display<'a> { impl<'a, 'e, E: Engine> Base64Display<'a, 'e, E> {
/// Create a `Base64Display` with the provided config. /// Create a `Base64Display` with the provided engine.
pub fn with_config(bytes: &[u8], config: Config) -> Base64Display { pub fn new(bytes: &'a [u8], engine: &'e E) -> Base64Display<'a, 'e, E> {
Base64Display { Base64Display {
bytes, bytes,
chunked_encoder: ChunkedEncoder::new(config), chunked_encoder: ChunkedEncoder::new(engine),
} }
} }
} }
impl<'a> Display for Base64Display<'a> { impl<'a, 'e, E: Engine> Display for Base64Display<'a, 'e, E> {
fn fmt(&self, formatter: &mut Formatter) -> Result<(), fmt::Error> { fn fmt(&self, formatter: &mut Formatter) -> Result<(), fmt::Error> {
let mut sink = FormatterSink { f: formatter }; let mut sink = FormatterSink { f: formatter };
self.chunked_encoder.encode(self.bytes, &mut sink) self.chunked_encoder.encode(self.bytes, &mut sink)
@ -57,18 +57,18 @@ mod tests {
use super::super::chunked_encoder::tests::{ use super::super::chunked_encoder::tests::{
chunked_encode_matches_normal_encode_random, SinkTestHelper, chunked_encode_matches_normal_encode_random, SinkTestHelper,
}; };
use super::super::*;
use super::*; use super::*;
use crate::engine::general_purpose::STANDARD;
#[test] #[test]
fn basic_display() { fn basic_display() {
assert_eq!( assert_eq!(
"~$Zm9vYmFy#*", "~$Zm9vYmFy#*",
format!("~${}#*", Base64Display::with_config(b"foobar", STANDARD)) format!("~${}#*", Base64Display::new(b"foobar", &STANDARD))
); );
assert_eq!( assert_eq!(
"~$Zm9vYmFyZg==#*", "~$Zm9vYmFyZg==#*",
format!("~${}#*", Base64Display::with_config(b"foobarf", STANDARD)) format!("~${}#*", Base64Display::new(b"foobarf", &STANDARD))
); );
} }
@ -81,8 +81,8 @@ mod tests {
struct DisplaySinkTestHelper; struct DisplaySinkTestHelper;
impl SinkTestHelper for DisplaySinkTestHelper { impl SinkTestHelper for DisplaySinkTestHelper {
fn encode_to_string(&self, config: Config, bytes: &[u8]) -> String { fn encode_to_string<E: Engine>(&self, engine: &E, bytes: &[u8]) -> String {
format!("{}", Base64Display::with_config(bytes, config)) format!("{}", Base64Display::new(bytes, engine))
} }
} }
} }

View File

@ -1,130 +1,59 @@
use crate::{Config, PAD_BYTE};
#[cfg(any(feature = "alloc", feature = "std", test))] #[cfg(any(feature = "alloc", feature = "std", test))]
use crate::{chunked_encoder, STANDARD}; use alloc::string::String;
#[cfg(any(feature = "alloc", feature = "std", test))] use core::fmt;
use alloc::{string::String, vec}; #[cfg(any(feature = "std", test))]
use core::convert::TryInto; use std::error;
///Encode arbitrary octets as base64. #[cfg(any(feature = "alloc", feature = "std", test))]
///Returns a String. use crate::engine::general_purpose::STANDARD;
///Convenience for `encode_config(input, base64::STANDARD);`. use crate::engine::{Config, Engine};
use crate::PAD_BYTE;
/// Encode arbitrary octets as base64 using the [`STANDARD` engine](STANDARD).
/// ///
///# Example /// See [Engine::encode].
/// #[allow(unused)]
///```rust #[deprecated(since = "0.21.0", note = "Use Engine::encode")]
///extern crate base64;
///
///fn main() {
/// let b64 = base64::encode(b"hello world");
/// println!("{}", b64);
///}
///```
#[cfg(any(feature = "alloc", feature = "std", test))] #[cfg(any(feature = "alloc", feature = "std", test))]
pub fn encode<T: AsRef<[u8]>>(input: T) -> String { pub fn encode<T: AsRef<[u8]>>(input: T) -> String {
encode_config(input, STANDARD) STANDARD.encode(input)
} }
///Encode arbitrary octets as base64. ///Encode arbitrary octets as base64 using the provided `Engine` into a new `String`.
///Returns a String.
/// ///
///# Example /// See [Engine::encode].
/// #[allow(unused)]
///```rust #[deprecated(since = "0.21.0", note = "Use Engine::encode")]
///extern crate base64;
///
///fn main() {
/// let b64 = base64::encode_config(b"hello world~", base64::STANDARD);
/// println!("{}", b64);
///
/// let b64_url = base64::encode_config(b"hello internet~", base64::URL_SAFE);
/// println!("{}", b64_url);
///}
///```
#[cfg(any(feature = "alloc", feature = "std", test))] #[cfg(any(feature = "alloc", feature = "std", test))]
pub fn encode_config<T: AsRef<[u8]>>(input: T, config: Config) -> String { pub fn encode_engine<E: Engine, T: AsRef<[u8]>>(input: T, engine: &E) -> String {
let mut buf = match encoded_size(input.as_ref().len(), config) { engine.encode(input)
Some(n) => vec![0; n],
None => panic!("integer overflow when calculating buffer size"),
};
encode_with_padding(input.as_ref(), config, buf.len(), &mut buf[..]);
String::from_utf8(buf).expect("Invalid UTF8")
} }
///Encode arbitrary octets as base64. ///Encode arbitrary octets as base64 into a supplied `String`.
///Writes into the supplied output buffer, which will grow the buffer if needed.
/// ///
///# Example /// See [Engine::encode_string].
/// #[allow(unused)]
///```rust #[deprecated(since = "0.21.0", note = "Use Engine::encode_string")]
///extern crate base64;
///
///fn main() {
/// let mut buf = String::new();
/// base64::encode_config_buf(b"hello world~", base64::STANDARD, &mut buf);
/// println!("{}", buf);
///
/// buf.clear();
/// base64::encode_config_buf(b"hello internet~", base64::URL_SAFE, &mut buf);
/// println!("{}", buf);
///}
///```
#[cfg(any(feature = "alloc", feature = "std", test))] #[cfg(any(feature = "alloc", feature = "std", test))]
pub fn encode_config_buf<T: AsRef<[u8]>>(input: T, config: Config, buf: &mut String) { pub fn encode_engine_string<E: Engine, T: AsRef<[u8]>>(
let input_bytes = input.as_ref(); input: T,
output_buf: &mut String,
{ engine: &E,
let mut sink = chunked_encoder::StringSink::new(buf); ) {
let encoder = chunked_encoder::ChunkedEncoder::new(config); engine.encode_string(input, output_buf)
encoder
.encode(input_bytes, &mut sink)
.expect("Writing to a String shouldn't fail")
}
} }
/// Encode arbitrary octets as base64. /// Encode arbitrary octets as base64 into a supplied slice.
/// Writes into the supplied output buffer.
/// ///
/// This is useful if you wish to avoid allocation entirely (e.g. encoding into a stack-resident /// See [Engine::encode_slice].
/// or statically-allocated buffer). #[allow(unused)]
/// #[deprecated(since = "0.21.0", note = "Use Engine::encode_slice")]
/// # Panics pub fn encode_engine_slice<E: Engine, T: AsRef<[u8]>>(
/// input: T,
/// If `output` is too small to hold the encoded version of `input`, a panic will result. output_buf: &mut [u8],
/// engine: &E,
/// # Example ) -> Result<usize, EncodeSliceError> {
/// engine.encode_slice(input, output_buf)
/// ```rust
/// extern crate base64;
///
/// fn main() {
/// let s = b"hello internet!";
/// let mut buf = Vec::new();
/// // make sure we'll have a slice big enough for base64 + padding
/// buf.resize(s.len() * 4 / 3 + 4, 0);
///
/// let bytes_written = base64::encode_config_slice(s,
/// base64::STANDARD, &mut buf);
///
/// // shorten our vec down to just what was written
/// buf.resize(bytes_written, 0);
///
/// assert_eq!(s, base64::decode(&buf).unwrap().as_slice());
/// }
/// ```
pub fn encode_config_slice<T: AsRef<[u8]>>(input: T, config: Config, output: &mut [u8]) -> usize {
let input_bytes = input.as_ref();
let encoded_size = encoded_size(input_bytes.len(), config)
.expect("usize overflow when calculating buffer size");
let mut b64_output = &mut output[0..encoded_size];
encode_with_padding(&input_bytes, config, encoded_size, &mut b64_output);
encoded_size
} }
/// B64-encode and pad (if configured). /// B64-encode and pad (if configured).
@ -137,12 +66,17 @@ pub fn encode_config_slice<T: AsRef<[u8]>>(input: T, config: Config, output: &mu
/// `output` must be of size `encoded_size`. /// `output` must be of size `encoded_size`.
/// ///
/// All bytes in `output` will be written to since it is exactly the size of the output. /// All bytes in `output` will be written to since it is exactly the size of the output.
fn encode_with_padding(input: &[u8], config: Config, encoded_size: usize, output: &mut [u8]) { pub(crate) fn encode_with_padding<E: Engine + ?Sized>(
debug_assert_eq!(encoded_size, output.len()); input: &[u8],
output: &mut [u8],
engine: &E,
expected_encoded_size: usize,
) {
debug_assert_eq!(expected_encoded_size, output.len());
let b64_bytes_written = encode_to_slice(input, output, config.char_set.encode_table()); let b64_bytes_written = engine.internal_encode(input, output);
let padding_bytes = if config.pad { let padding_bytes = if engine.config().encode_padding() {
add_padding(input.len(), &mut output[b64_bytes_written..]) add_padding(input.len(), &mut output[b64_bytes_written..])
} else { } else {
0 0
@ -152,144 +86,22 @@ fn encode_with_padding(input: &[u8], config: Config, encoded_size: usize, output
.checked_add(padding_bytes) .checked_add(padding_bytes)
.expect("usize overflow when calculating b64 length"); .expect("usize overflow when calculating b64 length");
debug_assert_eq!(encoded_size, encoded_bytes); debug_assert_eq!(expected_encoded_size, encoded_bytes);
} }
#[inline] /// Calculate the base64 encoded length for a given input length, optionally including any
fn read_u64(s: &[u8]) -> u64 { /// appropriate padding bytes.
u64::from_be_bytes(s[..8].try_into().unwrap()) ///
} /// Returns `None` if the encoded length can't be represented in `usize`. This will happen for
/// input lengths in approximately the top quarter of the range of `usize`.
/// Encode input bytes to utf8 base64 bytes. Does not pad. pub fn encoded_len(bytes_len: usize, padding: bool) -> Option<usize> {
/// `output` must be long enough to hold the encoded `input` without padding.
/// Returns the number of bytes written.
#[inline]
pub fn encode_to_slice(input: &[u8], output: &mut [u8], encode_table: &[u8; 64]) -> usize {
let mut input_index: usize = 0;
const BLOCKS_PER_FAST_LOOP: usize = 4;
const LOW_SIX_BITS: u64 = 0x3F;
// we read 8 bytes at a time (u64) but only actually consume 6 of those bytes. Thus, we need
// 2 trailing bytes to be available to read..
let last_fast_index = input.len().saturating_sub(BLOCKS_PER_FAST_LOOP * 6 + 2);
let mut output_index = 0;
if last_fast_index > 0 {
while input_index <= last_fast_index {
// Major performance wins from letting the optimizer do the bounds check once, mostly
// on the output side
let input_chunk = &input[input_index..(input_index + (BLOCKS_PER_FAST_LOOP * 6 + 2))];
let output_chunk = &mut output[output_index..(output_index + BLOCKS_PER_FAST_LOOP * 8)];
// Hand-unrolling for 32 vs 16 or 8 bytes produces yields performance about equivalent
// to unsafe pointer code on a Xeon E5-1650v3. 64 byte unrolling was slightly better for
// large inputs but significantly worse for 50-byte input, unsurprisingly. I suspect
// that it's a not uncommon use case to encode smallish chunks of data (e.g. a 64-byte
// SHA-512 digest), so it would be nice if that fit in the unrolled loop at least once.
// Plus, single-digit percentage performance differences might well be quite different
// on different hardware.
let input_u64 = read_u64(&input_chunk[0..]);
output_chunk[0] = encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize];
output_chunk[1] = encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize];
output_chunk[2] = encode_table[((input_u64 >> 46) & LOW_SIX_BITS) as usize];
output_chunk[3] = encode_table[((input_u64 >> 40) & LOW_SIX_BITS) as usize];
output_chunk[4] = encode_table[((input_u64 >> 34) & LOW_SIX_BITS) as usize];
output_chunk[5] = encode_table[((input_u64 >> 28) & LOW_SIX_BITS) as usize];
output_chunk[6] = encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize];
output_chunk[7] = encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize];
let input_u64 = read_u64(&input_chunk[6..]);
output_chunk[8] = encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize];
output_chunk[9] = encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize];
output_chunk[10] = encode_table[((input_u64 >> 46) & LOW_SIX_BITS) as usize];
output_chunk[11] = encode_table[((input_u64 >> 40) & LOW_SIX_BITS) as usize];
output_chunk[12] = encode_table[((input_u64 >> 34) & LOW_SIX_BITS) as usize];
output_chunk[13] = encode_table[((input_u64 >> 28) & LOW_SIX_BITS) as usize];
output_chunk[14] = encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize];
output_chunk[15] = encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize];
let input_u64 = read_u64(&input_chunk[12..]);
output_chunk[16] = encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize];
output_chunk[17] = encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize];
output_chunk[18] = encode_table[((input_u64 >> 46) & LOW_SIX_BITS) as usize];
output_chunk[19] = encode_table[((input_u64 >> 40) & LOW_SIX_BITS) as usize];
output_chunk[20] = encode_table[((input_u64 >> 34) & LOW_SIX_BITS) as usize];
output_chunk[21] = encode_table[((input_u64 >> 28) & LOW_SIX_BITS) as usize];
output_chunk[22] = encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize];
output_chunk[23] = encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize];
let input_u64 = read_u64(&input_chunk[18..]);
output_chunk[24] = encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize];
output_chunk[25] = encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize];
output_chunk[26] = encode_table[((input_u64 >> 46) & LOW_SIX_BITS) as usize];
output_chunk[27] = encode_table[((input_u64 >> 40) & LOW_SIX_BITS) as usize];
output_chunk[28] = encode_table[((input_u64 >> 34) & LOW_SIX_BITS) as usize];
output_chunk[29] = encode_table[((input_u64 >> 28) & LOW_SIX_BITS) as usize];
output_chunk[30] = encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize];
output_chunk[31] = encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize];
output_index += BLOCKS_PER_FAST_LOOP * 8;
input_index += BLOCKS_PER_FAST_LOOP * 6;
}
}
// Encode what's left after the fast loop.
const LOW_SIX_BITS_U8: u8 = 0x3F;
let rem = input.len() % 3;
let start_of_rem = input.len() - rem;
// start at the first index not handled by fast loop, which may be 0.
while input_index < start_of_rem {
let input_chunk = &input[input_index..(input_index + 3)];
let output_chunk = &mut output[output_index..(output_index + 4)];
output_chunk[0] = encode_table[(input_chunk[0] >> 2) as usize];
output_chunk[1] =
encode_table[((input_chunk[0] << 4 | input_chunk[1] >> 4) & LOW_SIX_BITS_U8) as usize];
output_chunk[2] =
encode_table[((input_chunk[1] << 2 | input_chunk[2] >> 6) & LOW_SIX_BITS_U8) as usize];
output_chunk[3] = encode_table[(input_chunk[2] & LOW_SIX_BITS_U8) as usize];
input_index += 3;
output_index += 4;
}
if rem == 2 {
output[output_index] = encode_table[(input[start_of_rem] >> 2) as usize];
output[output_index + 1] = encode_table[((input[start_of_rem] << 4
| input[start_of_rem + 1] >> 4)
& LOW_SIX_BITS_U8) as usize];
output[output_index + 2] =
encode_table[((input[start_of_rem + 1] << 2) & LOW_SIX_BITS_U8) as usize];
output_index += 3;
} else if rem == 1 {
output[output_index] = encode_table[(input[start_of_rem] >> 2) as usize];
output[output_index + 1] =
encode_table[((input[start_of_rem] << 4) & LOW_SIX_BITS_U8) as usize];
output_index += 2;
}
output_index
}
/// calculate the base64 encoded string size, including padding if appropriate
pub fn encoded_size(bytes_len: usize, config: Config) -> Option<usize> {
let rem = bytes_len % 3; let rem = bytes_len % 3;
let complete_input_chunks = bytes_len / 3; let complete_input_chunks = bytes_len / 3;
let complete_chunk_output = complete_input_chunks.checked_mul(4); let complete_chunk_output = complete_input_chunks.checked_mul(4);
if rem > 0 { if rem > 0 {
if config.pad { if padding {
complete_chunk_output.and_then(|c| c.checked_add(4)) complete_chunk_output.and_then(|c| c.checked_add(4))
} else { } else {
let encoded_rem = match rem { let encoded_rem = match rem {
@ -305,10 +117,12 @@ pub fn encoded_size(bytes_len: usize, config: Config) -> Option<usize> {
} }
/// Write padding characters. /// Write padding characters.
/// `input_len` is the size of the original, not encoded, input.
/// `output` is the slice where padding should be written, of length at least 2. /// `output` is the slice where padding should be written, of length at least 2.
/// ///
/// Returns the number of padding bytes written. /// Returns the number of padding bytes written.
pub fn add_padding(input_len: usize, output: &mut [u8]) -> usize { pub(crate) fn add_padding(input_len: usize, output: &mut [u8]) -> usize {
// TODO base on encoded len to use cheaper mod by 4 (aka & 7)
let rem = input_len % 3; let rem = input_len % 3;
let mut bytes_written = 0; let mut bytes_written = 0;
for _ in 0..((3 - rem) % 3) { for _ in 0..((3 - rem) % 3) {
@ -319,79 +133,102 @@ pub fn add_padding(input_len: usize, output: &mut [u8]) -> usize {
bytes_written bytes_written
} }
/// Errors that can occur while encoding into a slice.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum EncodeSliceError {
/// The provided slice is too small.
OutputSliceTooSmall,
}
impl fmt::Display for EncodeSliceError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::OutputSliceTooSmall => write!(f, "Output slice too small"),
}
}
}
#[cfg(any(feature = "std", test))]
impl error::Error for EncodeSliceError {
fn cause(&self) -> Option<&dyn error::Error> {
None
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::{
decode::decode_config_buf,
tests::{assert_encode_sanity, random_config},
Config, STANDARD, URL_SAFE_NO_PAD,
};
use crate::{
alphabet,
engine::general_purpose::{GeneralPurpose, NO_PAD, STANDARD},
tests::{assert_encode_sanity, random_config, random_engine},
};
use rand::{ use rand::{
distributions::{Distribution, Uniform}, distributions::{Distribution, Uniform},
FromEntropy, Rng, Rng, SeedableRng,
}; };
use std;
use std::str; use std::str;
const URL_SAFE_NO_PAD_ENGINE: GeneralPurpose = GeneralPurpose::new(&alphabet::URL_SAFE, NO_PAD);
#[test] #[test]
fn encoded_size_correct_standard() { fn encoded_size_correct_standard() {
assert_encoded_length(0, 0, STANDARD); assert_encoded_length(0, 0, &STANDARD, true);
assert_encoded_length(1, 4, STANDARD); assert_encoded_length(1, 4, &STANDARD, true);
assert_encoded_length(2, 4, STANDARD); assert_encoded_length(2, 4, &STANDARD, true);
assert_encoded_length(3, 4, STANDARD); assert_encoded_length(3, 4, &STANDARD, true);
assert_encoded_length(4, 8, STANDARD); assert_encoded_length(4, 8, &STANDARD, true);
assert_encoded_length(5, 8, STANDARD); assert_encoded_length(5, 8, &STANDARD, true);
assert_encoded_length(6, 8, STANDARD); assert_encoded_length(6, 8, &STANDARD, true);
assert_encoded_length(7, 12, STANDARD); assert_encoded_length(7, 12, &STANDARD, true);
assert_encoded_length(8, 12, STANDARD); assert_encoded_length(8, 12, &STANDARD, true);
assert_encoded_length(9, 12, STANDARD); assert_encoded_length(9, 12, &STANDARD, true);
assert_encoded_length(54, 72, STANDARD); assert_encoded_length(54, 72, &STANDARD, true);
assert_encoded_length(55, 76, STANDARD); assert_encoded_length(55, 76, &STANDARD, true);
assert_encoded_length(56, 76, STANDARD); assert_encoded_length(56, 76, &STANDARD, true);
assert_encoded_length(57, 76, STANDARD); assert_encoded_length(57, 76, &STANDARD, true);
assert_encoded_length(58, 80, STANDARD); assert_encoded_length(58, 80, &STANDARD, true);
} }
#[test] #[test]
fn encoded_size_correct_no_pad() { fn encoded_size_correct_no_pad() {
assert_encoded_length(0, 0, URL_SAFE_NO_PAD); assert_encoded_length(0, 0, &URL_SAFE_NO_PAD_ENGINE, false);
assert_encoded_length(1, 2, URL_SAFE_NO_PAD); assert_encoded_length(1, 2, &URL_SAFE_NO_PAD_ENGINE, false);
assert_encoded_length(2, 3, URL_SAFE_NO_PAD); assert_encoded_length(2, 3, &URL_SAFE_NO_PAD_ENGINE, false);
assert_encoded_length(3, 4, URL_SAFE_NO_PAD); assert_encoded_length(3, 4, &URL_SAFE_NO_PAD_ENGINE, false);
assert_encoded_length(4, 6, URL_SAFE_NO_PAD); assert_encoded_length(4, 6, &URL_SAFE_NO_PAD_ENGINE, false);
assert_encoded_length(5, 7, URL_SAFE_NO_PAD); assert_encoded_length(5, 7, &URL_SAFE_NO_PAD_ENGINE, false);
assert_encoded_length(6, 8, URL_SAFE_NO_PAD); assert_encoded_length(6, 8, &URL_SAFE_NO_PAD_ENGINE, false);
assert_encoded_length(7, 10, URL_SAFE_NO_PAD); assert_encoded_length(7, 10, &URL_SAFE_NO_PAD_ENGINE, false);
assert_encoded_length(8, 11, URL_SAFE_NO_PAD); assert_encoded_length(8, 11, &URL_SAFE_NO_PAD_ENGINE, false);
assert_encoded_length(9, 12, URL_SAFE_NO_PAD); assert_encoded_length(9, 12, &URL_SAFE_NO_PAD_ENGINE, false);
assert_encoded_length(54, 72, URL_SAFE_NO_PAD); assert_encoded_length(54, 72, &URL_SAFE_NO_PAD_ENGINE, false);
assert_encoded_length(55, 74, URL_SAFE_NO_PAD); assert_encoded_length(55, 74, &URL_SAFE_NO_PAD_ENGINE, false);
assert_encoded_length(56, 75, URL_SAFE_NO_PAD); assert_encoded_length(56, 75, &URL_SAFE_NO_PAD_ENGINE, false);
assert_encoded_length(57, 76, URL_SAFE_NO_PAD); assert_encoded_length(57, 76, &URL_SAFE_NO_PAD_ENGINE, false);
assert_encoded_length(58, 78, URL_SAFE_NO_PAD); assert_encoded_length(58, 78, &URL_SAFE_NO_PAD_ENGINE, false);
} }
#[test] #[test]
fn encoded_size_overflow() { fn encoded_size_overflow() {
assert_eq!(None, encoded_size(std::usize::MAX, STANDARD)); assert_eq!(None, encoded_len(usize::MAX, true));
} }
#[test] #[test]
fn encode_config_buf_into_nonempty_buffer_doesnt_clobber_prefix() { fn encode_engine_string_into_nonempty_buffer_doesnt_clobber_prefix() {
let mut orig_data = Vec::new(); let mut orig_data = Vec::new();
let mut prefix = String::new(); let mut prefix = String::new();
let mut encoded_data_no_prefix = String::new(); let mut encoded_data_no_prefix = String::new();
@ -424,29 +261,39 @@ mod tests {
} }
encoded_data_with_prefix.push_str(&prefix); encoded_data_with_prefix.push_str(&prefix);
let config = random_config(&mut rng); let engine = random_engine(&mut rng);
encode_config_buf(&orig_data, config, &mut encoded_data_no_prefix); engine.encode_string(&orig_data, &mut encoded_data_no_prefix);
encode_config_buf(&orig_data, config, &mut encoded_data_with_prefix); engine.encode_string(&orig_data, &mut encoded_data_with_prefix);
assert_eq!( assert_eq!(
encoded_data_no_prefix.len() + prefix_len, encoded_data_no_prefix.len() + prefix_len,
encoded_data_with_prefix.len() encoded_data_with_prefix.len()
); );
assert_encode_sanity(&encoded_data_no_prefix, config, input_len); assert_encode_sanity(
assert_encode_sanity(&encoded_data_with_prefix[prefix_len..], config, input_len); &encoded_data_no_prefix,
engine.config().encode_padding(),
input_len,
);
assert_encode_sanity(
&encoded_data_with_prefix[prefix_len..],
engine.config().encode_padding(),
input_len,
);
// append plain encode onto prefix // append plain encode onto prefix
prefix.push_str(&mut encoded_data_no_prefix); prefix.push_str(&encoded_data_no_prefix);
assert_eq!(prefix, encoded_data_with_prefix); assert_eq!(prefix, encoded_data_with_prefix);
decode_config_buf(&encoded_data_no_prefix, config, &mut decoded).unwrap(); engine
.decode_vec(&encoded_data_no_prefix, &mut decoded)
.unwrap();
assert_eq!(orig_data, decoded); assert_eq!(orig_data, decoded);
} }
} }
#[test] #[test]
fn encode_config_slice_into_nonempty_buffer_doesnt_clobber_suffix() { fn encode_engine_slice_into_nonempty_buffer_doesnt_clobber_suffix() {
let mut orig_data = Vec::new(); let mut orig_data = Vec::new();
let mut encoded_data = Vec::new(); let mut encoded_data = Vec::new();
let mut encoded_data_original_state = Vec::new(); let mut encoded_data_original_state = Vec::new();
@ -475,18 +322,18 @@ mod tests {
encoded_data_original_state.extend_from_slice(&encoded_data); encoded_data_original_state.extend_from_slice(&encoded_data);
let config = random_config(&mut rng); let engine = random_engine(&mut rng);
let encoded_size = encoded_size(input_len, config).unwrap(); let encoded_size = encoded_len(input_len, engine.config().encode_padding()).unwrap();
assert_eq!( assert_eq!(
encoded_size, encoded_size,
encode_config_slice(&orig_data, config, &mut encoded_data) engine.encode_slice(&orig_data, &mut encoded_data).unwrap()
); );
assert_encode_sanity( assert_encode_sanity(
std::str::from_utf8(&encoded_data[0..encoded_size]).unwrap(), str::from_utf8(&encoded_data[0..encoded_size]).unwrap(),
config, engine.config().encode_padding(),
input_len, input_len,
); );
@ -495,50 +342,9 @@ mod tests {
&encoded_data_original_state[encoded_size..] &encoded_data_original_state[encoded_size..]
); );
decode_config_buf(&encoded_data[0..encoded_size], config, &mut decoded).unwrap(); engine
assert_eq!(orig_data, decoded); .decode_vec(&encoded_data[0..encoded_size], &mut decoded)
} .unwrap();
}
#[test]
fn encode_config_slice_fits_into_precisely_sized_slice() {
let mut orig_data = Vec::new();
let mut encoded_data = Vec::new();
let mut decoded = Vec::new();
let input_len_range = Uniform::new(0, 1000);
let mut rng = rand::rngs::SmallRng::from_entropy();
for _ in 0..10_000 {
orig_data.clear();
encoded_data.clear();
decoded.clear();
let input_len = input_len_range.sample(&mut rng);
for _ in 0..input_len {
orig_data.push(rng.gen());
}
let config = random_config(&mut rng);
let encoded_size = encoded_size(input_len, config).unwrap();
encoded_data.resize(encoded_size, 0);
assert_eq!(
encoded_size,
encode_config_slice(&orig_data, config, &mut encoded_data)
);
assert_encode_sanity(
std::str::from_utf8(&encoded_data[0..encoded_size]).unwrap(),
config,
input_len,
);
decode_config_buf(&encoded_data[0..encoded_size], config, &mut decoded).unwrap();
assert_eq!(orig_data, decoded); assert_eq!(orig_data, decoded);
} }
} }
@ -563,17 +369,17 @@ mod tests {
} }
let config = random_config(&mut rng); let config = random_config(&mut rng);
let engine = random_engine(&mut rng);
// fill up the output buffer with garbage // fill up the output buffer with garbage
let encoded_size = encoded_size(input_len, config).unwrap(); let encoded_size = encoded_len(input_len, config.encode_padding()).unwrap();
for _ in 0..encoded_size { for _ in 0..encoded_size {
output.push(rng.gen()); output.push(rng.gen());
} }
let orig_output_buf = output.to_vec(); let orig_output_buf = output.clone();
let bytes_written = let bytes_written = engine.internal_encode(&input, &mut output);
encode_to_slice(&input, &mut output, config.char_set.encode_table());
// make sure the part beyond bytes_written is the same garbage it was before // make sure the part beyond bytes_written is the same garbage it was before
assert_eq!(orig_output_buf[bytes_written..], output[bytes_written..]); assert_eq!(orig_output_buf[bytes_written..], output[bytes_written..]);
@ -602,17 +408,17 @@ mod tests {
input.push(rng.gen()); input.push(rng.gen());
} }
let config = random_config(&mut rng); let engine = random_engine(&mut rng);
// fill up the output buffer with garbage // fill up the output buffer with garbage
let encoded_size = encoded_size(input_len, config).unwrap(); let encoded_size = encoded_len(input_len, engine.config().encode_padding()).unwrap();
for _ in 0..encoded_size + 1000 { for _ in 0..encoded_size + 1000 {
output.push(rng.gen()); output.push(rng.gen());
} }
let orig_output_buf = output.to_vec(); let orig_output_buf = output.clone();
encode_with_padding(&input, config, encoded_size, &mut output[0..encoded_size]); encode_with_padding(&input, &mut output[0..encoded_size], &engine, encoded_size);
// make sure the part beyond b64 is the same garbage it was before // make sure the part beyond b64 is the same garbage it was before
assert_eq!(orig_output_buf[encoded_size..], output[encoded_size..]); assert_eq!(orig_output_buf[encoded_size..], output[encoded_size..]);
@ -637,7 +443,7 @@ mod tests {
output.push(rng.gen()); output.push(rng.gen());
} }
let orig_output_buf = output.to_vec(); let orig_output_buf = output.clone();
let bytes_written = add_padding(input_len, &mut output); let bytes_written = add_padding(input_len, &mut output);
@ -649,8 +455,13 @@ mod tests {
} }
} }
fn assert_encoded_length(input_len: usize, encoded_len: usize, config: Config) { fn assert_encoded_length<E: Engine>(
assert_eq!(encoded_len, encoded_size(input_len, config).unwrap()); input_len: usize,
enc_len: usize,
engine: &E,
padded: bool,
) {
assert_eq!(enc_len, encoded_len(input_len, padded).unwrap());
let mut bytes: Vec<u8> = Vec::new(); let mut bytes: Vec<u8> = Vec::new();
let mut rng = rand::rngs::SmallRng::from_entropy(); let mut rng = rand::rngs::SmallRng::from_entropy();
@ -659,17 +470,19 @@ mod tests {
bytes.push(rng.gen()); bytes.push(rng.gen());
} }
let encoded = encode_config(&bytes, config); let encoded = engine.encode(&bytes);
assert_encode_sanity(&encoded, config, input_len); assert_encode_sanity(&encoded, padded, input_len);
assert_eq!(encoded_len, encoded.len()); assert_eq!(enc_len, encoded.len());
} }
#[test] #[test]
fn encode_imap() { fn encode_imap() {
assert_eq!( assert_eq!(
encode_config(b"\xFB\xFF", crate::IMAP_MUTF7), &GeneralPurpose::new(&alphabet::IMAP_MUTF7, NO_PAD).encode(b"\xFB\xFF"),
encode_config(b"\xFB\xFF", crate::STANDARD_NO_PAD).replace("/", ",") &GeneralPurpose::new(&alphabet::STANDARD, NO_PAD)
.encode(b"\xFB\xFF")
.replace('/', ",")
); );
} }
} }

View File

@ -0,0 +1,348 @@
use crate::{
engine::{general_purpose::INVALID_VALUE, DecodeEstimate, DecodePaddingMode},
DecodeError, 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,
}
impl GeneralPurposeEstimate {
pub(crate) fn new(encoded_len: usize) -> Self {
Self {
num_chunks: encoded_len
.checked_add(INPUT_CHUNK_LEN - 1)
.expect("Overflow when calculating number of chunks in input")
/ INPUT_CHUNK_LEN,
decoded_len_estimate: encoded_len
.checked_add(3)
.expect("Overflow when calculating decoded len estimate")
/ 4
* 3,
}
}
}
impl DecodeEstimate for GeneralPurposeEstimate {
fn decoded_len_estimate(&self) -> usize {
self.decoded_len_estimate
}
}
/// Helper to avoid duplicating num_chunks calculation, which is costly on short inputs.
/// Returns the number of bytes written, or an error.
// We're on the fragile edge of compiler heuristics here. If this is not inlined, slow. If this is
// inlined(always), a different slow. plain ol' inline makes the benchmarks happiest at the moment,
// but this is fragile and the best setting changes with only minor code modifications.
#[inline]
pub(crate) fn decode_helper(
input: &[u8],
estimate: GeneralPurposeEstimate,
output: &mut [u8],
decode_table: &[u8; 256],
decode_allow_trailing_bits: bool,
padding_mode: DecodePaddingMode,
) -> Result<usize, DecodeError> {
let remainder_len = input.len() % INPUT_CHUNK_LEN;
// 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));
}
}
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,
};
// rounded up to include partial chunks
let mut remaining_chunks = estimate.num_chunks;
let mut input_index = 0;
let mut output_index = 0;
{
let length_of_fast_decode_chunks = input.len().saturating_sub(trailing_bytes_to_skip);
// 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(
&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..],
)?;
input_index += INPUT_BLOCK_LEN;
output_index += DECODED_BLOCK_LEN - DECODED_CHUNK_SUFFIX;
remaining_chunks -= CHUNKS_PER_FAST_LOOP_BLOCK;
}
}
// 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;
}
}
}
// 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,
output,
output_index,
decode_table,
decode_allow_trailing_bits,
padding_mode,
)
}
/// 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.
///
/// `input` is the bytes to decode, of which the first 8 bytes will be processed.
/// `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.
// yes, really inline (worth 30-50% speedup)
#[inline(always)]
fn decode_chunk(
input: &[u8],
index_at_start_of_input: usize,
decode_table: &[u8; 256],
output: &mut [u8],
) -> Result<(), DecodeError> {
let morsel = decode_table[input[0] as usize];
if morsel == INVALID_VALUE {
return Err(DecodeError::InvalidByte(index_at_start_of_input, input[0]));
}
let mut accum = (morsel as u64) << 58;
let morsel = decode_table[input[1] as usize];
if morsel == INVALID_VALUE {
return Err(DecodeError::InvalidByte(
index_at_start_of_input + 1,
input[1],
));
}
accum |= (morsel as u64) << 52;
let morsel = decode_table[input[2] as usize];
if morsel == INVALID_VALUE {
return Err(DecodeError::InvalidByte(
index_at_start_of_input + 2,
input[2],
));
}
accum |= (morsel as u64) << 46;
let morsel = decode_table[input[3] as usize];
if morsel == INVALID_VALUE {
return Err(DecodeError::InvalidByte(
index_at_start_of_input + 3,
input[3],
));
}
accum |= (morsel as u64) << 40;
let morsel = decode_table[input[4] as usize];
if morsel == INVALID_VALUE {
return Err(DecodeError::InvalidByte(
index_at_start_of_input + 4,
input[4],
));
}
accum |= (morsel as u64) << 34;
let morsel = decode_table[input[5] as usize];
if morsel == INVALID_VALUE {
return Err(DecodeError::InvalidByte(
index_at_start_of_input + 5,
input[5],
));
}
accum |= (morsel as u64) << 28;
let morsel = decode_table[input[6] as usize];
if morsel == INVALID_VALUE {
return Err(DecodeError::InvalidByte(
index_at_start_of_input + 6,
input[6],
));
}
accum |= (morsel as u64) << 22;
let morsel = decode_table[input[7] as usize];
if morsel == INVALID_VALUE {
return Err(DecodeError::InvalidByte(
index_at_start_of_input + 7,
input[7],
));
}
accum |= (morsel as u64) << 16;
write_u64(output, accum);
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(
input: &[u8],
index_at_start_of_input: usize,
decode_table: &[u8; 256],
output: &mut [u8],
) -> Result<(), DecodeError> {
let mut tmp_buf = [0_u8; 8];
decode_chunk(
input,
index_at_start_of_input,
decode_table,
&mut tmp_buf[..],
)?;
output[0..6].copy_from_slice(&tmp_buf[0..6]);
Ok(())
}
#[inline]
fn write_u64(output: &mut [u8], value: u64) {
output[..8].copy_from_slice(&value.to_be_bytes());
}
#[cfg(test)]
mod tests {
use super::*;
use crate::engine::general_purpose::STANDARD;
#[test]
fn decode_chunk_precise_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();
assert_eq!(&vec![b'f', b'o', b'o', b'b', b'a', b'r', 6, 7], &output);
}
#[test]
fn decode_chunk_writes_8_bytes() {
let input = b"Zm9vYmFy"; // "foobar"
let mut output = [0_u8, 1, 2, 3, 4, 5, 6, 7];
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);
}
}

View File

@ -0,0 +1,161 @@
use crate::{
engine::{general_purpose::INVALID_VALUE, DecodePaddingMode},
DecodeError, PAD_BYTE,
};
/// Decode the last 1-8 bytes, checking for trailing set bits and padding per the provided
/// parameters.
///
/// Returns the total number of bytes decoded, including the ones indicated as already written by
/// `output_index`.
pub(crate) fn decode_suffix(
input: &[u8],
input_index: usize,
output: &mut [u8],
mut output_index: usize,
decode_table: &[u8; 256],
decode_allow_trailing_bits: bool,
padding_mode: DecodePaddingMode,
) -> Result<usize, 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;
let mut morsels_in_leftover = 0;
let mut padding_bytes = 0;
let mut first_padding_index: usize = 0;
let mut last_symbol = 0_u8;
let start_of_leftovers = input_index;
for (i, &b) in input[start_of_leftovers..].iter().enumerate() {
// '=' padding
if b == PAD_BYTE {
// There can be bad padding bytes in a few ways:
// 1 - Padding with non-padding characters after it
// 2 - Padding after zero or one characters in the current quad (should only
// be after 2 or 3 chars)
// 3 - More than two characters of padding. If 3 or 4 padding chars
// are in the same quad, that implies it will be caught by #2.
// If it spreads from one quad to another, it will be an invalid byte
// in the first quad.
// 4 - Non-canonical padding -- 1 byte when it should be 2, etc.
// Per config, non-canonical but still functional non- or partially-padded base64
// may be treated as an error condition.
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 padding_bytes == 0 {
first_padding_index = i;
}
padding_bytes += 1;
continue;
}
// Check for case #1.
// To make '=' handling consistent with the main loop, don't allow
// non-suffix '=' in trailing chunk either. Report error as first
// erroneous padding.
if padding_bytes > 0 {
return Err(DecodeError::InvalidByte(
start_of_leftovers + first_padding_index,
PAD_BYTE,
));
}
last_symbol = b;
// can use up to 8 * 6 = 48 bits of the u64, if last chunk has no padding.
// 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));
}
leftover_bits |= (morsel as u64) << shift;
morsels_in_leftover += 1;
}
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);
}
}
DecodePaddingMode::RequireNone => {
if padding_bytes > 0 {
// check at the end to make sure we let the cases of padding that should be InvalidByte
// get hit
return Err(DecodeError::InvalidPadding);
}
}
}
// When encoding 1 trailing byte (e.g. 0xFF), 2 base64 bytes ("/w") are needed.
// / is the symbol for 63 (0x3F, bottom 6 bits all set) and w is 48 (0x30, top 2 bits
// of bottom 6 bits set).
// When decoding two symbols back to one trailing byte, any final symbol higher than
// w would still decode to the original byte because we only care about the top two
// bits in the bottom 6, but would be a non-canonical encoding. So, we calculate a
// mask based on how many bits are used for just the canonical encoding, and optionally
// 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
// 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"
),
};
// 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 {
// last morsel is at `morsels_in_leftover` - 1
return Err(DecodeError::InvalidLastSymbol(
start_of_leftovers + morsels_in_leftover - 1,
last_symbol,
));
}
// 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;
output_index += 1;
leftover_bits_appended_to_buf += 8;
}
Ok(output_index)
}

View File

@ -0,0 +1,349 @@
//! Provides the [GeneralPurpose] engine and associated config types.
use crate::{
alphabet,
alphabet::Alphabet,
engine::{Config, DecodePaddingMode},
DecodeError,
};
use core::convert::TryInto;
mod decode;
pub(crate) mod decode_suffix;
pub use decode::GeneralPurposeEstimate;
pub(crate) const INVALID_VALUE: u8 = 255;
/// A general-purpose base64 engine.
///
/// - It uses no vector CPU instructions, so it will work on any system.
/// - It is reasonably fast (~2-3GiB/s).
/// - It is not constant-time, though, so it is vulnerable to timing side-channel attacks. For loading cryptographic keys, etc, it is suggested to use the forthcoming constant-time implementation.
pub struct GeneralPurpose {
encode_table: [u8; 64],
decode_table: [u8; 256],
config: GeneralPurposeConfig,
}
impl GeneralPurpose {
/// Create a `GeneralPurpose` engine from an [Alphabet].
///
/// While not very expensive to initialize, ideally these should be cached
/// if the engine will be used repeatedly.
pub const fn new(alphabet: &Alphabet, config: GeneralPurposeConfig) -> Self {
Self {
encode_table: encode_table(alphabet),
decode_table: decode_table(alphabet),
config,
}
}
}
impl super::Engine for GeneralPurpose {
type Config = GeneralPurposeConfig;
type DecodeEstimate = GeneralPurposeEstimate;
fn internal_encode(&self, input: &[u8], output: &mut [u8]) -> usize {
let mut input_index: usize = 0;
const BLOCKS_PER_FAST_LOOP: usize = 4;
const LOW_SIX_BITS: u64 = 0x3F;
// we read 8 bytes at a time (u64) but only actually consume 6 of those bytes. Thus, we need
// 2 trailing bytes to be available to read..
let last_fast_index = input.len().saturating_sub(BLOCKS_PER_FAST_LOOP * 6 + 2);
let mut output_index = 0;
if last_fast_index > 0 {
while input_index <= last_fast_index {
// Major performance wins from letting the optimizer do the bounds check once, mostly
// on the output side
let input_chunk =
&input[input_index..(input_index + (BLOCKS_PER_FAST_LOOP * 6 + 2))];
let output_chunk =
&mut output[output_index..(output_index + BLOCKS_PER_FAST_LOOP * 8)];
// Hand-unrolling for 32 vs 16 or 8 bytes produces yields performance about equivalent
// to unsafe pointer code on a Xeon E5-1650v3. 64 byte unrolling was slightly better for
// large inputs but significantly worse for 50-byte input, unsurprisingly. I suspect
// that it's a not uncommon use case to encode smallish chunks of data (e.g. a 64-byte
// SHA-512 digest), so it would be nice if that fit in the unrolled loop at least once.
// Plus, single-digit percentage performance differences might well be quite different
// on different hardware.
let input_u64 = read_u64(&input_chunk[0..]);
output_chunk[0] = self.encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize];
output_chunk[1] = self.encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize];
output_chunk[2] = self.encode_table[((input_u64 >> 46) & LOW_SIX_BITS) as usize];
output_chunk[3] = self.encode_table[((input_u64 >> 40) & LOW_SIX_BITS) as usize];
output_chunk[4] = self.encode_table[((input_u64 >> 34) & LOW_SIX_BITS) as usize];
output_chunk[5] = self.encode_table[((input_u64 >> 28) & LOW_SIX_BITS) as usize];
output_chunk[6] = self.encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize];
output_chunk[7] = self.encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize];
let input_u64 = read_u64(&input_chunk[6..]);
output_chunk[8] = self.encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize];
output_chunk[9] = self.encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize];
output_chunk[10] = self.encode_table[((input_u64 >> 46) & LOW_SIX_BITS) as usize];
output_chunk[11] = self.encode_table[((input_u64 >> 40) & LOW_SIX_BITS) as usize];
output_chunk[12] = self.encode_table[((input_u64 >> 34) & LOW_SIX_BITS) as usize];
output_chunk[13] = self.encode_table[((input_u64 >> 28) & LOW_SIX_BITS) as usize];
output_chunk[14] = self.encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize];
output_chunk[15] = self.encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize];
let input_u64 = read_u64(&input_chunk[12..]);
output_chunk[16] = self.encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize];
output_chunk[17] = self.encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize];
output_chunk[18] = self.encode_table[((input_u64 >> 46) & LOW_SIX_BITS) as usize];
output_chunk[19] = self.encode_table[((input_u64 >> 40) & LOW_SIX_BITS) as usize];
output_chunk[20] = self.encode_table[((input_u64 >> 34) & LOW_SIX_BITS) as usize];
output_chunk[21] = self.encode_table[((input_u64 >> 28) & LOW_SIX_BITS) as usize];
output_chunk[22] = self.encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize];
output_chunk[23] = self.encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize];
let input_u64 = read_u64(&input_chunk[18..]);
output_chunk[24] = self.encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize];
output_chunk[25] = self.encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize];
output_chunk[26] = self.encode_table[((input_u64 >> 46) & LOW_SIX_BITS) as usize];
output_chunk[27] = self.encode_table[((input_u64 >> 40) & LOW_SIX_BITS) as usize];
output_chunk[28] = self.encode_table[((input_u64 >> 34) & LOW_SIX_BITS) as usize];
output_chunk[29] = self.encode_table[((input_u64 >> 28) & LOW_SIX_BITS) as usize];
output_chunk[30] = self.encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize];
output_chunk[31] = self.encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize];
output_index += BLOCKS_PER_FAST_LOOP * 8;
input_index += BLOCKS_PER_FAST_LOOP * 6;
}
}
// Encode what's left after the fast loop.
const LOW_SIX_BITS_U8: u8 = 0x3F;
let rem = input.len() % 3;
let start_of_rem = input.len() - rem;
// start at the first index not handled by fast loop, which may be 0.
while input_index < start_of_rem {
let input_chunk = &input[input_index..(input_index + 3)];
let output_chunk = &mut output[output_index..(output_index + 4)];
output_chunk[0] = self.encode_table[(input_chunk[0] >> 2) as usize];
output_chunk[1] = self.encode_table
[((input_chunk[0] << 4 | input_chunk[1] >> 4) & LOW_SIX_BITS_U8) as usize];
output_chunk[2] = self.encode_table
[((input_chunk[1] << 2 | input_chunk[2] >> 6) & LOW_SIX_BITS_U8) as usize];
output_chunk[3] = self.encode_table[(input_chunk[2] & LOW_SIX_BITS_U8) as usize];
input_index += 3;
output_index += 4;
}
if rem == 2 {
output[output_index] = self.encode_table[(input[start_of_rem] >> 2) as usize];
output[output_index + 1] =
self.encode_table[((input[start_of_rem] << 4 | input[start_of_rem + 1] >> 4)
& LOW_SIX_BITS_U8) as usize];
output[output_index + 2] =
self.encode_table[((input[start_of_rem + 1] << 2) & LOW_SIX_BITS_U8) as usize];
output_index += 3;
} else if rem == 1 {
output[output_index] = self.encode_table[(input[start_of_rem] >> 2) as usize];
output[output_index + 1] =
self.encode_table[((input[start_of_rem] << 4) & LOW_SIX_BITS_U8) as usize];
output_index += 2;
}
output_index
}
fn internal_decoded_len_estimate(&self, input_len: usize) -> Self::DecodeEstimate {
GeneralPurposeEstimate::new(input_len)
}
fn internal_decode(
&self,
input: &[u8],
output: &mut [u8],
estimate: Self::DecodeEstimate,
) -> Result<usize, DecodeError> {
decode::decode_helper(
input,
estimate,
output,
&self.decode_table,
self.config.decode_allow_trailing_bits,
self.config.decode_padding_mode,
)
}
fn config(&self) -> &Self::Config {
&self.config
}
}
/// Returns a table mapping a 6-bit index to the ASCII byte encoding of the index
pub(crate) const fn encode_table(alphabet: &Alphabet) -> [u8; 64] {
// the encode table is just the alphabet:
// 6-bit index lookup -> printable byte
let mut encode_table = [0_u8; 64];
{
let mut index = 0;
while index < 64 {
encode_table[index] = alphabet.symbols[index];
index += 1;
}
}
encode_table
}
/// Returns a table mapping base64 bytes as the lookup index to either:
/// - [INVALID_VALUE] for bytes that aren't members of the alphabet
/// - a byte whose lower 6 bits are the value that was encoded into the index byte
pub(crate) const fn decode_table(alphabet: &Alphabet) -> [u8; 256] {
let mut decode_table = [INVALID_VALUE; 256];
// Since the table is full of `INVALID_VALUE` already, we only need to overwrite
// the parts that are valid.
let mut index = 0;
while index < 64 {
// The index in the alphabet is the 6-bit value we care about.
// Since the index is in 0-63, it is safe to cast to u8.
decode_table[alphabet.symbols[index] as usize] = index as u8;
index += 1;
}
decode_table
}
#[inline]
fn read_u64(s: &[u8]) -> u64 {
u64::from_be_bytes(s[..8].try_into().unwrap())
}
/// Contains configuration parameters for base64 encoding and decoding.
///
/// ```
/// # use base64::engine::GeneralPurposeConfig;
/// let config = GeneralPurposeConfig::new()
/// .with_encode_padding(false);
/// // further customize using `.with_*` methods as needed
/// ```
///
/// The constants [PAD] and [NO_PAD] cover most use cases.
///
/// To specify the characters used, see [Alphabet].
#[derive(Clone, Copy, Debug)]
pub struct GeneralPurposeConfig {
encode_padding: bool,
decode_allow_trailing_bits: bool,
decode_padding_mode: DecodePaddingMode,
}
impl GeneralPurposeConfig {
/// Create a new config with `padding` = `true`, `decode_allow_trailing_bits` = `false`, and
/// `decode_padding_mode = DecodePaddingMode::RequireCanonicalPadding`.
///
/// This probably matches most people's expectations, but consider disabling padding to save
/// a few bytes unless you specifically need it for compatibility with some legacy system.
pub const fn new() -> Self {
Self {
// RFC states that padding must be applied by default
encode_padding: true,
decode_allow_trailing_bits: false,
decode_padding_mode: DecodePaddingMode::RequireCanonical,
}
}
/// Create a new config based on `self` with an updated `padding` setting.
///
/// If `padding` is `true`, encoding will append either 1 or 2 `=` padding characters as needed
/// to produce an output whose length is a multiple of 4.
///
/// Padding is not needed for correct decoding and only serves to waste bytes, but it's in the
/// [spec](https://datatracker.ietf.org/doc/html/rfc4648#section-3.2).
///
/// For new applications, consider not using padding if the decoders you're using don't require
/// padding to be present.
pub const fn with_encode_padding(self, padding: bool) -> Self {
Self {
encode_padding: padding,
..self
}
}
/// Create a new config based on `self` with an updated `decode_allow_trailing_bits` setting.
///
/// Most users will not need to configure this. It's useful if you need to decode base64
/// produced by a buggy encoder that has bits set in the unused space on the last base64
/// character as per [forgiving-base64 decode](https://infra.spec.whatwg.org/#forgiving-base64-decode).
/// If invalid trailing bits are present and this is `true`, those bits will
/// be silently ignored, else `DecodeError::InvalidLastSymbol` will be emitted.
pub const fn with_decode_allow_trailing_bits(self, allow: bool) -> Self {
Self {
decode_allow_trailing_bits: allow,
..self
}
}
/// Create a new config based on `self` with an updated `decode_padding_mode` setting.
///
/// Padding is not useful in terms of representing encoded data -- it makes no difference to
/// the decoder if padding is present or not, so if you have some un-padded input to decode, it
/// is perfectly fine to use `DecodePaddingMode::Indifferent` to prevent errors from being
/// emitted.
///
/// However, since in practice
/// [people who learned nothing from BER vs DER seem to expect base64 to have one canonical encoding](https://eprint.iacr.org/2022/361),
/// the default setting is the stricter `DecodePaddingMode::RequireCanonicalPadding`.
///
/// Or, if "canonical" in your circumstance means _no_ padding rather than padding to the
/// next multiple of four, there's `DecodePaddingMode::RequireNoPadding`.
pub const fn with_decode_padding_mode(self, mode: DecodePaddingMode) -> Self {
Self {
decode_padding_mode: mode,
..self
}
}
}
impl Default for GeneralPurposeConfig {
/// Delegates to [GeneralPurposeConfig::new].
fn default() -> Self {
Self::new()
}
}
impl Config for GeneralPurposeConfig {
fn encode_padding(&self) -> bool {
self.encode_padding
}
}
/// A [GeneralPurpose] engine using the [alphabet::STANDARD] base64 alphabet and [PAD] config.
pub const STANDARD: GeneralPurpose = GeneralPurpose::new(&alphabet::STANDARD, PAD);
/// A [GeneralPurpose] engine using the [alphabet::STANDARD] base64 alphabet and [NO_PAD] config.
pub const STANDARD_NO_PAD: GeneralPurpose = GeneralPurpose::new(&alphabet::STANDARD, NO_PAD);
/// A [GeneralPurpose] engine using the [alphabet::URL_SAFE] base64 alphabet and [PAD] config.
pub const URL_SAFE: GeneralPurpose = GeneralPurpose::new(&alphabet::URL_SAFE, PAD);
/// A [GeneralPurpose] engine using the [alphabet::URL_SAFE] base64 alphabet and [NO_PAD] config.
pub const URL_SAFE_NO_PAD: GeneralPurpose = GeneralPurpose::new(&alphabet::URL_SAFE, NO_PAD);
/// Include padding bytes when encoding, and require that they be present when decoding.
///
/// This is the standard per the base64 RFC, but consider using [NO_PAD] instead as padding serves
/// little purpose in practice.
pub const PAD: GeneralPurposeConfig = GeneralPurposeConfig::new();
/// Don't add padding when encoding, and require no padding when decoding.
pub const NO_PAD: GeneralPurposeConfig = GeneralPurposeConfig::new()
.with_encode_padding(false)
.with_decode_padding_mode(DecodePaddingMode::RequireNone);

410
zeroidc/vendor/base64/src/engine/mod.rs vendored Normal file
View File

@ -0,0 +1,410 @@
//! Provides the [Engine] abstraction and out of the box implementations.
#[cfg(any(feature = "alloc", feature = "std", test))]
use crate::chunked_encoder;
use crate::{
encode::{encode_with_padding, EncodeSliceError},
encoded_len, DecodeError, DecodeSliceError,
};
#[cfg(any(feature = "alloc", feature = "std", test))]
use alloc::vec::Vec;
#[cfg(any(feature = "alloc", feature = "std", test))]
use alloc::{string::String, vec};
pub mod general_purpose;
#[cfg(test)]
mod naive;
#[cfg(test)]
mod tests;
pub use general_purpose::{GeneralPurpose, GeneralPurposeConfig};
/// An `Engine` provides low-level encoding and decoding operations that all other higher-level parts of the API use. Users of the library will generally not need to implement this.
///
/// Different implementations offer different characteristics. The library currently ships with
/// [GeneralPurpose] that offers good speed and works on any CPU, with more choices
/// coming later, like a constant-time one when side channel resistance is called for, and vendor-specific vectorized ones for more speed.
///
/// See [general_purpose::STANDARD_NO_PAD] if you just want standard base64. Otherwise, when possible, it's
/// recommended to store the engine in a `const` so that references to it won't pose any lifetime
/// issues, and to avoid repeating the cost of engine setup.
///
/// Since almost nobody will need to implement `Engine`, docs for internal methods are hidden.
// When adding an implementation of Engine, include them in the engine test suite:
// - add an implementation of [engine::tests::EngineWrapper]
// - add the implementation to the `all_engines` macro
// All tests run on all engines listed in the macro.
pub trait Engine: Send + Sync {
/// The config type used by this engine
type Config: Config;
/// The decode estimate used by this engine
type DecodeEstimate: DecodeEstimate;
/// This is not meant to be called directly; it is only for `Engine` implementors.
/// See the other `encode*` functions on this trait.
///
/// Encode the `input` bytes into the `output` buffer based on the mapping in `encode_table`.
///
/// `output` will be long enough to hold the encoded data.
///
/// Returns the number of bytes written.
///
/// No padding should be written; that is handled separately.
///
/// Must not write any bytes into the output slice other than the encoded data.
#[doc(hidden)]
fn internal_encode(&self, input: &[u8], output: &mut [u8]) -> usize;
/// This is not meant to be called directly; it is only for `Engine` implementors.
///
/// As an optimization to prevent the decoded length from being calculated twice, it is
/// sometimes helpful to have a conservative estimate of the decoded size before doing the
/// decoding, so this calculation is done separately and passed to [Engine::decode()] as needed.
///
/// # Panics
///
/// Panics if decoded length estimation overflows.
#[doc(hidden)]
fn internal_decoded_len_estimate(&self, input_len: usize) -> Self::DecodeEstimate;
/// This is not meant to be called directly; it is only for `Engine` implementors.
/// See the other `decode*` functions on this trait.
///
/// Decode `input` base64 bytes into the `output` buffer.
///
/// `decode_estimate` is the result of [Engine::internal_decoded_len_estimate()], which is passed in to avoid
/// calculating it again (expensive on short inputs).`
///
/// Returns the number of bytes written to `output`.
///
/// Each complete 4-byte chunk of encoded data decodes to 3 bytes of decoded data, but this
/// function must also handle the final possibly partial chunk.
/// If the input length is not a multiple of 4, or uses padding bytes to reach a multiple of 4,
/// the trailing 2 or 3 bytes must decode to 1 or 2 bytes, respectively, as per the
/// [RFC](https://tools.ietf.org/html/rfc4648#section-3.5).
///
/// Decoding must not write any bytes into the output slice other than the decoded data.
///
/// Non-canonical trailing bits in the final tokens or non-canonical padding must be reported as
/// errors unless the engine is configured otherwise.
///
/// # Panics
///
/// Panics if `output` is too small.
#[doc(hidden)]
fn internal_decode(
&self,
input: &[u8],
output: &mut [u8],
decode_estimate: Self::DecodeEstimate,
) -> Result<usize, DecodeError>;
/// Returns the config for this engine.
fn config(&self) -> &Self::Config;
/// Encode arbitrary octets as base64 using the provided `Engine`.
/// Returns a `String`.
///
/// # Example
///
/// ```rust
/// use base64::{Engine as _, engine::{self, general_purpose}, alphabet};
///
/// let b64 = general_purpose::STANDARD.encode(b"hello world~");
/// println!("{}", b64);
///
/// const CUSTOM_ENGINE: engine::GeneralPurpose =
/// engine::GeneralPurpose::new(&alphabet::URL_SAFE, general_purpose::NO_PAD);
///
/// let b64_url = CUSTOM_ENGINE.encode(b"hello internet~");
#[cfg(any(feature = "alloc", feature = "std", test))]
fn encode<T: AsRef<[u8]>>(&self, input: T) -> String {
let encoded_size = encoded_len(input.as_ref().len(), self.config().encode_padding())
.expect("integer overflow when calculating buffer size");
let mut buf = vec![0; encoded_size];
encode_with_padding(input.as_ref(), &mut buf[..], self, encoded_size);
String::from_utf8(buf).expect("Invalid UTF8")
}
/// Encode arbitrary octets as base64 into a supplied `String`.
/// Writes into the supplied `String`, which may allocate if its internal buffer isn't big enough.
///
/// # Example
///
/// ```rust
/// use base64::{Engine as _, engine::{self, general_purpose}, alphabet};
/// const CUSTOM_ENGINE: engine::GeneralPurpose =
/// engine::GeneralPurpose::new(&alphabet::URL_SAFE, general_purpose::NO_PAD);
///
/// fn main() {
/// let mut buf = String::new();
/// general_purpose::STANDARD.encode_string(b"hello world~", &mut buf);
/// println!("{}", buf);
///
/// buf.clear();
/// CUSTOM_ENGINE.encode_string(b"hello internet~", &mut buf);
/// println!("{}", buf);
/// }
/// ```
#[cfg(any(feature = "alloc", feature = "std", test))]
fn encode_string<T: AsRef<[u8]>>(&self, input: T, output_buf: &mut String) {
let input_bytes = input.as_ref();
{
let mut sink = chunked_encoder::StringSink::new(output_buf);
chunked_encoder::ChunkedEncoder::new(self)
.encode(input_bytes, &mut sink)
.expect("Writing to a String shouldn't fail");
}
}
/// Encode arbitrary octets as base64 into a supplied slice.
/// Writes into the supplied output buffer.
///
/// This is useful if you wish to avoid allocation entirely (e.g. encoding into a stack-resident
/// or statically-allocated buffer).
///
/// # Example
///
/// ```rust
/// use base64::{Engine as _, engine::general_purpose};
/// let s = b"hello internet!";
/// let mut buf = Vec::new();
/// // make sure we'll have a slice big enough for base64 + padding
/// buf.resize(s.len() * 4 / 3 + 4, 0);
///
/// let bytes_written = general_purpose::STANDARD.encode_slice(s, &mut buf).unwrap();
///
/// // shorten our vec down to just what was written
/// buf.truncate(bytes_written);
///
/// assert_eq!(s, general_purpose::STANDARD.decode(&buf).unwrap().as_slice());
/// ```
fn encode_slice<T: AsRef<[u8]>>(
&self,
input: T,
output_buf: &mut [u8],
) -> Result<usize, EncodeSliceError> {
let input_bytes = input.as_ref();
let encoded_size = encoded_len(input_bytes.len(), self.config().encode_padding())
.expect("usize overflow when calculating buffer size");
if output_buf.len() < encoded_size {
return Err(EncodeSliceError::OutputSliceTooSmall);
}
let b64_output = &mut output_buf[0..encoded_size];
encode_with_padding(input_bytes, b64_output, self, encoded_size);
Ok(encoded_size)
}
/// Decode from string reference as octets using the specified [Engine].
/// Returns a `Result` containing a `Vec<u8>`.
///
/// # Example
///
/// ```rust
/// use base64::{Engine as _, alphabet, engine::{self, general_purpose}};
///
/// let bytes = general_purpose::STANDARD
/// .decode("aGVsbG8gd29ybGR+Cg==").unwrap();
/// println!("{:?}", bytes);
///
/// // custom engine setup
/// let bytes_url = engine::GeneralPurpose::new(
/// &alphabet::URL_SAFE,
/// general_purpose::NO_PAD)
/// .decode("aGVsbG8gaW50ZXJuZXR-Cg").unwrap();
/// println!("{:?}", bytes_url);
/// ```
///
/// # Panics
///
/// Panics if decoded length estimation overflows.
/// This would happen for sizes within a few bytes of the maximum value of `usize`.
#[cfg(any(feature = "alloc", feature = "std", test))]
fn decode<T: AsRef<[u8]>>(&self, input: T) -> Result<Vec<u8>, DecodeError> {
let input_bytes = input.as_ref();
let estimate = self.internal_decoded_len_estimate(input_bytes.len());
let mut buffer = vec![0; estimate.decoded_len_estimate()];
let bytes_written = self.internal_decode(input_bytes, &mut buffer, estimate)?;
buffer.truncate(bytes_written);
Ok(buffer)
}
/// Decode from string reference as octets.
/// Writes into the supplied `Vec`, which may allocate if its internal buffer isn't big enough.
/// Returns a `Result` containing an empty tuple, aka `()`.
///
/// # Example
///
/// ```rust
/// use base64::{Engine as _, alphabet, engine::{self, general_purpose}};
/// const CUSTOM_ENGINE: engine::GeneralPurpose =
/// engine::GeneralPurpose::new(&alphabet::URL_SAFE, general_purpose::PAD);
///
/// fn main() {
/// use base64::Engine;
/// let mut buffer = Vec::<u8>::new();
/// // with the default engine
/// general_purpose::STANDARD
/// .decode_vec("aGVsbG8gd29ybGR+Cg==", &mut buffer,).unwrap();
/// println!("{:?}", buffer);
///
/// buffer.clear();
///
/// // with a custom engine
/// CUSTOM_ENGINE.decode_vec(
/// "aGVsbG8gaW50ZXJuZXR-Cg==",
/// &mut buffer,
/// ).unwrap();
/// println!("{:?}", buffer);
/// }
/// ```
///
/// # Panics
///
/// Panics if decoded length estimation overflows.
/// This would happen for sizes within a few bytes of the maximum value of `usize`.
#[cfg(any(feature = "alloc", feature = "std", test))]
fn decode_vec<T: AsRef<[u8]>>(
&self,
input: T,
buffer: &mut Vec<u8>,
) -> Result<(), DecodeError> {
let input_bytes = input.as_ref();
let starting_output_len = buffer.len();
let estimate = self.internal_decoded_len_estimate(input_bytes.len());
let total_len_estimate = estimate
.decoded_len_estimate()
.checked_add(starting_output_len)
.expect("Overflow when calculating output buffer length");
buffer.resize(total_len_estimate, 0);
let buffer_slice = &mut buffer.as_mut_slice()[starting_output_len..];
let bytes_written = self.internal_decode(input_bytes, buffer_slice, estimate)?;
buffer.truncate(starting_output_len + bytes_written);
Ok(())
}
/// Decode the input into the provided output slice.
///
/// Returns an error if `output` is smaller than the estimated decoded length.
///
/// This will not write any bytes past exactly what is decoded (no stray garbage bytes at the end).
///
/// See [crate::decoded_len_estimate] for calculating buffer sizes.
///
/// See [Engine::decode_slice_unchecked] for a version that panics instead of returning an error
/// if the output buffer is too small.
///
/// # Panics
///
/// Panics if decoded length estimation overflows.
/// This would happen for sizes within a few bytes of the maximum value of `usize`.
fn decode_slice<T: AsRef<[u8]>>(
&self,
input: T,
output: &mut [u8],
) -> Result<usize, DecodeSliceError> {
let input_bytes = input.as_ref();
let estimate = self.internal_decoded_len_estimate(input_bytes.len());
if output.len() < estimate.decoded_len_estimate() {
return Err(DecodeSliceError::OutputSliceTooSmall);
}
self.internal_decode(input_bytes, output, estimate)
.map_err(|e| e.into())
}
/// Decode the input into the provided output slice.
///
/// This will not write any bytes past exactly what is decoded (no stray garbage bytes at the end).
///
/// See [crate::decoded_len_estimate] for calculating buffer sizes.
///
/// See [Engine::decode_slice] for a version that returns an error instead of panicking if the output
/// buffer is too small.
///
/// # Panics
///
/// Panics if decoded length estimation overflows.
/// This would happen for sizes within a few bytes of the maximum value of `usize`.
///
/// Panics if the provided output buffer is too small for the decoded data.
fn decode_slice_unchecked<T: AsRef<[u8]>>(
&self,
input: T,
output: &mut [u8],
) -> Result<usize, DecodeError> {
let input_bytes = input.as_ref();
self.internal_decode(
input_bytes,
output,
self.internal_decoded_len_estimate(input_bytes.len()),
)
}
}
/// The minimal level of configuration that engines must support.
pub trait Config {
/// Returns `true` if padding should be added after the encoded output.
///
/// Padding is added outside the engine's encode() since the engine may be used
/// to encode only a chunk of the overall output, so it can't always know when
/// the output is "done" and would therefore need padding (if configured).
// It could be provided as a separate parameter when encoding, but that feels like
// leaking an implementation detail to the user, and it's hopefully more convenient
// to have to only pass one thing (the engine) to any part of the API.
fn encode_padding(&self) -> bool;
}
/// The decode estimate used by an engine implementation. Users do not need to interact with this;
/// it is only for engine implementors.
///
/// Implementors may store relevant data here when constructing this to avoid having to calculate
/// them again during actual decoding.
pub trait DecodeEstimate {
/// Returns a conservative (err on the side of too big) estimate of the decoded length to use
/// for pre-allocating buffers, etc.
///
/// The estimate must be no larger than the next largest complete triple of decoded bytes.
/// That is, the final quad of tokens to decode may be assumed to be complete with no padding.
///
/// # Panics
///
/// Panics if decoded length estimation overflows.
/// This would happen for sizes within a few bytes of the maximum value of `usize`.
fn decoded_len_estimate(&self) -> usize;
}
/// Controls how pad bytes are handled when decoding.
///
/// Each [Engine] must support at least the behavior indicated by
/// [DecodePaddingMode::RequireCanonical], and may support other modes.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DecodePaddingMode {
/// Canonical padding is allowed, but any fewer padding bytes than that is also allowed.
Indifferent,
/// Padding must be canonical (0, 1, or 2 `=` as needed to produce a 4 byte suffix).
RequireCanonical,
/// Padding must be absent -- for when you want predictable padding, without any wasted bytes.
RequireNone,
}

View File

@ -0,0 +1,219 @@
use crate::{
alphabet::Alphabet,
engine::{
general_purpose::{self, decode_table, encode_table},
Config, DecodeEstimate, DecodePaddingMode, Engine,
},
DecodeError, PAD_BYTE,
};
use alloc::ops::BitOr;
use std::ops::{BitAnd, Shl, Shr};
/// Comparatively simple implementation that can be used as something to compare against in tests
pub struct Naive {
encode_table: [u8; 64],
decode_table: [u8; 256],
config: NaiveConfig,
}
impl Naive {
const ENCODE_INPUT_CHUNK_SIZE: usize = 3;
const DECODE_INPUT_CHUNK_SIZE: usize = 4;
pub const fn new(alphabet: &Alphabet, config: NaiveConfig) -> Self {
Self {
encode_table: encode_table(alphabet),
decode_table: decode_table(alphabet),
config,
}
}
fn decode_byte_into_u32(&self, offset: usize, byte: u8) -> Result<u32, DecodeError> {
let decoded = self.decode_table[byte as usize];
if decoded == general_purpose::INVALID_VALUE {
return Err(DecodeError::InvalidByte(offset, byte));
}
Ok(decoded as u32)
}
}
impl Engine for Naive {
type Config = NaiveConfig;
type DecodeEstimate = NaiveEstimate;
fn internal_encode(&self, input: &[u8], output: &mut [u8]) -> usize {
// complete chunks first
const LOW_SIX_BITS: u32 = 0x3F;
let rem = input.len() % Self::ENCODE_INPUT_CHUNK_SIZE;
// will never underflow
let complete_chunk_len = input.len() - rem;
let mut input_index = 0_usize;
let mut output_index = 0_usize;
if let Some(last_complete_chunk_index) =
complete_chunk_len.checked_sub(Self::ENCODE_INPUT_CHUNK_SIZE)
{
while input_index <= last_complete_chunk_index {
let chunk = &input[input_index..input_index + Self::ENCODE_INPUT_CHUNK_SIZE];
// populate low 24 bits from 3 bytes
let chunk_int: u32 =
(chunk[0] as u32).shl(16) | (chunk[1] as u32).shl(8) | (chunk[2] as u32);
// encode 4x 6-bit output bytes
output[output_index] = self.encode_table[chunk_int.shr(18) as usize];
output[output_index + 1] =
self.encode_table[chunk_int.shr(12_u8).bitand(LOW_SIX_BITS) as usize];
output[output_index + 2] =
self.encode_table[chunk_int.shr(6_u8).bitand(LOW_SIX_BITS) as usize];
output[output_index + 3] =
self.encode_table[chunk_int.bitand(LOW_SIX_BITS) as usize];
input_index += Self::ENCODE_INPUT_CHUNK_SIZE;
output_index += 4;
}
}
// then leftovers
if rem == 2 {
let chunk = &input[input_index..input_index + 2];
// high six bits of chunk[0]
output[output_index] = self.encode_table[chunk[0].shr(2) as usize];
// bottom 2 bits of [0], high 4 bits of [1]
output[output_index + 1] =
self.encode_table[(chunk[0].shl(4_u8).bitor(chunk[1].shr(4_u8)) as u32)
.bitand(LOW_SIX_BITS) as usize];
// bottom 4 bits of [1], with the 2 bottom bits as zero
output[output_index + 2] =
self.encode_table[(chunk[1].shl(2_u8) as u32).bitand(LOW_SIX_BITS) as usize];
output_index += 3;
} else if rem == 1 {
let byte = input[input_index];
output[output_index] = self.encode_table[byte.shr(2) as usize];
output[output_index + 1] =
self.encode_table[(byte.shl(4_u8) as u32).bitand(LOW_SIX_BITS) as usize];
output_index += 2;
}
output_index
}
fn internal_decoded_len_estimate(&self, input_len: usize) -> Self::DecodeEstimate {
NaiveEstimate::new(input_len)
}
fn internal_decode(
&self,
input: &[u8],
output: &mut [u8],
estimate: Self::DecodeEstimate,
) -> Result<usize, 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));
}
}
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
};
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])?;
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;
}
}
general_purpose::decode_suffix::decode_suffix(
input,
input_index,
output,
output_index,
&self.decode_table,
self.config.decode_allow_trailing_bits,
self.config.decode_padding_mode,
)
}
fn config(&self) -> &Self::Config {
&self.config
}
}
pub struct NaiveEstimate {
/// remainder from dividing input by `Naive::DECODE_CHUNK_SIZE`
rem: usize,
/// Length of input that is in complete `Naive::DECODE_CHUNK_SIZE`-length chunks
complete_chunk_len: usize,
}
impl NaiveEstimate {
fn new(input_len: usize) -> Self {
let rem = input_len % Naive::DECODE_INPUT_CHUNK_SIZE;
let complete_chunk_len = input_len - rem;
Self {
rem,
complete_chunk_len,
}
}
}
impl DecodeEstimate for NaiveEstimate {
fn decoded_len_estimate(&self) -> usize {
((self.complete_chunk_len / 4) + ((self.rem > 0) as usize)) * 3
}
}
#[derive(Clone, Copy, Debug)]
pub struct NaiveConfig {
pub encode_padding: bool,
pub decode_allow_trailing_bits: bool,
pub decode_padding_mode: DecodePaddingMode,
}
impl Config for NaiveConfig {
fn encode_padding(&self) -> bool {
self.encode_padding
}
}

1430
zeroidc/vendor/base64/src/engine/tests.rs vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,61 +1,123 @@
//! # Configs //! # Getting started
//! //!
//! There isn't just one type of Base64; that would be too simple. You need to choose a character //! 1. Perhaps one of the preconfigured engines in [engine::general_purpose] will suit, e.g.
//! set (standard, URL-safe, etc) and padding suffix (yes/no). //! [engine::general_purpose::STANDARD_NO_PAD].
//! The `Config` struct encapsulates this info. There are some common configs included: `STANDARD`, //! - These are re-exported in [prelude] with a `BASE64_` prefix for those who prefer to
//! `URL_SAFE`, etc. You can also make your own `Config` if needed. //! `use base64::prelude::*` or equivalent, e.g. [prelude::BASE64_STANDARD_NO_PAD]
//! 1. If not, choose which alphabet you want. Most usage will want [alphabet::STANDARD] or [alphabet::URL_SAFE].
//! 1. Choose which [Engine] implementation you want. For the moment there is only one: [engine::GeneralPurpose].
//! 1. Configure the engine appropriately using the engine's `Config` type.
//! - This is where you'll select whether to add padding (when encoding) or expect it (when
//! decoding). If given the choice, prefer no padding.
//! 1. Build the engine using the selected alphabet and config.
//! //!
//! The functions that don't have `config` in the name (e.g. `encode()` and `decode()`) use the //! For more detail, see below.
//! `STANDARD` config .
//! //!
//! The functions that write to a slice (the ones that end in `_slice`) are generally the fastest //! ## Alphabets
//! because they don't need to resize anything. If it fits in your workflow and you care about //!
//! performance, keep using the same buffer (growing as need be) and use the `_slice` methods for //! An [alphabet::Alphabet] defines what ASCII symbols are used to encode to or decode from.
//! the best performance. //!
//! Constants in [alphabet] like [alphabet::STANDARD] or [alphabet::URL_SAFE] provide commonly used
//! alphabets, but you can also build your own custom [alphabet::Alphabet] if needed.
//!
//! ## Engines
//!
//! Once you have an `Alphabet`, you can pick which `Engine` you want. A few parts of the public
//! API provide a default, but otherwise the user must provide an `Engine` to use.
//!
//! See [Engine] for more.
//!
//! ## Config
//!
//! In addition to an `Alphabet`, constructing an `Engine` also requires an [engine::Config]. Each
//! `Engine` has a corresponding `Config` implementation since different `Engine`s may offer different
//! levels of configurability.
//! //!
//! # Encoding //! # Encoding
//! //!
//! Several different encoding functions are available to you depending on your desire for //! Several different encoding methods on [Engine] are available to you depending on your desire for
//! convenience vs performance. //! convenience vs performance.
//! //!
//! | Function | Output | Allocates | //! | Method | Output | Allocates |
//! | ----------------------- | ---------------------------- | ------------------------------ | //! | ------------------------ | ---------------------------- | ------------------------------ |
//! | `encode` | Returns a new `String` | Always | //! | [Engine::encode] | Returns a new `String` | Always |
//! | `encode_config` | Returns a new `String` | Always | //! | [Engine::encode_string] | Appends to provided `String` | Only if `String` needs to grow |
//! | `encode_config_buf` | Appends to provided `String` | Only if `String` needs to grow | //! | [Engine::encode_slice] | Writes to provided `&[u8]` | Never - fastest |
//! | `encode_config_slice` | Writes to provided `&[u8]` | Never |
//! //!
//! All of the encoding functions that take a `Config` will pad as per the config. //! All of the encoding methods will pad as per the engine's config.
//! //!
//! # Decoding //! # Decoding
//! //!
//! Just as for encoding, there are different decoding functions available. //! Just as for encoding, there are different decoding methods available.
//! //!
//! | Function | Output | Allocates | //! | Method | Output | Allocates |
//! | ----------------------- | ----------------------------- | ------------------------------ | //! | ------------------------ | ----------------------------- | ------------------------------ |
//! | `decode` | Returns a new `Vec<u8>` | Always | //! | [Engine::decode] | Returns a new `Vec<u8>` | Always |
//! | `decode_config` | Returns a new `Vec<u8>` | Always | //! | [Engine::decode_vec] | Appends to provided `Vec<u8>` | Only if `Vec` needs to grow |
//! | `decode_config_buf` | Appends to provided `Vec<u8>` | Only if `Vec` needs to grow | //! | [Engine::decode_slice] | Writes to provided `&[u8]` | Never - fastest |
//! | `decode_config_slice` | Writes to provided `&[u8]` | Never |
//! //!
//! Unlike encoding, where all possible input is valid, decoding can fail (see `DecodeError`). //! Unlike encoding, where all possible input is valid, decoding can fail (see [DecodeError]).
//! //!
//! Input can be invalid because it has invalid characters or invalid padding. (No padding at all is //! Input can be invalid because it has invalid characters or invalid padding. The nature of how
//! valid, but excess padding is not.) Whitespace in the input is invalid. //! padding is checked depends on the engine's config.
//! Whitespace in the input is invalid, just like any other non-base64 byte.
//! //!
//! # `Read` and `Write` //! # `Read` and `Write`
//! //!
//! To map a `Read` of b64 bytes to the decoded bytes, wrap a reader (file, network socket, etc) //! To decode a [std::io::Read] of b64 bytes, wrap a reader (file, network socket, etc) with
//! with `base64::read::DecoderReader`. To write raw bytes and have them b64 encoded on the fly, //! [read::DecoderReader].
//! wrap a writer with `base64::write::EncoderWriter`. There is some performance overhead (15% or //!
//! so) because of the necessary buffer shuffling -- still fast enough that almost nobody cares. //! To write raw bytes and have them b64 encoded on the fly, wrap a [std::io::Write] with
//! Also, these implementations do not heap allocate. //! [write::EncoderWriter].
//!
//! There is some performance overhead (15% or so) because of the necessary buffer shuffling --
//! still fast enough that almost nobody cares. Also, these implementations do not heap allocate.
//!
//! # `Display`
//!
//! See [display] for how to transparently base64 data via a `Display` implementation.
//!
//! # Examples
//!
//! ## Using predefined engines
//!
//! ```
//! use base64::{Engine as _, engine::general_purpose};
//!
//! let orig = b"data";
//! let encoded: String = general_purpose::STANDARD_NO_PAD.encode(orig);
//! assert_eq!("ZGF0YQ", encoded);
//! assert_eq!(orig.as_slice(), &general_purpose::STANDARD_NO_PAD.decode(encoded).unwrap());
//!
//! // or, URL-safe
//! let encoded_url = general_purpose::URL_SAFE_NO_PAD.encode(orig);
//! ```
//!
//! ## Custom alphabet, config, and engine
//!
//! ```
//! use base64::{engine, alphabet, Engine as _};
//!
//! // bizarro-world base64: +/ as the first symbols instead of the last
//! let alphabet =
//! alphabet::Alphabet::new("+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
//! .unwrap();
//!
//! // a very weird config that encodes with padding but requires no padding when decoding...?
//! let crazy_config = engine::GeneralPurposeConfig::new()
//! .with_decode_allow_trailing_bits(true)
//! .with_encode_padding(true)
//! .with_decode_padding_mode(engine::DecodePaddingMode::RequireNone);
//!
//! let crazy_engine = engine::GeneralPurpose::new(&alphabet, crazy_config);
//!
//! let encoded = crazy_engine.encode(b"abc 123");
//!
//! ```
//! //!
//! # Panics //! # Panics
//! //!
//! If length calculations result in overflowing `usize`, a panic will result. //! If length calculations result in overflowing `usize`, a panic will result.
//!
//! The `_slice` flavors of encode or decode will panic if the provided output slice is too small,
#![cfg_attr(feature = "cargo-clippy", allow(clippy::cast_lossless))] #![cfg_attr(feature = "cargo-clippy", allow(clippy::cast_lossless))]
#![deny( #![deny(
@ -69,6 +131,9 @@
warnings warnings
)] )]
#![forbid(unsafe_code)] #![forbid(unsafe_code)]
// Allow globally until https://github.com/rust-lang/rust-clippy/issues/8768 is resolved.
// The desired state is to allow it only for the rstest_reuse import.
#![allow(clippy::single_component_path_imports)]
#![cfg_attr(not(any(feature = "std", test)), no_std)] #![cfg_attr(not(any(feature = "std", test)), no_std)]
#[cfg(all(feature = "alloc", not(any(feature = "std", test))))] #[cfg(all(feature = "alloc", not(any(feature = "std", test))))]
@ -76,170 +141,39 @@ extern crate alloc;
#[cfg(any(feature = "std", test))] #[cfg(any(feature = "std", test))]
extern crate std as alloc; extern crate std as alloc;
// has to be included at top level because of the way rstest_reuse defines its macros
#[cfg(test)]
use rstest_reuse;
mod chunked_encoder; mod chunked_encoder;
pub mod display; pub mod display;
#[cfg(any(feature = "std", test))] #[cfg(any(feature = "std", test))]
pub mod read; pub mod read;
mod tables;
#[cfg(any(feature = "std", test))] #[cfg(any(feature = "std", test))]
pub mod write; pub mod write;
pub mod engine;
pub use engine::Engine;
pub mod alphabet;
mod encode; mod encode;
pub use crate::encode::encode_config_slice; #[allow(deprecated)]
#[cfg(any(feature = "alloc", feature = "std", test))] #[cfg(any(feature = "alloc", feature = "std", test))]
pub use crate::encode::{encode, encode_config, encode_config_buf}; pub use crate::encode::{encode, encode_engine, encode_engine_string};
#[allow(deprecated)]
pub use crate::encode::{encode_engine_slice, encoded_len, EncodeSliceError};
mod decode; mod decode;
#[allow(deprecated)]
#[cfg(any(feature = "alloc", feature = "std", test))] #[cfg(any(feature = "alloc", feature = "std", test))]
pub use crate::decode::{decode, decode_config, decode_config_buf}; pub use crate::decode::{decode, decode_engine, decode_engine_vec};
pub use crate::decode::{decode_config_slice, DecodeError}; #[allow(deprecated)]
pub use crate::decode::{decode_engine_slice, decoded_len_estimate, DecodeError, DecodeSliceError};
pub mod prelude;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
/// Available encoding character sets
#[derive(Clone, Copy, Debug)]
pub enum CharacterSet {
/// The standard character set (uses `+` and `/`).
///
/// See [RFC 3548](https://tools.ietf.org/html/rfc3548#section-3).
Standard,
/// The URL safe character set (uses `-` and `_`).
///
/// See [RFC 3548](https://tools.ietf.org/html/rfc3548#section-4).
UrlSafe,
/// The `crypt(3)` character set (uses `./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`).
///
/// Not standardized, but folk wisdom on the net asserts that this alphabet is what crypt uses.
Crypt,
/// The bcrypt character set (uses `./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789`).
Bcrypt,
/// The character set used in IMAP-modified UTF-7 (uses `+` and `,`).
///
/// See [RFC 3501](https://tools.ietf.org/html/rfc3501#section-5.1.3)
ImapMutf7,
/// The character set used in BinHex 4.0 files.
///
/// See [BinHex 4.0 Definition](http://files.stairways.com/other/binhex-40-specs-info.txt)
BinHex,
}
impl CharacterSet {
fn encode_table(self) -> &'static [u8; 64] {
match self {
CharacterSet::Standard => tables::STANDARD_ENCODE,
CharacterSet::UrlSafe => tables::URL_SAFE_ENCODE,
CharacterSet::Crypt => tables::CRYPT_ENCODE,
CharacterSet::Bcrypt => tables::BCRYPT_ENCODE,
CharacterSet::ImapMutf7 => tables::IMAP_MUTF7_ENCODE,
CharacterSet::BinHex => tables::BINHEX_ENCODE,
}
}
fn decode_table(self) -> &'static [u8; 256] {
match self {
CharacterSet::Standard => tables::STANDARD_DECODE,
CharacterSet::UrlSafe => tables::URL_SAFE_DECODE,
CharacterSet::Crypt => tables::CRYPT_DECODE,
CharacterSet::Bcrypt => tables::BCRYPT_DECODE,
CharacterSet::ImapMutf7 => tables::IMAP_MUTF7_DECODE,
CharacterSet::BinHex => tables::BINHEX_DECODE,
}
}
}
/// Contains configuration parameters for base64 encoding
#[derive(Clone, Copy, Debug)]
pub struct Config {
/// Character set to use
char_set: CharacterSet,
/// True to pad output with `=` characters
pad: bool,
/// True to ignore excess nonzero bits in the last few symbols, otherwise an error is returned.
decode_allow_trailing_bits: bool,
}
impl Config {
/// Create a new `Config`.
pub const fn new(char_set: CharacterSet, pad: bool) -> Config {
Config {
char_set,
pad,
decode_allow_trailing_bits: false,
}
}
/// Sets whether to pad output with `=` characters.
pub const fn pad(self, pad: bool) -> Config {
Config { pad, ..self }
}
/// Sets whether to emit errors for nonzero trailing bits.
///
/// This is useful when implementing
/// [forgiving-base64 decode](https://infra.spec.whatwg.org/#forgiving-base64-decode).
pub const fn decode_allow_trailing_bits(self, allow: bool) -> Config {
Config {
decode_allow_trailing_bits: allow,
..self
}
}
}
/// Standard character set with padding.
pub const STANDARD: Config = Config {
char_set: CharacterSet::Standard,
pad: true,
decode_allow_trailing_bits: false,
};
/// Standard character set without padding.
pub const STANDARD_NO_PAD: Config = Config {
char_set: CharacterSet::Standard,
pad: false,
decode_allow_trailing_bits: false,
};
/// URL-safe character set with padding
pub const URL_SAFE: Config = Config {
char_set: CharacterSet::UrlSafe,
pad: true,
decode_allow_trailing_bits: false,
};
/// URL-safe character set without padding
pub const URL_SAFE_NO_PAD: Config = Config {
char_set: CharacterSet::UrlSafe,
pad: false,
decode_allow_trailing_bits: false,
};
/// As per `crypt(3)` requirements
pub const CRYPT: Config = Config {
char_set: CharacterSet::Crypt,
pad: false,
decode_allow_trailing_bits: false,
};
/// Bcrypt character set
pub const BCRYPT: Config = Config {
char_set: CharacterSet::Bcrypt,
pad: false,
decode_allow_trailing_bits: false,
};
/// IMAP modified UTF-7 requirements
pub const IMAP_MUTF7: Config = Config {
char_set: CharacterSet::ImapMutf7,
pad: false,
decode_allow_trailing_bits: false,
};
/// BinHex character set
pub const BINHEX: Config = Config {
char_set: CharacterSet::BinHex,
pad: false,
decode_allow_trailing_bits: false,
};
const PAD_BYTE: u8 = b'='; const PAD_BYTE: u8 = b'=';

19
zeroidc/vendor/base64/src/prelude.rs vendored Normal file
View File

@ -0,0 +1,19 @@
//! Preconfigured engines for common use cases.
//!
//! These are re-exports of `const` engines in [crate::engine::general_purpose], renamed with a `BASE64_`
//! prefix for those who prefer to `use` the entire path to a name.
//!
//! # Examples
//!
//! ```
//! use base64::prelude::{Engine as _, BASE64_STANDARD_NO_PAD};
//!
//! assert_eq!("c29tZSBieXRlcw", &BASE64_STANDARD_NO_PAD.encode(b"some bytes"));
//! ```
pub use crate::engine::Engine;
pub use crate::engine::general_purpose::STANDARD as BASE64_STANDARD;
pub use crate::engine::general_purpose::STANDARD_NO_PAD as BASE64_STANDARD_NO_PAD;
pub use crate::engine::general_purpose::URL_SAFE as BASE64_URL_SAFE;
pub use crate::engine::general_purpose::URL_SAFE_NO_PAD as BASE64_URL_SAFE_NO_PAD;

View File

@ -1,5 +1,4 @@
use crate::{decode_config_slice, Config, DecodeError}; use crate::{engine::Engine, DecodeError};
use std::io::Read;
use std::{cmp, fmt, io}; use std::{cmp, fmt, io};
// This should be large, but it has to fit on the stack. // This should be large, but it has to fit on the stack.
@ -16,11 +15,13 @@ const DECODED_CHUNK_SIZE: usize = 3;
/// ``` /// ```
/// use std::io::Read; /// use std::io::Read;
/// use std::io::Cursor; /// use std::io::Cursor;
/// use base64::engine::general_purpose;
/// ///
/// // use a cursor as the simplest possible `Read` -- in real code this is probably a file, etc. /// // use a cursor as the simplest possible `Read` -- in real code this is probably a file, etc.
/// let mut wrapped_reader = Cursor::new(b"YXNkZg=="); /// let mut wrapped_reader = Cursor::new(b"YXNkZg==");
/// let mut decoder = base64::read::DecoderReader::new( /// let mut decoder = base64::read::DecoderReader::new(
/// &mut wrapped_reader, base64::STANDARD); /// &mut wrapped_reader,
/// &general_purpose::STANDARD);
/// ///
/// // handle errors as you normally would /// // handle errors as you normally would
/// let mut result = Vec::new(); /// let mut result = Vec::new();
@ -29,10 +30,10 @@ const DECODED_CHUNK_SIZE: usize = 3;
/// assert_eq!(b"asdf", &result[..]); /// assert_eq!(b"asdf", &result[..]);
/// ///
/// ``` /// ```
pub struct DecoderReader<'a, R: 'a + io::Read> { pub struct DecoderReader<'e, E: Engine, R: io::Read> {
config: Config, engine: &'e E,
/// Where b64 data is read from /// Where b64 data is read from
r: &'a mut R, inner: R,
// Holds b64 data read from the delegate reader. // Holds b64 data read from the delegate reader.
b64_buffer: [u8; BUF_SIZE], b64_buffer: [u8; BUF_SIZE],
@ -54,10 +55,9 @@ pub struct DecoderReader<'a, R: 'a + io::Read> {
total_b64_decoded: usize, total_b64_decoded: usize,
} }
impl<'a, R: io::Read> fmt::Debug for DecoderReader<'a, R> { impl<'e, E: Engine, R: io::Read> fmt::Debug for DecoderReader<'e, E, R> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("DecoderReader") f.debug_struct("DecoderReader")
.field("config", &self.config)
.field("b64_offset", &self.b64_offset) .field("b64_offset", &self.b64_offset)
.field("b64_len", &self.b64_len) .field("b64_len", &self.b64_len)
.field("decoded_buffer", &self.decoded_buffer) .field("decoded_buffer", &self.decoded_buffer)
@ -68,12 +68,12 @@ impl<'a, R: io::Read> fmt::Debug for DecoderReader<'a, R> {
} }
} }
impl<'a, R: io::Read> DecoderReader<'a, R> { impl<'e, E: Engine, R: io::Read> DecoderReader<'e, E, R> {
/// Create a new decoder that will read from the provided reader `r`. /// Create a new decoder that will read from the provided reader `r`.
pub fn new(r: &'a mut R, config: Config) -> Self { pub fn new(reader: R, engine: &'e E) -> Self {
DecoderReader { DecoderReader {
config, engine,
r, inner: reader,
b64_buffer: [0; BUF_SIZE], b64_buffer: [0; BUF_SIZE],
b64_offset: 0, b64_offset: 0,
b64_len: 0, b64_len: 0,
@ -89,7 +89,7 @@ impl<'a, R: io::Read> DecoderReader<'a, R> {
/// Returns a Result with the number of (decoded) bytes copied. /// Returns a Result with the number of (decoded) bytes copied.
fn flush_decoded_buf(&mut self, buf: &mut [u8]) -> io::Result<usize> { fn flush_decoded_buf(&mut self, buf: &mut [u8]) -> io::Result<usize> {
debug_assert!(self.decoded_len > 0); debug_assert!(self.decoded_len > 0);
debug_assert!(buf.len() > 0); debug_assert!(!buf.is_empty());
let copy_len = cmp::min(self.decoded_len, buf.len()); let copy_len = cmp::min(self.decoded_len, buf.len());
debug_assert!(copy_len > 0); debug_assert!(copy_len > 0);
@ -114,13 +114,13 @@ impl<'a, R: io::Read> DecoderReader<'a, R> {
debug_assert!(self.b64_offset + self.b64_len < BUF_SIZE); debug_assert!(self.b64_offset + self.b64_len < BUF_SIZE);
let read = self let read = self
.r .inner
.read(&mut self.b64_buffer[self.b64_offset + self.b64_len..])?; .read(&mut self.b64_buffer[self.b64_offset + self.b64_len..])?;
self.b64_len += read; self.b64_len += read;
debug_assert!(self.b64_offset + self.b64_len <= BUF_SIZE); debug_assert!(self.b64_offset + self.b64_len <= BUF_SIZE);
return Ok(read); Ok(read)
} }
/// Decode the requested number of bytes from the b64 buffer into the provided buffer. It's the /// Decode the requested number of bytes from the b64 buffer into the provided buffer. It's the
@ -130,23 +130,26 @@ impl<'a, R: io::Read> DecoderReader<'a, R> {
fn decode_to_buf(&mut self, num_bytes: usize, buf: &mut [u8]) -> io::Result<usize> { fn decode_to_buf(&mut self, num_bytes: usize, buf: &mut [u8]) -> io::Result<usize> {
debug_assert!(self.b64_len >= num_bytes); debug_assert!(self.b64_len >= num_bytes);
debug_assert!(self.b64_offset + self.b64_len <= BUF_SIZE); debug_assert!(self.b64_offset + self.b64_len <= BUF_SIZE);
debug_assert!(buf.len() > 0); debug_assert!(!buf.is_empty());
let decoded = decode_config_slice( let decoded = self
&self.b64_buffer[self.b64_offset..self.b64_offset + num_bytes], .engine
self.config, .internal_decode(
&mut buf[..], &self.b64_buffer[self.b64_offset..self.b64_offset + num_bytes],
) buf,
.map_err(|e| match e { self.engine.internal_decoded_len_estimate(num_bytes),
DecodeError::InvalidByte(offset, byte) => { )
DecodeError::InvalidByte(self.total_b64_decoded + offset, byte) .map_err(|e| match e {
} DecodeError::InvalidByte(offset, byte) => {
DecodeError::InvalidLength => DecodeError::InvalidLength, DecodeError::InvalidByte(self.total_b64_decoded + offset, byte)
DecodeError::InvalidLastSymbol(offset, byte) => { }
DecodeError::InvalidLastSymbol(self.total_b64_decoded + offset, byte) DecodeError::InvalidLength => DecodeError::InvalidLength,
} DecodeError::InvalidLastSymbol(offset, byte) => {
}) DecodeError::InvalidLastSymbol(self.total_b64_decoded + offset, byte)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; }
DecodeError::InvalidPadding => DecodeError::InvalidPadding,
})
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
self.total_b64_decoded += num_bytes; self.total_b64_decoded += num_bytes;
self.b64_offset += num_bytes; self.b64_offset += num_bytes;
@ -156,9 +159,19 @@ impl<'a, R: io::Read> DecoderReader<'a, R> {
Ok(decoded) Ok(decoded)
} }
/// Unwraps this `DecoderReader`, returning the base reader which it reads base64 encoded
/// input from.
///
/// Because `DecoderReader` performs internal buffering, the state of the inner reader is
/// unspecified. This function is mainly provided because the inner reader type may provide
/// additional functionality beyond the `Read` implementation which may still be useful.
pub fn into_inner(self) -> R {
self.inner
}
} }
impl<'a, R: Read> Read for DecoderReader<'a, R> { impl<'e, E: Engine, R: io::Read> io::Read for DecoderReader<'e, E, R> {
/// Decode input from the wrapped reader. /// Decode input from the wrapped reader.
/// ///
/// Under non-error circumstances, this returns `Ok` with the value being the number of bytes /// Under non-error circumstances, this returns `Ok` with the value being the number of bytes
@ -172,7 +185,7 @@ impl<'a, R: Read> Read for DecoderReader<'a, R> {
/// Any errors emitted by the delegate reader are returned. Decoding errors due to invalid /// Any errors emitted by the delegate reader are returned. Decoding errors due to invalid
/// base64 are also possible, and will have `io::ErrorKind::InvalidData`. /// base64 are also possible, and will have `io::ErrorKind::InvalidData`.
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if buf.len() == 0 { if buf.is_empty() {
return Ok(0); return Ok(0);
} }

View File

@ -1,12 +1,17 @@
use std::io::{self, Read}; use std::{
cmp,
io::{self, Read as _},
iter,
};
use rand::{Rng, RngCore}; use rand::{Rng as _, RngCore as _};
use std::{cmp, iter};
use super::decoder::{DecoderReader, BUF_SIZE}; use super::decoder::{DecoderReader, BUF_SIZE};
use crate::encode::encode_config_buf; use crate::{
use crate::tests::random_config; engine::{general_purpose::STANDARD, Engine, GeneralPurpose},
use crate::{decode_config_buf, DecodeError, STANDARD}; tests::{random_alphabet, random_config, random_engine},
DecodeError,
};
#[test] #[test]
fn simple() { fn simple() {
@ -27,7 +32,7 @@ fn simple() {
// Read n bytes at a time. // Read n bytes at a time.
for n in 1..base64data.len() + 1 { for n in 1..base64data.len() + 1 {
let mut wrapped_reader = io::Cursor::new(base64data); let mut wrapped_reader = io::Cursor::new(base64data);
let mut decoder = DecoderReader::new(&mut wrapped_reader, STANDARD); let mut decoder = DecoderReader::new(&mut wrapped_reader, &STANDARD);
// handle errors as you normally would // handle errors as you normally would
let mut text_got = Vec::new(); let mut text_got = Vec::new();
@ -59,7 +64,7 @@ fn trailing_junk() {
// Read n bytes at a time. // Read n bytes at a time.
for n in 1..base64data.len() + 1 { for n in 1..base64data.len() + 1 {
let mut wrapped_reader = io::Cursor::new(base64data); let mut wrapped_reader = io::Cursor::new(base64data);
let mut decoder = DecoderReader::new(&mut wrapped_reader, STANDARD); let mut decoder = DecoderReader::new(&mut wrapped_reader, &STANDARD);
// handle errors as you normally would // handle errors as you normally would
let mut buffer = vec![0u8; n]; let mut buffer = vec![0u8; n];
@ -92,14 +97,14 @@ fn handles_short_read_from_delegate() {
b64.clear(); b64.clear();
decoded.clear(); decoded.clear();
let size = rng.gen_range(0, 10 * BUF_SIZE); let size = rng.gen_range(0..(10 * BUF_SIZE));
bytes.extend(iter::repeat(0).take(size)); bytes.extend(iter::repeat(0).take(size));
bytes.truncate(size); bytes.truncate(size);
rng.fill_bytes(&mut bytes[..size]); rng.fill_bytes(&mut bytes[..size]);
assert_eq!(size, bytes.len()); assert_eq!(size, bytes.len());
let config = random_config(&mut rng); let engine = random_engine(&mut rng);
encode_config_buf(&bytes[..], config, &mut b64); engine.encode_string(&bytes[..], &mut b64);
let mut wrapped_reader = io::Cursor::new(b64.as_bytes()); let mut wrapped_reader = io::Cursor::new(b64.as_bytes());
let mut short_reader = RandomShortRead { let mut short_reader = RandomShortRead {
@ -107,7 +112,7 @@ fn handles_short_read_from_delegate() {
rng: &mut rng, rng: &mut rng,
}; };
let mut decoder = DecoderReader::new(&mut short_reader, config); let mut decoder = DecoderReader::new(&mut short_reader, &engine);
let decoded_len = decoder.read_to_end(&mut decoded).unwrap(); let decoded_len = decoder.read_to_end(&mut decoded).unwrap();
assert_eq!(size, decoded_len); assert_eq!(size, decoded_len);
@ -127,7 +132,7 @@ fn read_in_short_increments() {
b64.clear(); b64.clear();
decoded.clear(); decoded.clear();
let size = rng.gen_range(0, 10 * BUF_SIZE); let size = rng.gen_range(0..(10 * BUF_SIZE));
bytes.extend(iter::repeat(0).take(size)); bytes.extend(iter::repeat(0).take(size));
// leave room to play around with larger buffers // leave room to play around with larger buffers
decoded.extend(iter::repeat(0).take(size * 3)); decoded.extend(iter::repeat(0).take(size * 3));
@ -135,12 +140,12 @@ fn read_in_short_increments() {
rng.fill_bytes(&mut bytes[..]); rng.fill_bytes(&mut bytes[..]);
assert_eq!(size, bytes.len()); assert_eq!(size, bytes.len());
let config = random_config(&mut rng); let engine = random_engine(&mut rng);
encode_config_buf(&bytes[..], config, &mut b64); engine.encode_string(&bytes[..], &mut b64);
let mut wrapped_reader = io::Cursor::new(&b64[..]); let mut wrapped_reader = io::Cursor::new(&b64[..]);
let mut decoder = DecoderReader::new(&mut wrapped_reader, config); let mut decoder = DecoderReader::new(&mut wrapped_reader, &engine);
consume_with_short_reads_and_validate(&mut rng, &bytes[..], &mut decoded, &mut decoder); consume_with_short_reads_and_validate(&mut rng, &bytes[..], &mut decoded, &mut decoder);
} }
@ -158,7 +163,7 @@ fn read_in_short_increments_with_short_delegate_reads() {
b64.clear(); b64.clear();
decoded.clear(); decoded.clear();
let size = rng.gen_range(0, 10 * BUF_SIZE); let size = rng.gen_range(0..(10 * BUF_SIZE));
bytes.extend(iter::repeat(0).take(size)); bytes.extend(iter::repeat(0).take(size));
// leave room to play around with larger buffers // leave room to play around with larger buffers
decoded.extend(iter::repeat(0).take(size * 3)); decoded.extend(iter::repeat(0).take(size * 3));
@ -166,18 +171,23 @@ fn read_in_short_increments_with_short_delegate_reads() {
rng.fill_bytes(&mut bytes[..]); rng.fill_bytes(&mut bytes[..]);
assert_eq!(size, bytes.len()); assert_eq!(size, bytes.len());
let config = random_config(&mut rng); let engine = random_engine(&mut rng);
encode_config_buf(&bytes[..], config, &mut b64); engine.encode_string(&bytes[..], &mut b64);
let mut base_reader = io::Cursor::new(&b64[..]); let mut base_reader = io::Cursor::new(&b64[..]);
let mut decoder = DecoderReader::new(&mut base_reader, config); let mut decoder = DecoderReader::new(&mut base_reader, &engine);
let mut short_reader = RandomShortRead { let mut short_reader = RandomShortRead {
delegate: &mut decoder, delegate: &mut decoder,
rng: &mut rand::thread_rng(), rng: &mut rand::thread_rng(),
}; };
consume_with_short_reads_and_validate(&mut rng, &bytes[..], &mut decoded, &mut short_reader) consume_with_short_reads_and_validate(
&mut rng,
&bytes[..],
&mut decoded,
&mut short_reader,
);
} }
} }
@ -195,32 +205,32 @@ fn reports_invalid_last_symbol_correctly() {
b64.clear(); b64.clear();
b64_bytes.clear(); b64_bytes.clear();
let size = rng.gen_range(1, 10 * BUF_SIZE); let size = rng.gen_range(1..(10 * BUF_SIZE));
bytes.extend(iter::repeat(0).take(size)); bytes.extend(iter::repeat(0).take(size));
decoded.extend(iter::repeat(0).take(size)); decoded.extend(iter::repeat(0).take(size));
rng.fill_bytes(&mut bytes[..]); rng.fill_bytes(&mut bytes[..]);
assert_eq!(size, bytes.len()); assert_eq!(size, bytes.len());
let mut config = random_config(&mut rng); let config = random_config(&mut rng);
let alphabet = random_alphabet(&mut rng);
// changing padding will cause invalid padding errors when we twiddle the last byte // changing padding will cause invalid padding errors when we twiddle the last byte
config.pad = false; let engine = GeneralPurpose::new(alphabet, config.with_encode_padding(false));
engine.encode_string(&bytes[..], &mut b64);
encode_config_buf(&bytes[..], config, &mut b64);
b64_bytes.extend(b64.bytes()); b64_bytes.extend(b64.bytes());
assert_eq!(b64_bytes.len(), b64.len()); assert_eq!(b64_bytes.len(), b64.len());
// change the last character to every possible symbol. Should behave the same as bulk // change the last character to every possible symbol. Should behave the same as bulk
// decoding whether invalid or valid. // decoding whether invalid or valid.
for &s1 in config.char_set.encode_table().iter() { for &s1 in alphabet.symbols.iter() {
decoded.clear(); decoded.clear();
bulk_decoded.clear(); bulk_decoded.clear();
// replace the last // replace the last
*b64_bytes.last_mut().unwrap() = s1; *b64_bytes.last_mut().unwrap() = s1;
let bulk_res = decode_config_buf(&b64_bytes[..], config, &mut bulk_decoded); let bulk_res = engine.decode_vec(&b64_bytes[..], &mut bulk_decoded);
let mut wrapped_reader = io::Cursor::new(&b64_bytes[..]); let mut wrapped_reader = io::Cursor::new(&b64_bytes[..]);
let mut decoder = DecoderReader::new(&mut wrapped_reader, config); let mut decoder = DecoderReader::new(&mut wrapped_reader, &engine);
let stream_res = decoder.read_to_end(&mut decoded).map(|_| ()).map_err(|e| { let stream_res = decoder.read_to_end(&mut decoded).map(|_| ()).map_err(|e| {
e.into_inner() e.into_inner()
@ -244,20 +254,21 @@ fn reports_invalid_byte_correctly() {
b64.clear(); b64.clear();
decoded.clear(); decoded.clear();
let size = rng.gen_range(1, 10 * BUF_SIZE); let size = rng.gen_range(1..(10 * BUF_SIZE));
bytes.extend(iter::repeat(0).take(size)); bytes.extend(iter::repeat(0).take(size));
rng.fill_bytes(&mut bytes[..size]); rng.fill_bytes(&mut bytes[..size]);
assert_eq!(size, bytes.len()); assert_eq!(size, bytes.len());
let config = random_config(&mut rng); let engine = random_engine(&mut rng);
encode_config_buf(&bytes[..], config, &mut b64);
engine.encode_string(&bytes[..], &mut b64);
// replace one byte, somewhere, with '*', which is invalid // replace one byte, somewhere, with '*', which is invalid
let bad_byte_pos = rng.gen_range(0, &b64.len()); let bad_byte_pos = rng.gen_range(0..b64.len());
let mut b64_bytes = b64.bytes().collect::<Vec<u8>>(); let mut b64_bytes = b64.bytes().collect::<Vec<u8>>();
b64_bytes[bad_byte_pos] = b'*'; b64_bytes[bad_byte_pos] = b'*';
let mut wrapped_reader = io::Cursor::new(b64_bytes.clone()); let mut wrapped_reader = io::Cursor::new(b64_bytes.clone());
let mut decoder = DecoderReader::new(&mut wrapped_reader, config); let mut decoder = DecoderReader::new(&mut wrapped_reader, &engine);
// some gymnastics to avoid double-moving the io::Error, which is not Copy // some gymnastics to avoid double-moving the io::Error, which is not Copy
let read_decode_err = decoder let read_decode_err = decoder
@ -273,7 +284,7 @@ fn reports_invalid_byte_correctly() {
.and_then(|o| o); .and_then(|o| o);
let mut bulk_buf = Vec::new(); let mut bulk_buf = Vec::new();
let bulk_decode_err = decode_config_buf(&b64_bytes[..], config, &mut bulk_buf).err(); let bulk_decode_err = engine.decode_vec(&b64_bytes[..], &mut bulk_buf).err();
// it's tricky to predict where the invalid data's offset will be since if it's in the last // it's tricky to predict where the invalid data's offset will be since if it's in the last
// chunk it will be reported at the first padding location because it's treated as invalid // chunk it will be reported at the first padding location because it's treated as invalid
@ -285,12 +296,12 @@ fn reports_invalid_byte_correctly() {
} }
} }
fn consume_with_short_reads_and_validate<R: Read>( fn consume_with_short_reads_and_validate<R: io::Read>(
rng: &mut rand::rngs::ThreadRng, rng: &mut rand::rngs::ThreadRng,
expected_bytes: &[u8], expected_bytes: &[u8],
decoded: &mut Vec<u8>, decoded: &mut [u8],
short_reader: &mut R, short_reader: &mut R,
) -> () { ) {
let mut total_read = 0_usize; let mut total_read = 0_usize;
loop { loop {
assert!( assert!(
@ -302,13 +313,13 @@ fn consume_with_short_reads_and_validate<R: Read>(
if total_read == expected_bytes.len() { if total_read == expected_bytes.len() {
assert_eq!(expected_bytes, &decoded[..total_read]); assert_eq!(expected_bytes, &decoded[..total_read]);
// should be done // should be done
assert_eq!(0, short_reader.read(&mut decoded[..]).unwrap()); assert_eq!(0, short_reader.read(&mut *decoded).unwrap());
// didn't write anything // didn't write anything
assert_eq!(expected_bytes, &decoded[..total_read]); assert_eq!(expected_bytes, &decoded[..total_read]);
break; break;
} }
let decode_len = rng.gen_range(1, cmp::max(2, expected_bytes.len() * 2)); let decode_len = rng.gen_range(1..cmp::max(2, expected_bytes.len() * 2));
let read = short_reader let read = short_reader
.read(&mut decoded[total_read..total_read + decode_len]) .read(&mut decoded[total_read..total_read + decode_len])
@ -328,7 +339,7 @@ struct RandomShortRead<'a, 'b, R: io::Read, N: rand::Rng> {
impl<'a, 'b, R: io::Read, N: rand::Rng> io::Read for RandomShortRead<'a, 'b, R, N> { impl<'a, 'b, R: io::Read, N: rand::Rng> io::Read for RandomShortRead<'a, 'b, R, N> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, io::Error> { fn read(&mut self, buf: &mut [u8]) -> Result<usize, io::Error> {
// avoid 0 since it means EOF for non-empty buffers // avoid 0 since it means EOF for non-empty buffers
let effective_len = cmp::min(self.rng.gen_range(1, 20), buf.len()); let effective_len = cmp::min(self.rng.gen_range(1..20), buf.len());
self.delegate.read(&mut buf[..effective_len]) self.delegate.read(&mut buf[..effective_len])
} }

View File

@ -1,11 +1,19 @@
use crate::{decode_config, encode::encoded_size, encode_config_buf, CharacterSet, Config};
use std::str; use std::str;
use rand::{ use rand::{
distributions::{Distribution, Uniform}, distributions,
distributions::{Distribution as _, Uniform},
seq::SliceRandom, seq::SliceRandom,
FromEntropy, Rng, Rng, SeedableRng,
};
use crate::{
alphabet,
encode::encoded_len,
engine::{
general_purpose::{GeneralPurpose, GeneralPurposeConfig},
Config, DecodePaddingMode, Engine,
},
}; };
#[test] #[test]
@ -19,10 +27,10 @@ fn roundtrip_random_config_long() {
roundtrip_random_config(Uniform::new(0, 1000), 10_000); roundtrip_random_config(Uniform::new(0, 1000), 10_000);
} }
pub fn assert_encode_sanity(encoded: &str, config: Config, input_len: usize) { pub fn assert_encode_sanity(encoded: &str, padded: bool, input_len: usize) {
let input_rem = input_len % 3; let input_rem = input_len % 3;
let expected_padding_len = if input_rem > 0 { let expected_padding_len = if input_rem > 0 {
if config.pad { if padded {
3 - input_rem 3 - input_rem
} else { } else {
0 0
@ -31,7 +39,7 @@ pub fn assert_encode_sanity(encoded: &str, config: Config, input_len: usize) {
0 0
}; };
let expected_encoded_len = encoded_size(input_len, config).unwrap(); let expected_encoded_len = encoded_len(input_len, padded).unwrap();
assert_eq!(expected_encoded_len, encoded.len()); assert_eq!(expected_encoded_len, encoded.len());
@ -53,29 +61,57 @@ fn roundtrip_random_config(input_len_range: Uniform<usize>, iterations: u32) {
let input_len = input_len_range.sample(&mut rng); let input_len = input_len_range.sample(&mut rng);
let config = random_config(&mut rng); let engine = random_engine(&mut rng);
for _ in 0..input_len { for _ in 0..input_len {
input_buf.push(rng.gen()); input_buf.push(rng.gen());
} }
encode_config_buf(&input_buf, config, &mut encoded_buf); engine.encode_string(&input_buf, &mut encoded_buf);
assert_encode_sanity(&encoded_buf, config, input_len); assert_encode_sanity(&encoded_buf, engine.config().encode_padding(), input_len);
assert_eq!(input_buf, decode_config(&encoded_buf, config).unwrap()); assert_eq!(input_buf, engine.decode(&encoded_buf).unwrap());
} }
} }
pub fn random_config<R: Rng>(rng: &mut R) -> Config { pub fn random_config<R: Rng>(rng: &mut R) -> GeneralPurposeConfig {
const CHARSETS: &[CharacterSet] = &[ let mode = rng.gen();
CharacterSet::UrlSafe, GeneralPurposeConfig::new()
CharacterSet::Standard, .with_encode_padding(match mode {
CharacterSet::Crypt, DecodePaddingMode::Indifferent => rng.gen(),
CharacterSet::ImapMutf7, DecodePaddingMode::RequireCanonical => true,
CharacterSet::BinHex, DecodePaddingMode::RequireNone => false,
]; })
let charset = *CHARSETS.choose(rng).unwrap(); .with_decode_padding_mode(mode)
.with_decode_allow_trailing_bits(rng.gen())
Config::new(charset, rng.gen())
} }
impl distributions::Distribution<DecodePaddingMode> for distributions::Standard {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> DecodePaddingMode {
match rng.gen_range(0..=2) {
0 => DecodePaddingMode::Indifferent,
1 => DecodePaddingMode::RequireCanonical,
_ => DecodePaddingMode::RequireNone,
}
}
}
pub fn random_alphabet<R: Rng>(rng: &mut R) -> &'static alphabet::Alphabet {
ALPHABETS.choose(rng).unwrap()
}
pub fn random_engine<R: Rng>(rng: &mut R) -> GeneralPurpose {
let alphabet = random_alphabet(rng);
let config = random_config(rng);
GeneralPurpose::new(alphabet, config)
}
const ALPHABETS: &[alphabet::Alphabet] = &[
alphabet::URL_SAFE,
alphabet::STANDARD,
alphabet::CRYPT,
alphabet::BCRYPT,
alphabet::IMAP_MUTF7,
alphabet::BIN_HEX,
];

View File

@ -1,8 +1,7 @@
use crate::encode::encode_to_slice; use crate::engine::Engine;
use crate::{encode_config_slice, Config};
use std::{ use std::{
cmp, fmt, cmp, fmt, io,
io::{ErrorKind, Result, Write}, io::{ErrorKind, Result},
}; };
pub(crate) const BUF_SIZE: usize = 1024; pub(crate) const BUF_SIZE: usize = 1024;
@ -23,9 +22,10 @@ const MIN_ENCODE_CHUNK_SIZE: usize = 3;
/// ///
/// ``` /// ```
/// use std::io::Write; /// use std::io::Write;
/// use base64::engine::general_purpose;
/// ///
/// // use a vec as the simplest possible `Write` -- in real code this is probably a file, etc. /// // use a vec as the simplest possible `Write` -- in real code this is probably a file, etc.
/// let mut enc = base64::write::EncoderWriter::new(Vec::new(), base64::STANDARD); /// let mut enc = base64::write::EncoderWriter::new(Vec::new(), &general_purpose::STANDARD);
/// ///
/// // handle errors as you normally would /// // handle errors as you normally would
/// enc.write_all(b"asdf").unwrap(); /// enc.write_all(b"asdf").unwrap();
@ -53,8 +53,15 @@ const MIN_ENCODE_CHUNK_SIZE: usize = 3;
/// ///
/// It has some minor performance loss compared to encoding slices (a couple percent). /// It has some minor performance loss compared to encoding slices (a couple percent).
/// It does not do any heap allocation. /// It does not do any heap allocation.
pub struct EncoderWriter<W: Write> { ///
config: Config, /// # Limitations
///
/// Owing to the specification of the `write` and `flush` methods on the `Write` trait and their
/// implications for a buffering implementation, these methods may not behave as expected. In
/// particular, calling `write_all` on this interface may fail with `io::ErrorKind::WriteZero`.
/// See the documentation of the `Write` trait implementation for further details.
pub struct EncoderWriter<'e, E: Engine, W: io::Write> {
engine: &'e E,
/// Where encoded data is written to. It's an Option as it's None immediately before Drop is /// Where encoded data is written to. It's an Option as it's None immediately before Drop is
/// called so that finish() can return the underlying writer. None implies that finish() has /// called so that finish() can return the underlying writer. None implies that finish() has
/// been called successfully. /// been called successfully.
@ -73,7 +80,7 @@ pub struct EncoderWriter<W: Write> {
panicked: bool, panicked: bool,
} }
impl<W: Write> fmt::Debug for EncoderWriter<W> { impl<'e, E: Engine, W: io::Write> fmt::Debug for EncoderWriter<'e, E, W> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!( write!(
f, f,
@ -86,12 +93,12 @@ impl<W: Write> fmt::Debug for EncoderWriter<W> {
} }
} }
impl<W: Write> EncoderWriter<W> { impl<'e, E: Engine, W: io::Write> EncoderWriter<'e, E, W> {
/// Create a new encoder that will write to the provided delegate writer `w`. /// Create a new encoder that will write to the provided delegate writer.
pub fn new(w: W, config: Config) -> EncoderWriter<W> { pub fn new(delegate: W, engine: &'e E) -> EncoderWriter<'e, E, W> {
EncoderWriter { EncoderWriter {
config, engine,
delegate: Some(w), delegate: Some(delegate),
extra_input: [0u8; MIN_ENCODE_CHUNK_SIZE], extra_input: [0u8; MIN_ENCODE_CHUNK_SIZE],
extra_input_occupied_len: 0, extra_input_occupied_len: 0,
output: [0u8; BUF_SIZE], output: [0u8; BUF_SIZE],
@ -120,7 +127,7 @@ impl<W: Write> EncoderWriter<W> {
// If we could consume self in finish(), we wouldn't have to worry about this case, but // If we could consume self in finish(), we wouldn't have to worry about this case, but
// finish() is retryable in the face of I/O errors, so we can't consume here. // finish() is retryable in the face of I/O errors, so we can't consume here.
if self.delegate.is_none() { if self.delegate.is_none() {
panic!("Encoder has already had finish() called") panic!("Encoder has already had finish() called");
}; };
self.write_final_leftovers()?; self.write_final_leftovers()?;
@ -141,11 +148,13 @@ impl<W: Write> EncoderWriter<W> {
self.write_all_encoded_output()?; self.write_all_encoded_output()?;
if self.extra_input_occupied_len > 0 { if self.extra_input_occupied_len > 0 {
let encoded_len = encode_config_slice( let encoded_len = self
&self.extra_input[..self.extra_input_occupied_len], .engine
self.config, .encode_slice(
&mut self.output[..], &self.extra_input[..self.extra_input_occupied_len],
); &mut self.output[..],
)
.expect("buffer is large enough");
self.output_occupied_len = encoded_len; self.output_occupied_len = encoded_len;
@ -182,7 +191,7 @@ impl<W: Write> EncoderWriter<W> {
self.output_occupied_len = current_output_len.checked_sub(consumed).unwrap(); self.output_occupied_len = current_output_len.checked_sub(consumed).unwrap();
// If we're blocking on I/O, the minor inefficiency of copying bytes to the // If we're blocking on I/O, the minor inefficiency of copying bytes to the
// start of the buffer is the least of our concerns... // start of the buffer is the least of our concerns...
// Rotate moves more than we need to, but copy_within isn't stabilized yet. // TODO Rotate moves more than we need to; copy_within now stable.
self.output.rotate_left(consumed); self.output.rotate_left(consumed);
} else { } else {
self.output_occupied_len = 0; self.output_occupied_len = 0;
@ -215,15 +224,34 @@ impl<W: Write> EncoderWriter<W> {
debug_assert_eq!(0, self.output_occupied_len); debug_assert_eq!(0, self.output_occupied_len);
Ok(()) Ok(())
} }
/// Unwraps this `EncoderWriter`, returning the base writer it writes base64 encoded output
/// to.
///
/// Normally this method should not be needed, since `finish()` returns the inner writer if
/// it completes successfully. That will also ensure all data has been flushed, which the
/// `into_inner()` function does *not* do.
///
/// Calling this method after `finish()` has completed successfully will panic, since the
/// writer has already been returned.
///
/// This method may be useful if the writer implements additional APIs beyond the `Write`
/// trait. Note that the inner writer might be in an error state or have an incomplete
/// base64 string written to it.
pub fn into_inner(mut self) -> W {
self.delegate
.take()
.expect("Encoder has already had finish() called")
}
} }
impl<W: Write> Write for EncoderWriter<W> { impl<'e, E: Engine, W: io::Write> io::Write for EncoderWriter<'e, E, W> {
/// Encode input and then write to the delegate writer. /// Encode input and then write to the delegate writer.
/// ///
/// Under non-error circumstances, this returns `Ok` with the value being the number of bytes /// Under non-error circumstances, this returns `Ok` with the value being the number of bytes
/// of `input` consumed. The value may be `0`, which interacts poorly with `write_all`, which /// of `input` consumed. The value may be `0`, which interacts poorly with `write_all`, which
/// interprets `Ok(0)` as an error, despite it being allowed by the contract of `write`. See /// interprets `Ok(0)` as an error, despite it being allowed by the contract of `write`. See
/// https://github.com/rust-lang/rust/issues/56889 for more on that. /// <https://github.com/rust-lang/rust/issues/56889> for more on that.
/// ///
/// If the previous call to `write` provided more (encoded) data than the delegate writer could /// If the previous call to `write` provided more (encoded) data than the delegate writer could
/// accept in a single call to its `write`, the remaining data is buffered. As long as buffered /// accept in a single call to its `write`, the remaining data is buffered. As long as buffered
@ -286,10 +314,9 @@ impl<W: Write> Write for EncoderWriter<W> {
self.extra_input[self.extra_input_occupied_len..MIN_ENCODE_CHUNK_SIZE] self.extra_input[self.extra_input_occupied_len..MIN_ENCODE_CHUNK_SIZE]
.copy_from_slice(&input[0..extra_input_read_len]); .copy_from_slice(&input[0..extra_input_read_len]);
let len = encode_to_slice( let len = self.engine.internal_encode(
&self.extra_input[0..MIN_ENCODE_CHUNK_SIZE], &self.extra_input[0..MIN_ENCODE_CHUNK_SIZE],
&mut self.output[..], &mut self.output[..],
self.config.char_set.encode_table(),
); );
debug_assert_eq!(4, len); debug_assert_eq!(4, len);
@ -335,10 +362,9 @@ impl<W: Write> Write for EncoderWriter<W> {
debug_assert_eq!(0, max_input_len % MIN_ENCODE_CHUNK_SIZE); debug_assert_eq!(0, max_input_len % MIN_ENCODE_CHUNK_SIZE);
debug_assert_eq!(0, input_chunks_to_encode_len % MIN_ENCODE_CHUNK_SIZE); debug_assert_eq!(0, input_chunks_to_encode_len % MIN_ENCODE_CHUNK_SIZE);
encoded_size += encode_to_slice( encoded_size += self.engine.internal_encode(
&input[..(input_chunks_to_encode_len)], &input[..(input_chunks_to_encode_len)],
&mut self.output[encoded_size..], &mut self.output[encoded_size..],
self.config.char_set.encode_table(),
); );
// not updating `self.output_occupied_len` here because if the below write fails, it should // not updating `self.output_occupied_len` here because if the below write fails, it should
@ -371,7 +397,7 @@ impl<W: Write> Write for EncoderWriter<W> {
} }
} }
impl<W: Write> Drop for EncoderWriter<W> { impl<'e, E: Engine, W: io::Write> Drop for EncoderWriter<'e, E, W> {
fn drop(&mut self) { fn drop(&mut self) {
if !self.panicked { if !self.panicked {
// like `BufWriter`, ignore errors during drop // like `BufWriter`, ignore errors during drop

View File

@ -1,10 +1,10 @@
use super::encoder::EncoderWriter; use super::encoder::EncoderWriter;
use crate::Config; use crate::engine::Engine;
use std::io; use std::io;
use std::io::Write;
/// A `Write` implementation that base64-encodes data using the provided config and accumulates the /// A `Write` implementation that base64-encodes data using the provided config and accumulates the
/// resulting base64 in memory, which is then exposed as a String via `into_inner()`. /// resulting base64 utf8 `&str` in a [StrConsumer] implementation (typically `String`), which is
/// then exposed via `into_inner()`.
/// ///
/// # Examples /// # Examples
/// ///
@ -12,8 +12,9 @@ use std::io::Write;
/// ///
/// ``` /// ```
/// use std::io::Write; /// use std::io::Write;
/// use base64::engine::general_purpose;
/// ///
/// let mut enc = base64::write::EncoderStringWriter::new(base64::STANDARD); /// let mut enc = base64::write::EncoderStringWriter::new(&general_purpose::STANDARD);
/// ///
/// enc.write_all(b"asdf").unwrap(); /// enc.write_all(b"asdf").unwrap();
/// ///
@ -23,14 +24,17 @@ use std::io::Write;
/// assert_eq!("YXNkZg==", &b64_string); /// assert_eq!("YXNkZg==", &b64_string);
/// ``` /// ```
/// ///
/// Or, append to an existing String: /// Or, append to an existing `String`, which implements `StrConsumer`:
/// ///
/// ``` /// ```
/// use std::io::Write; /// use std::io::Write;
/// use base64::engine::general_purpose;
/// ///
/// let mut buf = String::from("base64: "); /// let mut buf = String::from("base64: ");
/// ///
/// let mut enc = base64::write::EncoderStringWriter::from(&mut buf, base64::STANDARD); /// let mut enc = base64::write::EncoderStringWriter::from_consumer(
/// &mut buf,
/// &general_purpose::STANDARD);
/// ///
/// enc.write_all(b"asdf").unwrap(); /// enc.write_all(b"asdf").unwrap();
/// ///
@ -49,40 +53,38 @@ use std::io::Write;
/// ///
/// Because it has to validate that the base64 is UTF-8, it is about 80% as fast as writing plain /// Because it has to validate that the base64 is UTF-8, it is about 80% as fast as writing plain
/// bytes to a `io::Write`. /// bytes to a `io::Write`.
pub struct EncoderStringWriter<S: StrConsumer> { pub struct EncoderStringWriter<'e, E: Engine, S: StrConsumer> {
encoder: EncoderWriter<Utf8SingleCodeUnitWriter<S>>, encoder: EncoderWriter<'e, E, Utf8SingleCodeUnitWriter<S>>,
} }
impl<S: StrConsumer> EncoderStringWriter<S> { impl<'e, E: Engine, S: StrConsumer> EncoderStringWriter<'e, E, S> {
/// Create a EncoderStringWriter that will append to the provided `StrConsumer`. /// Create a EncoderStringWriter that will append to the provided `StrConsumer`.
pub fn from(str_consumer: S, config: Config) -> Self { pub fn from_consumer(str_consumer: S, engine: &'e E) -> Self {
EncoderStringWriter { EncoderStringWriter {
encoder: EncoderWriter::new(Utf8SingleCodeUnitWriter { str_consumer }, config), encoder: EncoderWriter::new(Utf8SingleCodeUnitWriter { str_consumer }, engine),
} }
} }
/// Encode all remaining buffered data, including any trailing incomplete input triples and /// Encode all remaining buffered data, including any trailing incomplete input triples and
/// associated padding. /// associated padding.
/// ///
/// Once this succeeds, no further writes or calls to this method are allowed.
///
/// Returns the base64-encoded form of the accumulated written data. /// Returns the base64-encoded form of the accumulated written data.
pub fn into_inner(mut self) -> S { pub fn into_inner(mut self) -> S {
self.encoder self.encoder
.finish() .finish()
.expect("Writing to a Vec<u8> should never fail") .expect("Writing to a consumer should never fail")
.str_consumer .str_consumer
} }
} }
impl EncoderStringWriter<String> { impl<'e, E: Engine> EncoderStringWriter<'e, E, String> {
/// Create a EncoderStringWriter that will encode into a new String with the provided config. /// Create a EncoderStringWriter that will encode into a new `String` with the provided config.
pub fn new(config: Config) -> Self { pub fn new(engine: &'e E) -> Self {
EncoderStringWriter::from(String::new(), config) EncoderStringWriter::from_consumer(String::new(), engine)
} }
} }
impl<S: StrConsumer> Write for EncoderStringWriter<S> { impl<'e, E: Engine, S: StrConsumer> io::Write for EncoderStringWriter<'e, E, S> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> { fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.encoder.write(buf) self.encoder.write(buf)
} }
@ -101,14 +103,14 @@ pub trait StrConsumer {
/// As for io::Write, `StrConsumer` is implemented automatically for `&mut S`. /// As for io::Write, `StrConsumer` is implemented automatically for `&mut S`.
impl<S: StrConsumer + ?Sized> StrConsumer for &mut S { impl<S: StrConsumer + ?Sized> StrConsumer for &mut S {
fn consume(&mut self, buf: &str) { fn consume(&mut self, buf: &str) {
(**self).consume(buf) (**self).consume(buf);
} }
} }
/// Pushes the str onto the end of the String /// Pushes the str onto the end of the String
impl StrConsumer for String { impl StrConsumer for String {
fn consume(&mut self, buf: &str) { fn consume(&mut self, buf: &str) {
self.push_str(buf) self.push_str(buf);
} }
} }
@ -138,9 +140,9 @@ impl<S: StrConsumer> io::Write for Utf8SingleCodeUnitWriter<S> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::encode_config_buf; use crate::{
use crate::tests::random_config; engine::Engine, tests::random_engine, write::encoder_string_writer::EncoderStringWriter,
use crate::write::encoder_string_writer::EncoderStringWriter; };
use rand::Rng; use rand::Rng;
use std::io::Write; use std::io::Write;
@ -160,10 +162,10 @@ mod tests {
orig_data.push(rng.gen()); orig_data.push(rng.gen());
} }
let config = random_config(&mut rng); let engine = random_engine(&mut rng);
encode_config_buf(&orig_data, config, &mut normal_encoded); engine.encode_string(&orig_data, &mut normal_encoded);
let mut stream_encoder = EncoderStringWriter::new(config); let mut stream_encoder = EncoderStringWriter::new(&engine);
// Write the first i bytes, then the rest // Write the first i bytes, then the rest
stream_encoder.write_all(&orig_data[0..i]).unwrap(); stream_encoder.write_all(&orig_data[0..i]).unwrap();
stream_encoder.write_all(&orig_data[i..]).unwrap(); stream_encoder.write_all(&orig_data[i..]).unwrap();

View File

@ -1,29 +1,39 @@
use super::EncoderWriter;
use crate::tests::random_config;
use crate::{encode_config, encode_config_buf, STANDARD_NO_PAD, URL_SAFE};
use std::io::{Cursor, Write}; use std::io::{Cursor, Write};
use std::{cmp, io, str}; use std::{cmp, io, str};
use rand::Rng; use rand::Rng;
use crate::{
alphabet::{STANDARD, URL_SAFE},
engine::{
general_purpose::{GeneralPurpose, NO_PAD, PAD},
Engine,
},
tests::random_engine,
};
use super::EncoderWriter;
const URL_SAFE_ENGINE: GeneralPurpose = GeneralPurpose::new(&URL_SAFE, PAD);
const NO_PAD_ENGINE: GeneralPurpose = GeneralPurpose::new(&STANDARD, NO_PAD);
#[test] #[test]
fn encode_three_bytes() { fn encode_three_bytes() {
let mut c = Cursor::new(Vec::new()); let mut c = Cursor::new(Vec::new());
{ {
let mut enc = EncoderWriter::new(&mut c, URL_SAFE); let mut enc = EncoderWriter::new(&mut c, &URL_SAFE_ENGINE);
let sz = enc.write(b"abc").unwrap(); let sz = enc.write(b"abc").unwrap();
assert_eq!(sz, 3); assert_eq!(sz, 3);
} }
assert_eq!(&c.get_ref()[..], encode_config("abc", URL_SAFE).as_bytes()); assert_eq!(&c.get_ref()[..], URL_SAFE_ENGINE.encode("abc").as_bytes());
} }
#[test] #[test]
fn encode_nine_bytes_two_writes() { fn encode_nine_bytes_two_writes() {
let mut c = Cursor::new(Vec::new()); let mut c = Cursor::new(Vec::new());
{ {
let mut enc = EncoderWriter::new(&mut c, URL_SAFE); let mut enc = EncoderWriter::new(&mut c, &URL_SAFE_ENGINE);
let sz = enc.write(b"abcdef").unwrap(); let sz = enc.write(b"abcdef").unwrap();
assert_eq!(sz, 6); assert_eq!(sz, 6);
@ -32,7 +42,7 @@ fn encode_nine_bytes_two_writes() {
} }
assert_eq!( assert_eq!(
&c.get_ref()[..], &c.get_ref()[..],
encode_config("abcdefghi", URL_SAFE).as_bytes() URL_SAFE_ENGINE.encode("abcdefghi").as_bytes()
); );
} }
@ -40,21 +50,21 @@ fn encode_nine_bytes_two_writes() {
fn encode_one_then_two_bytes() { fn encode_one_then_two_bytes() {
let mut c = Cursor::new(Vec::new()); let mut c = Cursor::new(Vec::new());
{ {
let mut enc = EncoderWriter::new(&mut c, URL_SAFE); let mut enc = EncoderWriter::new(&mut c, &URL_SAFE_ENGINE);
let sz = enc.write(b"a").unwrap(); let sz = enc.write(b"a").unwrap();
assert_eq!(sz, 1); assert_eq!(sz, 1);
let sz = enc.write(b"bc").unwrap(); let sz = enc.write(b"bc").unwrap();
assert_eq!(sz, 2); assert_eq!(sz, 2);
} }
assert_eq!(&c.get_ref()[..], encode_config("abc", URL_SAFE).as_bytes()); assert_eq!(&c.get_ref()[..], URL_SAFE_ENGINE.encode("abc").as_bytes());
} }
#[test] #[test]
fn encode_one_then_five_bytes() { fn encode_one_then_five_bytes() {
let mut c = Cursor::new(Vec::new()); let mut c = Cursor::new(Vec::new());
{ {
let mut enc = EncoderWriter::new(&mut c, URL_SAFE); let mut enc = EncoderWriter::new(&mut c, &URL_SAFE_ENGINE);
let sz = enc.write(b"a").unwrap(); let sz = enc.write(b"a").unwrap();
assert_eq!(sz, 1); assert_eq!(sz, 1);
@ -63,7 +73,7 @@ fn encode_one_then_five_bytes() {
} }
assert_eq!( assert_eq!(
&c.get_ref()[..], &c.get_ref()[..],
encode_config("abcdef", URL_SAFE).as_bytes() URL_SAFE_ENGINE.encode("abcdef").as_bytes()
); );
} }
@ -71,7 +81,7 @@ fn encode_one_then_five_bytes() {
fn encode_1_2_3_bytes() { fn encode_1_2_3_bytes() {
let mut c = Cursor::new(Vec::new()); let mut c = Cursor::new(Vec::new());
{ {
let mut enc = EncoderWriter::new(&mut c, URL_SAFE); let mut enc = EncoderWriter::new(&mut c, &URL_SAFE_ENGINE);
let sz = enc.write(b"a").unwrap(); let sz = enc.write(b"a").unwrap();
assert_eq!(sz, 1); assert_eq!(sz, 1);
@ -82,7 +92,7 @@ fn encode_1_2_3_bytes() {
} }
assert_eq!( assert_eq!(
&c.get_ref()[..], &c.get_ref()[..],
encode_config("abcdef", URL_SAFE).as_bytes() URL_SAFE_ENGINE.encode("abcdef").as_bytes()
); );
} }
@ -90,20 +100,20 @@ fn encode_1_2_3_bytes() {
fn encode_with_padding() { fn encode_with_padding() {
let mut c = Cursor::new(Vec::new()); let mut c = Cursor::new(Vec::new());
{ {
let mut enc = EncoderWriter::new(&mut c, URL_SAFE); let mut enc = EncoderWriter::new(&mut c, &URL_SAFE_ENGINE);
enc.write_all(b"abcd").unwrap(); enc.write_all(b"abcd").unwrap();
enc.flush().unwrap(); enc.flush().unwrap();
} }
assert_eq!(&c.get_ref()[..], encode_config("abcd", URL_SAFE).as_bytes()); assert_eq!(&c.get_ref()[..], URL_SAFE_ENGINE.encode("abcd").as_bytes());
} }
#[test] #[test]
fn encode_with_padding_multiple_writes() { fn encode_with_padding_multiple_writes() {
let mut c = Cursor::new(Vec::new()); let mut c = Cursor::new(Vec::new());
{ {
let mut enc = EncoderWriter::new(&mut c, URL_SAFE); let mut enc = EncoderWriter::new(&mut c, &URL_SAFE_ENGINE);
assert_eq!(1, enc.write(b"a").unwrap()); assert_eq!(1, enc.write(b"a").unwrap());
assert_eq!(2, enc.write(b"bc").unwrap()); assert_eq!(2, enc.write(b"bc").unwrap());
@ -114,7 +124,7 @@ fn encode_with_padding_multiple_writes() {
} }
assert_eq!( assert_eq!(
&c.get_ref()[..], &c.get_ref()[..],
encode_config("abcdefg", URL_SAFE).as_bytes() URL_SAFE_ENGINE.encode("abcdefg").as_bytes()
); );
} }
@ -122,7 +132,7 @@ fn encode_with_padding_multiple_writes() {
fn finish_writes_extra_byte() { fn finish_writes_extra_byte() {
let mut c = Cursor::new(Vec::new()); let mut c = Cursor::new(Vec::new());
{ {
let mut enc = EncoderWriter::new(&mut c, URL_SAFE); let mut enc = EncoderWriter::new(&mut c, &URL_SAFE_ENGINE);
assert_eq!(6, enc.write(b"abcdef").unwrap()); assert_eq!(6, enc.write(b"abcdef").unwrap());
@ -134,7 +144,7 @@ fn finish_writes_extra_byte() {
} }
assert_eq!( assert_eq!(
&c.get_ref()[..], &c.get_ref()[..],
encode_config("abcdefg", URL_SAFE).as_bytes() URL_SAFE_ENGINE.encode("abcdefg").as_bytes()
); );
} }
@ -142,17 +152,14 @@ fn finish_writes_extra_byte() {
fn write_partial_chunk_encodes_partial_chunk() { fn write_partial_chunk_encodes_partial_chunk() {
let mut c = Cursor::new(Vec::new()); let mut c = Cursor::new(Vec::new());
{ {
let mut enc = EncoderWriter::new(&mut c, STANDARD_NO_PAD); let mut enc = EncoderWriter::new(&mut c, &NO_PAD_ENGINE);
// nothing encoded yet // nothing encoded yet
assert_eq!(2, enc.write(b"ab").unwrap()); assert_eq!(2, enc.write(b"ab").unwrap());
// encoded here // encoded here
let _ = enc.finish().unwrap(); let _ = enc.finish().unwrap();
} }
assert_eq!( assert_eq!(&c.get_ref()[..], NO_PAD_ENGINE.encode("ab").as_bytes());
&c.get_ref()[..],
encode_config("ab", STANDARD_NO_PAD).as_bytes()
);
assert_eq!(3, c.get_ref().len()); assert_eq!(3, c.get_ref().len());
} }
@ -160,15 +167,12 @@ fn write_partial_chunk_encodes_partial_chunk() {
fn write_1_chunk_encodes_complete_chunk() { fn write_1_chunk_encodes_complete_chunk() {
let mut c = Cursor::new(Vec::new()); let mut c = Cursor::new(Vec::new());
{ {
let mut enc = EncoderWriter::new(&mut c, STANDARD_NO_PAD); let mut enc = EncoderWriter::new(&mut c, &NO_PAD_ENGINE);
assert_eq!(3, enc.write(b"abc").unwrap()); assert_eq!(3, enc.write(b"abc").unwrap());
let _ = enc.finish().unwrap(); let _ = enc.finish().unwrap();
} }
assert_eq!( assert_eq!(&c.get_ref()[..], NO_PAD_ENGINE.encode("abc").as_bytes());
&c.get_ref()[..],
encode_config("abc", STANDARD_NO_PAD).as_bytes()
);
assert_eq!(4, c.get_ref().len()); assert_eq!(4, c.get_ref().len());
} }
@ -176,16 +180,13 @@ fn write_1_chunk_encodes_complete_chunk() {
fn write_1_chunk_and_partial_encodes_only_complete_chunk() { fn write_1_chunk_and_partial_encodes_only_complete_chunk() {
let mut c = Cursor::new(Vec::new()); let mut c = Cursor::new(Vec::new());
{ {
let mut enc = EncoderWriter::new(&mut c, STANDARD_NO_PAD); let mut enc = EncoderWriter::new(&mut c, &NO_PAD_ENGINE);
// "d" not written // "d" not consumed since it's not a full chunk
assert_eq!(3, enc.write(b"abcd").unwrap()); assert_eq!(3, enc.write(b"abcd").unwrap());
let _ = enc.finish().unwrap(); let _ = enc.finish().unwrap();
} }
assert_eq!( assert_eq!(&c.get_ref()[..], NO_PAD_ENGINE.encode("abc").as_bytes());
&c.get_ref()[..],
encode_config("abc", STANDARD_NO_PAD).as_bytes()
);
assert_eq!(4, c.get_ref().len()); assert_eq!(4, c.get_ref().len());
} }
@ -193,16 +194,13 @@ fn write_1_chunk_and_partial_encodes_only_complete_chunk() {
fn write_2_partials_to_exactly_complete_chunk_encodes_complete_chunk() { fn write_2_partials_to_exactly_complete_chunk_encodes_complete_chunk() {
let mut c = Cursor::new(Vec::new()); let mut c = Cursor::new(Vec::new());
{ {
let mut enc = EncoderWriter::new(&mut c, STANDARD_NO_PAD); let mut enc = EncoderWriter::new(&mut c, &NO_PAD_ENGINE);
assert_eq!(1, enc.write(b"a").unwrap()); assert_eq!(1, enc.write(b"a").unwrap());
assert_eq!(2, enc.write(b"bc").unwrap()); assert_eq!(2, enc.write(b"bc").unwrap());
let _ = enc.finish().unwrap(); let _ = enc.finish().unwrap();
} }
assert_eq!( assert_eq!(&c.get_ref()[..], NO_PAD_ENGINE.encode("abc").as_bytes());
&c.get_ref()[..],
encode_config("abc", STANDARD_NO_PAD).as_bytes()
);
assert_eq!(4, c.get_ref().len()); assert_eq!(4, c.get_ref().len());
} }
@ -211,17 +209,14 @@ fn write_partial_then_enough_to_complete_chunk_but_not_complete_another_chunk_en
) { ) {
let mut c = Cursor::new(Vec::new()); let mut c = Cursor::new(Vec::new());
{ {
let mut enc = EncoderWriter::new(&mut c, STANDARD_NO_PAD); let mut enc = EncoderWriter::new(&mut c, &NO_PAD_ENGINE);
assert_eq!(1, enc.write(b"a").unwrap()); assert_eq!(1, enc.write(b"a").unwrap());
// doesn't consume "d" // doesn't consume "d"
assert_eq!(2, enc.write(b"bcd").unwrap()); assert_eq!(2, enc.write(b"bcd").unwrap());
let _ = enc.finish().unwrap(); let _ = enc.finish().unwrap();
} }
assert_eq!( assert_eq!(&c.get_ref()[..], NO_PAD_ENGINE.encode("abc").as_bytes());
&c.get_ref()[..],
encode_config("abc", STANDARD_NO_PAD).as_bytes()
);
assert_eq!(4, c.get_ref().len()); assert_eq!(4, c.get_ref().len());
} }
@ -229,17 +224,14 @@ fn write_partial_then_enough_to_complete_chunk_but_not_complete_another_chunk_en
fn write_partial_then_enough_to_complete_chunk_and_another_chunk_encodes_complete_chunks() { fn write_partial_then_enough_to_complete_chunk_and_another_chunk_encodes_complete_chunks() {
let mut c = Cursor::new(Vec::new()); let mut c = Cursor::new(Vec::new());
{ {
let mut enc = EncoderWriter::new(&mut c, STANDARD_NO_PAD); let mut enc = EncoderWriter::new(&mut c, &NO_PAD_ENGINE);
assert_eq!(1, enc.write(b"a").unwrap()); assert_eq!(1, enc.write(b"a").unwrap());
// completes partial chunk, and another chunk // completes partial chunk, and another chunk
assert_eq!(5, enc.write(b"bcdef").unwrap()); assert_eq!(5, enc.write(b"bcdef").unwrap());
let _ = enc.finish().unwrap(); let _ = enc.finish().unwrap();
} }
assert_eq!( assert_eq!(&c.get_ref()[..], NO_PAD_ENGINE.encode("abcdef").as_bytes());
&c.get_ref()[..],
encode_config("abcdef", STANDARD_NO_PAD).as_bytes()
);
assert_eq!(8, c.get_ref().len()); assert_eq!(8, c.get_ref().len());
} }
@ -248,7 +240,7 @@ fn write_partial_then_enough_to_complete_chunk_and_another_chunk_and_another_par
) { ) {
let mut c = Cursor::new(Vec::new()); let mut c = Cursor::new(Vec::new());
{ {
let mut enc = EncoderWriter::new(&mut c, STANDARD_NO_PAD); let mut enc = EncoderWriter::new(&mut c, &NO_PAD_ENGINE);
assert_eq!(1, enc.write(b"a").unwrap()); assert_eq!(1, enc.write(b"a").unwrap());
// completes partial chunk, and another chunk, with one more partial chunk that's not // completes partial chunk, and another chunk, with one more partial chunk that's not
@ -256,10 +248,7 @@ fn write_partial_then_enough_to_complete_chunk_and_another_chunk_and_another_par
assert_eq!(5, enc.write(b"bcdefe").unwrap()); assert_eq!(5, enc.write(b"bcdefe").unwrap());
let _ = enc.finish().unwrap(); let _ = enc.finish().unwrap();
} }
assert_eq!( assert_eq!(&c.get_ref()[..], NO_PAD_ENGINE.encode("abcdef").as_bytes());
&c.get_ref()[..],
encode_config("abcdef", STANDARD_NO_PAD).as_bytes()
);
assert_eq!(8, c.get_ref().len()); assert_eq!(8, c.get_ref().len());
} }
@ -267,13 +256,10 @@ fn write_partial_then_enough_to_complete_chunk_and_another_chunk_and_another_par
fn drop_calls_finish_for_you() { fn drop_calls_finish_for_you() {
let mut c = Cursor::new(Vec::new()); let mut c = Cursor::new(Vec::new());
{ {
let mut enc = EncoderWriter::new(&mut c, STANDARD_NO_PAD); let mut enc = EncoderWriter::new(&mut c, &NO_PAD_ENGINE);
assert_eq!(1, enc.write(b"a").unwrap()); assert_eq!(1, enc.write(b"a").unwrap());
} }
assert_eq!( assert_eq!(&c.get_ref()[..], NO_PAD_ENGINE.encode("a").as_bytes());
&c.get_ref()[..],
encode_config("a", STANDARD_NO_PAD).as_bytes()
);
assert_eq!(2, c.get_ref().len()); assert_eq!(2, c.get_ref().len());
} }
@ -295,11 +281,11 @@ fn every_possible_split_of_input() {
orig_data.push(rng.gen()); orig_data.push(rng.gen());
} }
let config = random_config(&mut rng); let engine = random_engine(&mut rng);
encode_config_buf(&orig_data, config, &mut normal_encoded); engine.encode_string(&orig_data, &mut normal_encoded);
{ {
let mut stream_encoder = EncoderWriter::new(&mut stream_encoded, config); let mut stream_encoder = EncoderWriter::new(&mut stream_encoded, &engine);
// Write the first i bytes, then the rest // Write the first i bytes, then the rest
stream_encoder.write_all(&orig_data[0..i]).unwrap(); stream_encoder.write_all(&orig_data[0..i]).unwrap();
stream_encoder.write_all(&orig_data[i..]).unwrap(); stream_encoder.write_all(&orig_data[i..]).unwrap();
@ -312,12 +298,12 @@ fn every_possible_split_of_input() {
#[test] #[test]
fn encode_random_config_matches_normal_encode_reasonable_input_len() { fn encode_random_config_matches_normal_encode_reasonable_input_len() {
// choose up to 2 * buf size, so ~half the time it'll use a full buffer // choose up to 2 * buf size, so ~half the time it'll use a full buffer
do_encode_random_config_matches_normal_encode(super::encoder::BUF_SIZE * 2) do_encode_random_config_matches_normal_encode(super::encoder::BUF_SIZE * 2);
} }
#[test] #[test]
fn encode_random_config_matches_normal_encode_tiny_input_len() { fn encode_random_config_matches_normal_encode_tiny_input_len() {
do_encode_random_config_matches_normal_encode(10) do_encode_random_config_matches_normal_encode(10);
} }
#[test] #[test]
@ -332,14 +318,14 @@ fn retrying_writes_that_error_with_interrupted_works() {
stream_encoded.clear(); stream_encoded.clear();
normal_encoded.clear(); normal_encoded.clear();
let orig_len: usize = rng.gen_range(100, 20_000); let orig_len: usize = rng.gen_range(100..20_000);
for _ in 0..orig_len { for _ in 0..orig_len {
orig_data.push(rng.gen()); orig_data.push(rng.gen());
} }
// encode the normal way // encode the normal way
let config = random_config(&mut rng); let engine = random_engine(&mut rng);
encode_config_buf(&orig_data, config, &mut normal_encoded); engine.encode_string(&orig_data, &mut normal_encoded);
// encode via the stream encoder // encode via the stream encoder
{ {
@ -350,12 +336,12 @@ fn retrying_writes_that_error_with_interrupted_works() {
fraction: 0.8, fraction: 0.8,
}; };
let mut stream_encoder = EncoderWriter::new(&mut interrupting_writer, config); let mut stream_encoder = EncoderWriter::new(&mut interrupting_writer, &engine);
let mut bytes_consumed = 0; let mut bytes_consumed = 0;
while bytes_consumed < orig_len { while bytes_consumed < orig_len {
// use short inputs since we want to use `extra` a lot as that's what needs rollback // use short inputs since we want to use `extra` a lot as that's what needs rollback
// when errors occur // when errors occur
let input_len: usize = cmp::min(rng.gen_range(0, 10), orig_len - bytes_consumed); let input_len: usize = cmp::min(rng.gen_range(0..10), orig_len - bytes_consumed);
retry_interrupted_write_all( retry_interrupted_write_all(
&mut stream_encoder, &mut stream_encoder,
@ -396,14 +382,14 @@ fn writes_that_only_write_part_of_input_and_sometimes_interrupt_produce_correct_
stream_encoded.clear(); stream_encoded.clear();
normal_encoded.clear(); normal_encoded.clear();
let orig_len: usize = rng.gen_range(100, 20_000); let orig_len: usize = rng.gen_range(100..20_000);
for _ in 0..orig_len { for _ in 0..orig_len {
orig_data.push(rng.gen()); orig_data.push(rng.gen());
} }
// encode the normal way // encode the normal way
let config = random_config(&mut rng); let engine = random_engine(&mut rng);
encode_config_buf(&orig_data, config, &mut normal_encoded); engine.encode_string(&orig_data, &mut normal_encoded);
// encode via the stream encoder // encode via the stream encoder
{ {
@ -415,11 +401,11 @@ fn writes_that_only_write_part_of_input_and_sometimes_interrupt_produce_correct_
no_interrupt_fraction: 0.1, no_interrupt_fraction: 0.1,
}; };
let mut stream_encoder = EncoderWriter::new(&mut partial_writer, config); let mut stream_encoder = EncoderWriter::new(&mut partial_writer, &engine);
let mut bytes_consumed = 0; let mut bytes_consumed = 0;
while bytes_consumed < orig_len { while bytes_consumed < orig_len {
// use at most medium-length inputs to exercise retry logic more aggressively // use at most medium-length inputs to exercise retry logic more aggressively
let input_len: usize = cmp::min(rng.gen_range(0, 100), orig_len - bytes_consumed); let input_len: usize = cmp::min(rng.gen_range(0..100), orig_len - bytes_consumed);
let res = let res =
stream_encoder.write(&orig_data[bytes_consumed..bytes_consumed + input_len]); stream_encoder.write(&orig_data[bytes_consumed..bytes_consumed + input_len]);
@ -475,22 +461,22 @@ fn do_encode_random_config_matches_normal_encode(max_input_len: usize) {
stream_encoded.clear(); stream_encoded.clear();
normal_encoded.clear(); normal_encoded.clear();
let orig_len: usize = rng.gen_range(100, 20_000); let orig_len: usize = rng.gen_range(100..20_000);
for _ in 0..orig_len { for _ in 0..orig_len {
orig_data.push(rng.gen()); orig_data.push(rng.gen());
} }
// encode the normal way // encode the normal way
let config = random_config(&mut rng); let engine = random_engine(&mut rng);
encode_config_buf(&orig_data, config, &mut normal_encoded); engine.encode_string(&orig_data, &mut normal_encoded);
// encode via the stream encoder // encode via the stream encoder
{ {
let mut stream_encoder = EncoderWriter::new(&mut stream_encoded, config); let mut stream_encoder = EncoderWriter::new(&mut stream_encoded, &engine);
let mut bytes_consumed = 0; let mut bytes_consumed = 0;
while bytes_consumed < orig_len { while bytes_consumed < orig_len {
let input_len: usize = let input_len: usize =
cmp::min(rng.gen_range(0, max_input_len), orig_len - bytes_consumed); cmp::min(rng.gen_range(0..max_input_len), orig_len - bytes_consumed);
// write a little bit of the data // write a little bit of the data
stream_encoder stream_encoder
@ -520,7 +506,7 @@ struct InterruptingWriter<'a, W: 'a + Write, R: 'a + Rng> {
impl<'a, W: Write, R: Rng> Write for InterruptingWriter<'a, W, R> { impl<'a, W: Write, R: Rng> Write for InterruptingWriter<'a, W, R> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> { fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
if self.rng.gen_range(0.0, 1.0) <= self.fraction { if self.rng.gen_range(0.0..1.0) <= self.fraction {
return Err(io::Error::new(io::ErrorKind::Interrupted, "interrupted")); return Err(io::Error::new(io::ErrorKind::Interrupted, "interrupted"));
} }
@ -528,7 +514,7 @@ impl<'a, W: Write, R: Rng> Write for InterruptingWriter<'a, W, R> {
} }
fn flush(&mut self) -> io::Result<()> { fn flush(&mut self) -> io::Result<()> {
if self.rng.gen_range(0.0, 1.0) <= self.fraction { if self.rng.gen_range(0.0..1.0) <= self.fraction {
return Err(io::Error::new(io::ErrorKind::Interrupted, "interrupted")); return Err(io::Error::new(io::ErrorKind::Interrupted, "interrupted"));
} }
@ -548,17 +534,17 @@ struct PartialInterruptingWriter<'a, W: 'a + Write, R: 'a + Rng> {
impl<'a, W: Write, R: Rng> Write for PartialInterruptingWriter<'a, W, R> { impl<'a, W: Write, R: Rng> Write for PartialInterruptingWriter<'a, W, R> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> { fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
if self.rng.gen_range(0.0, 1.0) > self.no_interrupt_fraction { if self.rng.gen_range(0.0..1.0) > self.no_interrupt_fraction {
return Err(io::Error::new(io::ErrorKind::Interrupted, "interrupted")); return Err(io::Error::new(io::ErrorKind::Interrupted, "interrupted"));
} }
if self.rng.gen_range(0.0, 1.0) <= self.full_input_fraction || buf.len() == 0 { if self.rng.gen_range(0.0..1.0) <= self.full_input_fraction || buf.is_empty() {
// pass through the buf untouched // pass through the buf untouched
self.w.write(buf) self.w.write(buf)
} else { } else {
// only use a prefix of it // only use a prefix of it
self.w self.w
.write(&buf[0..(self.rng.gen_range(0, buf.len() - 1))]) .write(&buf[0..(self.rng.gen_range(0..(buf.len() - 1)))])
} }
} }

View File

@ -1,8 +1,11 @@
//! Implementations of `io::Write` to transparently handle base64. //! Implementations of `io::Write` to transparently handle base64.
mod encoder; mod encoder;
mod encoder_string_writer; mod encoder_string_writer;
pub use self::encoder::EncoderWriter;
pub use self::encoder_string_writer::EncoderStringWriter; pub use self::{
encoder::EncoderWriter,
encoder_string_writer::{EncoderStringWriter, StrConsumer},
};
#[cfg(test)] #[cfg(test)]
mod encoder_tests; mod encoder_tests;

View File

@ -1,44 +1,9 @@
extern crate base64; use base64::{
alphabet::URL_SAFE, engine::general_purpose::PAD, engine::general_purpose::STANDARD, *,
use base64::*; };
fn compare_encode(expected: &str, target: &[u8]) { fn compare_encode(expected: &str, target: &[u8]) {
assert_eq!(expected, encode(target)); assert_eq!(expected, STANDARD.encode(target));
}
#[test]
fn encode_rfc4648_0() {
compare_encode("", b"");
}
#[test]
fn encode_rfc4648_1() {
compare_encode("Zg==", b"f");
}
#[test]
fn encode_rfc4648_2() {
compare_encode("Zm8=", b"fo");
}
#[test]
fn encode_rfc4648_3() {
compare_encode("Zm9v", b"foo");
}
#[test]
fn encode_rfc4648_4() {
compare_encode("Zm9vYg==", b"foob");
}
#[test]
fn encode_rfc4648_5() {
compare_encode("Zm9vYmE=", b"fooba");
}
#[test]
fn encode_rfc4648_6() {
compare_encode("Zm9vYmFy", b"foobar");
} }
#[test] #[test]
@ -90,16 +55,6 @@ fn encode_all_bytes_url() {
-AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq\ -AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq\
-wsbKztLW2t7i5uru8vb6_wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t_g4eLj5OXm5-jp6uvs7e7v8PHy\ -wsbKztLW2t7i5uru8vb6_wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t_g4eLj5OXm5-jp6uvs7e7v8PHy\
8_T19vf4-fr7_P3-_w==", 8_T19vf4-fr7_P3-_w==",
encode_config(&bytes, URL_SAFE) &engine::GeneralPurpose::new(&URL_SAFE, PAD).encode(&bytes)
);
}
#[test]
fn encode_url_safe_without_padding() {
let encoded = encode_config(b"alice", URL_SAFE_NO_PAD);
assert_eq!(&encoded, "YWxpY2U");
assert_eq!(
String::from_utf8(decode(&encoded).unwrap()).unwrap(),
"alice"
); );
} }

View File

@ -1,18 +1,15 @@
extern crate base64; use rand::{Rng, SeedableRng};
extern crate rand;
use rand::{FromEntropy, Rng};
use base64::engine::{general_purpose::STANDARD, Engine};
use base64::*; use base64::*;
mod helpers; use base64::engine::general_purpose::{GeneralPurpose, NO_PAD};
use self::helpers::*;
// generate random contents of the specified length and test encode/decode roundtrip // generate random contents of the specified length and test encode/decode roundtrip
fn roundtrip_random( fn roundtrip_random<E: Engine>(
byte_buf: &mut Vec<u8>, byte_buf: &mut Vec<u8>,
str_buf: &mut String, str_buf: &mut String,
config: Config, engine: &E,
byte_len: usize, byte_len: usize,
approx_values_per_byte: u8, approx_values_per_byte: u8,
max_rounds: u64, max_rounds: u64,
@ -30,8 +27,8 @@ fn roundtrip_random(
byte_buf.push(r.gen::<u8>()); byte_buf.push(r.gen::<u8>());
} }
encode_config_buf(&byte_buf, config, str_buf); engine.encode_string(&byte_buf, str_buf);
decode_config_buf(&str_buf, config, &mut decode_buf).unwrap(); engine.decode_vec(&str_buf, &mut decode_buf).unwrap();
assert_eq!(byte_buf, &decode_buf); assert_eq!(byte_buf, &decode_buf);
} }
@ -52,17 +49,13 @@ fn calculate_number_of_rounds(byte_len: usize, approx_values_per_byte: u8, max:
prod prod
} }
fn no_pad_config() -> Config {
Config::new(CharacterSet::Standard, false)
}
#[test] #[test]
fn roundtrip_random_short_standard() { fn roundtrip_random_short_standard() {
let mut byte_buf: Vec<u8> = Vec::new(); let mut byte_buf: Vec<u8> = Vec::new();
let mut str_buf = String::new(); let mut str_buf = String::new();
for input_len in 0..40 { for input_len in 0..40 {
roundtrip_random(&mut byte_buf, &mut str_buf, STANDARD, input_len, 4, 10000); roundtrip_random(&mut byte_buf, &mut str_buf, &STANDARD, input_len, 4, 10000);
} }
} }
@ -72,7 +65,7 @@ fn roundtrip_random_with_fast_loop_standard() {
let mut str_buf = String::new(); let mut str_buf = String::new();
for input_len in 40..100 { for input_len in 40..100 {
roundtrip_random(&mut byte_buf, &mut str_buf, STANDARD, input_len, 4, 1000); roundtrip_random(&mut byte_buf, &mut str_buf, &STANDARD, input_len, 4, 1000);
} }
} }
@ -81,15 +74,9 @@ fn roundtrip_random_short_no_padding() {
let mut byte_buf: Vec<u8> = Vec::new(); let mut byte_buf: Vec<u8> = Vec::new();
let mut str_buf = String::new(); let mut str_buf = String::new();
let engine = GeneralPurpose::new(&alphabet::STANDARD, NO_PAD);
for input_len in 0..40 { for input_len in 0..40 {
roundtrip_random( roundtrip_random(&mut byte_buf, &mut str_buf, &engine, input_len, 4, 10000);
&mut byte_buf,
&mut str_buf,
no_pad_config(),
input_len,
4,
10000,
);
} }
} }
@ -98,15 +85,10 @@ fn roundtrip_random_no_padding() {
let mut byte_buf: Vec<u8> = Vec::new(); let mut byte_buf: Vec<u8> = Vec::new();
let mut str_buf = String::new(); let mut str_buf = String::new();
let engine = GeneralPurpose::new(&alphabet::STANDARD, NO_PAD);
for input_len in 40..100 { for input_len in 40..100 {
roundtrip_random( roundtrip_random(&mut byte_buf, &mut str_buf, &engine, input_len, 4, 1000);
&mut byte_buf,
&mut str_buf,
no_pad_config(),
input_len,
4,
1000,
);
} }
} }
@ -120,13 +102,14 @@ fn roundtrip_decode_trailing_10_bytes() {
// to handle that case. // to handle that case.
for num_quads in 0..25 { for num_quads in 0..25 {
let mut s: String = std::iter::repeat("ABCD").take(num_quads).collect(); let mut s: String = "ABCD".repeat(num_quads);
s.push_str("EFGHIJKLZg"); s.push_str("EFGHIJKLZg");
let decoded = decode(&s).unwrap(); let engine = GeneralPurpose::new(&alphabet::STANDARD, NO_PAD);
let decoded = engine.decode(&s).unwrap();
assert_eq!(num_quads * 3 + 7, decoded.len()); assert_eq!(num_quads * 3 + 7, decoded.len());
assert_eq!(s, encode_config(&decoded, STANDARD_NO_PAD)); assert_eq!(s, engine.encode(&decoded));
} }
} }
@ -140,55 +123,39 @@ fn display_wrapper_matches_normal_encode() {
bytes.push(255); bytes.push(255);
assert_eq!( assert_eq!(
encode(&bytes), STANDARD.encode(&bytes),
format!( format!("{}", display::Base64Display::new(&bytes, &STANDARD))
"{}",
base64::display::Base64Display::with_config(&bytes, STANDARD)
)
); );
} }
#[test] #[test]
fn because_we_can() { fn encode_engine_slice_error_when_buffer_too_small() {
compare_decode("alice", "YWxpY2U="); for num_triples in 1..100 {
compare_decode("alice", &encode(b"alice")); let input = "AAA".repeat(num_triples);
compare_decode("alice", &encode(&decode(&encode(b"alice")).unwrap())); let mut vec = vec![0; (num_triples - 1) * 4];
} assert_eq!(
EncodeSliceError::OutputSliceTooSmall,
#[test] STANDARD.encode_slice(&input, &mut vec).unwrap_err()
fn encode_config_slice_can_use_inline_buffer() { );
let mut buf: [u8; 22] = [0; 22]; vec.push(0);
let mut larger_buf: [u8; 24] = [0; 24]; assert_eq!(
let mut input: [u8; 16] = [0; 16]; EncodeSliceError::OutputSliceTooSmall,
STANDARD.encode_slice(&input, &mut vec).unwrap_err()
let mut rng = rand::rngs::SmallRng::from_entropy(); );
for elt in &mut input { vec.push(0);
*elt = rng.gen(); assert_eq!(
EncodeSliceError::OutputSliceTooSmall,
STANDARD.encode_slice(&input, &mut vec).unwrap_err()
);
vec.push(0);
assert_eq!(
EncodeSliceError::OutputSliceTooSmall,
STANDARD.encode_slice(&input, &mut vec).unwrap_err()
);
vec.push(0);
assert_eq!(
num_triples * 4,
STANDARD.encode_slice(&input, &mut vec).unwrap()
);
} }
assert_eq!(22, encode_config_slice(&input, STANDARD_NO_PAD, &mut buf));
let decoded = decode_config(&buf, STANDARD_NO_PAD).unwrap();
assert_eq!(decoded, input);
// let's try it again with padding
assert_eq!(24, encode_config_slice(&input, STANDARD, &mut larger_buf));
let decoded = decode_config(&buf, STANDARD).unwrap();
assert_eq!(decoded, input);
}
#[test]
#[should_panic(expected = "index 24 out of range for slice of length 22")]
fn encode_config_slice_panics_when_buffer_too_small() {
let mut buf: [u8; 22] = [0; 22];
let mut input: [u8; 16] = [0; 16];
let mut rng = rand::rngs::SmallRng::from_entropy();
for elt in &mut input {
*elt = rng.gen();
}
encode_config_slice(&input, STANDARD, &mut buf);
} }

View File

@ -1 +1 @@
{"files":{"CHANGELOG.md":"3ae0ceffbd69f54380bb44fdaf82dd674015471875a8da55686718afc3e58bdd","Cargo.toml":"5e1c5c02693e7afe119c1f82caec24ad41f51ce3b6899393ebeb1769ff50f1ab","LICENSE":"45f522cacecb1023856e46df79ca625dfc550c94910078bd8aec6e02880b3d42","README.md":"b691d6e144eb133c181e869dc2f6a6eedbf76c4f832a9ecfbed5b9c560160c7f","benches/buf.rs":"f76240b8c8872185d831382216eb536b3e05b23913c815cd36edd5d903fbeaf7","benches/bytes.rs":"dc5289a9ce82be35e71ed5853ab33aa108a30460e481135f6058fe4d2f7dc15e","benches/bytes_mut.rs":"1326fe6224b26826228e02b4133151e756f38152c2d9cfe66adf83af76c3ec98","ci/miri.sh":"1f27dc786a0f1e930c1c8429b1d60d2e107ff6998ec8efd4674c78a5d0594dd7","ci/test-stable.sh":"57dd709bc25a20103ee85e24965566900817b2e603f067fb1251a5c03e4b1d93","ci/tsan.sh":"466b86b19225dd26c756cf2252cb1973f87a145642c99364b462ed7ceb55c7dd","src/buf/buf_impl.rs":"bdd9d5bc3318185ef1bea8d7c6a9dd3712ec297e0045fd84024f188c0ad96ac0","src/buf/buf_mut.rs":"d4387228d687414d0ad3eb2bd1c2f0fc84be8ec7d8746b95075f186b467293d4","src/buf/chain.rs":"d31989886d8ca01a9e3b42d6756391f5bdf8c102f83fa6dac51dd86312a91c14","src/buf/iter.rs":"49e9990a2303252ef7c66c2cc24459097dbbf4900c978453982ef513467bbf67","src/buf/limit.rs":"e005ba140b70f68654877c96b981a220477e415ff5c92438c1b0cb9bc866d872","src/buf/mod.rs":"19ff6fb7e19cba3884bc3f1a50ef20117dbc807f6d146ed355f42344a74fdf44","src/buf/reader.rs":"856c1e7129a1eceaa3c8f9ed4da8c3b5e1cc267eeffa99fa8f7c56c5ca7834d1","src/buf/take.rs":"a897e79bf579391227816973b2aa1f1d63614bd48bc029d9371f61607dcfa23f","src/buf/uninit_slice.rs":"0532041bf0128311eb6a2edbc4b720be30395882744dbc437874753fd8f249b4","src/buf/vec_deque.rs":"8d552c26ac6ce28a471f74c388e4749432e86b1d8f5a9759b9fc32a2549d395f","src/buf/writer.rs":"c92b5f8b9b42e2e784de474c987fe4ac50af4b5c51ac9548d19a54e8ac9ff521","src/bytes.rs":"f8d26a3de35977225abb4a416846f713f3ab2dc1215119bdac6b43ce4ef3fa0e","src/bytes_mut.rs":"6dab0856996c1bf07fd8786cf876a6c8c27df001ae78d23ba2d220d6d3ef9360","src/fmt/debug.rs":"19ebe7e5516e40ab712995f3ec2e0ba78ddfa905cce117e6d01e8eb330f3970a","src/fmt/hex.rs":"13755ec6f1b79923e1f1a05c51b179a38c03c40bb8ed2db0210e8901812e61e7","src/fmt/mod.rs":"176da4e359da99b8e5cf16e480cb7b978f574876827f1b9bb9c08da4d74ac0f5","src/lib.rs":"d8be90ade0cf78a30d73493086c109049d8ff442d69589a07f16480578eb4b17","src/loom.rs":"5dc97a5afce14875a66e44cbf0afa67e084c8b6b8c560bc14e7a70ef73aee96e","src/serde.rs":"3ecd7e828cd4c2b7db93c807cb1548fad209e674df493edf7cda69a7b04d405d","tests/test_buf.rs":"a04fb90644fcf0444092c49a4ca848bb0fd8b2ffeeebcb705eeea2de58560859","tests/test_buf_mut.rs":"5643866cd7b0967fb36053a1da73a23b26ffaa2746c05dca91e82df91aee7f81","tests/test_bytes.rs":"2349daa82fd079037ba4059273a8339fadf2a1d59ac2ce58e83269de6f133a0f","tests/test_bytes_odd_alloc.rs":"9a02cc9b1f09e2353554d9a33f6630250e6b5cf04faa00de3b9fecf247e65edb","tests/test_bytes_vec_alloc.rs":"2b686b6ab44f924e69d8270a4f256eb3626a3b4db8c1919b74bc422c10124899","tests/test_chain.rs":"69661c21b7257bf9c52792cb66d16f4dd5b62131381b8e6dbee1fb177433aec9","tests/test_debug.rs":"13299107172809e8cbbd823964ac9450cd0d6b6de79f2e6a2e0f44b9225a0593","tests/test_iter.rs":"c1f46823df26a90139645fd8728a03138edd95b2849dfec830452a80ddd9726d","tests/test_reader.rs":"bf83669d4e0960dad6aa47b46a9a454814fab626eb83572aba914c3d71618f43","tests/test_serde.rs":"2691f891796ba259de0ecf926de05c514f4912cc5fcd3e6a1591efbcd23ed4d0","tests/test_take.rs":"db01bf6855097f318336e90d12c0725a92cee426d330e477a6bd1d32dac34a27"},"package":"c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"} {"files":{"CHANGELOG.md":"f0a59a40feba4e8949867f78306369b348f173a30103242fc6b56ccd695164a2","Cargo.toml":"4241f067d731580f52f81d6af804ec4c8d5ff2e67a4fc6144732eb465f48cadf","LICENSE":"45f522cacecb1023856e46df79ca625dfc550c94910078bd8aec6e02880b3d42","README.md":"b691d6e144eb133c181e869dc2f6a6eedbf76c4f832a9ecfbed5b9c560160c7f","benches/buf.rs":"72e6b6120b52d568da068f17c66a793d65602e400c595778581b63092e41d8dc","benches/bytes.rs":"f8cc255be7e8afedf6ade95cd529d105c537c5ec51110d46d470a26b497afa05","benches/bytes_mut.rs":"1326fe6224b26826228e02b4133151e756f38152c2d9cfe66adf83af76c3ec98","ci/miri.sh":"1ee54575b55a0e495e52ca1a934beed674bc8f375f03c4cfc3e81d221ec4fe98","ci/test-stable.sh":"57dd709bc25a20103ee85e24965566900817b2e603f067fb1251a5c03e4b1d93","ci/tsan.sh":"466b86b19225dd26c756cf2252cb1973f87a145642c99364b462ed7ceb55c7dd","clippy.toml":"8522f448dfa3b33ac334ce47d233ebb6b58e8ae115e45107a64fc1b4510fe560","src/buf/buf_impl.rs":"b8d501d3d9c887336560bbe1c334f3f1b33d9c3e973b727f3928f265c08a8fd5","src/buf/buf_mut.rs":"88ea083992db5afba46b609d70fe73fc3959d96c99973cf4a6863a21874b80b6","src/buf/chain.rs":"46ec16a7cc370374218c2621ad738df77d95b25216099900ad9195a08a234375","src/buf/iter.rs":"49e9990a2303252ef7c66c2cc24459097dbbf4900c978453982ef513467bbf67","src/buf/limit.rs":"e005ba140b70f68654877c96b981a220477e415ff5c92438c1b0cb9bc866d872","src/buf/mod.rs":"19ff6fb7e19cba3884bc3f1a50ef20117dbc807f6d146ed355f42344a74fdf44","src/buf/reader.rs":"856c1e7129a1eceaa3c8f9ed4da8c3b5e1cc267eeffa99fa8f7c56c5ca7834d1","src/buf/take.rs":"a897e79bf579391227816973b2aa1f1d63614bd48bc029d9371f61607dcfa23f","src/buf/uninit_slice.rs":"fccd4e90f5b4f7eb7774e10d7da0838952e4ddc5b324301d37bb7680eac26e36","src/buf/vec_deque.rs":"8d552c26ac6ce28a471f74c388e4749432e86b1d8f5a9759b9fc32a2549d395f","src/buf/writer.rs":"c92b5f8b9b42e2e784de474c987fe4ac50af4b5c51ac9548d19a54e8ac9ff521","src/bytes.rs":"e6a3cb50ec50ca026137f0f23a912b7fe8a5d77a826b2ae7e6df866b9196114d","src/bytes_mut.rs":"64fe05016fef2cbaa5b0b3d0d01279b99ad0ecc6d9ed99ce27e43fe9c6b2844b","src/fmt/debug.rs":"97b23cfa1d2701fa187005421302eeb260e635cd4f9a9e02b044ff89fcc8b8ad","src/fmt/hex.rs":"13755ec6f1b79923e1f1a05c51b179a38c03c40bb8ed2db0210e8901812e61e7","src/fmt/mod.rs":"176da4e359da99b8e5cf16e480cb7b978f574876827f1b9bb9c08da4d74ac0f5","src/lib.rs":"d8be90ade0cf78a30d73493086c109049d8ff442d69589a07f16480578eb4b17","src/loom.rs":"eb3f577d8cce39a84155c241c4dc308f024631f02085833f7fe9f0ea817bcea9","src/serde.rs":"3ecd7e828cd4c2b7db93c807cb1548fad209e674df493edf7cda69a7b04d405d","tests/test_buf.rs":"a04fb90644fcf0444092c49a4ca848bb0fd8b2ffeeebcb705eeea2de58560859","tests/test_buf_mut.rs":"5643866cd7b0967fb36053a1da73a23b26ffaa2746c05dca91e82df91aee7f81","tests/test_bytes.rs":"fe5beb749d3d48ec0c57d2ecf0ca56edc5a08cdc07a7842d863ee64d78d0ce69","tests/test_bytes_odd_alloc.rs":"aeb7a86bf8b31f67b6f453399f3649e0d3878247debc1325d98e66201b1da15f","tests/test_bytes_vec_alloc.rs":"dd7e3c3a71abcfdcad7e3b2f52a6bd106ad6ea0d4bc634372e81dae097233cf0","tests/test_chain.rs":"e9f094539bb42b3135f50033c44122a6b44cf0f953e51e8b488f43243f1e7f10","tests/test_debug.rs":"13299107172809e8cbbd823964ac9450cd0d6b6de79f2e6a2e0f44b9225a0593","tests/test_iter.rs":"c1f46823df26a90139645fd8728a03138edd95b2849dfec830452a80ddd9726d","tests/test_reader.rs":"bf83669d4e0960dad6aa47b46a9a454814fab626eb83572aba914c3d71618f43","tests/test_serde.rs":"2691f891796ba259de0ecf926de05c514f4912cc5fcd3e6a1591efbcd23ed4d0","tests/test_take.rs":"db01bf6855097f318336e90d12c0725a92cee426d330e477a6bd1d32dac34a27"},"package":"dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c"}

View File

@ -1,3 +1,51 @@
# 1.3.0 (November 20, 2022)
### Added
- Rename and expose `BytesMut::spare_capacity_mut` (#572)
- Implement native-endian get and put functions for `Buf` and `BufMut` (#576)
### Fixed
- Don't have important data in unused capacity when calling reserve (#563)
### Documented
- `Bytes::new` etc should return `Self` not `Bytes` (#568)
# 1.2.1 (July 30, 2022)
### Fixed
- Fix unbounded memory growth when using `reserve` (#560)
# 1.2.0 (July 19, 2022)
### Added
- Add `BytesMut::zeroed` (#517)
- Implement `Extend<Bytes>` for `BytesMut` (#527)
- Add conversion from `BytesMut` to `Vec<u8>` (#543, #554)
- Add conversion from `Bytes` to `Vec<u8>` (#547)
- Add `UninitSlice::as_uninit_slice_mut()` (#548)
- Add const to `Bytes::{len,is_empty}` (#514)
### Changed
- Reuse vector in `BytesMut::reserve` (#539, #544)
### Fixed
- Make miri happy (#515, #523, #542, #545, #553)
- Make tsan happy (#541)
- Fix `remaining_mut()` on chain (#488)
- Fix amortized asymptotics of `BytesMut` (#555)
### Documented
- Redraw layout diagram with box drawing characters (#539)
- Clarify `BytesMut::unsplit` docs (#535)
# 1.1.0 (August 25, 2021) # 1.1.0 (August 25, 2021)
### Added ### Added

View File

@ -3,36 +3,52 @@
# When uploading crates to the registry Cargo will automatically # When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility # "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies # with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies # to registry (e.g., crates.io) dependencies.
# #
# If you believe there's an error in this file please file an # If you are reading this file be aware that the original Cargo.toml
# issue against the rust-lang/cargo repository. If you're # will likely look very different (and much more reasonable).
# editing this file be aware that the upstream Cargo.toml # See Cargo.toml.orig for the original contents.
# will likely look very different (and much more reasonable)
[package] [package]
edition = "2018" edition = "2018"
name = "bytes" name = "bytes"
version = "1.1.0" version = "1.3.0"
authors = ["Carl Lerche <me@carllerche.com>", "Sean McArthur <sean@seanmonstar.com>"] authors = [
"Carl Lerche <me@carllerche.com>",
"Sean McArthur <sean@seanmonstar.com>",
]
description = "Types and traits for working with bytes" description = "Types and traits for working with bytes"
readme = "README.md" readme = "README.md"
keywords = ["buffers", "zero-copy", "io"] keywords = [
categories = ["network-programming", "data-structures"] "buffers",
"zero-copy",
"io",
]
categories = [
"network-programming",
"data-structures",
]
license = "MIT" license = "MIT"
repository = "https://github.com/tokio-rs/bytes" repository = "https://github.com/tokio-rs/bytes"
[package.metadata.docs.rs] [package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"] rustdoc-args = [
"--cfg",
"docsrs",
]
[dependencies.serde] [dependencies.serde]
version = "1.0.60" version = "1.0.60"
features = ["alloc"] features = ["alloc"]
optional = true optional = true
default-features = false default-features = false
[dev-dependencies.serde_test] [dev-dependencies.serde_test]
version = "1.0" version = "1.0"
[features] [features]
default = ["std"] default = ["std"]
std = [] std = []
[target."cfg(loom)".dev-dependencies.loom] [target."cfg(loom)".dev-dependencies.loom]
version = "0.5" version = "0.5"

View File

@ -46,7 +46,7 @@ impl TestBuf {
} }
impl Buf for TestBuf { impl Buf for TestBuf {
fn remaining(&self) -> usize { fn remaining(&self) -> usize {
return self.buf.len() - self.pos; self.buf.len() - self.pos
} }
fn advance(&mut self, cnt: usize) { fn advance(&mut self, cnt: usize) {
self.pos += cnt; self.pos += cnt;

View File

@ -88,6 +88,7 @@ fn from_long_slice(b: &mut Bencher) {
#[bench] #[bench]
fn slice_empty(b: &mut Bencher) { fn slice_empty(b: &mut Bencher) {
b.iter(|| { b.iter(|| {
// `clone` is to convert to ARC
let b = Bytes::from(vec![17; 1024]).clone(); let b = Bytes::from(vec![17; 1024]).clone();
for i in 0..1000 { for i in 0..1000 {
test::black_box(b.slice(i % 100..i % 100)); test::black_box(b.slice(i % 100..i % 100));

View File

@ -1,11 +1,11 @@
#!/bin/bash #!/bin/bash
set -e set -e
MIRI_NIGHTLY=nightly-$(curl -s https://rust-lang.github.io/rustup-components-history/x86_64-unknown-linux-gnu/miri) rustup toolchain install nightly --component miri
echo "Installing latest nightly with Miri: $MIRI_NIGHTLY" rustup override set nightly
rustup set profile minimal cargo miri setup
rustup default "$MIRI_NIGHTLY"
rustup component add miri export MIRIFLAGS="-Zmiri-strict-provenance"
cargo miri test cargo miri test
cargo miri test --target mips64-unknown-linux-gnuabi64 cargo miri test --target mips64-unknown-linux-gnuabi64

1
zeroidc/vendor/bytes/clippy.toml vendored Normal file
View File

@ -0,0 +1 @@
msrv = "1.39"

View File

@ -354,6 +354,29 @@ pub trait Buf {
buf_get_impl!(self, u16::from_le_bytes); buf_get_impl!(self, u16::from_le_bytes);
} }
/// Gets an unsigned 16 bit integer from `self` in native-endian byte order.
///
/// The current position is advanced by 2.
///
/// # Examples
///
/// ```
/// use bytes::Buf;
///
/// let mut buf: &[u8] = match cfg!(target_endian = "big") {
/// true => b"\x08\x09 hello",
/// false => b"\x09\x08 hello",
/// };
/// assert_eq!(0x0809, buf.get_u16_ne());
/// ```
///
/// # Panics
///
/// This function panics if there is not enough remaining data in `self`.
fn get_u16_ne(&mut self) -> u16 {
buf_get_impl!(self, u16::from_ne_bytes);
}
/// Gets a signed 16 bit integer from `self` in big-endian byte order. /// Gets a signed 16 bit integer from `self` in big-endian byte order.
/// ///
/// The current position is advanced by 2. /// The current position is advanced by 2.
@ -394,6 +417,29 @@ pub trait Buf {
buf_get_impl!(self, i16::from_le_bytes); buf_get_impl!(self, i16::from_le_bytes);
} }
/// Gets a signed 16 bit integer from `self` in native-endian byte order.
///
/// The current position is advanced by 2.
///
/// # Examples
///
/// ```
/// use bytes::Buf;
///
/// let mut buf: &[u8] = match cfg!(target_endian = "big") {
/// true => b"\x08\x09 hello",
/// false => b"\x09\x08 hello",
/// };
/// assert_eq!(0x0809, buf.get_i16_ne());
/// ```
///
/// # Panics
///
/// This function panics if there is not enough remaining data in `self`.
fn get_i16_ne(&mut self) -> i16 {
buf_get_impl!(self, i16::from_ne_bytes);
}
/// Gets an unsigned 32 bit integer from `self` in the big-endian byte order. /// Gets an unsigned 32 bit integer from `self` in the big-endian byte order.
/// ///
/// The current position is advanced by 4. /// The current position is advanced by 4.
@ -434,6 +480,29 @@ pub trait Buf {
buf_get_impl!(self, u32::from_le_bytes); buf_get_impl!(self, u32::from_le_bytes);
} }
/// Gets an unsigned 32 bit integer from `self` in native-endian byte order.
///
/// The current position is advanced by 4.
///
/// # Examples
///
/// ```
/// use bytes::Buf;
///
/// let mut buf: &[u8] = match cfg!(target_endian = "big") {
/// true => b"\x08\x09\xA0\xA1 hello",
/// false => b"\xA1\xA0\x09\x08 hello",
/// };
/// assert_eq!(0x0809A0A1, buf.get_u32_ne());
/// ```
///
/// # Panics
///
/// This function panics if there is not enough remaining data in `self`.
fn get_u32_ne(&mut self) -> u32 {
buf_get_impl!(self, u32::from_ne_bytes);
}
/// Gets a signed 32 bit integer from `self` in big-endian byte order. /// Gets a signed 32 bit integer from `self` in big-endian byte order.
/// ///
/// The current position is advanced by 4. /// The current position is advanced by 4.
@ -474,6 +543,29 @@ pub trait Buf {
buf_get_impl!(self, i32::from_le_bytes); buf_get_impl!(self, i32::from_le_bytes);
} }
/// Gets a signed 32 bit integer from `self` in native-endian byte order.
///
/// The current position is advanced by 4.
///
/// # Examples
///
/// ```
/// use bytes::Buf;
///
/// let mut buf: &[u8] = match cfg!(target_endian = "big") {
/// true => b"\x08\x09\xA0\xA1 hello",
/// false => b"\xA1\xA0\x09\x08 hello",
/// };
/// assert_eq!(0x0809A0A1, buf.get_i32_ne());
/// ```
///
/// # Panics
///
/// This function panics if there is not enough remaining data in `self`.
fn get_i32_ne(&mut self) -> i32 {
buf_get_impl!(self, i32::from_ne_bytes);
}
/// Gets an unsigned 64 bit integer from `self` in big-endian byte order. /// Gets an unsigned 64 bit integer from `self` in big-endian byte order.
/// ///
/// The current position is advanced by 8. /// The current position is advanced by 8.
@ -514,6 +606,29 @@ pub trait Buf {
buf_get_impl!(self, u64::from_le_bytes); buf_get_impl!(self, u64::from_le_bytes);
} }
/// Gets an unsigned 64 bit integer from `self` in native-endian byte order.
///
/// The current position is advanced by 8.
///
/// # Examples
///
/// ```
/// use bytes::Buf;
///
/// let mut buf: &[u8] = match cfg!(target_endian = "big") {
/// true => b"\x01\x02\x03\x04\x05\x06\x07\x08 hello",
/// false => b"\x08\x07\x06\x05\x04\x03\x02\x01 hello",
/// };
/// assert_eq!(0x0102030405060708, buf.get_u64_ne());
/// ```
///
/// # Panics
///
/// This function panics if there is not enough remaining data in `self`.
fn get_u64_ne(&mut self) -> u64 {
buf_get_impl!(self, u64::from_ne_bytes);
}
/// Gets a signed 64 bit integer from `self` in big-endian byte order. /// Gets a signed 64 bit integer from `self` in big-endian byte order.
/// ///
/// The current position is advanced by 8. /// The current position is advanced by 8.
@ -554,6 +669,29 @@ pub trait Buf {
buf_get_impl!(self, i64::from_le_bytes); buf_get_impl!(self, i64::from_le_bytes);
} }
/// Gets a signed 64 bit integer from `self` in native-endian byte order.
///
/// The current position is advanced by 8.
///
/// # Examples
///
/// ```
/// use bytes::Buf;
///
/// let mut buf: &[u8] = match cfg!(target_endian = "big") {
/// true => b"\x01\x02\x03\x04\x05\x06\x07\x08 hello",
/// false => b"\x08\x07\x06\x05\x04\x03\x02\x01 hello",
/// };
/// assert_eq!(0x0102030405060708, buf.get_i64_ne());
/// ```
///
/// # Panics
///
/// This function panics if there is not enough remaining data in `self`.
fn get_i64_ne(&mut self) -> i64 {
buf_get_impl!(self, i64::from_ne_bytes);
}
/// Gets an unsigned 128 bit integer from `self` in big-endian byte order. /// Gets an unsigned 128 bit integer from `self` in big-endian byte order.
/// ///
/// The current position is advanced by 16. /// The current position is advanced by 16.
@ -594,6 +732,29 @@ pub trait Buf {
buf_get_impl!(self, u128::from_le_bytes); buf_get_impl!(self, u128::from_le_bytes);
} }
/// Gets an unsigned 128 bit integer from `self` in native-endian byte order.
///
/// The current position is advanced by 16.
///
/// # Examples
///
/// ```
/// use bytes::Buf;
///
/// let mut buf: &[u8] = match cfg!(target_endian = "big") {
/// true => b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x10\x11\x12\x13\x14\x15\x16 hello",
/// false => b"\x16\x15\x14\x13\x12\x11\x10\x09\x08\x07\x06\x05\x04\x03\x02\x01 hello",
/// };
/// assert_eq!(0x01020304050607080910111213141516, buf.get_u128_ne());
/// ```
///
/// # Panics
///
/// This function panics if there is not enough remaining data in `self`.
fn get_u128_ne(&mut self) -> u128 {
buf_get_impl!(self, u128::from_ne_bytes);
}
/// Gets a signed 128 bit integer from `self` in big-endian byte order. /// Gets a signed 128 bit integer from `self` in big-endian byte order.
/// ///
/// The current position is advanced by 16. /// The current position is advanced by 16.
@ -634,6 +795,29 @@ pub trait Buf {
buf_get_impl!(self, i128::from_le_bytes); buf_get_impl!(self, i128::from_le_bytes);
} }
/// Gets a signed 128 bit integer from `self` in native-endian byte order.
///
/// The current position is advanced by 16.
///
/// # Examples
///
/// ```
/// use bytes::Buf;
///
/// let mut buf: &[u8] = match cfg!(target_endian = "big") {
/// true => b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x10\x11\x12\x13\x14\x15\x16 hello",
/// false => b"\x16\x15\x14\x13\x12\x11\x10\x09\x08\x07\x06\x05\x04\x03\x02\x01 hello",
/// };
/// assert_eq!(0x01020304050607080910111213141516, buf.get_i128_ne());
/// ```
///
/// # Panics
///
/// This function panics if there is not enough remaining data in `self`.
fn get_i128_ne(&mut self) -> i128 {
buf_get_impl!(self, i128::from_ne_bytes);
}
/// Gets an unsigned n-byte integer from `self` in big-endian byte order. /// Gets an unsigned n-byte integer from `self` in big-endian byte order.
/// ///
/// The current position is advanced by `nbytes`. /// The current position is advanced by `nbytes`.
@ -674,6 +858,33 @@ pub trait Buf {
buf_get_impl!(le => self, u64, nbytes); buf_get_impl!(le => self, u64, nbytes);
} }
/// Gets an unsigned n-byte integer from `self` in native-endian byte order.
///
/// The current position is advanced by `nbytes`.
///
/// # Examples
///
/// ```
/// use bytes::Buf;
///
/// let mut buf: &[u8] = match cfg!(target_endian = "big") {
/// true => b"\x01\x02\x03 hello",
/// false => b"\x03\x02\x01 hello",
/// };
/// assert_eq!(0x010203, buf.get_uint_ne(3));
/// ```
///
/// # Panics
///
/// This function panics if there is not enough remaining data in `self`.
fn get_uint_ne(&mut self, nbytes: usize) -> u64 {
if cfg!(target_endian = "big") {
self.get_uint(nbytes)
} else {
self.get_uint_le(nbytes)
}
}
/// Gets a signed n-byte integer from `self` in big-endian byte order. /// Gets a signed n-byte integer from `self` in big-endian byte order.
/// ///
/// The current position is advanced by `nbytes`. /// The current position is advanced by `nbytes`.
@ -714,6 +925,33 @@ pub trait Buf {
buf_get_impl!(le => self, i64, nbytes); buf_get_impl!(le => self, i64, nbytes);
} }
/// Gets a signed n-byte integer from `self` in native-endian byte order.
///
/// The current position is advanced by `nbytes`.
///
/// # Examples
///
/// ```
/// use bytes::Buf;
///
/// let mut buf: &[u8] = match cfg!(target_endian = "big") {
/// true => b"\x01\x02\x03 hello",
/// false => b"\x03\x02\x01 hello",
/// };
/// assert_eq!(0x010203, buf.get_int_ne(3));
/// ```
///
/// # Panics
///
/// This function panics if there is not enough remaining data in `self`.
fn get_int_ne(&mut self, nbytes: usize) -> i64 {
if cfg!(target_endian = "big") {
self.get_int(nbytes)
} else {
self.get_int_le(nbytes)
}
}
/// Gets an IEEE754 single-precision (4 bytes) floating point number from /// Gets an IEEE754 single-precision (4 bytes) floating point number from
/// `self` in big-endian byte order. /// `self` in big-endian byte order.
/// ///
@ -756,6 +994,30 @@ pub trait Buf {
f32::from_bits(Self::get_u32_le(self)) f32::from_bits(Self::get_u32_le(self))
} }
/// Gets an IEEE754 single-precision (4 bytes) floating point number from
/// `self` in native-endian byte order.
///
/// The current position is advanced by 4.
///
/// # Examples
///
/// ```
/// use bytes::Buf;
///
/// let mut buf: &[u8] = match cfg!(target_endian = "big") {
/// true => b"\x3F\x99\x99\x9A hello",
/// false => b"\x9A\x99\x99\x3F hello",
/// };
/// assert_eq!(1.2f32, buf.get_f32_ne());
/// ```
///
/// # Panics
///
/// This function panics if there is not enough remaining data in `self`.
fn get_f32_ne(&mut self) -> f32 {
f32::from_bits(Self::get_u32_ne(self))
}
/// Gets an IEEE754 double-precision (8 bytes) floating point number from /// Gets an IEEE754 double-precision (8 bytes) floating point number from
/// `self` in big-endian byte order. /// `self` in big-endian byte order.
/// ///
@ -798,6 +1060,30 @@ pub trait Buf {
f64::from_bits(Self::get_u64_le(self)) f64::from_bits(Self::get_u64_le(self))
} }
/// Gets an IEEE754 double-precision (8 bytes) floating point number from
/// `self` in native-endian byte order.
///
/// The current position is advanced by 8.
///
/// # Examples
///
/// ```
/// use bytes::Buf;
///
/// let mut buf: &[u8] = match cfg!(target_endian = "big") {
/// true => b"\x3F\xF3\x33\x33\x33\x33\x33\x33 hello",
/// false => b"\x33\x33\x33\x33\x33\x33\xF3\x3F hello",
/// };
/// assert_eq!(1.2f64, buf.get_f64_ne());
/// ```
///
/// # Panics
///
/// This function panics if there is not enough remaining data in `self`.
fn get_f64_ne(&mut self) -> f64 {
f64::from_bits(Self::get_u64_ne(self))
}
/// Consumes `len` bytes inside self and returns new instance of `Bytes` /// Consumes `len` bytes inside self and returns new instance of `Bytes`
/// with this data. /// with this data.
/// ///
@ -948,6 +1234,10 @@ macro_rules! deref_forward_buf {
(**self).get_u16_le() (**self).get_u16_le()
} }
fn get_u16_ne(&mut self) -> u16 {
(**self).get_u16_ne()
}
fn get_i16(&mut self) -> i16 { fn get_i16(&mut self) -> i16 {
(**self).get_i16() (**self).get_i16()
} }
@ -956,6 +1246,10 @@ macro_rules! deref_forward_buf {
(**self).get_i16_le() (**self).get_i16_le()
} }
fn get_i16_ne(&mut self) -> i16 {
(**self).get_i16_ne()
}
fn get_u32(&mut self) -> u32 { fn get_u32(&mut self) -> u32 {
(**self).get_u32() (**self).get_u32()
} }
@ -964,6 +1258,10 @@ macro_rules! deref_forward_buf {
(**self).get_u32_le() (**self).get_u32_le()
} }
fn get_u32_ne(&mut self) -> u32 {
(**self).get_u32_ne()
}
fn get_i32(&mut self) -> i32 { fn get_i32(&mut self) -> i32 {
(**self).get_i32() (**self).get_i32()
} }
@ -972,6 +1270,10 @@ macro_rules! deref_forward_buf {
(**self).get_i32_le() (**self).get_i32_le()
} }
fn get_i32_ne(&mut self) -> i32 {
(**self).get_i32_ne()
}
fn get_u64(&mut self) -> u64 { fn get_u64(&mut self) -> u64 {
(**self).get_u64() (**self).get_u64()
} }
@ -980,6 +1282,10 @@ macro_rules! deref_forward_buf {
(**self).get_u64_le() (**self).get_u64_le()
} }
fn get_u64_ne(&mut self) -> u64 {
(**self).get_u64_ne()
}
fn get_i64(&mut self) -> i64 { fn get_i64(&mut self) -> i64 {
(**self).get_i64() (**self).get_i64()
} }
@ -988,6 +1294,10 @@ macro_rules! deref_forward_buf {
(**self).get_i64_le() (**self).get_i64_le()
} }
fn get_i64_ne(&mut self) -> i64 {
(**self).get_i64_ne()
}
fn get_uint(&mut self, nbytes: usize) -> u64 { fn get_uint(&mut self, nbytes: usize) -> u64 {
(**self).get_uint(nbytes) (**self).get_uint(nbytes)
} }
@ -996,6 +1306,10 @@ macro_rules! deref_forward_buf {
(**self).get_uint_le(nbytes) (**self).get_uint_le(nbytes)
} }
fn get_uint_ne(&mut self, nbytes: usize) -> u64 {
(**self).get_uint_ne(nbytes)
}
fn get_int(&mut self, nbytes: usize) -> i64 { fn get_int(&mut self, nbytes: usize) -> i64 {
(**self).get_int(nbytes) (**self).get_int(nbytes)
} }
@ -1004,6 +1318,10 @@ macro_rules! deref_forward_buf {
(**self).get_int_le(nbytes) (**self).get_int_le(nbytes)
} }
fn get_int_ne(&mut self, nbytes: usize) -> i64 {
(**self).get_int_ne(nbytes)
}
fn copy_to_bytes(&mut self, len: usize) -> crate::Bytes { fn copy_to_bytes(&mut self, len: usize) -> crate::Bytes {
(**self).copy_to_bytes(len) (**self).copy_to_bytes(len)
} }

View File

@ -56,6 +56,10 @@ pub unsafe trait BufMut {
/// Implementations of `remaining_mut` should ensure that the return value /// Implementations of `remaining_mut` should ensure that the return value
/// does not change unless a call is made to `advance_mut` or any other /// does not change unless a call is made to `advance_mut` or any other
/// function that is documented to change the `BufMut`'s current position. /// function that is documented to change the `BufMut`'s current position.
///
/// # Note
///
/// `remaining_mut` may return value smaller than actual available space.
fn remaining_mut(&self) -> usize; fn remaining_mut(&self) -> usize;
/// Advance the internal cursor of the BufMut /// Advance the internal cursor of the BufMut
@ -382,6 +386,32 @@ pub unsafe trait BufMut {
self.put_slice(&n.to_le_bytes()) self.put_slice(&n.to_le_bytes())
} }
/// Writes an unsigned 16 bit integer to `self` in native-endian byte order.
///
/// The current position is advanced by 2.
///
/// # Examples
///
/// ```
/// use bytes::BufMut;
///
/// let mut buf = vec![];
/// buf.put_u16_ne(0x0809);
/// if cfg!(target_endian = "big") {
/// assert_eq!(buf, b"\x08\x09");
/// } else {
/// assert_eq!(buf, b"\x09\x08");
/// }
/// ```
///
/// # Panics
///
/// This function panics if there is not enough remaining capacity in
/// `self`.
fn put_u16_ne(&mut self, n: u16) {
self.put_slice(&n.to_ne_bytes())
}
/// Writes a signed 16 bit integer to `self` in big-endian byte order. /// Writes a signed 16 bit integer to `self` in big-endian byte order.
/// ///
/// The current position is advanced by 2. /// The current position is advanced by 2.
@ -426,6 +456,32 @@ pub unsafe trait BufMut {
self.put_slice(&n.to_le_bytes()) self.put_slice(&n.to_le_bytes())
} }
/// Writes a signed 16 bit integer to `self` in native-endian byte order.
///
/// The current position is advanced by 2.
///
/// # Examples
///
/// ```
/// use bytes::BufMut;
///
/// let mut buf = vec![];
/// buf.put_i16_ne(0x0809);
/// if cfg!(target_endian = "big") {
/// assert_eq!(buf, b"\x08\x09");
/// } else {
/// assert_eq!(buf, b"\x09\x08");
/// }
/// ```
///
/// # Panics
///
/// This function panics if there is not enough remaining capacity in
/// `self`.
fn put_i16_ne(&mut self, n: i16) {
self.put_slice(&n.to_ne_bytes())
}
/// Writes an unsigned 32 bit integer to `self` in big-endian byte order. /// Writes an unsigned 32 bit integer to `self` in big-endian byte order.
/// ///
/// The current position is advanced by 4. /// The current position is advanced by 4.
@ -470,6 +526,32 @@ pub unsafe trait BufMut {
self.put_slice(&n.to_le_bytes()) self.put_slice(&n.to_le_bytes())
} }
/// Writes an unsigned 32 bit integer to `self` in native-endian byte order.
///
/// The current position is advanced by 4.
///
/// # Examples
///
/// ```
/// use bytes::BufMut;
///
/// let mut buf = vec![];
/// buf.put_u32_ne(0x0809A0A1);
/// if cfg!(target_endian = "big") {
/// assert_eq!(buf, b"\x08\x09\xA0\xA1");
/// } else {
/// assert_eq!(buf, b"\xA1\xA0\x09\x08");
/// }
/// ```
///
/// # Panics
///
/// This function panics if there is not enough remaining capacity in
/// `self`.
fn put_u32_ne(&mut self, n: u32) {
self.put_slice(&n.to_ne_bytes())
}
/// Writes a signed 32 bit integer to `self` in big-endian byte order. /// Writes a signed 32 bit integer to `self` in big-endian byte order.
/// ///
/// The current position is advanced by 4. /// The current position is advanced by 4.
@ -514,6 +596,32 @@ pub unsafe trait BufMut {
self.put_slice(&n.to_le_bytes()) self.put_slice(&n.to_le_bytes())
} }
/// Writes a signed 32 bit integer to `self` in native-endian byte order.
///
/// The current position is advanced by 4.
///
/// # Examples
///
/// ```
/// use bytes::BufMut;
///
/// let mut buf = vec![];
/// buf.put_i32_ne(0x0809A0A1);
/// if cfg!(target_endian = "big") {
/// assert_eq!(buf, b"\x08\x09\xA0\xA1");
/// } else {
/// assert_eq!(buf, b"\xA1\xA0\x09\x08");
/// }
/// ```
///
/// # Panics
///
/// This function panics if there is not enough remaining capacity in
/// `self`.
fn put_i32_ne(&mut self, n: i32) {
self.put_slice(&n.to_ne_bytes())
}
/// Writes an unsigned 64 bit integer to `self` in the big-endian byte order. /// Writes an unsigned 64 bit integer to `self` in the big-endian byte order.
/// ///
/// The current position is advanced by 8. /// The current position is advanced by 8.
@ -558,6 +666,32 @@ pub unsafe trait BufMut {
self.put_slice(&n.to_le_bytes()) self.put_slice(&n.to_le_bytes())
} }
/// Writes an unsigned 64 bit integer to `self` in native-endian byte order.
///
/// The current position is advanced by 8.
///
/// # Examples
///
/// ```
/// use bytes::BufMut;
///
/// let mut buf = vec![];
/// buf.put_u64_ne(0x0102030405060708);
/// if cfg!(target_endian = "big") {
/// assert_eq!(buf, b"\x01\x02\x03\x04\x05\x06\x07\x08");
/// } else {
/// assert_eq!(buf, b"\x08\x07\x06\x05\x04\x03\x02\x01");
/// }
/// ```
///
/// # Panics
///
/// This function panics if there is not enough remaining capacity in
/// `self`.
fn put_u64_ne(&mut self, n: u64) {
self.put_slice(&n.to_ne_bytes())
}
/// Writes a signed 64 bit integer to `self` in the big-endian byte order. /// Writes a signed 64 bit integer to `self` in the big-endian byte order.
/// ///
/// The current position is advanced by 8. /// The current position is advanced by 8.
@ -602,6 +736,32 @@ pub unsafe trait BufMut {
self.put_slice(&n.to_le_bytes()) self.put_slice(&n.to_le_bytes())
} }
/// Writes a signed 64 bit integer to `self` in native-endian byte order.
///
/// The current position is advanced by 8.
///
/// # Examples
///
/// ```
/// use bytes::BufMut;
///
/// let mut buf = vec![];
/// buf.put_i64_ne(0x0102030405060708);
/// if cfg!(target_endian = "big") {
/// assert_eq!(buf, b"\x01\x02\x03\x04\x05\x06\x07\x08");
/// } else {
/// assert_eq!(buf, b"\x08\x07\x06\x05\x04\x03\x02\x01");
/// }
/// ```
///
/// # Panics
///
/// This function panics if there is not enough remaining capacity in
/// `self`.
fn put_i64_ne(&mut self, n: i64) {
self.put_slice(&n.to_ne_bytes())
}
/// Writes an unsigned 128 bit integer to `self` in the big-endian byte order. /// Writes an unsigned 128 bit integer to `self` in the big-endian byte order.
/// ///
/// The current position is advanced by 16. /// The current position is advanced by 16.
@ -646,6 +806,32 @@ pub unsafe trait BufMut {
self.put_slice(&n.to_le_bytes()) self.put_slice(&n.to_le_bytes())
} }
/// Writes an unsigned 128 bit integer to `self` in native-endian byte order.
///
/// The current position is advanced by 16.
///
/// # Examples
///
/// ```
/// use bytes::BufMut;
///
/// let mut buf = vec![];
/// buf.put_u128_ne(0x01020304050607080910111213141516);
/// if cfg!(target_endian = "big") {
/// assert_eq!(buf, b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x10\x11\x12\x13\x14\x15\x16");
/// } else {
/// assert_eq!(buf, b"\x16\x15\x14\x13\x12\x11\x10\x09\x08\x07\x06\x05\x04\x03\x02\x01");
/// }
/// ```
///
/// # Panics
///
/// This function panics if there is not enough remaining capacity in
/// `self`.
fn put_u128_ne(&mut self, n: u128) {
self.put_slice(&n.to_ne_bytes())
}
/// Writes a signed 128 bit integer to `self` in the big-endian byte order. /// Writes a signed 128 bit integer to `self` in the big-endian byte order.
/// ///
/// The current position is advanced by 16. /// The current position is advanced by 16.
@ -690,6 +876,32 @@ pub unsafe trait BufMut {
self.put_slice(&n.to_le_bytes()) self.put_slice(&n.to_le_bytes())
} }
/// Writes a signed 128 bit integer to `self` in native-endian byte order.
///
/// The current position is advanced by 16.
///
/// # Examples
///
/// ```
/// use bytes::BufMut;
///
/// let mut buf = vec![];
/// buf.put_i128_ne(0x01020304050607080910111213141516);
/// if cfg!(target_endian = "big") {
/// assert_eq!(buf, b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x10\x11\x12\x13\x14\x15\x16");
/// } else {
/// assert_eq!(buf, b"\x16\x15\x14\x13\x12\x11\x10\x09\x08\x07\x06\x05\x04\x03\x02\x01");
/// }
/// ```
///
/// # Panics
///
/// This function panics if there is not enough remaining capacity in
/// `self`.
fn put_i128_ne(&mut self, n: i128) {
self.put_slice(&n.to_ne_bytes())
}
/// Writes an unsigned n-byte integer to `self` in big-endian byte order. /// Writes an unsigned n-byte integer to `self` in big-endian byte order.
/// ///
/// The current position is advanced by `nbytes`. /// The current position is advanced by `nbytes`.
@ -734,6 +946,36 @@ pub unsafe trait BufMut {
self.put_slice(&n.to_le_bytes()[0..nbytes]); self.put_slice(&n.to_le_bytes()[0..nbytes]);
} }
/// Writes an unsigned n-byte integer to `self` in the native-endian byte order.
///
/// The current position is advanced by `nbytes`.
///
/// # Examples
///
/// ```
/// use bytes::BufMut;
///
/// let mut buf = vec![];
/// buf.put_uint_ne(0x010203, 3);
/// if cfg!(target_endian = "big") {
/// assert_eq!(buf, b"\x01\x02\x03");
/// } else {
/// assert_eq!(buf, b"\x03\x02\x01");
/// }
/// ```
///
/// # Panics
///
/// This function panics if there is not enough remaining capacity in
/// `self`.
fn put_uint_ne(&mut self, n: u64, nbytes: usize) {
if cfg!(target_endian = "big") {
self.put_uint(n, nbytes)
} else {
self.put_uint_le(n, nbytes)
}
}
/// Writes low `nbytes` of a signed integer to `self` in big-endian byte order. /// Writes low `nbytes` of a signed integer to `self` in big-endian byte order.
/// ///
/// The current position is advanced by `nbytes`. /// The current position is advanced by `nbytes`.
@ -778,6 +1020,36 @@ pub unsafe trait BufMut {
self.put_slice(&n.to_le_bytes()[0..nbytes]); self.put_slice(&n.to_le_bytes()[0..nbytes]);
} }
/// Writes low `nbytes` of a signed integer to `self` in native-endian byte order.
///
/// The current position is advanced by `nbytes`.
///
/// # Examples
///
/// ```
/// use bytes::BufMut;
///
/// let mut buf = vec![];
/// buf.put_int_ne(0x010203, 3);
/// if cfg!(target_endian = "big") {
/// assert_eq!(buf, b"\x01\x02\x03");
/// } else {
/// assert_eq!(buf, b"\x03\x02\x01");
/// }
/// ```
///
/// # Panics
///
/// This function panics if there is not enough remaining capacity in
/// `self` or if `nbytes` is greater than 8.
fn put_int_ne(&mut self, n: i64, nbytes: usize) {
if cfg!(target_endian = "big") {
self.put_int(n, nbytes)
} else {
self.put_int_le(n, nbytes)
}
}
/// Writes an IEEE754 single-precision (4 bytes) floating point number to /// Writes an IEEE754 single-precision (4 bytes) floating point number to
/// `self` in big-endian byte order. /// `self` in big-endian byte order.
/// ///
@ -824,6 +1096,33 @@ pub unsafe trait BufMut {
self.put_u32_le(n.to_bits()); self.put_u32_le(n.to_bits());
} }
/// Writes an IEEE754 single-precision (4 bytes) floating point number to
/// `self` in native-endian byte order.
///
/// The current position is advanced by 4.
///
/// # Examples
///
/// ```
/// use bytes::BufMut;
///
/// let mut buf = vec![];
/// buf.put_f32_ne(1.2f32);
/// if cfg!(target_endian = "big") {
/// assert_eq!(buf, b"\x3F\x99\x99\x9A");
/// } else {
/// assert_eq!(buf, b"\x9A\x99\x99\x3F");
/// }
/// ```
///
/// # Panics
///
/// This function panics if there is not enough remaining capacity in
/// `self`.
fn put_f32_ne(&mut self, n: f32) {
self.put_u32_ne(n.to_bits());
}
/// Writes an IEEE754 double-precision (8 bytes) floating point number to /// Writes an IEEE754 double-precision (8 bytes) floating point number to
/// `self` in big-endian byte order. /// `self` in big-endian byte order.
/// ///
@ -870,6 +1169,33 @@ pub unsafe trait BufMut {
self.put_u64_le(n.to_bits()); self.put_u64_le(n.to_bits());
} }
/// Writes an IEEE754 double-precision (8 bytes) floating point number to
/// `self` in native-endian byte order.
///
/// The current position is advanced by 8.
///
/// # Examples
///
/// ```
/// use bytes::BufMut;
///
/// let mut buf = vec![];
/// buf.put_f64_ne(1.2f64);
/// if cfg!(target_endian = "big") {
/// assert_eq!(buf, b"\x3F\xF3\x33\x33\x33\x33\x33\x33");
/// } else {
/// assert_eq!(buf, b"\x33\x33\x33\x33\x33\x33\xF3\x3F");
/// }
/// ```
///
/// # Panics
///
/// This function panics if there is not enough remaining capacity in
/// `self`.
fn put_f64_ne(&mut self, n: f64) {
self.put_u64_ne(n.to_bits());
}
/// Creates an adaptor which can write at most `limit` bytes to `self`. /// Creates an adaptor which can write at most `limit` bytes to `self`.
/// ///
/// # Examples /// # Examples
@ -982,6 +1308,10 @@ macro_rules! deref_forward_bufmut {
(**self).put_u16_le(n) (**self).put_u16_le(n)
} }
fn put_u16_ne(&mut self, n: u16) {
(**self).put_u16_ne(n)
}
fn put_i16(&mut self, n: i16) { fn put_i16(&mut self, n: i16) {
(**self).put_i16(n) (**self).put_i16(n)
} }
@ -990,6 +1320,10 @@ macro_rules! deref_forward_bufmut {
(**self).put_i16_le(n) (**self).put_i16_le(n)
} }
fn put_i16_ne(&mut self, n: i16) {
(**self).put_i16_ne(n)
}
fn put_u32(&mut self, n: u32) { fn put_u32(&mut self, n: u32) {
(**self).put_u32(n) (**self).put_u32(n)
} }
@ -998,6 +1332,10 @@ macro_rules! deref_forward_bufmut {
(**self).put_u32_le(n) (**self).put_u32_le(n)
} }
fn put_u32_ne(&mut self, n: u32) {
(**self).put_u32_ne(n)
}
fn put_i32(&mut self, n: i32) { fn put_i32(&mut self, n: i32) {
(**self).put_i32(n) (**self).put_i32(n)
} }
@ -1006,6 +1344,10 @@ macro_rules! deref_forward_bufmut {
(**self).put_i32_le(n) (**self).put_i32_le(n)
} }
fn put_i32_ne(&mut self, n: i32) {
(**self).put_i32_ne(n)
}
fn put_u64(&mut self, n: u64) { fn put_u64(&mut self, n: u64) {
(**self).put_u64(n) (**self).put_u64(n)
} }
@ -1014,6 +1356,10 @@ macro_rules! deref_forward_bufmut {
(**self).put_u64_le(n) (**self).put_u64_le(n)
} }
fn put_u64_ne(&mut self, n: u64) {
(**self).put_u64_ne(n)
}
fn put_i64(&mut self, n: i64) { fn put_i64(&mut self, n: i64) {
(**self).put_i64(n) (**self).put_i64(n)
} }
@ -1021,6 +1367,10 @@ macro_rules! deref_forward_bufmut {
fn put_i64_le(&mut self, n: i64) { fn put_i64_le(&mut self, n: i64) {
(**self).put_i64_le(n) (**self).put_i64_le(n)
} }
fn put_i64_ne(&mut self, n: i64) {
(**self).put_i64_ne(n)
}
}; };
} }

View File

@ -198,8 +198,7 @@ where
fn remaining_mut(&self) -> usize { fn remaining_mut(&self) -> usize {
self.a self.a
.remaining_mut() .remaining_mut()
.checked_add(self.b.remaining_mut()) .saturating_add(self.b.remaining_mut())
.unwrap()
} }
fn chunk_mut(&mut self) -> &mut UninitSlice { fn chunk_mut(&mut self) -> &mut UninitSlice {

View File

@ -22,6 +22,10 @@ use core::ops::{
pub struct UninitSlice([MaybeUninit<u8>]); pub struct UninitSlice([MaybeUninit<u8>]);
impl UninitSlice { impl UninitSlice {
pub(crate) fn from_slice(slice: &mut [MaybeUninit<u8>]) -> &mut UninitSlice {
unsafe { &mut *(slice as *mut [MaybeUninit<u8>] as *mut UninitSlice) }
}
/// Create a `&mut UninitSlice` from a pointer and a length. /// Create a `&mut UninitSlice` from a pointer and a length.
/// ///
/// # Safety /// # Safety
@ -44,7 +48,7 @@ impl UninitSlice {
pub unsafe fn from_raw_parts_mut<'a>(ptr: *mut u8, len: usize) -> &'a mut UninitSlice { pub unsafe fn from_raw_parts_mut<'a>(ptr: *mut u8, len: usize) -> &'a mut UninitSlice {
let maybe_init: &mut [MaybeUninit<u8>] = let maybe_init: &mut [MaybeUninit<u8>] =
core::slice::from_raw_parts_mut(ptr as *mut _, len); core::slice::from_raw_parts_mut(ptr as *mut _, len);
&mut *(maybe_init as *mut [MaybeUninit<u8>] as *mut UninitSlice) Self::from_slice(maybe_init)
} }
/// Write a single byte at the specified offset. /// Write a single byte at the specified offset.
@ -124,6 +128,32 @@ impl UninitSlice {
self.0.as_mut_ptr() as *mut _ self.0.as_mut_ptr() as *mut _
} }
/// Return a `&mut [MaybeUninit<u8>]` to this slice's buffer.
///
/// # Safety
///
/// The caller **must not** read from the referenced memory and **must not** write
/// **uninitialized** bytes to the slice either. This is because `BufMut` implementation
/// that created the `UninitSlice` knows which parts are initialized. Writing uninitalized
/// bytes to the slice may cause the `BufMut` to read those bytes and trigger undefined
/// behavior.
///
/// # Examples
///
/// ```
/// use bytes::BufMut;
///
/// let mut data = [0, 1, 2];
/// let mut slice = &mut data[..];
/// unsafe {
/// let uninit_slice = BufMut::chunk_mut(&mut slice).as_uninit_slice_mut();
/// };
/// ```
#[inline]
pub unsafe fn as_uninit_slice_mut<'a>(&'a mut self) -> &'a mut [MaybeUninit<u8>] {
&mut *(self as *mut _ as *mut [MaybeUninit<u8>])
}
/// Returns the number of bytes in the slice. /// Returns the number of bytes in the slice.
/// ///
/// # Examples /// # Examples

View File

@ -2,12 +2,18 @@ use core::iter::FromIterator;
use core::ops::{Deref, RangeBounds}; use core::ops::{Deref, RangeBounds};
use core::{cmp, fmt, hash, mem, ptr, slice, usize}; use core::{cmp, fmt, hash, mem, ptr, slice, usize};
use alloc::{borrow::Borrow, boxed::Box, string::String, vec::Vec}; use alloc::{
alloc::{dealloc, Layout},
borrow::Borrow,
boxed::Box,
string::String,
vec::Vec,
};
use crate::buf::IntoIter; use crate::buf::IntoIter;
#[allow(unused)] #[allow(unused)]
use crate::loom::sync::atomic::AtomicMut; use crate::loom::sync::atomic::AtomicMut;
use crate::loom::sync::atomic::{self, AtomicPtr, AtomicUsize, Ordering}; use crate::loom::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
use crate::Buf; use crate::Buf;
/// A cheaply cloneable and sliceable chunk of contiguous memory. /// A cheaply cloneable and sliceable chunk of contiguous memory.
@ -55,7 +61,7 @@ use crate::Buf;
/// # Sharing /// # Sharing
/// ///
/// `Bytes` contains a vtable, which allows implementations of `Bytes` to define /// `Bytes` contains a vtable, which allows implementations of `Bytes` to define
/// how sharing/cloneing is implemented in detail. /// how sharing/cloning is implemented in detail.
/// When `Bytes::clone()` is called, `Bytes` will call the vtable function for /// When `Bytes::clone()` is called, `Bytes` will call the vtable function for
/// cloning the backing storage in order to share it behind between multiple /// cloning the backing storage in order to share it behind between multiple
/// `Bytes` instances. /// `Bytes` instances.
@ -78,18 +84,18 @@ use crate::Buf;
/// ///
/// ```text /// ```text
/// ///
/// Arc ptrs +---------+ /// Arc ptrs ┌─────────┐
/// ________________________ / | Bytes 2 | /// ________________________ / │ Bytes 2 │
/// / +---------+ /// / └─────────┘
/// / +-----------+ | | /// / ┌───────────┐ | |
/// |_________/ | Bytes 1 | | | /// |_________/ │ Bytes 1 │ | |
/// | +-----------+ | | /// | └───────────┘ | |
/// | | | ___/ data | tail /// | | | ___/ data | tail
/// | data | tail |/ | /// | data | tail |/ |
/// v v v v /// v v v v
/// +-----+---------------------------------+-----+ /// ┌─────┬─────┬───────────┬───────────────┬─────┐
/// | Arc | | | | | /// │ Arc │ │ │ │ │
/// +-----+---------------------------------+-----+ /// └─────┴─────┴───────────┴───────────────┴─────┘
/// ``` /// ```
pub struct Bytes { pub struct Bytes {
ptr: *const u8, ptr: *const u8,
@ -103,6 +109,10 @@ pub(crate) struct Vtable {
/// fn(data, ptr, len) /// fn(data, ptr, len)
pub clone: unsafe fn(&AtomicPtr<()>, *const u8, usize) -> Bytes, pub clone: unsafe fn(&AtomicPtr<()>, *const u8, usize) -> Bytes,
/// fn(data, ptr, len) /// fn(data, ptr, len)
///
/// takes `Bytes` to value
pub to_vec: unsafe fn(&AtomicPtr<()>, *const u8, usize) -> Vec<u8>,
/// fn(data, ptr, len)
pub drop: unsafe fn(&mut AtomicPtr<()>, *const u8, usize), pub drop: unsafe fn(&mut AtomicPtr<()>, *const u8, usize),
} }
@ -121,7 +131,7 @@ impl Bytes {
/// ``` /// ```
#[inline] #[inline]
#[cfg(not(all(loom, test)))] #[cfg(not(all(loom, test)))]
pub const fn new() -> Bytes { pub const fn new() -> Self {
// Make it a named const to work around // Make it a named const to work around
// "unsizing casts are not allowed in const fn" // "unsizing casts are not allowed in const fn"
const EMPTY: &[u8] = &[]; const EMPTY: &[u8] = &[];
@ -129,7 +139,7 @@ impl Bytes {
} }
#[cfg(all(loom, test))] #[cfg(all(loom, test))]
pub fn new() -> Bytes { pub fn new() -> Self {
const EMPTY: &[u8] = &[]; const EMPTY: &[u8] = &[];
Bytes::from_static(EMPTY) Bytes::from_static(EMPTY)
} }
@ -149,7 +159,7 @@ impl Bytes {
/// ``` /// ```
#[inline] #[inline]
#[cfg(not(all(loom, test)))] #[cfg(not(all(loom, test)))]
pub const fn from_static(bytes: &'static [u8]) -> Bytes { pub const fn from_static(bytes: &'static [u8]) -> Self {
Bytes { Bytes {
ptr: bytes.as_ptr(), ptr: bytes.as_ptr(),
len: bytes.len(), len: bytes.len(),
@ -159,7 +169,7 @@ impl Bytes {
} }
#[cfg(all(loom, test))] #[cfg(all(loom, test))]
pub fn from_static(bytes: &'static [u8]) -> Bytes { pub fn from_static(bytes: &'static [u8]) -> Self {
Bytes { Bytes {
ptr: bytes.as_ptr(), ptr: bytes.as_ptr(),
len: bytes.len(), len: bytes.len(),
@ -179,7 +189,7 @@ impl Bytes {
/// assert_eq!(b.len(), 5); /// assert_eq!(b.len(), 5);
/// ``` /// ```
#[inline] #[inline]
pub fn len(&self) -> usize { pub const fn len(&self) -> usize {
self.len self.len
} }
@ -194,7 +204,7 @@ impl Bytes {
/// assert!(b.is_empty()); /// assert!(b.is_empty());
/// ``` /// ```
#[inline] #[inline]
pub fn is_empty(&self) -> bool { pub const fn is_empty(&self) -> bool {
self.len == 0 self.len == 0
} }
@ -225,7 +235,7 @@ impl Bytes {
/// ///
/// Requires that `begin <= end` and `end <= self.len()`, otherwise slicing /// Requires that `begin <= end` and `end <= self.len()`, otherwise slicing
/// will panic. /// will panic.
pub fn slice(&self, range: impl RangeBounds<usize>) -> Bytes { pub fn slice(&self, range: impl RangeBounds<usize>) -> Self {
use core::ops::Bound; use core::ops::Bound;
let len = self.len(); let len = self.len();
@ -262,7 +272,7 @@ impl Bytes {
let mut ret = self.clone(); let mut ret = self.clone();
ret.len = end - begin; ret.len = end - begin;
ret.ptr = unsafe { ret.ptr.offset(begin as isize) }; ret.ptr = unsafe { ret.ptr.add(begin) };
ret ret
} }
@ -292,7 +302,7 @@ impl Bytes {
/// ///
/// Requires that the given `sub` slice is in fact contained within the /// Requires that the given `sub` slice is in fact contained within the
/// `Bytes` buffer; otherwise this function will panic. /// `Bytes` buffer; otherwise this function will panic.
pub fn slice_ref(&self, subset: &[u8]) -> Bytes { pub fn slice_ref(&self, subset: &[u8]) -> Self {
// Empty slice and empty Bytes may have their pointers reset // Empty slice and empty Bytes may have their pointers reset
// so explicitly allow empty slice to be a subslice of any slice. // so explicitly allow empty slice to be a subslice of any slice.
if subset.is_empty() { if subset.is_empty() {
@ -308,15 +318,15 @@ impl Bytes {
assert!( assert!(
sub_p >= bytes_p, sub_p >= bytes_p,
"subset pointer ({:p}) is smaller than self pointer ({:p})", "subset pointer ({:p}) is smaller than self pointer ({:p})",
sub_p as *const u8, subset.as_ptr(),
bytes_p as *const u8, self.as_ptr(),
); );
assert!( assert!(
sub_p + sub_len <= bytes_p + bytes_len, sub_p + sub_len <= bytes_p + bytes_len,
"subset is out of bounds: self = ({:p}, {}), subset = ({:p}, {})", "subset is out of bounds: self = ({:p}, {}), subset = ({:p}, {})",
bytes_p as *const u8, self.as_ptr(),
bytes_len, bytes_len,
sub_p as *const u8, subset.as_ptr(),
sub_len, sub_len,
); );
@ -349,7 +359,7 @@ impl Bytes {
/// ///
/// Panics if `at > len`. /// Panics if `at > len`.
#[must_use = "consider Bytes::truncate if you don't need the other half"] #[must_use = "consider Bytes::truncate if you don't need the other half"]
pub fn split_off(&mut self, at: usize) -> Bytes { pub fn split_off(&mut self, at: usize) -> Self {
assert!( assert!(
at <= self.len(), at <= self.len(),
"split_off out of bounds: {:?} <= {:?}", "split_off out of bounds: {:?} <= {:?}",
@ -398,7 +408,7 @@ impl Bytes {
/// ///
/// Panics if `at > len`. /// Panics if `at > len`.
#[must_use = "consider Bytes::advance if you don't need the other half"] #[must_use = "consider Bytes::advance if you don't need the other half"]
pub fn split_to(&mut self, at: usize) -> Bytes { pub fn split_to(&mut self, at: usize) -> Self {
assert!( assert!(
at <= self.len(), at <= self.len(),
"split_to out of bounds: {:?} <= {:?}", "split_to out of bounds: {:?} <= {:?}",
@ -501,7 +511,7 @@ impl Bytes {
// should already be asserted, but debug assert for tests // should already be asserted, but debug assert for tests
debug_assert!(self.len >= by, "internal: inc_start out of bounds"); debug_assert!(self.len >= by, "internal: inc_start out of bounds");
self.len -= by; self.len -= by;
self.ptr = self.ptr.offset(by as isize); self.ptr = self.ptr.add(by);
} }
} }
@ -604,7 +614,7 @@ impl<'a> IntoIterator for &'a Bytes {
type IntoIter = core::slice::Iter<'a, u8>; type IntoIter = core::slice::Iter<'a, u8>;
fn into_iter(self) -> Self::IntoIter { fn into_iter(self) -> Self::IntoIter {
self.as_slice().into_iter() self.as_slice().iter()
} }
} }
@ -686,7 +696,7 @@ impl PartialOrd<Bytes> for str {
impl PartialEq<Vec<u8>> for Bytes { impl PartialEq<Vec<u8>> for Bytes {
fn eq(&self, other: &Vec<u8>) -> bool { fn eq(&self, other: &Vec<u8>) -> bool {
*self == &other[..] *self == other[..]
} }
} }
@ -710,7 +720,7 @@ impl PartialOrd<Bytes> for Vec<u8> {
impl PartialEq<String> for Bytes { impl PartialEq<String> for Bytes {
fn eq(&self, other: &String) -> bool { fn eq(&self, other: &String) -> bool {
*self == &other[..] *self == other[..]
} }
} }
@ -815,18 +825,18 @@ impl From<Box<[u8]>> for Bytes {
let ptr = Box::into_raw(slice) as *mut u8; let ptr = Box::into_raw(slice) as *mut u8;
if ptr as usize & 0x1 == 0 { if ptr as usize & 0x1 == 0 {
let data = ptr as usize | KIND_VEC; let data = ptr_map(ptr, |addr| addr | KIND_VEC);
Bytes { Bytes {
ptr, ptr,
len, len,
data: AtomicPtr::new(data as *mut _), data: AtomicPtr::new(data.cast()),
vtable: &PROMOTABLE_EVEN_VTABLE, vtable: &PROMOTABLE_EVEN_VTABLE,
} }
} else { } else {
Bytes { Bytes {
ptr, ptr,
len, len,
data: AtomicPtr::new(ptr as *mut _), data: AtomicPtr::new(ptr.cast()),
vtable: &PROMOTABLE_ODD_VTABLE, vtable: &PROMOTABLE_ODD_VTABLE,
} }
} }
@ -839,6 +849,13 @@ impl From<String> for Bytes {
} }
} }
impl From<Bytes> for Vec<u8> {
fn from(bytes: Bytes) -> Vec<u8> {
let bytes = mem::ManuallyDrop::new(bytes);
unsafe { (bytes.vtable.to_vec)(&bytes.data, bytes.ptr, bytes.len) }
}
}
// ===== impl Vtable ===== // ===== impl Vtable =====
impl fmt::Debug for Vtable { impl fmt::Debug for Vtable {
@ -854,6 +871,7 @@ impl fmt::Debug for Vtable {
const STATIC_VTABLE: Vtable = Vtable { const STATIC_VTABLE: Vtable = Vtable {
clone: static_clone, clone: static_clone,
to_vec: static_to_vec,
drop: static_drop, drop: static_drop,
}; };
@ -862,6 +880,11 @@ unsafe fn static_clone(_: &AtomicPtr<()>, ptr: *const u8, len: usize) -> Bytes {
Bytes::from_static(slice) Bytes::from_static(slice)
} }
unsafe fn static_to_vec(_: &AtomicPtr<()>, ptr: *const u8, len: usize) -> Vec<u8> {
let slice = slice::from_raw_parts(ptr, len);
slice.to_vec()
}
unsafe fn static_drop(_: &mut AtomicPtr<()>, _: *const u8, _: usize) { unsafe fn static_drop(_: &mut AtomicPtr<()>, _: *const u8, _: usize) {
// nothing to drop for &'static [u8] // nothing to drop for &'static [u8]
} }
@ -870,11 +893,13 @@ unsafe fn static_drop(_: &mut AtomicPtr<()>, _: *const u8, _: usize) {
static PROMOTABLE_EVEN_VTABLE: Vtable = Vtable { static PROMOTABLE_EVEN_VTABLE: Vtable = Vtable {
clone: promotable_even_clone, clone: promotable_even_clone,
to_vec: promotable_even_to_vec,
drop: promotable_even_drop, drop: promotable_even_drop,
}; };
static PROMOTABLE_ODD_VTABLE: Vtable = Vtable { static PROMOTABLE_ODD_VTABLE: Vtable = Vtable {
clone: promotable_odd_clone, clone: promotable_odd_clone,
to_vec: promotable_odd_to_vec,
drop: promotable_odd_drop, drop: promotable_odd_drop,
}; };
@ -883,25 +908,57 @@ unsafe fn promotable_even_clone(data: &AtomicPtr<()>, ptr: *const u8, len: usize
let kind = shared as usize & KIND_MASK; let kind = shared as usize & KIND_MASK;
if kind == KIND_ARC { if kind == KIND_ARC {
shallow_clone_arc(shared as _, ptr, len) shallow_clone_arc(shared.cast(), ptr, len)
} else { } else {
debug_assert_eq!(kind, KIND_VEC); debug_assert_eq!(kind, KIND_VEC);
let buf = (shared as usize & !KIND_MASK) as *mut u8; let buf = ptr_map(shared.cast(), |addr| addr & !KIND_MASK);
shallow_clone_vec(data, shared, buf, ptr, len) shallow_clone_vec(data, shared, buf, ptr, len)
} }
} }
unsafe fn promotable_to_vec(
data: &AtomicPtr<()>,
ptr: *const u8,
len: usize,
f: fn(*mut ()) -> *mut u8,
) -> Vec<u8> {
let shared = data.load(Ordering::Acquire);
let kind = shared as usize & KIND_MASK;
if kind == KIND_ARC {
shared_to_vec_impl(shared.cast(), ptr, len)
} else {
// If Bytes holds a Vec, then the offset must be 0.
debug_assert_eq!(kind, KIND_VEC);
let buf = f(shared);
let cap = (ptr as usize - buf as usize) + len;
// Copy back buffer
ptr::copy(ptr, buf, len);
Vec::from_raw_parts(buf, len, cap)
}
}
unsafe fn promotable_even_to_vec(data: &AtomicPtr<()>, ptr: *const u8, len: usize) -> Vec<u8> {
promotable_to_vec(data, ptr, len, |shared| {
ptr_map(shared.cast(), |addr| addr & !KIND_MASK)
})
}
unsafe fn promotable_even_drop(data: &mut AtomicPtr<()>, ptr: *const u8, len: usize) { unsafe fn promotable_even_drop(data: &mut AtomicPtr<()>, ptr: *const u8, len: usize) {
data.with_mut(|shared| { data.with_mut(|shared| {
let shared = *shared; let shared = *shared;
let kind = shared as usize & KIND_MASK; let kind = shared as usize & KIND_MASK;
if kind == KIND_ARC { if kind == KIND_ARC {
release_shared(shared as *mut Shared); release_shared(shared.cast());
} else { } else {
debug_assert_eq!(kind, KIND_VEC); debug_assert_eq!(kind, KIND_VEC);
let buf = (shared as usize & !KIND_MASK) as *mut u8; let buf = ptr_map(shared.cast(), |addr| addr & !KIND_MASK);
drop(rebuild_boxed_slice(buf, ptr, len)); free_boxed_slice(buf, ptr, len);
} }
}); });
} }
@ -914,38 +971,49 @@ unsafe fn promotable_odd_clone(data: &AtomicPtr<()>, ptr: *const u8, len: usize)
shallow_clone_arc(shared as _, ptr, len) shallow_clone_arc(shared as _, ptr, len)
} else { } else {
debug_assert_eq!(kind, KIND_VEC); debug_assert_eq!(kind, KIND_VEC);
shallow_clone_vec(data, shared, shared as *mut u8, ptr, len) shallow_clone_vec(data, shared, shared.cast(), ptr, len)
} }
} }
unsafe fn promotable_odd_to_vec(data: &AtomicPtr<()>, ptr: *const u8, len: usize) -> Vec<u8> {
promotable_to_vec(data, ptr, len, |shared| shared.cast())
}
unsafe fn promotable_odd_drop(data: &mut AtomicPtr<()>, ptr: *const u8, len: usize) { unsafe fn promotable_odd_drop(data: &mut AtomicPtr<()>, ptr: *const u8, len: usize) {
data.with_mut(|shared| { data.with_mut(|shared| {
let shared = *shared; let shared = *shared;
let kind = shared as usize & KIND_MASK; let kind = shared as usize & KIND_MASK;
if kind == KIND_ARC { if kind == KIND_ARC {
release_shared(shared as *mut Shared); release_shared(shared.cast());
} else { } else {
debug_assert_eq!(kind, KIND_VEC); debug_assert_eq!(kind, KIND_VEC);
drop(rebuild_boxed_slice(shared as *mut u8, ptr, len)); free_boxed_slice(shared.cast(), ptr, len);
} }
}); });
} }
unsafe fn rebuild_boxed_slice(buf: *mut u8, offset: *const u8, len: usize) -> Box<[u8]> { unsafe fn free_boxed_slice(buf: *mut u8, offset: *const u8, len: usize) {
let cap = (offset as usize - buf as usize) + len; let cap = (offset as usize - buf as usize) + len;
Box::from_raw(slice::from_raw_parts_mut(buf, cap)) dealloc(buf, Layout::from_size_align(cap, 1).unwrap())
} }
// ===== impl SharedVtable ===== // ===== impl SharedVtable =====
struct Shared { struct Shared {
// holds vec for drop, but otherwise doesnt access it // Holds arguments to dealloc upon Drop, but otherwise doesn't use them
_vec: Vec<u8>, buf: *mut u8,
cap: usize,
ref_cnt: AtomicUsize, ref_cnt: AtomicUsize,
} }
impl Drop for Shared {
fn drop(&mut self) {
unsafe { dealloc(self.buf, Layout::from_size_align(self.cap, 1).unwrap()) }
}
}
// Assert that the alignment of `Shared` is divisible by 2. // Assert that the alignment of `Shared` is divisible by 2.
// This is a necessary invariant since we depend on allocating `Shared` a // This is a necessary invariant since we depend on allocating `Shared` a
// shared object to implicitly carry the `KIND_ARC` flag in its pointer. // shared object to implicitly carry the `KIND_ARC` flag in its pointer.
@ -954,6 +1022,7 @@ const _: [(); 0 - mem::align_of::<Shared>() % 2] = []; // Assert that the alignm
static SHARED_VTABLE: Vtable = Vtable { static SHARED_VTABLE: Vtable = Vtable {
clone: shared_clone, clone: shared_clone,
to_vec: shared_to_vec,
drop: shared_drop, drop: shared_drop,
}; };
@ -966,9 +1035,42 @@ unsafe fn shared_clone(data: &AtomicPtr<()>, ptr: *const u8, len: usize) -> Byte
shallow_clone_arc(shared as _, ptr, len) shallow_clone_arc(shared as _, ptr, len)
} }
unsafe fn shared_to_vec_impl(shared: *mut Shared, ptr: *const u8, len: usize) -> Vec<u8> {
// Check that the ref_cnt is 1 (unique).
//
// If it is unique, then it is set to 0 with AcqRel fence for the same
// reason in release_shared.
//
// Otherwise, we take the other branch and call release_shared.
if (*shared)
.ref_cnt
.compare_exchange(1, 0, Ordering::AcqRel, Ordering::Relaxed)
.is_ok()
{
let buf = (*shared).buf;
let cap = (*shared).cap;
// Deallocate Shared
drop(Box::from_raw(shared as *mut mem::ManuallyDrop<Shared>));
// Copy back buffer
ptr::copy(ptr, buf, len);
Vec::from_raw_parts(buf, len, cap)
} else {
let v = slice::from_raw_parts(ptr, len).to_vec();
release_shared(shared);
v
}
}
unsafe fn shared_to_vec(data: &AtomicPtr<()>, ptr: *const u8, len: usize) -> Vec<u8> {
shared_to_vec_impl(data.load(Ordering::Relaxed).cast(), ptr, len)
}
unsafe fn shared_drop(data: &mut AtomicPtr<()>, _ptr: *const u8, _len: usize) { unsafe fn shared_drop(data: &mut AtomicPtr<()>, _ptr: *const u8, _len: usize) {
data.with_mut(|shared| { data.with_mut(|shared| {
release_shared(*shared as *mut Shared); release_shared(shared.cast());
}); });
} }
@ -1006,9 +1108,9 @@ unsafe fn shallow_clone_vec(
// updated and since the buffer hasn't been promoted to an // updated and since the buffer hasn't been promoted to an
// `Arc`, those three fields still are the components of the // `Arc`, those three fields still are the components of the
// vector. // vector.
let vec = rebuild_boxed_slice(buf, offset, len).into_vec();
let shared = Box::new(Shared { let shared = Box::new(Shared {
_vec: vec, buf,
cap: (offset as usize - buf as usize) + len,
// Initialize refcount to 2. One for this reference, and one // Initialize refcount to 2. One for this reference, and one
// for the new clone that will be returned from // for the new clone that will be returned from
// `shallow_clone`. // `shallow_clone`.
@ -1082,10 +1184,40 @@ unsafe fn release_shared(ptr: *mut Shared) {
// > "acquire" operation before deleting the object. // > "acquire" operation before deleting the object.
// //
// [1]: (www.boost.org/doc/libs/1_55_0/doc/html/atomic/usage_examples.html) // [1]: (www.boost.org/doc/libs/1_55_0/doc/html/atomic/usage_examples.html)
atomic::fence(Ordering::Acquire); //
// Thread sanitizer does not support atomic fences. Use an atomic load
// instead.
(*ptr).ref_cnt.load(Ordering::Acquire);
// Drop the data // Drop the data
Box::from_raw(ptr); drop(Box::from_raw(ptr));
}
// Ideally we would always use this version of `ptr_map` since it is strict
// provenance compatible, but it results in worse codegen. We will however still
// use it on miri because it gives better diagnostics for people who test bytes
// code with miri.
//
// See https://github.com/tokio-rs/bytes/pull/545 for more info.
#[cfg(miri)]
fn ptr_map<F>(ptr: *mut u8, f: F) -> *mut u8
where
F: FnOnce(usize) -> usize,
{
let old_addr = ptr as usize;
let new_addr = f(old_addr);
let diff = new_addr.wrapping_sub(old_addr);
ptr.wrapping_add(diff)
}
#[cfg(not(miri))]
fn ptr_map<F>(ptr: *mut u8, f: F) -> *mut u8
where
F: FnOnce(usize) -> usize,
{
let old_addr = ptr as usize;
let new_addr = f(old_addr);
new_addr as *mut u8
} }
// compile-fails // compile-fails

View File

@ -1,5 +1,5 @@
use core::iter::{FromIterator, Iterator}; use core::iter::{FromIterator, Iterator};
use core::mem::{self, ManuallyDrop}; use core::mem::{self, ManuallyDrop, MaybeUninit};
use core::ops::{Deref, DerefMut}; use core::ops::{Deref, DerefMut};
use core::ptr::{self, NonNull}; use core::ptr::{self, NonNull};
use core::{cmp, fmt, hash, isize, slice, usize}; use core::{cmp, fmt, hash, isize, slice, usize};
@ -8,6 +8,7 @@ use alloc::{
borrow::{Borrow, BorrowMut}, borrow::{Borrow, BorrowMut},
boxed::Box, boxed::Box,
string::String, string::String,
vec,
vec::Vec, vec::Vec,
}; };
@ -15,7 +16,7 @@ use crate::buf::{IntoIter, UninitSlice};
use crate::bytes::Vtable; use crate::bytes::Vtable;
#[allow(unused)] #[allow(unused)]
use crate::loom::sync::atomic::AtomicMut; use crate::loom::sync::atomic::AtomicMut;
use crate::loom::sync::atomic::{self, AtomicPtr, AtomicUsize, Ordering}; use crate::loom::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
use crate::{Buf, BufMut, Bytes}; use crate::{Buf, BufMut, Bytes};
/// A unique reference to a contiguous slice of memory. /// A unique reference to a contiguous slice of memory.
@ -252,12 +253,28 @@ impl BytesMut {
let ptr = self.ptr.as_ptr(); let ptr = self.ptr.as_ptr();
let len = self.len; let len = self.len;
let data = AtomicPtr::new(self.data as _); let data = AtomicPtr::new(self.data.cast());
mem::forget(self); mem::forget(self);
unsafe { Bytes::with_vtable(ptr, len, data, &SHARED_VTABLE) } unsafe { Bytes::with_vtable(ptr, len, data, &SHARED_VTABLE) }
} }
} }
/// Creates a new `BytesMut`, which is initialized with zero.
///
/// # Examples
///
/// ```
/// use bytes::BytesMut;
///
/// let zeros = BytesMut::zeroed(42);
///
/// assert_eq!(zeros.len(), 42);
/// zeros.into_iter().for_each(|x| assert_eq!(x, 0));
/// ```
pub fn zeroed(len: usize) -> BytesMut {
BytesMut::from_vec(vec![0; len])
}
/// Splits the bytes into two at the given index. /// Splits the bytes into two at the given index.
/// ///
/// Afterwards `self` contains elements `[0, at)`, and the returned /// Afterwards `self` contains elements `[0, at)`, and the returned
@ -494,11 +511,20 @@ impl BytesMut {
/// reallocations. A call to `reserve` may result in an allocation. /// reallocations. A call to `reserve` may result in an allocation.
/// ///
/// Before allocating new buffer space, the function will attempt to reclaim /// Before allocating new buffer space, the function will attempt to reclaim
/// space in the existing buffer. If the current handle references a small /// space in the existing buffer. If the current handle references a view
/// view in the original buffer and all other handles have been dropped, /// into a larger original buffer, and all other handles referencing part
/// and the requested capacity is less than or equal to the existing /// of the same original buffer have been dropped, then the current view
/// buffer's capacity, then the current view will be copied to the front of /// can be copied/shifted to the front of the buffer and the handle can take
/// the buffer and the handle will take ownership of the full buffer. /// ownership of the full buffer, provided that the full buffer is large
/// enough to fit the requested additional capacity.
///
/// This optimization will only happen if shifting the data from the current
/// view to the front of the buffer is not too expensive in terms of the
/// (amortized) time required. The precise condition is subject to change;
/// as of now, the length of the data being shifted needs to be at least as
/// large as the distance that it's shifted by. If the current view is empty
/// and the original buffer is large enough to fit the requested additional
/// capacity, then reallocations will never happen.
/// ///
/// # Examples /// # Examples
/// ///
@ -562,17 +588,34 @@ impl BytesMut {
// space. // space.
// //
// Otherwise, since backed by a vector, use `Vec::reserve` // Otherwise, since backed by a vector, use `Vec::reserve`
//
// We need to make sure that this optimization does not kill the
// amortized runtimes of BytesMut's operations.
unsafe { unsafe {
let (off, prev) = self.get_vec_pos(); let (off, prev) = self.get_vec_pos();
// Only reuse space if we can satisfy the requested additional space. // Only reuse space if we can satisfy the requested additional space.
if self.capacity() - self.len() + off >= additional { //
// There's space - reuse it // Also check if the value of `off` suggests that enough bytes
// have been read to account for the overhead of shifting all
// the data (in an amortized analysis).
// Hence the condition `off >= self.len()`.
//
// This condition also already implies that the buffer is going
// to be (at least) half-empty in the end; so we do not break
// the (amortized) runtime with future resizes of the underlying
// `Vec`.
//
// [For more details check issue #524, and PR #525.]
if self.capacity() - self.len() + off >= additional && off >= self.len() {
// There's enough space, and it's not too much overhead:
// reuse the space!
// //
// Just move the pointer back to the start after copying // Just move the pointer back to the start after copying
// data back. // data back.
let base_ptr = self.ptr.as_ptr().offset(-(off as isize)); let base_ptr = self.ptr.as_ptr().offset(-(off as isize));
ptr::copy(self.ptr.as_ptr(), base_ptr, self.len); // Since `off >= self.len()`, the two regions don't overlap.
ptr::copy_nonoverlapping(self.ptr.as_ptr(), base_ptr, self.len);
self.ptr = vptr(base_ptr); self.ptr = vptr(base_ptr);
self.set_vec_pos(0, prev); self.set_vec_pos(0, prev);
@ -580,13 +623,14 @@ impl BytesMut {
// can gain capacity back. // can gain capacity back.
self.cap += off; self.cap += off;
} else { } else {
// No space - allocate more // Not enough space, or reusing might be too much overhead:
// allocate more space!
let mut v = let mut v =
ManuallyDrop::new(rebuild_vec(self.ptr.as_ptr(), self.len, self.cap, off)); ManuallyDrop::new(rebuild_vec(self.ptr.as_ptr(), self.len, self.cap, off));
v.reserve(additional); v.reserve(additional);
// Update the info // Update the info
self.ptr = vptr(v.as_mut_ptr().offset(off as isize)); self.ptr = vptr(v.as_mut_ptr().add(off));
self.len = v.len() - off; self.len = v.len() - off;
self.cap = v.capacity() - off; self.cap = v.capacity() - off;
} }
@ -596,7 +640,7 @@ impl BytesMut {
} }
debug_assert_eq!(kind, KIND_ARC); debug_assert_eq!(kind, KIND_ARC);
let shared: *mut Shared = self.data as _; let shared: *mut Shared = self.data;
// Reserving involves abandoning the currently shared buffer and // Reserving involves abandoning the currently shared buffer and
// allocating a new vector with the requested capacity. // allocating a new vector with the requested capacity.
@ -619,29 +663,65 @@ impl BytesMut {
// sure that the vector has enough capacity. // sure that the vector has enough capacity.
let v = &mut (*shared).vec; let v = &mut (*shared).vec;
if v.capacity() >= new_cap { let v_capacity = v.capacity();
// The capacity is sufficient, reclaim the buffer let ptr = v.as_mut_ptr();
let ptr = v.as_mut_ptr();
ptr::copy(self.ptr.as_ptr(), ptr, len); let offset = offset_from(self.ptr.as_ptr(), ptr);
// Compare the condition in the `kind == KIND_VEC` case above
// for more details.
if v_capacity >= new_cap + offset {
self.cap = new_cap;
// no copy is necessary
} else if v_capacity >= new_cap && offset >= len {
// The capacity is sufficient, and copying is not too much
// overhead: reclaim the buffer!
// `offset >= len` means: no overlap
ptr::copy_nonoverlapping(self.ptr.as_ptr(), ptr, len);
self.ptr = vptr(ptr); self.ptr = vptr(ptr);
self.cap = v.capacity(); self.cap = v.capacity();
} else {
// calculate offset
let off = (self.ptr.as_ptr() as usize) - (v.as_ptr() as usize);
return; // new_cap is calculated in terms of `BytesMut`, not the underlying
// `Vec`, so it does not take the offset into account.
//
// Thus we have to manually add it here.
new_cap = new_cap.checked_add(off).expect("overflow");
// The vector capacity is not sufficient. The reserve request is
// asking for more than the initial buffer capacity. Allocate more
// than requested if `new_cap` is not much bigger than the current
// capacity.
//
// There are some situations, using `reserve_exact` that the
// buffer capacity could be below `original_capacity`, so do a
// check.
let double = v.capacity().checked_shl(1).unwrap_or(new_cap);
new_cap = cmp::max(double, new_cap);
// No space - allocate more
//
// The length field of `Shared::vec` is not used by the `BytesMut`;
// instead we use the `len` field in the `BytesMut` itself. However,
// when calling `reserve`, it doesn't guarantee that data stored in
// the unused capacity of the vector is copied over to the new
// allocation, so we need to ensure that we don't have any data we
// care about in the unused capacity before calling `reserve`.
debug_assert!(off + len <= v.capacity());
v.set_len(off + len);
v.reserve(new_cap - v.len());
// Update the info
self.ptr = vptr(v.as_mut_ptr().add(off));
self.cap = v.capacity() - off;
} }
// The vector capacity is not sufficient. The reserve request is return;
// asking for more than the initial buffer capacity. Allocate more
// than requested if `new_cap` is not much bigger than the current
// capacity.
//
// There are some situations, using `reserve_exact` that the
// buffer capacity could be below `original_capacity`, so do a
// check.
let double = v.capacity().checked_shl(1).unwrap_or(new_cap);
new_cap = cmp::max(cmp::max(double, new_cap), original_capacity);
} else { } else {
new_cap = cmp::max(new_cap, original_capacity); new_cap = cmp::max(new_cap, original_capacity);
} }
@ -659,7 +739,7 @@ impl BytesMut {
// Update self // Update self
let data = (original_capacity_repr << ORIGINAL_CAPACITY_OFFSET) | KIND_VEC; let data = (original_capacity_repr << ORIGINAL_CAPACITY_OFFSET) | KIND_VEC;
self.data = data as _; self.data = invalid_ptr(data);
self.ptr = vptr(v.as_mut_ptr()); self.ptr = vptr(v.as_mut_ptr());
self.len = v.len(); self.len = v.len();
self.cap = v.capacity(); self.cap = v.capacity();
@ -686,11 +766,11 @@ impl BytesMut {
self.reserve(cnt); self.reserve(cnt);
unsafe { unsafe {
let dst = self.uninit_slice(); let dst = self.spare_capacity_mut();
// Reserved above // Reserved above
debug_assert!(dst.len() >= cnt); debug_assert!(dst.len() >= cnt);
ptr::copy_nonoverlapping(extend.as_ptr(), dst.as_mut_ptr() as *mut u8, cnt); ptr::copy_nonoverlapping(extend.as_ptr(), dst.as_mut_ptr().cast(), cnt);
} }
unsafe { unsafe {
@ -700,10 +780,11 @@ impl BytesMut {
/// Absorbs a `BytesMut` that was previously split off. /// Absorbs a `BytesMut` that was previously split off.
/// ///
/// If the two `BytesMut` objects were previously contiguous, i.e., if /// If the two `BytesMut` objects were previously contiguous and not mutated
/// `other` was created by calling `split_off` on this `BytesMut`, then /// in a way that causes re-allocation i.e., if `other` was created by
/// this is an `O(1)` operation that just decreases a reference /// calling `split_off` on this `BytesMut`, then this is an `O(1)` operation
/// count and sets a few indices. Otherwise this method degenerates to /// that just decreases a reference count and sets a few indices.
/// Otherwise this method degenerates to
/// `self.extend_from_slice(other.as_ref())`. /// `self.extend_from_slice(other.as_ref())`.
/// ///
/// # Examples /// # Examples
@ -754,7 +835,7 @@ impl BytesMut {
ptr, ptr,
len, len,
cap, cap,
data: data as *mut _, data: invalid_ptr(data),
} }
} }
@ -801,7 +882,7 @@ impl BytesMut {
// Updating the start of the view is setting `ptr` to point to the // Updating the start of the view is setting `ptr` to point to the
// new start and updating the `len` field to reflect the new length // new start and updating the `len` field to reflect the new length
// of the view. // of the view.
self.ptr = vptr(self.ptr.as_ptr().offset(start as isize)); self.ptr = vptr(self.ptr.as_ptr().add(start));
if self.len >= start { if self.len >= start {
self.len -= start; self.len -= start;
@ -825,7 +906,7 @@ impl BytesMut {
return Ok(()); return Ok(());
} }
let ptr = unsafe { self.ptr.as_ptr().offset(self.len as isize) }; let ptr = unsafe { self.ptr.as_ptr().add(self.len) };
if ptr == other.ptr.as_ptr() if ptr == other.ptr.as_ptr()
&& self.kind() == KIND_ARC && self.kind() == KIND_ARC
&& other.kind() == KIND_ARC && other.kind() == KIND_ARC
@ -875,7 +956,7 @@ impl BytesMut {
// always succeed. // always succeed.
debug_assert_eq!(shared as usize & KIND_MASK, KIND_ARC); debug_assert_eq!(shared as usize & KIND_MASK, KIND_ARC);
self.data = shared as _; self.data = shared;
} }
/// Makes an exact shallow clone of `self`. /// Makes an exact shallow clone of `self`.
@ -908,16 +989,45 @@ impl BytesMut {
debug_assert_eq!(self.kind(), KIND_VEC); debug_assert_eq!(self.kind(), KIND_VEC);
debug_assert!(pos <= MAX_VEC_POS); debug_assert!(pos <= MAX_VEC_POS);
self.data = ((pos << VEC_POS_OFFSET) | (prev & NOT_VEC_POS_MASK)) as *mut _; self.data = invalid_ptr((pos << VEC_POS_OFFSET) | (prev & NOT_VEC_POS_MASK));
} }
/// Returns the remaining spare capacity of the buffer as a slice of `MaybeUninit<u8>`.
///
/// The returned slice can be used to fill the buffer with data (e.g. by
/// reading from a file) before marking the data as initialized using the
/// [`set_len`] method.
///
/// [`set_len`]: BytesMut::set_len
///
/// # Examples
///
/// ```
/// use bytes::BytesMut;
///
/// // Allocate buffer big enough for 10 bytes.
/// let mut buf = BytesMut::with_capacity(10);
///
/// // Fill in the first 3 elements.
/// let uninit = buf.spare_capacity_mut();
/// uninit[0].write(0);
/// uninit[1].write(1);
/// uninit[2].write(2);
///
/// // Mark the first 3 bytes of the buffer as being initialized.
/// unsafe {
/// buf.set_len(3);
/// }
///
/// assert_eq!(&buf[..], &[0, 1, 2]);
/// ```
#[inline] #[inline]
fn uninit_slice(&mut self) -> &mut UninitSlice { pub fn spare_capacity_mut(&mut self) -> &mut [MaybeUninit<u8>] {
unsafe { unsafe {
let ptr = self.ptr.as_ptr().offset(self.len as isize); let ptr = self.ptr.as_ptr().add(self.len);
let len = self.cap - self.len; let len = self.cap - self.len;
UninitSlice::from_raw_parts_mut(ptr, len) slice::from_raw_parts_mut(ptr.cast(), len)
} }
} }
} }
@ -934,7 +1044,7 @@ impl Drop for BytesMut {
let _ = rebuild_vec(self.ptr.as_ptr(), self.len, self.cap, off); let _ = rebuild_vec(self.ptr.as_ptr(), self.len, self.cap, off);
} }
} else if kind == KIND_ARC { } else if kind == KIND_ARC {
unsafe { release_shared(self.data as _) }; unsafe { release_shared(self.data) };
} }
} }
} }
@ -991,7 +1101,7 @@ unsafe impl BufMut for BytesMut {
if self.capacity() == self.len() { if self.capacity() == self.len() {
self.reserve(64); self.reserve(64);
} }
self.uninit_slice() UninitSlice::from_slice(self.spare_capacity_mut())
} }
// Specialize these methods so they can skip checking `remaining_mut` // Specialize these methods so they can skip checking `remaining_mut`
@ -1016,7 +1126,7 @@ unsafe impl BufMut for BytesMut {
fn put_bytes(&mut self, val: u8, cnt: usize) { fn put_bytes(&mut self, val: u8, cnt: usize) {
self.reserve(cnt); self.reserve(cnt);
unsafe { unsafe {
let dst = self.uninit_slice(); let dst = self.spare_capacity_mut();
// Reserved above // Reserved above
debug_assert!(dst.len() >= cnt); debug_assert!(dst.len() >= cnt);
@ -1161,7 +1271,7 @@ impl<'a> IntoIterator for &'a BytesMut {
type IntoIter = core::slice::Iter<'a, u8>; type IntoIter = core::slice::Iter<'a, u8>;
fn into_iter(self) -> Self::IntoIter { fn into_iter(self) -> Self::IntoIter {
self.as_ref().into_iter() self.as_ref().iter()
} }
} }
@ -1190,7 +1300,18 @@ impl<'a> Extend<&'a u8> for BytesMut {
where where
T: IntoIterator<Item = &'a u8>, T: IntoIterator<Item = &'a u8>,
{ {
self.extend(iter.into_iter().map(|b| *b)) self.extend(iter.into_iter().copied())
}
}
impl Extend<Bytes> for BytesMut {
fn extend<T>(&mut self, iter: T)
where
T: IntoIterator<Item = Bytes>,
{
for bytes in iter {
self.extend_from_slice(&bytes)
}
} }
} }
@ -1202,7 +1323,7 @@ impl FromIterator<u8> for BytesMut {
impl<'a> FromIterator<&'a u8> for BytesMut { impl<'a> FromIterator<&'a u8> for BytesMut {
fn from_iter<T: IntoIterator<Item = &'a u8>>(into_iter: T) -> Self { fn from_iter<T: IntoIterator<Item = &'a u8>>(into_iter: T) -> Self {
BytesMut::from_iter(into_iter.into_iter().map(|b| *b)) BytesMut::from_iter(into_iter.into_iter().copied())
} }
} }
@ -1243,10 +1364,13 @@ unsafe fn release_shared(ptr: *mut Shared) {
// > "acquire" operation before deleting the object. // > "acquire" operation before deleting the object.
// //
// [1]: (www.boost.org/doc/libs/1_55_0/doc/html/atomic/usage_examples.html) // [1]: (www.boost.org/doc/libs/1_55_0/doc/html/atomic/usage_examples.html)
atomic::fence(Ordering::Acquire); //
// Thread sanitizer does not support atomic fences. Use an atomic load
// instead.
(*ptr).ref_count.load(Ordering::Acquire);
// Drop the data // Drop the data
Box::from_raw(ptr); drop(Box::from_raw(ptr));
} }
impl Shared { impl Shared {
@ -1392,7 +1516,7 @@ impl PartialOrd<BytesMut> for str {
impl PartialEq<Vec<u8>> for BytesMut { impl PartialEq<Vec<u8>> for BytesMut {
fn eq(&self, other: &Vec<u8>) -> bool { fn eq(&self, other: &Vec<u8>) -> bool {
*self == &other[..] *self == other[..]
} }
} }
@ -1416,7 +1540,7 @@ impl PartialOrd<BytesMut> for Vec<u8> {
impl PartialEq<String> for BytesMut { impl PartialEq<String> for BytesMut {
fn eq(&self, other: &String) -> bool { fn eq(&self, other: &String) -> bool {
*self == &other[..] *self == other[..]
} }
} }
@ -1482,13 +1606,51 @@ impl PartialOrd<BytesMut> for &str {
impl PartialEq<BytesMut> for Bytes { impl PartialEq<BytesMut> for Bytes {
fn eq(&self, other: &BytesMut) -> bool { fn eq(&self, other: &BytesMut) -> bool {
&other[..] == &self[..] other[..] == self[..]
} }
} }
impl PartialEq<Bytes> for BytesMut { impl PartialEq<Bytes> for BytesMut {
fn eq(&self, other: &Bytes) -> bool { fn eq(&self, other: &Bytes) -> bool {
&other[..] == &self[..] other[..] == self[..]
}
}
impl From<BytesMut> for Vec<u8> {
fn from(mut bytes: BytesMut) -> Self {
let kind = bytes.kind();
let mut vec = if kind == KIND_VEC {
unsafe {
let (off, _) = bytes.get_vec_pos();
rebuild_vec(bytes.ptr.as_ptr(), bytes.len, bytes.cap, off)
}
} else if kind == KIND_ARC {
let shared = bytes.data as *mut Shared;
if unsafe { (*shared).is_unique() } {
let vec = mem::replace(unsafe { &mut (*shared).vec }, Vec::new());
unsafe { release_shared(shared) };
vec
} else {
return bytes.deref().to_vec();
}
} else {
return bytes.deref().to_vec();
};
let len = bytes.len;
unsafe {
ptr::copy(bytes.ptr.as_ptr(), vec.as_mut_ptr(), len);
vec.set_len(len);
}
mem::forget(bytes);
vec
} }
} }
@ -1501,6 +1663,35 @@ fn vptr(ptr: *mut u8) -> NonNull<u8> {
} }
} }
/// Returns a dangling pointer with the given address. This is used to store
/// integer data in pointer fields.
///
/// It is equivalent to `addr as *mut T`, but this fails on miri when strict
/// provenance checking is enabled.
#[inline]
fn invalid_ptr<T>(addr: usize) -> *mut T {
let ptr = core::ptr::null_mut::<u8>().wrapping_add(addr);
debug_assert_eq!(ptr as usize, addr);
ptr.cast::<T>()
}
/// Precondition: dst >= original
///
/// The following line is equivalent to:
///
/// ```rust,ignore
/// self.ptr.as_ptr().offset_from(ptr) as usize;
/// ```
///
/// But due to min rust is 1.39 and it is only stablised
/// in 1.47, we cannot use it.
#[inline]
fn offset_from(dst: *mut u8, original: *mut u8) -> usize {
debug_assert!(dst >= original);
dst as usize - original as usize
}
unsafe fn rebuild_vec(ptr: *mut u8, mut len: usize, mut cap: usize, off: usize) -> Vec<u8> { unsafe fn rebuild_vec(ptr: *mut u8, mut len: usize, mut cap: usize, off: usize) -> Vec<u8> {
let ptr = ptr.offset(-(off as isize)); let ptr = ptr.offset(-(off as isize));
len += off; len += off;
@ -1513,6 +1704,7 @@ unsafe fn rebuild_vec(ptr: *mut u8, mut len: usize, mut cap: usize, off: usize)
static SHARED_VTABLE: Vtable = Vtable { static SHARED_VTABLE: Vtable = Vtable {
clone: shared_v_clone, clone: shared_v_clone,
to_vec: shared_v_to_vec,
drop: shared_v_drop, drop: shared_v_drop,
}; };
@ -1520,10 +1712,32 @@ unsafe fn shared_v_clone(data: &AtomicPtr<()>, ptr: *const u8, len: usize) -> By
let shared = data.load(Ordering::Relaxed) as *mut Shared; let shared = data.load(Ordering::Relaxed) as *mut Shared;
increment_shared(shared); increment_shared(shared);
let data = AtomicPtr::new(shared as _); let data = AtomicPtr::new(shared as *mut ());
Bytes::with_vtable(ptr, len, data, &SHARED_VTABLE) Bytes::with_vtable(ptr, len, data, &SHARED_VTABLE)
} }
unsafe fn shared_v_to_vec(data: &AtomicPtr<()>, ptr: *const u8, len: usize) -> Vec<u8> {
let shared: *mut Shared = data.load(Ordering::Relaxed).cast();
if (*shared).is_unique() {
let shared = &mut *shared;
// Drop shared
let mut vec = mem::replace(&mut shared.vec, Vec::new());
release_shared(shared);
// Copy back buffer
ptr::copy(ptr, vec.as_mut_ptr(), len);
vec.set_len(len);
vec
} else {
let v = slice::from_raw_parts(ptr, len).to_vec();
release_shared(shared);
v
}
}
unsafe fn shared_v_drop(data: &mut AtomicPtr<()>, _ptr: *const u8, _len: usize) { unsafe fn shared_v_drop(data: &mut AtomicPtr<()>, _ptr: *const u8, _len: usize) {
data.with_mut(|shared| { data.with_mut(|shared| {
release_shared(*shared as *mut Shared); release_shared(*shared as *mut Shared);

View File

@ -25,7 +25,7 @@ impl Debug for BytesRef<'_> {
} else if b == b'\0' { } else if b == b'\0' {
write!(f, "\\0")?; write!(f, "\\0")?;
// ASCII printable // ASCII printable
} else if b >= 0x20 && b < 0x7f { } else if (0x20..0x7f).contains(&b) {
write!(f, "{}", b as char)?; write!(f, "{}", b as char)?;
} else { } else {
write!(f, "\\x{:02x}", b)?; write!(f, "\\x{:02x}", b)?;
@ -38,12 +38,12 @@ impl Debug for BytesRef<'_> {
impl Debug for Bytes { impl Debug for Bytes {
fn fmt(&self, f: &mut Formatter<'_>) -> Result { fn fmt(&self, f: &mut Formatter<'_>) -> Result {
Debug::fmt(&BytesRef(&self.as_ref()), f) Debug::fmt(&BytesRef(self.as_ref()), f)
} }
} }
impl Debug for BytesMut { impl Debug for BytesMut {
fn fmt(&self, f: &mut Formatter<'_>) -> Result { fn fmt(&self, f: &mut Formatter<'_>) -> Result {
Debug::fmt(&BytesRef(&self.as_ref()), f) Debug::fmt(&BytesRef(self.as_ref()), f)
} }
} }

View File

@ -1,7 +1,7 @@
#[cfg(not(all(test, loom)))] #[cfg(not(all(test, loom)))]
pub(crate) mod sync { pub(crate) mod sync {
pub(crate) mod atomic { pub(crate) mod atomic {
pub(crate) use core::sync::atomic::{fence, AtomicPtr, AtomicUsize, Ordering}; pub(crate) use core::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
pub(crate) trait AtomicMut<T> { pub(crate) trait AtomicMut<T> {
fn with_mut<F, R>(&mut self, f: F) -> R fn with_mut<F, R>(&mut self, f: F) -> R
@ -23,7 +23,7 @@ pub(crate) mod sync {
#[cfg(all(test, loom))] #[cfg(all(test, loom))]
pub(crate) mod sync { pub(crate) mod sync {
pub(crate) mod atomic { pub(crate) mod atomic {
pub(crate) use loom::sync::atomic::{fence, AtomicPtr, AtomicUsize, Ordering}; pub(crate) use loom::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
pub(crate) trait AtomicMut<T> {} pub(crate) trait AtomicMut<T> {}
} }

View File

@ -4,8 +4,8 @@ use bytes::{Buf, BufMut, Bytes, BytesMut};
use std::usize; use std::usize;
const LONG: &'static [u8] = b"mary had a little lamb, little lamb, little lamb"; const LONG: &[u8] = b"mary had a little lamb, little lamb, little lamb";
const SHORT: &'static [u8] = b"hello world"; const SHORT: &[u8] = b"hello world";
fn is_sync<T: Sync>() {} fn is_sync<T: Sync>() {}
fn is_send<T: Send>() {} fn is_send<T: Send>() {}
@ -411,8 +411,8 @@ fn freeze_after_split_off() {
fn fns_defined_for_bytes_mut() { fn fns_defined_for_bytes_mut() {
let mut bytes = BytesMut::from(&b"hello world"[..]); let mut bytes = BytesMut::from(&b"hello world"[..]);
bytes.as_ptr(); let _ = bytes.as_ptr();
bytes.as_mut_ptr(); let _ = bytes.as_mut_ptr();
// Iterator // Iterator
let v: Vec<u8> = bytes.as_ref().iter().cloned().collect(); let v: Vec<u8> = bytes.as_ref().iter().cloned().collect();
@ -443,7 +443,7 @@ fn reserve_growth() {
let _ = bytes.split(); let _ = bytes.split();
bytes.reserve(65); bytes.reserve(65);
assert_eq!(bytes.capacity(), 128); assert_eq!(bytes.capacity(), 117);
} }
#[test] #[test]
@ -515,6 +515,34 @@ fn reserve_in_arc_unique_doubles() {
assert_eq!(2000, bytes.capacity()); assert_eq!(2000, bytes.capacity());
} }
#[test]
fn reserve_in_arc_unique_does_not_overallocate_after_split() {
let mut bytes = BytesMut::from(LONG);
let orig_capacity = bytes.capacity();
drop(bytes.split_off(LONG.len() / 2));
// now bytes is Arc and refcount == 1
let new_capacity = bytes.capacity();
bytes.reserve(orig_capacity - new_capacity);
assert_eq!(bytes.capacity(), orig_capacity);
}
#[test]
fn reserve_in_arc_unique_does_not_overallocate_after_multiple_splits() {
let mut bytes = BytesMut::from(LONG);
let orig_capacity = bytes.capacity();
for _ in 0..10 {
drop(bytes.split_off(LONG.len() / 2));
// now bytes is Arc and refcount == 1
let new_capacity = bytes.capacity();
bytes.reserve(orig_capacity - new_capacity);
}
assert_eq!(bytes.capacity(), orig_capacity);
}
#[test] #[test]
fn reserve_in_arc_nonunique_does_not_overallocate() { fn reserve_in_arc_nonunique_does_not_overallocate() {
let mut bytes = BytesMut::with_capacity(1000); let mut bytes = BytesMut::with_capacity(1000);
@ -527,6 +555,25 @@ fn reserve_in_arc_nonunique_does_not_overallocate() {
assert_eq!(2001, bytes.capacity()); assert_eq!(2001, bytes.capacity());
} }
/// This function tests `BytesMut::reserve_inner`, where `BytesMut` holds
/// a unique reference to the shared vector and decide to reuse it
/// by reallocating the `Vec`.
#[test]
fn reserve_shared_reuse() {
let mut bytes = BytesMut::with_capacity(1000);
bytes.put_slice(b"Hello, World!");
drop(bytes.split());
bytes.put_slice(b"!123ex123,sadchELLO,_wORLD!");
// Use split_off so that v.capacity() - self.cap != off
drop(bytes.split_off(9));
assert_eq!(&*bytes, b"!123ex123");
bytes.reserve(2000);
assert_eq!(&*bytes, b"!123ex123");
assert_eq!(bytes.capacity(), 2009);
}
#[test] #[test]
fn extend_mut() { fn extend_mut() {
let mut bytes = BytesMut::with_capacity(0); let mut bytes = BytesMut::with_capacity(0);
@ -544,6 +591,13 @@ fn extend_from_slice_mut() {
} }
} }
#[test]
fn extend_mut_from_bytes() {
let mut bytes = BytesMut::with_capacity(0);
bytes.extend([Bytes::from(LONG)]);
assert_eq!(*bytes, LONG[..]);
}
#[test] #[test]
fn extend_mut_without_size_hint() { fn extend_mut_without_size_hint() {
let mut bytes = BytesMut::with_capacity(0); let mut bytes = BytesMut::with_capacity(0);
@ -874,7 +928,7 @@ fn from_iter_no_size_hint() {
fn test_slice_ref(bytes: &Bytes, start: usize, end: usize, expected: &[u8]) { fn test_slice_ref(bytes: &Bytes, start: usize, end: usize, expected: &[u8]) {
let slice = &(bytes.as_ref()[start..end]); let slice = &(bytes.as_ref()[start..end]);
let sub = bytes.slice_ref(&slice); let sub = bytes.slice_ref(slice);
assert_eq!(&sub[..], expected); assert_eq!(&sub[..], expected);
} }
@ -894,7 +948,7 @@ fn slice_ref_empty() {
let bytes = Bytes::from(&b""[..]); let bytes = Bytes::from(&b""[..]);
let slice = &(bytes.as_ref()[0..0]); let slice = &(bytes.as_ref()[0..0]);
let sub = bytes.slice_ref(&slice); let sub = bytes.slice_ref(slice);
assert_eq!(&sub[..], b""); assert_eq!(&sub[..], b"");
} }
@ -1002,3 +1056,110 @@ fn box_slice_empty() {
let b = Bytes::from(empty); let b = Bytes::from(empty);
assert!(b.is_empty()); assert!(b.is_empty());
} }
#[test]
fn bytes_into_vec() {
// Test kind == KIND_VEC
let content = b"helloworld";
let mut bytes = BytesMut::new();
bytes.put_slice(content);
let vec: Vec<u8> = bytes.into();
assert_eq!(&vec, content);
// Test kind == KIND_ARC, shared.is_unique() == True
let mut bytes = BytesMut::new();
bytes.put_slice(b"abcdewe23");
bytes.put_slice(content);
// Overwrite the bytes to make sure only one reference to the underlying
// Vec exists.
bytes = bytes.split_off(9);
let vec: Vec<u8> = bytes.into();
assert_eq!(&vec, content);
// Test kind == KIND_ARC, shared.is_unique() == False
let prefix = b"abcdewe23";
let mut bytes = BytesMut::new();
bytes.put_slice(prefix);
bytes.put_slice(content);
let vec: Vec<u8> = bytes.split_off(prefix.len()).into();
assert_eq!(&vec, content);
let vec: Vec<u8> = bytes.into();
assert_eq!(&vec, prefix);
}
#[test]
fn test_bytes_into_vec() {
// Test STATIC_VTABLE.to_vec
let bs = b"1b23exfcz3r";
let vec: Vec<u8> = Bytes::from_static(bs).into();
assert_eq!(&*vec, bs);
// Test bytes_mut.SHARED_VTABLE.to_vec impl
eprintln!("1");
let mut bytes_mut: BytesMut = bs[..].into();
// Set kind to KIND_ARC so that after freeze, Bytes will use bytes_mut.SHARED_VTABLE
eprintln!("2");
drop(bytes_mut.split_off(bs.len()));
eprintln!("3");
let b1 = bytes_mut.freeze();
eprintln!("4");
let b2 = b1.clone();
eprintln!("{:#?}", (&*b1).as_ptr());
// shared.is_unique() = False
eprintln!("5");
assert_eq!(&*Vec::from(b2), bs);
// shared.is_unique() = True
eprintln!("6");
assert_eq!(&*Vec::from(b1), bs);
// Test bytes_mut.SHARED_VTABLE.to_vec impl where offset != 0
let mut bytes_mut1: BytesMut = bs[..].into();
let bytes_mut2 = bytes_mut1.split_off(9);
let b1 = bytes_mut1.freeze();
let b2 = bytes_mut2.freeze();
assert_eq!(Vec::from(b2), bs[9..]);
assert_eq!(Vec::from(b1), bs[..9]);
}
#[test]
fn test_bytes_into_vec_promotable_even() {
let vec = vec![33u8; 1024];
// Test cases where kind == KIND_VEC
let b1 = Bytes::from(vec.clone());
assert_eq!(Vec::from(b1), vec);
// Test cases where kind == KIND_ARC, ref_cnt == 1
let b1 = Bytes::from(vec.clone());
drop(b1.clone());
assert_eq!(Vec::from(b1), vec);
// Test cases where kind == KIND_ARC, ref_cnt == 2
let b1 = Bytes::from(vec.clone());
let b2 = b1.clone();
assert_eq!(Vec::from(b1), vec);
// Test cases where vtable = SHARED_VTABLE, kind == KIND_ARC, ref_cnt == 1
assert_eq!(Vec::from(b2), vec);
// Test cases where offset != 0
let mut b1 = Bytes::from(vec.clone());
let b2 = b1.split_off(20);
assert_eq!(Vec::from(b2), vec[20..]);
assert_eq!(Vec::from(b1), vec[..20]);
}

View File

@ -24,8 +24,7 @@ unsafe impl GlobalAlloc for Odd {
}; };
let ptr = System.alloc(new_layout); let ptr = System.alloc(new_layout);
if !ptr.is_null() { if !ptr.is_null() {
let ptr = ptr.offset(1); ptr.offset(1)
ptr
} else { } else {
ptr ptr
} }
@ -67,3 +66,32 @@ fn test_bytes_clone_drop() {
let b1 = Bytes::from(vec); let b1 = Bytes::from(vec);
let _b2 = b1.clone(); let _b2 = b1.clone();
} }
#[test]
fn test_bytes_into_vec() {
let vec = vec![33u8; 1024];
// Test cases where kind == KIND_VEC
let b1 = Bytes::from(vec.clone());
assert_eq!(Vec::from(b1), vec);
// Test cases where kind == KIND_ARC, ref_cnt == 1
let b1 = Bytes::from(vec.clone());
drop(b1.clone());
assert_eq!(Vec::from(b1), vec);
// Test cases where kind == KIND_ARC, ref_cnt == 2
let b1 = Bytes::from(vec.clone());
let b2 = b1.clone();
assert_eq!(Vec::from(b1), vec);
// Test cases where vtable = SHARED_VTABLE, kind == KIND_ARC, ref_cnt == 1
assert_eq!(Vec::from(b2), vec);
// Test cases where offset != 0
let mut b1 = Bytes::from(vec.clone());
let b2 = b1.split_off(20);
assert_eq!(Vec::from(b2), vec[20..]);
assert_eq!(Vec::from(b1), vec[..20]);
}

View File

@ -1,61 +1,87 @@
use std::alloc::{GlobalAlloc, Layout, System}; use std::alloc::{GlobalAlloc, Layout, System};
use std::{mem, ptr}; use std::ptr::null_mut;
use std::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
use bytes::{Buf, Bytes}; use bytes::{Buf, Bytes};
#[global_allocator] #[global_allocator]
static LEDGER: Ledger = Ledger; static LEDGER: Ledger = Ledger::new();
struct Ledger; const LEDGER_LENGTH: usize = 2048;
const USIZE_SIZE: usize = mem::size_of::<usize>(); struct Ledger {
alloc_table: [(AtomicPtr<u8>, AtomicUsize); LEDGER_LENGTH],
}
unsafe impl GlobalAlloc for Ledger { impl Ledger {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 { const fn new() -> Self {
if layout.align() == 1 && layout.size() > 0 { const ELEM: (AtomicPtr<u8>, AtomicUsize) =
// Allocate extra space to stash a record of (AtomicPtr::new(null_mut()), AtomicUsize::new(0));
// how much space there was. let alloc_table = [ELEM; LEDGER_LENGTH];
let orig_size = layout.size();
let size = orig_size + USIZE_SIZE; Self { alloc_table }
let new_layout = match Layout::from_size_align(size, 1) { }
Ok(layout) => layout,
Err(_err) => return ptr::null_mut(), /// Iterate over our table until we find an open entry, then insert into said entry
}; fn insert(&self, ptr: *mut u8, size: usize) {
let ptr = System.alloc(new_layout); for (entry_ptr, entry_size) in self.alloc_table.iter() {
if !ptr.is_null() { // SeqCst is good enough here, we don't care about perf, i just want to be correct!
(ptr as *mut usize).write(orig_size); if entry_ptr
let ptr = ptr.offset(USIZE_SIZE as isize); .compare_exchange(null_mut(), ptr, Ordering::SeqCst, Ordering::SeqCst)
ptr .is_ok()
} else { {
ptr entry_size.store(size, Ordering::SeqCst);
break;
} }
} else {
System.alloc(layout)
} }
} }
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { fn remove(&self, ptr: *mut u8) -> usize {
if layout.align() == 1 && layout.size() > 0 { for (entry_ptr, entry_size) in self.alloc_table.iter() {
let off_ptr = (ptr as *mut usize).offset(-1); // set the value to be something that will never try and be deallocated, so that we
let orig_size = off_ptr.read(); // don't have any chance of a race condition
if orig_size != layout.size() { //
panic!( // dont worry, LEDGER_LENGTH is really long to compensate for us not reclaiming space
"bad dealloc: alloc size was {}, dealloc size is {}", if entry_ptr
orig_size, .compare_exchange(
layout.size() ptr,
); invalid_ptr(usize::MAX),
Ordering::SeqCst,
Ordering::SeqCst,
)
.is_ok()
{
return entry_size.load(Ordering::SeqCst);
} }
}
let new_layout = match Layout::from_size_align(layout.size() + USIZE_SIZE, 1) { panic!("Couldn't find a matching entry for {:x?}", ptr);
Ok(layout) => layout, }
Err(_err) => std::process::abort(), }
};
System.dealloc(off_ptr as *mut u8, new_layout); unsafe impl GlobalAlloc for Ledger {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let size = layout.size();
let ptr = System.alloc(layout);
self.insert(ptr, size);
ptr
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
let orig_size = self.remove(ptr);
if orig_size != layout.size() {
panic!(
"bad dealloc: alloc size was {}, dealloc size is {}",
orig_size,
layout.size()
);
} else { } else {
System.dealloc(ptr, layout); System.dealloc(ptr, layout);
} }
} }
} }
#[test] #[test]
fn test_bytes_advance() { fn test_bytes_advance() {
let mut bytes = Bytes::from(vec![10, 20, 30]); let mut bytes = Bytes::from(vec![10, 20, 30]);
@ -77,3 +103,41 @@ fn test_bytes_truncate_and_advance() {
bytes.advance(1); bytes.advance(1);
drop(bytes); drop(bytes);
} }
/// Returns a dangling pointer with the given address. This is used to store
/// integer data in pointer fields.
#[inline]
fn invalid_ptr<T>(addr: usize) -> *mut T {
let ptr = std::ptr::null_mut::<u8>().wrapping_add(addr);
debug_assert_eq!(ptr as usize, addr);
ptr.cast::<T>()
}
#[test]
fn test_bytes_into_vec() {
let vec = vec![33u8; 1024];
// Test cases where kind == KIND_VEC
let b1 = Bytes::from(vec.clone());
assert_eq!(Vec::from(b1), vec);
// Test cases where kind == KIND_ARC, ref_cnt == 1
let b1 = Bytes::from(vec.clone());
drop(b1.clone());
assert_eq!(Vec::from(b1), vec);
// Test cases where kind == KIND_ARC, ref_cnt == 2
let b1 = Bytes::from(vec.clone());
let b2 = b1.clone();
assert_eq!(Vec::from(b1), vec);
// Test cases where vtable = SHARED_VTABLE, kind == KIND_ARC, ref_cnt == 1
assert_eq!(Vec::from(b2), vec);
// Test cases where offset != 0
let mut b1 = Bytes::from(vec.clone());
let b2 = b1.split_off(20);
assert_eq!(Vec::from(b2), vec[20..]);
assert_eq!(Vec::from(b1), vec[..20]);
}

View File

@ -133,6 +133,28 @@ fn vectored_read() {
} }
} }
#[test]
fn chain_growing_buffer() {
let mut buff = [' ' as u8; 10];
let mut vec = b"wassup".to_vec();
let mut chained = (&mut buff[..]).chain_mut(&mut vec).chain_mut(Vec::new()); // Required for potential overflow because remaining_mut for Vec is isize::MAX - vec.len(), but for chain_mut is usize::MAX
chained.put_slice(b"hey there123123");
assert_eq!(&buff, b"hey there1");
assert_eq!(&vec, b"wassup23123");
}
#[test]
fn chain_overflow_remaining_mut() {
let mut chained = Vec::<u8>::new().chain_mut(Vec::new()).chain_mut(Vec::new());
assert_eq!(chained.remaining_mut(), usize::MAX);
chained.put_slice(&[0; 256]);
assert_eq!(chained.remaining_mut(), usize::MAX);
}
#[test] #[test]
fn chain_get_bytes() { fn chain_get_bytes() {
let mut ab = Bytes::copy_from_slice(b"ab"); let mut ab = Bytes::copy_from_slice(b"ab");

View File

@ -0,0 +1 @@
{"files":{"CHANGELOG.md":"4d8c91cba3f3a1f83db2f6fa54c31351042e43cd5c8fde2ac440b538ebf23a06","Cargo.lock":"093e4a10f87fd7731dff20bc83f80b07c64c7acacb06026bbc2bfc5a83c6d494","Cargo.toml":"a569493c65bc480a358d787e544ca1a126828fb0eaec65118ae66f5322d4df5b","LICENSE":"8ea93490d74a5a1b1af3ff71d786271b3f1e5f0bea79ac16e02ec533cef040d6","README.md":"64a31447e7e04fb3cb53ef2d6d8c66fdd2dbb44e28007729013e12f0b54f2daf","clippy.toml":"0427eea1ddcf872469d073864d37b89728232ff7eb77e0e07f62d7eb1bc8667c","examples/automatic_bounds.rs":"2950c8d33bb40f095decba1990d7d0bcd48dffc6e9b7cefce2fcb3818ecf1d18","examples/consume_fields.rs":"f06483ec5bcb169f2d35df7f2aa1dbe2e75e9e79281ec955124e7e1e9e7d90f9","examples/fallible_read.rs":"1e5f2b69e436d899209dc8a4deec0dbe3e998f10a6632a79c0876c46f68267b4","examples/shorthand_or_long_field.rs":"db2fd9eba49d8703bc21c30c1413e26b2c09621a4c099982c75b8fad55887867","examples/supports_struct.rs":"08c5cc46400a0e5cf811c0d254a37b42de94b34cd04ac801069105bc792051f6","src/lib.rs":"44af6fbf658e70ecf44c826f7b61519e3224271df1ab8104812f57ba5f4867df","src/macros_public.rs":"7d2ce0c5026227ef7854db11d7a885ad891255438b2e49bbdfda56fa2f92feec","tests/accrue_errors.rs":"4f0f5be65c5cd639b107a6a14e9fb51573b27cfa9210a2182fa5f450bc1d56db","tests/computed_bound.rs":"aed9c00f2b8373e9426337137207e5b9341d236adef682bd2c9427db9ce1d1ff","tests/custom_bound.rs":"9b823063b7fc6c6b3b23905405ce7f316e8a937d5c67c416b89642e537bf9110","tests/defaults.rs":"fcbf2e55b2f60d25afb2c892da4cc1f9512286841ef5594f711edd4b32aa3028","tests/enums_newtype.rs":"ed63735b88fdfd9037580d878be895a311c13d7d8083ee3077f5ab61e754eb7c","tests/enums_struct.rs":"36ca3d4996644d566b45c78d8c343c4a74fcaa2eba96b4e012c8a1819eb6f4c6","tests/enums_unit.rs":"7f94f793e6adc265c6cdc4b657aa237da1ab0be03b03bce23d3e2180cd3e8d03","tests/error.rs":"f5f84991472e184e1167f0fe8d5f2cbad3844c4022987c9eff46b4db2bcc804a","tests/from_generics.rs":"eda4fe40e27fb15ab5e2d4f0660baa30ddca124003286216790da373393fbda2","tests/from_type_param.rs":"94d2766d5ae11d69750497225d6aa3c2f34b09fbc8c3580d61f077d7bb41265b","tests/from_type_param_default.rs":"e00e2f0c779753f66b95e5c0106451f65cbd6fbc28e676381d276290da6254b6","tests/from_variant.rs":"48046b156f6c5d9b3e9c3d0b36b5eebaba1d417447e3babf81ed9c74bee3bfcb","tests/generics.rs":"0c2830acf511148d71ecd4a8b5aa17f80e377aa89f7fe0fc10f6db34671d034a","tests/happy_path.rs":"c7a540fc1755cef757aa5e6cd202a49a47a2040facb0c05c167ec62f8ebbc557","tests/hash_map.rs":"c30764bf1845ca81bc1d752dbe0de965ba70cfbb1571884a20a873ed7bf26360","tests/multiple.rs":"1362ec057f4796ffabf7161033b561b51f069b818af7bac85fe66935c62038dd","tests/newtype.rs":"b5ecf605652b194372cab6d6fef96a2dd4b63ac24649cb52ca944ef9647512ad","tests/skip.rs":"7d95ba17c122e06a64474be8f24a88e234e8e42448f955352f635e544be433ca","tests/split_declaration.rs":"019863370414af227144aac13272fc39a1e256a9ed0bd3ca2dbf1114f1a9e1ba","tests/suggestions.rs":"e9f8ab55718a5853411a4606f1be1473b57fc7a2789f622d0ed807fcd8ac5606","tests/supports.rs":"5afcd511c5c197cb3557864bb7225e34205718e308112be5f59af2bfb7391a13","tests/unsupported_attributes.rs":"96333cd6602a6f18f47563d5faa923e423b2c02f2ae0d09a15e2d3514593c38d"},"package":"a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c"}

194
zeroidc/vendor/darling/CHANGELOG.md vendored Normal file
View File

@ -0,0 +1,194 @@
# Changelog
## v0.13.4 (April 6, 2022)
- Impl `FromMeta` for `syn::Visibility` [#173](https://github.com/TedDriggs/darling/pull/173)
## v0.13.3 (April 5, 2022)
- Add `error::Accumulator` for dealing with multiple errors [#164](https://github.com/TedDriggs/darling/pull/164)
- Impl `FromMeta` for `syn::Type` and its variants [#172](https://github.com/TedDriggs/darling/pulls/172)
## v0.13.2 (March 30, 2022)
- Impl `FromMeta` for `syn::ExprPath` [#169](https://github.com/TedDriggs/darling/issues/169)
## v0.13.1 (December 7, 2021)
- Add `FromAttributes` trait and macro [#151](https://github.com/TedDriggs/darling/issues/151)
## v0.13.0 (May 20, 2021)
- Update darling to 2018 edition [#129](https://github.com/TedDriggs/darling/pull/129)
- Error on duplicate fields in `#[darling(...)]` attributes [#130](https://github.com/TedDriggs/darling/pull/130)
- Impl `Copy` for `SpannedValue<T: Copy>`
- Add `SpannedValue::map_ref`
## v0.13.0-beta (April 20, 2021)
- Update darling to 2018 edition [#129](https://github.com/TedDriggs/darling/pull/129)
- Error on duplicate fields in `#[darling(...)]` attributes [#130](https://github.com/TedDriggs/darling/pull/130)
## v0.12.4 (April 20, 2021)
- Add `and_then` to derive macros for `darling`
## v0.12.3 (April 8, 2021)
- Fix `FromMeta` impl for `char` not to panic [#126](https://github.com/TedDriggs/darling/pull/126)
## v0.12.2 (February 23, 2021)
- Impl `FromMeta` for `HashMap<Ident, V>` and `HashMap<Path, V>`
## v0.12.1 (February 22, 2021)
- Impl `FromMeta` for `syn::ExprArray` [#122](https://github.com/TedDriggs/darling/pull/122)
- Remove use of `unreachable` from `darling::ast::Data` [#123](https://github.com/TedDriggs/darling/pull/123)
- Add `darling::ast::Data::try_empty_from` to avoid panics when trying to read a union body [#123](https://github.com/TedDriggs/darling/pull/123)
## v0.12.0 (January 5, 2021)
- POSSIBLY BREAKING: Derived impls of `FromDeriveInput`, `FromField`, `FromVariant`, and `FromTypeParam` will now error when encountering an attribute `darling` has been asked to parse that isn't a supported shape.
Any crates using `darling` that relied on those attributes being silently ignored could see new errors reported in their dependent crates. [#113](https://github.com/TedDriggs/darling/pull/113)
- Impl `syn::spanned::Spanned` for `darling::util::SpannedValue` [#113](https://github.com/TedDriggs/darling/pull/113)
- Add `darling::util::parse_attribute_to_meta_list` to provide useful errors during attribute parsing [#113](https://github.com/TedDriggs/darling/pull/113)
- Add `impl From<syn::Error> for Error` to losslessly propagate `syn` errors [#116](https://github.com/TedDriggs/darling/pull/116)
## v0.11.0 (December 14, 2020)
- Bump minor version due to unexpected breaking change [#107](https://github.com/TedDriggs/darling/issues/107)
## v0.10.3 (December 10, 2020)
- Add `discriminant` magic field when deriving `FromVariant` [#105](https://github.com/TedDriggs/darling/pull/105)
## v0.10.2 (October 30, 2019)
- Bump syn dependency to 1.0.1 [#83](https://github.com/TedDriggs/darling/pull/83)
## v0.10.1 (September 25, 2019)
- Fix test compilation errors [#81](https://github.com/TedDriggs/darling/pull/81)
## v0.10.0 (August 15, 2019)
- Bump syn and quote to 1.0 [#79](https://github.com/TedDriggs/darling/pull/79)
- Increase rust version to 1.31
## v0.9.0 (March 20, 2019)
- Enable "did you mean" suggestions by default
- Make `darling_core::{codegen, options}` private [#58](https://github.com/TedDriggs/darling/issues/58)
- Fix `Override::as_mut`: [#66](https://github.com/TedDriggs/darling/issues/66)
## v0.8.6 (March 18, 2019)
- Added "did you mean" suggestions for unknown fields behind the `suggestions` flag [#60](https://github.com/TedDriggs/issues/60)
- Added `Error::unknown_field_with_alts` to support the suggestion use-case.
- Added `ast::Fields::len` and `ast::Fields::is_empty` methods.
## v0.8.5 (February 4, 2019)
- Accept unquoted positive numeric literals [#52](https://github.com/TedDriggs/issues/52)
- Add `FromMeta` to the `syn::Lit` enum and its variants
- Improve error message for unexpected literal formats to not say "other"
## v0.8.4 (February 4, 2019)
- Use `syn::Error` to provide precise errors before `proc_macro::Diagnostic` is available
- Add `diagnostics` feature flag to toggle between stable and unstable error backends
- Attach error information in more contexts
- Add `allow_unknown_fields` to support parsing the same attribute multiple times for different macros [#51](https://github.com/darling/issues/51)
- Proc-macro authors will now see better errors in `darling` attributes
## v0.8.3 (January 21, 2019)
- Attach spans to errors in generated trait impls [#37](https://github.com/darling/issues/37)
- Attach spans to errors for types with provided bespoke implementations
- Deprecate `set_span` from 0.8.2, as spans should never be broadened after being initially set
## v0.8.2 (January 17, 2019)
- Add spans to errors to make quality warnings and errors easy in darling. This is blocked on diagnostics stabilizing.
- Add `darling::util::SpannedValue` so proc-macro authors can remember position information alongside parsed values.
## v0.8.0
- Update dependency on `syn` to 0.15 [#44](https://github.com/darling/pull/44). Thanks to @hcpl
## v0.7.0 (July 24, 2018)
- Update dependencies on `syn` and `proc-macro2`
- Add `util::IdentString`, which acts as an Ident or its string equivalent
## v0.6.3 (May 22, 2018)
- Add support for `Uses*` traits in where predicates
## v0.6.2 (May 22, 2018)
- Add `usage` module for tracking type param and lifetime usage in generic declarations
- Add `UsesTypeParams` and `CollectsTypeParams` traits [#37](https://github.com/darling/issues/37)
- Add `UsesLifetimes` and `CollectLifetimes` traits [#41](https://github.com/darling/pull/41)
- Don't add `FromMeta` bounds to type parameters only used by skipped fields [#40](https://github.com/darling/pull/40)
## v0.6.1 (May 17, 2018)
- Fix an issue where the `syn` update broke shape validation [#36](https://github.com/TedDriggs/darling/issues/36)
## v0.6.0 (May 15, 2018)
### Breaking Changes
- Renamed `FromMetaItem` to `FromMeta`, and renamed `from_meta_item` method to `from_meta`
- Added dedicated `derive(FromMetaItem)` which panics and redirects users to `FromMeta`
## v0.5.0 (May 10, 2018)
- Add `ast::Generics` and `ast::GenericParam` to work with generics in a manner similar to `ast::Data`
- Add `ast::GenericParamExt` to support alternate representations of generic parameters
- Add `util::WithOriginal` to get a parsed representation and syn's own struct for a syntax block
- Add `FromGenerics` and `FromGenericParam` traits (without derive support)
- Change generated code for `generics` magic field to invoke `FromGenerics` trait during parsing
- Add `FromTypeParam` trait [#30](https://github.com/TedDriggs/darling/pull/30). Thanks to @upsuper
## v0.4.0 (April 5, 2018)
- Update dependencies on `proc-macro`, `quote`, and `syn` [#26](https://github.com/TedDriggs/darling/pull/26). Thanks to @hcpl
## v0.3.3 (April 2, 2018)
**YANKED**
## v0.3.2 (March 13, 2018)
- Derive `Default` on `darling::Ignored` (fixes [#25](https://github.com/TedDriggs/darling/issues/25)).
## v0.3.1 (March 7, 2018)
- Support proc-macro2/nightly [#24](https://github.com/TedDriggs/darling/pull/24). Thanks to @kdy1
## v0.3.0 (January 26, 2018)
### Breaking Changes
- Update `syn` to 0.12 [#20](https://github.com/TedDriggs/darling/pull/20). Thanks to @Eijebong
- Update `quote` to 0.4 [#20](https://github.com/TedDriggs/darling/pull/20). Thanks to @Eijebong
- Rename magic field `body` in derived `FromDeriveInput` structs to `data` to stay in sync with `syn`
- Rename magic field `data` in derived `FromVariant` structs to `fields` to stay in sync with `syn`
## v0.2.2 (December 5, 2017)
- Update `lazy_static` to 1.0 [#15](https://github.com/TedDriggs/darling/pull/16). Thanks to @Eijebong
## v0.2.1 (November 28, 2017)
- Add `impl FromMetaItem` for integer types [#15](https://github.com/TedDriggs/darling/pull/15)
## v0.2.0 (June 18, 2017)
- Added support for returning multiple errors from parsing [#5](https://github.com/TedDriggs/darling/pull/5)
- Derived impls no longer return on first error [#5](https://github.com/TedDriggs/darling/pull/5)
- Removed default types for `V` and `F` from `ast::Body`
- Enum variants are automatically converted to snake_case [#12](https://github.com/TedDriggs/darling/pull/12)

92
zeroidc/vendor/darling/Cargo.lock generated vendored Normal file
View File

@ -0,0 +1,92 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "darling"
version = "0.13.4"
dependencies = [
"darling_core",
"darling_macro",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "darling_core"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn",
]
[[package]]
name = "darling_macro"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
dependencies = [
"darling_core",
"quote",
"syn",
]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "proc-macro2"
version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"

42
zeroidc/vendor/darling/Cargo.toml vendored Normal file
View File

@ -0,0 +1,42 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2018"
name = "darling"
version = "0.13.4"
authors = ["Ted Driggs <ted.driggs@outlook.com>"]
exclude = ["/.travis.yml", "/publish.sh", "/.github/**"]
description = "A proc-macro library for reading attributes into structs when\nimplementing custom derives.\n"
documentation = "https://docs.rs/darling/0.13.4"
readme = "README.md"
license = "MIT"
repository = "https://github.com/TedDriggs/darling"
[dependencies.darling_core]
version = "=0.13.4"
[dependencies.darling_macro]
version = "=0.13.4"
[dev-dependencies.proc-macro2]
version = "1.0.26"
[dev-dependencies.quote]
version = "1.0.9"
[dev-dependencies.syn]
version = "1.0.69"
[features]
default = ["suggestions"]
diagnostics = ["darling_core/diagnostics"]
suggestions = ["darling_core/suggestions"]
[badges.maintenance]
status = "actively-developed"

21
zeroidc/vendor/darling/LICENSE vendored Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 Ted Driggs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

114
zeroidc/vendor/darling/README.md vendored Normal file
View File

@ -0,0 +1,114 @@
Darling
=======
[![Build Status](https://github.com/TedDriggs/darling/workflows/CI/badge.svg)](https://github.com/TedDriggs/darling/actions)
[![Latest Version](https://img.shields.io/crates/v/darling.svg)](https://crates.io/crates/darling)
[![Rustc Version 1.31+](https://img.shields.io/badge/rustc-1.31+-lightgray.svg)](https://blog.rust-lang.org/2018/12/06/Rust-1.31-and-rust-2018.html)
`darling` is a crate for proc macro authors, which enables parsing attributes into structs. It is heavily inspired by `serde` both in its internals and in its API.
# Benefits
* Easy and declarative parsing of macro input - make your proc-macros highly controllable with minimal time investment.
* Great validation and errors, no work required. When users of your proc-macro make a mistake, `darling` makes sure they get error markers at the right place in their source, and provides "did you mean" suggestions for misspelled fields.
# Usage
`darling` provides a set of traits which can be derived or manually implemented.
1. `FromMeta` is used to extract values from a meta-item in an attribute. Implementations are likely reusable for many libraries, much like `FromStr` or `serde::Deserialize`. Trait implementations are provided for primitives, some std types, and some `syn` types.
2. `FromDeriveInput` is implemented or derived by each proc-macro crate which depends on `darling`. This is the root for input parsing; it gets access to the identity, generics, and visibility of the target type, and can specify which attribute names should be parsed or forwarded from the input AST.
3. `FromField` is implemented or derived by each proc-macro crate which depends on `darling`. Structs deriving this trait will get access to the identity (if it exists), type, and visibility of the field.
4. `FromVariant` is implemented or derived by each proc-macro crate which depends on `darling`. Structs deriving this trait will get access to the identity and contents of the variant, which can be transformed the same as any other `darling` input.
5. `FromAttributes` is a lower-level version of the more-specific `FromDeriveInput`, `FromField`, and `FromVariant` traits. Structs deriving this trait get a meta-item extractor and error collection which works for any syntax element, including traits, trait items, and functions. This is useful for non-derive proc macros.
## Additional Modules
* `darling::ast` provides generic types for representing the AST.
* `darling::usage` provides traits and functions for determining where type parameters and lifetimes are used in a struct or enum.
* `darling::util` provides helper types with special `FromMeta` implementations, such as `IdentList`.
# Example
```rust,ignore
#[macro_use]
extern crate darling;
extern crate syn;
#[derive(Default, FromMeta)]
#[darling(default)]
pub struct Lorem {
#[darling(rename = "sit")]
ipsum: bool,
dolor: Option<String>,
}
#[derive(FromDeriveInput)]
#[darling(attributes(my_crate), forward_attrs(allow, doc, cfg))]
pub struct MyTraitOpts {
ident: syn::Ident,
attrs: Vec<syn::Attribute>,
lorem: Lorem,
}
```
The above code will then be able to parse this input:
```rust,ignore
/// A doc comment which will be available in `MyTraitOpts::attrs`.
#[derive(MyTrait)]
#[my_crate(lorem(dolor = "Hello", sit))]
pub struct ConsumingType;
```
# Attribute Macros
Non-derive attribute macros are supported.
To parse arguments for attribute macros, derive `FromMeta` on the argument receiver type, then pass `&syn::AttributeArgs` to the `from_list` method.
This will produce a normal `darling::Result<T>` that can be used the same as a result from parsing a `DeriveInput`.
## Macro Code
```rust,ignore
use darling::FromMeta;
use syn::{AttributeArgs, ItemFn};
use proc_macro::TokenStream;
#[derive(Debug, FromMeta)]
pub struct MacroArgs {
#[darling(default)]
timeout_ms: Option<u16>,
path: String,
}
#[proc_macro_attribute]
fn your_attr(args: TokenStream, input: TokenStream) -> TokenStream {
let attr_args = parse_macro_input!(args as AttributeArgs);
let _input = parse_macro_input!(input as ItemFn);
let _args = match MacroArgs::from_list(&attr_args) {
Ok(v) => v,
Err(e) => { return TokenStream::from(e.write_errors()); }
};
// do things with `args`
unimplemented!()
}
```
## Consuming Code
```rust,ignore
use your_crate::your_attr;
#[your_attr(path = "hello", timeout_ms = 15)]
fn do_stuff() {
println!("Hello");
}
```
# Features
Darling's features are built to work well for real-world projects.
* **Defaults**: Supports struct- and field-level defaults, using the same path syntax as `serde`.
* **Field Renaming**: Fields can have different names in usage vs. the backing code.
* **Auto-populated fields**: Structs deriving `FromDeriveInput` and `FromField` can declare properties named `ident`, `vis`, `ty`, `attrs`, and `generics` to automatically get copies of the matching values from the input AST. `FromDeriveInput` additionally exposes `data` to get access to the body of the deriving type, and `FromVariant` exposes `fields`.
* **Mapping function**: Use `#[darling(map="path")]` or `#[darling(and_then="path")]` to specify a function that runs on the result of parsing a meta-item field. This can change the return type, which enables you to parse to an intermediate form and convert that to the type you need in your struct.
* **Skip fields**: Use `#[darling(skip)]` to mark a field that shouldn't be read from attribute meta-items.
* **Multiple-occurrence fields**: Use `#[darling(multiple)]` on a `Vec` field to allow that field to appear multiple times in the meta-item. Each occurrence will be pushed into the `Vec`.
* **Span access**: Use `darling::util::SpannedValue` in a struct to get access to that meta item's source code span. This can be used to emit warnings that point at a specific field from your proc macro. In addition, you can use `darling::Error::write_errors` to automatically get precise error location details in most cases.
* **"Did you mean" suggestions**: Compile errors from derived darling trait impls include suggestions for misspelled fields.

2
zeroidc/vendor/darling/clippy.toml vendored Normal file
View File

@ -0,0 +1,2 @@
msrv = "1.31.0"
blacklisted-names = [] # we want to be able to use placeholder names in tests

View File

@ -0,0 +1,73 @@
use darling::{FromDeriveInput, FromMeta};
#[derive(FromMeta, PartialEq, Eq, Debug)]
enum Volume {
Whisper,
Talk,
Shout,
}
/// A more complex example showing the ability to skip at a field or struct
/// level while still tracking which type parameters need to be bounded.
/// This can be seen by expanding this example using `cargo expand`.
#[derive(FromMeta)]
#[allow(dead_code)]
enum Emphasis<T> {
Constant(Volume),
Variable(darling::util::PathList),
#[darling(skip)]
PerPhoneme(Option<T>),
Strided {
#[darling(skip)]
step: Vec<T>,
#[darling(multiple)]
volume: Vec<Volume>,
},
}
#[derive(FromDeriveInput)]
#[darling(attributes(speak))]
struct SpeakingOptions<T, U> {
max_volume: U,
#[darling(skip, default)]
additional_data: Vec<T>,
}
#[derive(Default)]
struct Phoneme {
#[allow(dead_code)]
first: String,
}
// This is probably the holy grail for `darling`'s own internal use-case:
// Auto-apply `Default` bound to skipped *field* types in `where` clause.
impl<T, U> Default for SpeakingOptions<T, U>
where
Vec<T>: Default,
U: Default,
{
fn default() -> Self {
Self {
max_volume: Default::default(),
additional_data: Default::default(),
}
}
}
fn main() {
let derive_input = syn::parse_str(
r#"
#[derive(Speak)]
#[speak(max_volume = "shout")]
enum HtmlElement {
Div(String)
}
"#,
)
.unwrap();
let parsed: SpeakingOptions<Phoneme, Volume> =
FromDeriveInput::from_derive_input(&derive_input).unwrap();
assert_eq!(parsed.max_volume, Volume::Shout);
assert_eq!(parsed.additional_data.len(), 0);
}

View File

@ -0,0 +1,174 @@
// The use of fields in debug print commands does not count as "used",
// which causes the fields to trigger an unwanted dead code warning.
#![allow(dead_code)]
//! This example shows how to do struct and field parsing using darling.
use darling::{ast, FromDeriveInput, FromField, FromMeta};
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::parse_str;
/// A speaking volume. Deriving `FromMeta` will cause this to be usable
/// as a string value for a meta-item key.
#[derive(Debug, Clone, Copy, FromMeta)]
#[darling(default)]
enum Volume {
Normal,
Whisper,
Shout,
}
impl Default for Volume {
fn default() -> Self {
Volume::Normal
}
}
/// Support parsing from a full derive input. Unlike FromMeta, this isn't
/// composable; each darling-dependent crate should have its own struct to handle
/// when its trait is derived.
#[derive(Debug, FromDeriveInput)]
// This line says that we want to process all attributes declared with `my_trait`,
// and that darling should panic if this receiver is given an enum.
#[darling(attributes(my_trait), supports(struct_any))]
struct MyInputReceiver {
/// The struct ident.
ident: syn::Ident,
/// The type's generics. You'll need these any time your trait is expected
/// to work with types that declare generics.
generics: syn::Generics,
/// Receives the body of the struct or enum. We don't care about
/// struct fields because we previously told darling we only accept structs.
data: ast::Data<(), MyFieldReceiver>,
/// The Input Receiver demands a volume, so use `Volume::Normal` if the
/// caller doesn't provide one.
#[darling(default)]
volume: Volume,
}
impl ToTokens for MyInputReceiver {
fn to_tokens(&self, tokens: &mut TokenStream) {
let MyInputReceiver {
ref ident,
ref generics,
ref data,
volume,
} = *self;
let (imp, ty, wher) = generics.split_for_impl();
let fields = data
.as_ref()
.take_struct()
.expect("Should never be enum")
.fields;
// Generate the format string which shows each field and its name
let fmt_string = fields
.iter()
.enumerate()
.map(|(i, f)| {
// We have to preformat the ident in this case so we can fall back
// to the field index for unnamed fields. It's not easy to read,
// unfortunately.
format!(
"{} = {{}}",
f.ident
.as_ref()
.map(|v| format!("{}", v))
.unwrap_or_else(|| format!("{}", i))
)
})
.collect::<Vec<_>>()
.join(", ");
// Generate the actual values to fill the format string.
let field_list = fields
.into_iter()
.enumerate()
.map(|(i, f)| {
let field_volume = f.volume.unwrap_or(volume);
// This works with named or indexed fields, so we'll fall back to the index so we can
// write the output as a key-value pair.
let field_ident = f.ident
.as_ref()
.map(|v| quote!(#v))
.unwrap_or_else(|| {
let i = syn::Index::from(i);
quote!(#i)
});
match field_volume {
Volume::Normal => quote!(self.#field_ident),
Volume::Shout => {
quote!(::std::string::ToString::to_string(&self.#field_ident).to_uppercase())
}
Volume::Whisper => {
quote!(::std::string::ToString::to_string(&self.#field_ident).to_lowercase())
}
}
})
.collect::<Vec<_>>();
tokens.extend(quote! {
impl #imp Speak for #ident #ty #wher {
fn speak(&self, writer: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
write!(writer, #fmt_string, #(#field_list),*)
}
}
});
}
}
#[derive(Debug, FromField)]
#[darling(attributes(my_trait))]
struct MyFieldReceiver {
/// Get the ident of the field. For fields in tuple or newtype structs or
/// enum bodies, this can be `None`.
ident: Option<syn::Ident>,
/// This magic field name pulls the type from the input.
ty: syn::Type,
/// We declare this as an `Option` so that during tokenization we can write
/// `field.volume.unwrap_or(derive_input.volume)` to facilitate field-level
/// overrides of struct-level settings.
#[darling(default)]
volume: Option<Volume>,
}
fn main() {
let input = r#"#[derive(MyTrait)]
#[my_trait(volume = "shout")]
pub struct Foo {
#[my_trait(volume = "whisper")]
bar: bool,
baz: i64,
}"#;
let parsed = parse_str(input).unwrap();
let receiver = MyInputReceiver::from_derive_input(&parsed).unwrap();
let tokens = quote!(#receiver);
println!(
r#"
INPUT:
{}
PARSED AS:
{:?}
EMITS:
{}
"#,
input, receiver, tokens
);
}

View File

@ -0,0 +1,85 @@
//! This example demonstrates techniques for performing custom error handling
//! in a derive-input receiver.
//!
//! 1. Using `darling::Result` as a carrier to preserve the error for later display
//! 1. Using `Result<T, syn::Meta>` to attempt a recovery in imperative code
//! 1. Using the `map` darling meta-item to post-process a field before returning
//! 1. Using the `and_then` darling meta-item to post-process the receiver before returning
use darling::{FromDeriveInput, FromMeta};
use syn::parse_str;
#[derive(Debug, FromDeriveInput)]
#[darling(attributes(my_trait), and_then = "MyInputReceiver::autocorrect")]
pub struct MyInputReceiver {
/// This field must be present and a string or else parsing will panic.
#[darling(map = "MyInputReceiver::make_string_shouty")]
name: String,
/// If this field fails to parse, the struct can still be built; the field
/// will contain the error. The consuming struct can then decide if this
/// blocks code generation. If so, panic or fail in `and_then`.
frequency: darling::Result<i64>,
/// If this field fails to parse, the struct can still be built; the field
/// will contain an `Err` with the original `syn::Meta`. This can be used
/// for alternate parsing attempts before panicking.
amplitude: Result<u64, syn::Meta>,
}
impl MyInputReceiver {
/// This function will be called by `darling` _after_ it's finished parsing the
/// `name` field but before initializing `name` with the resulting value. It's
/// a good place for transforms that are easiest to express on already-built
/// types.
fn make_string_shouty(s: String) -> String {
s.to_uppercase()
}
/// This function will be called by `darling` _after_ it's finished parsing the
/// input but before returning to the caller. This is a good place to initialize
/// skipped fields or to perform corrections that don't lend themselves to being
/// done elsewhere.
fn autocorrect(self) -> darling::Result<Self> {
let Self {
name,
frequency,
amplitude,
} = self;
// Amplitude doesn't have a sign, so if we received a negative number then
// we'll go ahead and make it positive.
let amplitude = match amplitude {
Ok(amp) => amp,
Err(mi) => (i64::from_meta(&mi)?).abs() as u64,
};
Ok(Self {
name,
frequency,
amplitude: Ok(amplitude),
})
}
}
fn main() {
let input = r#"#[derive(MyTrait)]
#[my_trait(name="Jon", amplitude = "-1", frequency = 1)]
pub struct Foo;"#;
let parsed = parse_str(input).unwrap();
let receiver = MyInputReceiver::from_derive_input(&parsed).unwrap();
println!(
r#"
INPUT:
{}
PARSED AS:
{:?}
"#,
input, receiver
);
}

View File

@ -0,0 +1,80 @@
//! Example showing potentially-nested meta item parsing with `darling::util::Override`.
//!
//! Based on https://stackoverflow.com/q/68046070/86381 by https://github.com/peterjoel
// The use of fields in debug print commands does not count as "used",
// which causes the fields to trigger an unwanted dead code warning.
#![allow(dead_code)]
use std::borrow::Cow;
use darling::{util::Override, FromDeriveInput, FromMeta};
use syn::{Ident, Path};
#[derive(Debug, FromDeriveInput)]
#[darling(attributes(myderive))]
struct MyDeriveInput {
ident: Ident,
/// We can infer the right "table" behavior for this derive, but we want the caller to be
/// explicit that they're expecting the inference behavior to avoid cluttering some hypothetical
/// database. Therefore this field is required, but can take word form or key-value form.
///
/// To make this field optional, we could add `#[darling(default)]`, and we could additionally
/// wrap it in `Option` if the presence or absence of the word makes a difference.
table: Override<Table>,
}
impl MyDeriveInput {
fn table(&self) -> Cow<'_, Table> {
match &self.table {
Override::Explicit(value) => Cow::Borrowed(value),
Override::Inherit => Cow::Owned(Table {
name: self.ident.to_string(),
value: None,
}),
}
}
}
#[derive(Debug, Clone, FromMeta)]
struct Table {
name: String,
#[darling(default)]
value: Option<Path>,
}
fn from_str(s: &str) -> darling::Result<MyDeriveInput> {
FromDeriveInput::from_derive_input(&syn::parse_str(s)?)
}
fn main() {
let missing = from_str(
r#"
#[derive(MyTrait)]
struct Foo(u64);
"#,
)
.unwrap_err();
let short_form = from_str(
r#"
#[derive(MyTrait)]
#[myderive(table)]
struct Foo(u64);
"#,
)
.unwrap();
let long_form = from_str(
r#"
#[derive(MyTrait)]
#[myderive(table(name = "Custom"))]
struct Foo(u64);
"#,
)
.unwrap();
println!("Error when missing: {}", missing);
println!("Short form: {:?}", short_form.table());
println!("Long form: {:?}", long_form.table());
}

View File

@ -0,0 +1,61 @@
// The use of fields in debug print commands does not count as "used",
// which causes the fields to trigger an unwanted dead code warning.
#![allow(dead_code)]
use darling::{ast, util, FromDeriveInput, FromField};
use syn::{Ident, Type};
#[derive(Debug, FromField)]
#[darling(attributes(lorem))]
pub struct LoremField {
ident: Option<Ident>,
ty: Type,
#[darling(default)]
skip: bool,
}
#[derive(Debug, FromDeriveInput)]
#[darling(attributes(lorem), supports(struct_named))]
pub struct Lorem {
ident: Ident,
data: ast::Data<util::Ignored, LoremField>,
}
fn main() {
let good_input = r#"#[derive(Lorem)]
pub struct Foo {
#[lorem(skip)]
bar: bool,
baz: i64,
}"#;
let bad_input = r#"#[derive(Lorem)]
pub struct BadFoo(String, u32);"#;
let parsed = syn::parse_str(good_input).unwrap();
let receiver = Lorem::from_derive_input(&parsed).unwrap();
let wrong_shape_parsed = syn::parse_str(bad_input).unwrap();
let wrong_shape = Lorem::from_derive_input(&wrong_shape_parsed).expect_err("Shape was wrong");
println!(
r#"
INPUT:
{}
PARSED AS:
{:?}
BAD INPUT:
{}
PRODUCED ERROR:
{}
"#,
good_input, receiver, bad_input, wrong_shape
);
}

107
zeroidc/vendor/darling/src/lib.rs vendored Normal file
View File

@ -0,0 +1,107 @@
//! # Darling
//! Darling is a tool for declarative attribute parsing in proc macro implementations.
//!
//!
//! ## Design
//! Darling takes considerable design inspiration from [`serde`](https://serde.rs). A data structure that can be
//! read from any attribute implements `FromMeta` (or has an implementation automatically
//! generated using `derive`). Any crate can provide `FromMeta` implementations, even one not
//! specifically geared towards proc-macro authors.
//!
//! Proc-macro crates should provide their own structs which implement or derive `FromDeriveInput`,
//! `FromField`, `FromVariant`, `FromGenerics`, _et alia_ to gather settings relevant to their operation.
//!
//! ## Attributes
//! There are a number of attributes that `darling` exposes to enable finer-grained control over the code
//! it generates.
//!
//! * **Field renaming**: You can use `#[darling(rename="new_name")]` on a field to change the name Darling looks for.
//! You can also use `#[darling(rename_all="...")]` at the struct or enum level to apply a casing rule to all fields or variants.
//! * **Map function**: You can use `#[darling(map="path::to::function")]` to run code on a field before its stored in the struct.
//! * **Default values**: You can use `#[darling(default)]` at the type or field level to use that type's default value to fill
//! in values not specified by the caller.
//! * **Skipped fields**: You can skip a variant or field using `#[darling(skip)]`. Fields marked with this will fall back to
//! `Default::default()` for their value, but you can override that with an explicit default or a value from the type-level default.
//!
//! ## Forwarded Fields
//! All derivable traits except `FromMeta` support forwarding some fields from the input AST to the derived struct.
//! These fields are matched up by identifier **before** `rename` attribute values are considered,
//! allowing you to use their names for your own properties.
//! The deriving struct is responsible for making sure the types of fields it chooses to declare are compatible with this table.
//!
//! A deriving struct is free to include or exclude any of the fields below.
//!
//! ### `FromDeriveInput`
//! |Field name|Type|Meaning|
//! |---|---|---|
//! |`ident`|`syn::Ident`|The identifier of the passed-in type|
//! |`vis`|`syn::Visibility`|The visibility of the passed-in type|
//! |`generics`|`T: darling::FromGenerics`|The generics of the passed-in type. This can be `syn::Generics`, `darling::ast::Generics`, or any compatible type.|
//! |`data`|`darling::ast::Data`|The body of the passed-in type|
//! |`attrs`|`Vec<syn::Attribute>`|The forwarded attributes from the passed in type. These are controlled using the `forward_attrs` attribute.|
//!
//! ### `FromField`
//! |Field name|Type|Meaning|
//! |---|---|---|
//! |`ident`|`Option<syn::Ident>`|The identifier of the passed-in field, or `None` for tuple fields|
//! |`vis`|`syn::Visibility`|The visibility of the passed-in field|
//! |`ty`|`syn::Type`|The type of the passed-in field|
//! |`attrs`|`Vec<syn::Attribute>`|The forwarded attributes from the passed in field. These are controlled using the `forward_attrs` attribute.|
//!
//! ### `FromTypeParam`
//! |Field name|Type|Meaning|
//! |---|---|---|
//! |`ident`|`syn::Ident`|The identifier of the passed-in type param|
//! |`bounds`|`Vec<syn::TypeParamBound>`|The bounds applied to the type param|
//! |`default`|`Option<syn::Type>`|The default type of the parameter, if one exists|
//! |`attrs`|`Vec<syn::Attribute>`|The forwarded attributes from the passed in type param. These are controlled using the `forward_attrs` attribute.|
//!
//! ### `FromVariant`
//! |Field name|Type|Meaning|
//! |---|---|---|
//! |`ident`|`syn::Ident`|The identifier of the passed-in variant|
//! |`discriminant`|`Option<syn::Expr>`|For a variant such as `Example = 2`, the `2`|
//! |`fields`|`Option<darling::ast::Fields<__>>`|The fields associated with the variant|
//! |`attrs`|`Vec<syn::Attribute>`|The forwarded attributes from the passed in variant. These are controlled using the `forward_attrs` attribute.|
extern crate core;
#[allow(unused_imports)]
#[macro_use]
extern crate darling_macro;
#[doc(hidden)]
pub use darling_macro::*;
#[doc(inline)]
pub use darling_core::{
FromAttributes, FromDeriveInput, FromField, FromGenericParam, FromGenerics, FromMeta,
FromTypeParam, FromVariant,
};
#[doc(inline)]
pub use darling_core::{Error, Result};
#[doc(inline)]
pub use darling_core::{ast, error, usage, util};
// XXX exported so that `ExtractAttribute::extractor` can convert a path into tokens.
// This is likely to change in the future, so only generated code should depend on this export.
#[doc(hidden)]
pub use darling_core::ToTokens;
/// Core/std trait re-exports. This should help produce generated code which doesn't
/// depend on `std` unnecessarily, and avoids problems caused by aliasing `std` or any
/// of the referenced types.
#[doc(hidden)]
pub mod export {
pub use core::convert::From;
pub use core::default::Default;
pub use core::option::Option::{self, None, Some};
pub use core::result::Result::{self, Err, Ok};
pub use std::string::ToString;
pub use std::vec::Vec;
}
#[macro_use]
mod macros_public;

View File

@ -0,0 +1,96 @@
//! Macros that should be exported from both `darling_core` and `darling`.
//! Note that these are **sym-linked** into the main code, and so cannot declare on items that are exported differently
//! in `darling_core` vs. `darling`.
/// Generator for `UsesTypeParam` impls that unions the used type parameters of the selected fields.
///
/// # Usage
/// The macro takes the type implementing the trait as the first argument, then a comma-separated list of
/// fields for the rest of its arguments.
///
/// The type of each passed-in field must implement `UsesTypeParams`, or the resulting code won't compile.
///
/// ```rust
/// # extern crate syn;
/// # use darling_core::uses_type_params;
/// #
/// struct MyField {
/// ty: syn::Type,
/// }
///
/// uses_type_params!(MyField, ty);
///
/// fn main() {
/// // no test run
/// }
/// ```
///
/// `darling` cannot derive this trait automatically, as it doesn't know which information extracted from
/// proc-macro input is meant to constitute "using" the type parameter, but crate consumers should
/// implement it by hand or using the macro.
#[macro_export]
macro_rules! uses_type_params {
($impl_type:ty, $accessor:ident) => {
impl $crate::usage::UsesTypeParams for $impl_type {
fn uses_type_params<'gen>(
&self,
options: &$crate::usage::Options,
type_set: &'gen $crate::usage::IdentSet
) -> $crate::usage::IdentRefSet<'gen> {
self.$accessor.uses_type_params(options, type_set)
}
}
};
($impl_type:ty, $first:ident, $($field:ident),+) => {
impl $crate::usage::UsesTypeParams for $impl_type {
fn uses_type_params<'gen>(
&self,
options: &$crate::usage::Options,
type_set: &'gen $crate::usage::IdentSet
) -> $crate::usage::IdentRefSet<'gen> {
let mut hits = self.$first.uses_type_params(options, type_set);
$(
hits.extend(self.$field.uses_type_params(options, type_set));
)*
hits
}
}
};
}
/// Generator for `UsesLifetimes` impls that unions the used lifetimes of the selected fields.
///
/// # Usage
/// The macro takes the type implementing the trait as the first argument, then a comma-separated list of
/// fields for the rest of its arguments.
///
/// The type of each passed-in field must implement `UsesLifetimes`, or the resulting code won't compile.
#[macro_export]
macro_rules! uses_lifetimes {
($impl_type:ty, $accessor:ident) => {
impl $crate::usage::UsesLifetimes for $impl_type {
fn uses_lifetimes<'gen>(
&self,
options: &$crate::usage::Options,
type_set: &'gen $crate::usage::LifetimeSet
) -> $crate::usage::LifetimeRefSet<'gen> {
self.$accessor.uses_lifetimes(options, type_set)
}
}
};
($impl_type:ty, $first:ident, $($field:ident),+) => {
impl $crate::usage::UsesLifetimes for $impl_type {
fn uses_lifetimes<'gen>(
&self,
options: &$crate::usage::Options,
type_set: &'gen $crate::usage::LifetimeSet
) -> $crate::usage::LifetimeRefSet<'gen> {
let mut hits = self.$first.uses_lifetimes(options, type_set);
$(
hits.extend(self.$field.uses_lifetimes(options, type_set));
)*
hits
}
}
};
}

View File

@ -0,0 +1,102 @@
#![allow(dead_code)]
//! These tests verify that multiple errors will be collected up from throughout
//! the parsing process and returned correctly to the caller.
use darling::{ast, FromDeriveInput, FromField, FromMeta};
use syn::parse_quote;
#[derive(Debug, FromDeriveInput)]
#[darling(attributes(accrue))]
struct Lorem {
ipsum: String,
dolor: Dolor,
data: ast::Data<(), LoremField>,
}
#[derive(Debug, FromMeta)]
struct Dolor {
sit: bool,
}
#[derive(Debug, FromField)]
#[darling(attributes(accrue))]
struct LoremField {
ident: Option<syn::Ident>,
aliased_as: syn::Ident,
}
#[test]
fn bad_type_and_missing_fields() {
let input = parse_quote! {
#[accrue(ipsum = true, dolor(amet = "Hi"))]
pub struct NonConforming {
foo: ()
}
};
let s_result: ::darling::Error = Lorem::from_derive_input(&input).unwrap_err();
let err = s_result.flatten();
println!("{}", err);
assert_eq!(3, err.len());
}
#[test]
fn body_only_issues() {
let input = parse_quote! {
#[accrue(ipsum = "Hello", dolor(sit))]
pub struct NonConforming {
foo: (),
bar: bool,
}
};
let s_err = Lorem::from_derive_input(&input).unwrap_err();
println!("{:?}", s_err);
assert_eq!(2, s_err.len());
}
#[derive(Debug, FromMeta)]
enum Week {
Monday,
Tuesday { morning: bool, afternoon: String },
Wednesday(Dolor),
}
#[derive(Debug, FromDeriveInput)]
#[darling(attributes(accrue))]
struct Month {
schedule: Week,
}
#[test]
fn error_in_enum_fields() {
let input = parse_quote! {
#[accrue(schedule(tuesday(morning = "yes")))]
pub struct NonConforming {
foo: (),
bar: bool,
}
};
let s_err = Month::from_derive_input(&input).unwrap_err();
assert_eq!(2, s_err.len());
let err = s_err.flatten();
// TODO add tests to check location path is correct
println!("{}", err);
}
#[test]
fn error_in_newtype_variant() {
let input = parse_quote! {
#[accrue(schedule(wednesday(sit = "yes")))]
pub struct NonConforming {
foo: (),
bar: bool,
}
};
let s_err = Month::from_derive_input(&input).unwrap_err();
assert_eq!(1, s_err.len());
println!("{}", s_err);
println!("{}", s_err.flatten());
}

View File

@ -0,0 +1,42 @@
use darling::{FromDeriveInput, FromMeta};
fn parse<T: FromDeriveInput>(src: &str) -> T {
let ast = syn::parse_str(src).unwrap();
FromDeriveInput::from_derive_input(&ast).unwrap()
}
#[derive(FromMeta, PartialEq, Eq, Debug)]
enum Volume {
Whisper,
Talk,
Shout,
}
#[derive(FromDeriveInput)]
#[darling(attributes(speak))]
struct SpeakingOptions<T: Default, U> {
max_volume: U,
#[darling(skip)]
#[allow(dead_code)]
additional_data: T,
}
#[derive(Default)]
struct Phoneme {
#[allow(dead_code)]
first: String,
}
#[test]
fn skipped_field() {
let parsed: SpeakingOptions<Phoneme, Volume> = parse(
r#"
#[derive(Speak)]
#[speak(max_volume = "shout")]
enum HtmlElement {
Div(String)
}
"#,
);
assert_eq!(parsed.max_volume, Volume::Shout);
}

View File

@ -0,0 +1,25 @@
#![allow(dead_code)]
use std::ops::Add;
use darling::{FromDeriveInput, FromMeta};
#[derive(Debug, Clone, FromMeta)]
#[darling(bound = "T: FromMeta + Add")]
struct Wrapper<T>(pub T);
impl<T: Add> Add for Wrapper<T> {
type Output = Wrapper<<T as Add>::Output>;
fn add(self, rhs: Self) -> Wrapper<<T as Add>::Output> {
Wrapper(self.0 + rhs.0)
}
}
#[derive(Debug, FromDeriveInput)]
#[darling(attributes(hello), bound = "Wrapper<T>: Add, T: FromMeta")]
struct Foo<T> {
lorem: Wrapper<T>,
}
#[test]
fn expansion() {}

123
zeroidc/vendor/darling/tests/defaults.rs vendored Normal file
View File

@ -0,0 +1,123 @@
use darling::FromDeriveInput;
use syn::parse_quote;
mod foo {
pub mod bar {
pub fn init() -> String {
String::from("hello")
}
}
}
#[derive(FromDeriveInput)]
#[darling(attributes(speak))]
pub struct SpeakerOpts {
#[darling(default = "foo::bar::init")]
first_word: String,
}
#[test]
fn path_default() {
let speaker: SpeakerOpts = FromDeriveInput::from_derive_input(&parse_quote! {
struct Foo;
})
.expect("Unit struct with no attrs should parse");
assert_eq!(speaker.first_word, "hello");
}
/// Tests in this module capture the somewhat-confusing behavior observed when defaults
/// are set at both the field and container level.
///
/// The general rule is that more-specific declarations preempt less-specific ones; this is
/// unsurprising and allows for granular control over what happens when parsing an AST.
mod stacked_defaults {
use darling::{FromDeriveInput, FromMeta};
use syn::parse_quote;
fn jane() -> String {
"Jane".into()
}
#[derive(FromMeta)]
#[darling(default)]
struct PersonName {
#[darling(default = "jane")]
first: String,
#[darling(default)]
middle: String,
last: String,
}
impl Default for PersonName {
fn default() -> Self {
Self {
first: "John".into(),
middle: "T".into(),
last: "Doe".into(),
}
}
}
#[derive(FromDeriveInput)]
#[darling(attributes(person))]
struct Person {
#[darling(default)]
name: PersonName,
age: u8,
}
#[test]
fn name_first_only() {
let person = Person::from_derive_input(&parse_quote! {
#[person(name(first = "Bill"), age = 5)]
struct Foo;
})
.unwrap();
assert_eq!(person.name.first, "Bill");
assert_eq!(
person.name.middle, "",
"Explicit field-level default should preempt container-level default"
);
assert_eq!(
person.name.last, "Doe",
"Absence of a field-level default falls back to container-level default"
);
}
/// This is the most surprising case. The presence of `name()` means we invoke
/// `PersonName::from_list(&[])`. When that finishes parsing each of the zero nested
/// items it has received, it will then start filling in missing fields, using the
/// explicit field-level defaults for `first` and `middle`, while for `last` it will
/// use the `last` field from the container-level default.
#[test]
fn name_empty_list() {
let person = Person::from_derive_input(&parse_quote! {
#[person(name(), age = 5)]
struct Foo;
})
.unwrap();
assert_eq!(person.name.first, "Jane");
assert_eq!(person.name.middle, "");
assert_eq!(person.name.last, "Doe");
}
#[test]
fn no_name() {
let person = Person::from_derive_input(&parse_quote! {
#[person(age = 5)]
struct Foo;
})
.unwrap();
assert_eq!(person.age, 5);
assert_eq!(
person.name.first, "John",
"If `name` is not specified, `Person`'s field-level default should be used"
);
assert_eq!(person.name.middle, "T");
assert_eq!(person.name.last, "Doe");
}
}

View File

@ -0,0 +1,90 @@
use darling::{FromDeriveInput, FromMeta};
use syn::parse_quote;
#[derive(Debug, Default, PartialEq, Eq, FromMeta)]
#[darling(default)]
pub struct Amet {
hello: bool,
world: String,
}
#[derive(Debug, PartialEq, Eq, FromMeta)]
#[darling(rename_all = "snake_case")]
pub enum Lorem {
Ipsum(bool),
Dolor(String),
Sit(Amet),
}
#[derive(Debug, PartialEq, Eq, FromDeriveInput)]
#[darling(attributes(hello))]
pub struct Holder {
lorem: Lorem,
}
impl PartialEq<Lorem> for Holder {
fn eq(&self, other: &Lorem) -> bool {
self.lorem == *other
}
}
#[test]
fn bool_word() {
let di = parse_quote! {
#[hello(lorem(ipsum))]
pub struct Bar;
};
let pr = Holder::from_derive_input(&di).unwrap();
assert_eq!(pr, Lorem::Ipsum(true));
}
#[test]
fn bool_literal() {
let di = parse_quote! {
#[hello(lorem(ipsum = false))]
pub struct Bar;
};
let pr = Holder::from_derive_input(&di).unwrap();
assert_eq!(pr, Lorem::Ipsum(false));
}
#[test]
fn string_literal() {
let di = parse_quote! {
#[hello(lorem(dolor = "Hello"))]
pub struct Bar;
};
let pr = Holder::from_derive_input(&di).unwrap();
assert_eq!(pr, Lorem::Dolor("Hello".to_string()));
}
#[test]
fn struct_nested() {
let di = parse_quote! {
#[hello(lorem(sit(world = "Hello", hello = false)))]
pub struct Bar;
};
let pr = Holder::from_derive_input(&di).unwrap();
assert_eq!(
pr,
Lorem::Sit(Amet {
hello: false,
world: "Hello".to_string(),
})
);
}
#[test]
#[should_panic]
fn format_mismatch() {
let di = parse_quote! {
#[hello(lorem(dolor(world = "Hello", hello = false)))]
pub struct Bar;
};
Holder::from_derive_input(&di).unwrap();
}

View File

@ -0,0 +1,15 @@
#![allow(dead_code)]
//! Test expansion of enums which have struct variants.
use darling::FromMeta;
#[derive(Debug, FromMeta)]
#[darling(rename_all = "snake_case")]
enum Message {
Hello { user: String, silent: bool },
Ping,
Goodbye { user: String },
}
#[test]
fn expansion() {}

Some files were not shown because too many files have changed in this diff Show More