From a4ac6829a6a4cd35968231d14f09b91c8be47d6e Mon Sep 17 00:00:00 2001 From: Cameron Garnham Date: Tue, 8 Aug 2023 10:58:52 +0200 Subject: [PATCH] dev: copy bencode into local contrib folder --- Cargo.lock | 241 ++++++++++- Cargo.toml | 3 +- contrib/bencode/Cargo.toml | 34 ++ contrib/bencode/README.md | 4 + contrib/bencode/benches/bencode_benchmark.rs | 27 ++ contrib/bencode/benches/multi_kb.bencode | 1 + contrib/bencode/src/access/bencode.rs | 120 ++++++ contrib/bencode/src/access/convert.rs | 230 +++++++++++ contrib/bencode/src/access/dict.rs | 64 +++ contrib/bencode/src/access/list.rs | 108 +++++ contrib/bencode/src/access/mod.rs | 4 + contrib/bencode/src/cow.rs | 44 ++ contrib/bencode/src/error.rs | 101 +++++ contrib/bencode/src/lib.rs | 143 +++++++ contrib/bencode/src/mutable/bencode_mut.rs | 229 +++++++++++ contrib/bencode/src/mutable/encode.rs | 67 ++++ contrib/bencode/src/mutable/mod.rs | 2 + contrib/bencode/src/reference/bencode_ref.rs | 265 ++++++++++++ contrib/bencode/src/reference/decode.rs | 398 +++++++++++++++++++ contrib/bencode/src/reference/decode_opt.rs | 55 +++ contrib/bencode/src/reference/mod.rs | 3 + contrib/bencode/test/mod.rs | 18 + src/servers/http/v1/responses/announce.rs | 2 +- src/servers/http/v1/responses/scrape.rs | 2 +- 24 files changed, 2150 insertions(+), 15 deletions(-) create mode 100644 contrib/bencode/Cargo.toml create mode 100644 contrib/bencode/README.md create mode 100644 contrib/bencode/benches/bencode_benchmark.rs create mode 100644 contrib/bencode/benches/multi_kb.bencode create mode 100644 contrib/bencode/src/access/bencode.rs create mode 100644 contrib/bencode/src/access/convert.rs create mode 100644 contrib/bencode/src/access/dict.rs create mode 100644 contrib/bencode/src/access/list.rs create mode 100644 contrib/bencode/src/access/mod.rs create mode 100644 contrib/bencode/src/cow.rs create mode 100644 contrib/bencode/src/error.rs create mode 100644 contrib/bencode/src/lib.rs create mode 100644 contrib/bencode/src/mutable/bencode_mut.rs create mode 100644 contrib/bencode/src/mutable/encode.rs create mode 100644 contrib/bencode/src/mutable/mod.rs create mode 100644 contrib/bencode/src/reference/bencode_ref.rs create mode 100644 contrib/bencode/src/reference/decode.rs create mode 100644 contrib/bencode/src/reference/decode_opt.rs create mode 100644 contrib/bencode/src/reference/mod.rs create mode 100644 contrib/bencode/test/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 32d35cbe..f2053b04 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -69,6 +69,18 @@ dependencies = [ "libc", ] +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstyle" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" + [[package]] name = "aquatic_udp_protocol" version = "0.8.0" @@ -215,6 +227,14 @@ version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" +[[package]] +name = "bencode" +version = "1.0.0-alpha.1" +dependencies = [ + "criterion", + "error-chain", +] + [[package]] name = "bigdecimal" version = "0.3.1" @@ -251,15 +271,6 @@ dependencies = [ "shlex", ] -[[package]] -name = "bip_bencode" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6048cc5d9680544a5098a290d2845df7dae292c97687b9896b70365bad0ea416" -dependencies = [ - "error-chain", -] - [[package]] name = "bitflags" version = "1.3.2" @@ -384,6 +395,12 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "cc" version = "1.0.83" @@ -421,6 +438,33 @@ dependencies = [ "winapi", ] +[[package]] +name = "ciborium" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" + +[[package]] +name = "ciborium-ll" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "clang-sys" version = "1.6.1" @@ -432,6 +476,31 @@ dependencies = [ "libloading", ] +[[package]] +name = "clap" +version = "4.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03aef18ddf7d879c15ce20f04826ef8418101c7e528014c3eeea13321047dca3" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ce6fffb678c9b80a70b6b6de0aad31df727623a70fd9a842c30cd573e2fa98" +dependencies = [ + "anstyle", + "clap_lex", +] + +[[package]] +name = "clap_lex" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" + [[package]] name = "cmake" version = "0.1.50" @@ -500,6 +569,42 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "is-terminal", + "itertools", + "num-traits", + "once_cell", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools", +] + [[package]] name = "crossbeam" version = "0.8.2" @@ -717,11 +822,12 @@ dependencies = [ [[package]] name = "error-chain" -version = "0.11.0" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff511d5dc435d703f4971bc399647c9bc38e20cb41452e3b9feb4765419ed3f3" +checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" dependencies = [ "backtrace", + "version_check", ] [[package]] @@ -1016,6 +1122,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" + [[package]] name = "hashbrown" version = "0.12.3" @@ -1213,6 +1325,17 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" +[[package]] +name = "is-terminal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +dependencies = [ + "hermit-abi", + "rustix", + "windows-sys", +] + [[package]] name = "itertools" version = "0.10.5" @@ -1701,6 +1824,12 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "oorandom" +version = "11.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" + [[package]] name = "openssl" version = "0.10.56" @@ -1897,6 +2026,34 @@ version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +[[package]] +name = "plotters" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" + +[[package]] +name = "plotters-svg" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +dependencies = [ + "plotters-backend", +] + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -2048,6 +2205,28 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rayon" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "num_cpus", +] + [[package]] name = "redox_syscall" version = "0.3.5" @@ -2304,6 +2483,15 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "saturating" version = "0.1.0" @@ -2707,6 +2895,16 @@ dependencies = [ "time-core", ] +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -2837,8 +3035,8 @@ dependencies = [ "axum", "axum-client-ip", "axum-server", + "bencode", "binascii", - "bip_bencode", "chrono", "config", "derive_more", @@ -3052,6 +3250,16 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "walkdir" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -3159,6 +3367,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index e87f7b97..a939318c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,7 @@ uuid = { version = "1", features = ["v4"] } axum = "0.6.20" axum-server = { version = "0.5", features = ["tls-rustls"] } axum-client-ip = "0.4.1" -bip_bencode = "0.4" +bencode = { version = "1.0.0-alpha.1", path = "contrib/bencode" } torrust-tracker-primitives = { version = "3.0.0-alpha.3", path = "packages/primitives" } torrust-tracker-configuration = { version = "3.0.0-alpha.3", path = "packages/configuration" } torrust-tracker-located-error = { version = "3.0.0-alpha.3", path = "packages/located-error" } @@ -59,6 +59,7 @@ torrust-tracker-test-helpers = { version = "3.0.0-alpha.3", path = "packages/tes [workspace] members = [ + "contrib/bencode", "packages/configuration", "packages/primitives", "packages/test-helpers", diff --git a/contrib/bencode/Cargo.toml b/contrib/bencode/Cargo.toml new file mode 100644 index 00000000..8334e270 --- /dev/null +++ b/contrib/bencode/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "bencode" +description = "Efficient decoding and encoding for bencode." +keywords = ["bencode"] +readme = "README.md" + + +authors = [ + "Nautilus Cyberneering , Andrew ", +] +categories = ["network-programming", "web-programming"] +documentation = "https://github.com/torrust/bittorrent-infrastructure-project" +edition = "2021" +homepage = "https://github.com/torrust/bittorrent-infrastructure-project" +license = "Apache-2.0" +publish = false # until we decide where to publish. +repository = "https://github.com/torrust/bittorrent-infrastructure-project" +rust-version = "1.71" +version = "1.0.0-alpha.1" + + +[dependencies] +error-chain = "0.12" + +[dev-dependencies] +criterion = "0.5" + +[[test]] +name = "test" +path = "test/mod.rs" + +[[bench]] +name = "bencode_benchmark" +harness = false \ No newline at end of file diff --git a/contrib/bencode/README.md b/contrib/bencode/README.md new file mode 100644 index 00000000..7a203082 --- /dev/null +++ b/contrib/bencode/README.md @@ -0,0 +1,4 @@ +# Bencode +This library allows for the creation and parsing of bencode encodings. + +Bencode is the binary encoding used throughout bittorrent technologies from metainfo files to DHT messages. Bencode types include integers, byte arrays, lists, and dictionaries, of which the last two can hold any bencode type (they could be recursively constructed). \ No newline at end of file diff --git a/contrib/bencode/benches/bencode_benchmark.rs b/contrib/bencode/benches/bencode_benchmark.rs new file mode 100644 index 00000000..729197d8 --- /dev/null +++ b/contrib/bencode/benches/bencode_benchmark.rs @@ -0,0 +1,27 @@ +use bencode::{BDecodeOpt, BencodeRef}; +use criterion::{black_box, criterion_group, criterion_main, Criterion}; + +const B_NESTED_LISTS: &[u8; 100] = + b"lllllllllllllllllllllllllllllllllllllllllllllllllleeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"; // cspell:disable-line +const MULTI_KB_BENCODE: &[u8; 30004] = include_bytes!("multi_kb.bencode"); + +fn bench_nested_lists(bencode: &[u8]) { + BencodeRef::decode(bencode, BDecodeOpt::new(50, true, true)).unwrap(); +} + +fn bench_multi_kb_bencode(bencode: &[u8]) { + BencodeRef::decode(bencode, BDecodeOpt::default()).unwrap(); +} + +fn criterion_benchmark(c: &mut Criterion) { + c.bench_function("bencode nested lists", |b| { + b.iter(|| bench_nested_lists(black_box(B_NESTED_LISTS))); + }); + + c.bench_function("bencode multi kb", |b| { + b.iter(|| bench_multi_kb_bencode(black_box(MULTI_KB_BENCODE))); + }); +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); diff --git a/contrib/bencode/benches/multi_kb.bencode b/contrib/bencode/benches/multi_kb.bencode new file mode 100644 index 00000000..b86f2846 --- /dev/null +++ b/contrib/bencode/benches/multi_kb.bencode @@ -0,0 +1 @@ +d7:comment17:Just Some Comment10:created by12:bip_metainfo13:creation datei1496618058e4:infod5:filesld6:lengthi1024e4:pathl1:b11:small_1.txteed6:lengthi1024e4:pathl1:b12:small_10.txteed6:lengthi1024e4:pathl1:b13:small_100.txteed6:lengthi1024e4:pathl1:b12:small_11.txteed6:lengthi1024e4:pathl1:b12:small_12.txteed6:lengthi1024e4:pathl1:b12:small_13.txteed6:lengthi1024e4:pathl1:b12:small_14.txteed6:lengthi1024e4:pathl1:b12:small_15.txteed6:lengthi1024e4:pathl1:b12:small_16.txteed6:lengthi1024e4:pathl1:b12:small_17.txteed6:lengthi1024e4:pathl1:b12:small_18.txteed6:lengthi1024e4:pathl1:b12:small_19.txteed6:lengthi1024e4:pathl1:b11:small_2.txteed6:lengthi1024e4:pathl1:b12:small_20.txteed6:lengthi1024e4:pathl1:b12:small_21.txteed6:lengthi1024e4:pathl1:b12:small_22.txteed6:lengthi1024e4:pathl1:b12:small_23.txteed6:lengthi1024e4:pathl1:b12:small_24.txteed6:lengthi1024e4:pathl1:b12:small_25.txteed6:lengthi1024e4:pathl1:b12:small_26.txteed6:lengthi1024e4:pathl1:b12:small_27.txteed6:lengthi1024e4:pathl1:b12:small_28.txteed6:lengthi1024e4:pathl1:b12:small_29.txteed6:lengthi1024e4:pathl1:b11:small_3.txteed6:lengthi1024e4:pathl1:b12:small_30.txteed6:lengthi1024e4:pathl1:b12:small_31.txteed6:lengthi1024e4:pathl1:b12:small_32.txteed6:lengthi1024e4:pathl1:b12:small_33.txteed6:lengthi1024e4:pathl1:b12:small_34.txteed6:lengthi1024e4:pathl1:b12:small_35.txteed6:lengthi1024e4:pathl1:b12:small_36.txteed6:lengthi1024e4:pathl1:b12:small_37.txteed6:lengthi1024e4:pathl1:b12:small_38.txteed6:lengthi1024e4:pathl1:b12:small_39.txteed6:lengthi1024e4:pathl1:b11:small_4.txteed6:lengthi1024e4:pathl1:b12:small_40.txteed6:lengthi1024e4:pathl1:b12:small_41.txteed6:lengthi1024e4:pathl1:b12:small_42.txteed6:lengthi1024e4:pathl1:b12:small_43.txteed6:lengthi1024e4:pathl1:b12:small_44.txteed6:lengthi1024e4:pathl1:b12:small_45.txteed6:lengthi1024e4:pathl1:b12:small_46.txteed6:lengthi1024e4:pathl1:b12:small_47.txteed6:lengthi1024e4:pathl1:b12:small_48.txteed6:lengthi1024e4:pathl1:b12:small_49.txteed6:lengthi1024e4:pathl1:b11:small_5.txteed6:lengthi1024e4:pathl1:b12:small_50.txteed6:lengthi1024e4:pathl1:b12:small_51.txteed6:lengthi1024e4:pathl1:b12:small_52.txteed6:lengthi1024e4:pathl1:b12:small_53.txteed6:lengthi1024e4:pathl1:b12:small_54.txteed6:lengthi1024e4:pathl1:b12:small_55.txteed6:lengthi1024e4:pathl1:b12:small_56.txteed6:lengthi1024e4:pathl1:b12:small_57.txteed6:lengthi1024e4:pathl1:b12:small_58.txteed6:lengthi1024e4:pathl1:b12:small_59.txteed6:lengthi1024e4:pathl1:b11:small_6.txteed6:lengthi1024e4:pathl1:b12:small_60.txteed6:lengthi1024e4:pathl1:b12:small_61.txteed6:lengthi1024e4:pathl1:b12:small_62.txteed6:lengthi1024e4:pathl1:b12:small_63.txteed6:lengthi1024e4:pathl1:b12:small_64.txteed6:lengthi1024e4:pathl1:b12:small_65.txteed6:lengthi1024e4:pathl1:b12:small_66.txteed6:lengthi1024e4:pathl1:b12:small_67.txteed6:lengthi1024e4:pathl1:b12:small_68.txteed6:lengthi1024e4:pathl1:b12:small_69.txteed6:lengthi1024e4:pathl1:b11:small_7.txteed6:lengthi1024e4:pathl1:b12:small_70.txteed6:lengthi1024e4:pathl1:b12:small_71.txteed6:lengthi1024e4:pathl1:b12:small_72.txteed6:lengthi1024e4:pathl1:b12:small_73.txteed6:lengthi1024e4:pathl1:b12:small_74.txteed6:lengthi1024e4:pathl1:b12:small_75.txteed6:lengthi1024e4:pathl1:b12:small_76.txteed6:lengthi1024e4:pathl1:b12:small_77.txteed6:lengthi1024e4:pathl1:b12:small_78.txteed6:lengthi1024e4:pathl1:b12:small_79.txteed6:lengthi1024e4:pathl1:b11:small_8.txteed6:lengthi1024e4:pathl1:b12:small_80.txteed6:lengthi1024e4:pathl1:b12:small_81.txteed6:lengthi1024e4:pathl1:b12:small_82.txteed6:lengthi1024e4:pathl1:b12:small_83.txteed6:lengthi1024e4:pathl1:b12:small_84.txteed6:lengthi1024e4:pathl1:b12:small_85.txteed6:lengthi1024e4:pathl1:b12:small_86.txteed6:lengthi1024e4:pathl1:b12:small_87.txteed6:lengthi1024e4:pathl1:b12:small_88.txteed6:lengthi1024e4:pathl1:b12:small_89.txteed6:lengthi1024e4:pathl1:b11:small_9.txteed6:lengthi1024e4:pathl1:b12:small_90.txteed6:lengthi1024e4:pathl1:b12:small_91.txteed6:lengthi1024e4:pathl1:b12:small_92.txteed6:lengthi1024e4:pathl1:b12:small_93.txteed6:lengthi1024e4:pathl1:b12:small_94.txteed6:lengthi1024e4:pathl1:b12:small_95.txteed6:lengthi1024e4:pathl1:b12:small_96.txteed6:lengthi1024e4:pathl1:b12:small_97.txteed6:lengthi1024e4:pathl1:b12:small_98.txteed6:lengthi1024e4:pathl1:b12:small_99.txteed6:lengthi5368709120e4:pathl9:large.txteee4:name1:a12:piece lengthi4194304e6:pieces25620:+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;+̽/8\}Z;Zic^c.>N7ee \ No newline at end of file diff --git a/contrib/bencode/src/access/bencode.rs b/contrib/bencode/src/access/bencode.rs new file mode 100644 index 00000000..ee90296e --- /dev/null +++ b/contrib/bencode/src/access/bencode.rs @@ -0,0 +1,120 @@ +use crate::access::dict::BDictAccess; +use crate::access::list::BListAccess; + +/// Abstract representation of a `BencodeRef` object. +pub enum RefKind<'a, K, V> { + /// Bencode Integer. + Int(i64), + /// Bencode Bytes. + Bytes(&'a [u8]), + /// Bencode List. + List(&'a dyn BListAccess), + /// Bencode Dictionary. + Dict(&'a dyn BDictAccess), +} + +/// Trait for read access to some bencode type. +pub trait BRefAccess: Sized { + type BKey; + type BType: BRefAccess; + + /// Access the bencode as a `BencodeRefKind`. + fn kind(&self) -> RefKind<'_, Self::BKey, Self::BType>; + + /// Attempt to access the bencode as a `str`. + fn str(&self) -> Option<&str>; + + /// Attempt to access the bencode as an `i64`. + fn int(&self) -> Option; + + /// Attempt to access the bencode as an `[u8]`. + fn bytes(&self) -> Option<&[u8]>; + + /// Attempt to access the bencode as an `BListAccess`. + fn list(&self) -> Option<&dyn BListAccess>; + + /// Attempt to access the bencode as an `BDictAccess`. + fn dict(&self) -> Option<&dyn BDictAccess>; +} + +/// Trait for extended read access to some bencode type. +/// +/// Use this trait when you want to make sure that the lifetime of +/// the underlying buffers is tied to the lifetime of the backing +/// bencode buffer. +pub trait BRefAccessExt<'a>: BRefAccess { + /// Attempt to access the bencode as a `str`. + fn str_ext(&self) -> Option<&'a str>; + + /// Attempt to access the bencode as an `[u8]`. + fn bytes_ext(&self) -> Option<&'a [u8]>; +} + +impl<'a, T> BRefAccess for &'a T +where + T: BRefAccess, +{ + type BKey = T::BKey; + type BType = T::BType; + + fn kind(&self) -> RefKind<'_, Self::BKey, Self::BType> { + (*self).kind() + } + + fn str(&self) -> Option<&str> { + (*self).str() + } + + fn int(&self) -> Option { + (*self).int() + } + + fn bytes(&self) -> Option<&[u8]> { + (*self).bytes() + } + + fn list(&self) -> Option<&dyn BListAccess> { + (*self).list() + } + + fn dict(&self) -> Option<&dyn BDictAccess> { + (*self).dict() + } +} + +impl<'a: 'b, 'b, T> BRefAccessExt<'a> for &'b T +where + T: BRefAccessExt<'a>, +{ + fn str_ext(&self) -> Option<&'a str> { + (*self).str_ext() + } + + fn bytes_ext(&self) -> Option<&'a [u8]> { + (*self).bytes_ext() + } +} + +/// Abstract representation of a `BencodeMut` object. +pub enum MutKind<'a, K, V> { + /// Bencode Integer. + Int(i64), + /// Bencode Bytes. + Bytes(&'a [u8]), + /// Bencode List. + List(&'a mut dyn BListAccess), + /// Bencode Dictionary. + Dict(&'a mut dyn BDictAccess), +} + +/// Trait for write access to some bencode type. +pub trait BMutAccess: Sized + BRefAccess { + /// Access the bencode as a `BencodeMutKind`. + fn kind_mut(&mut self) -> MutKind<'_, Self::BKey, Self::BType>; + + /// Attempt to access the bencode as a mutable `BListAccess`. + fn list_mut(&mut self) -> Option<&mut dyn BListAccess>; + + /// Attempt to access the bencode as a mutable `BDictAccess`. + fn dict_mut(&mut self) -> Option<&mut dyn BDictAccess>; +} diff --git a/contrib/bencode/src/access/convert.rs b/contrib/bencode/src/access/convert.rs new file mode 100644 index 00000000..42b04f26 --- /dev/null +++ b/contrib/bencode/src/access/convert.rs @@ -0,0 +1,230 @@ +#![allow(clippy::missing_errors_doc)] +use crate::access::bencode::{BRefAccess, BRefAccessExt}; +use crate::access::dict::BDictAccess; +use crate::access::list::BListAccess; +use crate::{BencodeConvertError, BencodeConvertErrorKind}; + +/// Trait for extended casting of bencode objects and converting conversion errors into application specific errors. +pub trait BConvertExt: BConvert { + /// See `BConvert::convert_bytes`. + fn convert_bytes_ext<'a, B, E>(&self, bencode: B, error_key: E) -> Result<&'a [u8], Self::Error> + where + B: BRefAccessExt<'a>, + E: AsRef<[u8]>, + { + bencode.bytes_ext().ok_or( + self.handle_error(BencodeConvertError::from_kind(BencodeConvertErrorKind::WrongType { + key: error_key.as_ref().to_owned(), + expected_type: "Bytes".to_owned(), + })), + ) + } + + /// See `BConvert::convert_str`. + fn convert_str_ext<'a, B, E>(&self, bencode: &B, error_key: E) -> Result<&'a str, Self::Error> + where + B: BRefAccessExt<'a>, + E: AsRef<[u8]>, + { + bencode.str_ext().ok_or( + self.handle_error(BencodeConvertError::from_kind(BencodeConvertErrorKind::WrongType { + key: error_key.as_ref().to_owned(), + expected_type: "UTF-8 Bytes".to_owned(), + })), + ) + } + + /// See `BConvert::lookup_and_convert_bytes`. + fn lookup_and_convert_bytes_ext<'a, B, K1, K2>( + &self, + dictionary: &dyn BDictAccess, + key: K2, + ) -> Result<&'a [u8], Self::Error> + where + B: BRefAccessExt<'a>, + K2: AsRef<[u8]>, + { + self.convert_bytes_ext(self.lookup(dictionary, &key)?, &key) + } + + /// See `BConvert::lookup_and_convert_str`. + fn lookup_and_convert_str_ext<'a, B, K1, K2>( + &self, + dictionary: &dyn BDictAccess, + key: K2, + ) -> Result<&'a str, Self::Error> + where + B: BRefAccessExt<'a>, + K2: AsRef<[u8]>, + { + self.convert_str_ext(self.lookup(dictionary, &key)?, &key) + } +} + +/// Trait for casting bencode objects and converting conversion errors into application specific errors. +#[allow(clippy::module_name_repetitions)] +pub trait BConvert { + type Error; + + /// Convert the given conversion error into the appropriate error type. + fn handle_error(&self, error: BencodeConvertError) -> Self::Error; + + /// Attempt to convert the given bencode value into an integer. + /// + /// Error key is used to generate an appropriate error message should the operation return an error. + fn convert_int(&self, bencode: B, error_key: E) -> Result + where + B: BRefAccess, + E: AsRef<[u8]>, + { + bencode.int().ok_or( + self.handle_error(BencodeConvertError::from_kind(BencodeConvertErrorKind::WrongType { + key: error_key.as_ref().to_owned(), + expected_type: "Integer".to_owned(), + })), + ) + } + + /// Attempt to convert the given bencode value into bytes. + /// + /// Error key is used to generate an appropriate error message should the operation return an error. + fn convert_bytes<'a, B, E>(&self, bencode: &'a B, error_key: E) -> Result<&'a [u8], Self::Error> + where + B: BRefAccess, + E: AsRef<[u8]>, + { + bencode.bytes().ok_or( + self.handle_error(BencodeConvertError::from_kind(BencodeConvertErrorKind::WrongType { + key: error_key.as_ref().to_owned(), + expected_type: "Bytes".to_owned(), + })), + ) + } + + /// Attempt to convert the given bencode value into a UTF-8 string. + /// + /// Error key is used to generate an appropriate error message should the operation return an error. + fn convert_str<'a, B, E>(&self, bencode: &'a B, error_key: E) -> Result<&'a str, Self::Error> + where + B: BRefAccess, + E: AsRef<[u8]>, + { + bencode.str().ok_or( + self.handle_error(BencodeConvertError::from_kind(BencodeConvertErrorKind::WrongType { + key: error_key.as_ref().to_owned(), + expected_type: "UTF-8 Bytes".to_owned(), + })), + ) + } + + /// Attempt to convert the given bencode value into a list. + /// + /// Error key is used to generate an appropriate error message should the operation return an error. + fn convert_list<'a, B, E>(&self, bencode: &'a B, error_key: E) -> Result<&'a dyn BListAccess, Self::Error> + where + B: BRefAccess, + E: AsRef<[u8]>, + { + bencode.list().ok_or( + self.handle_error(BencodeConvertError::from_kind(BencodeConvertErrorKind::WrongType { + key: error_key.as_ref().to_owned(), + expected_type: "List".to_owned(), + })), + ) + } + + /// Attempt to convert the given bencode value into a dictionary. + /// + /// Error key is used to generate an appropriate error message should the operation return an error. + fn convert_dict<'a, B, E>(&self, bencode: &'a B, error_key: E) -> Result<&'a dyn BDictAccess, Self::Error> + where + B: BRefAccess, + E: AsRef<[u8]>, + { + bencode.dict().ok_or( + self.handle_error(BencodeConvertError::from_kind(BencodeConvertErrorKind::WrongType { + key: error_key.as_ref().to_owned(), + expected_type: "Dictionary".to_owned(), + })), + ) + } + + /// Look up a value in a dictionary of bencoded values using the given key. + fn lookup<'a, B, K1, K2>(&self, dictionary: &'a dyn BDictAccess, key: K2) -> Result<&'a B, Self::Error> + where + B: BRefAccess, + K2: AsRef<[u8]>, + { + let key_ref = key.as_ref(); + + match dictionary.lookup(key_ref) { + Some(n) => Ok(n), + None => Err( + self.handle_error(BencodeConvertError::from_kind(BencodeConvertErrorKind::MissingKey { + key: key_ref.to_owned(), + })), + ), + } + } + + /// Combines a lookup operation on the given key with a conversion of the value, if found, to an integer. + fn lookup_and_convert_int(&self, dictionary: &dyn BDictAccess, key: K2) -> Result + where + B: BRefAccess, + K2: AsRef<[u8]>, + { + self.convert_int(self.lookup(dictionary, &key)?, &key) + } + + /// Combines a lookup operation on the given key with a conversion of the value, if found, to a series of bytes. + fn lookup_and_convert_bytes<'a, B, K1, K2>( + &self, + dictionary: &'a dyn BDictAccess, + key: K2, + ) -> Result<&'a [u8], Self::Error> + where + B: BRefAccess, + K2: AsRef<[u8]>, + { + self.convert_bytes(self.lookup(dictionary, &key)?, &key) + } + + /// Combines a lookup operation on the given key with a conversion of the value, if found, to a UTF-8 string. + fn lookup_and_convert_str<'a, B, K1, K2>( + &self, + dictionary: &'a dyn BDictAccess, + key: K2, + ) -> Result<&'a str, Self::Error> + where + B: BRefAccess, + K2: AsRef<[u8]>, + { + self.convert_str(self.lookup(dictionary, &key)?, &key) + } + + /// Combines a lookup operation on the given key with a conversion of the value, if found, to a list. + fn lookup_and_convert_list<'a, B, K1, K2>( + &self, + dictionary: &'a dyn BDictAccess, + key: K2, + ) -> Result<&'a dyn BListAccess, Self::Error> + where + B: BRefAccess, + K2: AsRef<[u8]>, + { + self.convert_list(self.lookup(dictionary, &key)?, &key) + } + + /// Combines a lookup operation on the given key with a conversion of the value, if found, to a dictionary. + fn lookup_and_convert_dict<'a, B, K1, K2>( + &self, + dictionary: &'a dyn BDictAccess, + key: K2, + ) -> Result<&'a dyn BDictAccess, Self::Error> + where + B: BRefAccess, + K2: AsRef<[u8]>, + { + self.convert_dict(self.lookup(dictionary, &key)?, &key) + } +} diff --git a/contrib/bencode/src/access/dict.rs b/contrib/bencode/src/access/dict.rs new file mode 100644 index 00000000..596d9535 --- /dev/null +++ b/contrib/bencode/src/access/dict.rs @@ -0,0 +1,64 @@ +use std::borrow::Cow; +use std::collections::BTreeMap; + +/// Trait for working with generic map data structures. +pub trait BDictAccess { + /// Convert the dictionary to an unordered list of key/value pairs. + fn to_list(&self) -> Vec<(&K, &V)>; + + /// Lookup a value in the dictionary. + fn lookup(&self, key: &[u8]) -> Option<&V>; + + /// Lookup a mutable value in the dictionary. + fn lookup_mut(&mut self, key: &[u8]) -> Option<&mut V>; + + /// Insert a key/value pair into the dictionary. + fn insert(&mut self, key: K, value: V) -> Option; + + /// Remove a value from the dictionary and return it. + fn remove(&mut self, key: &[u8]) -> Option; +} + +impl<'a, V> BDictAccess<&'a [u8], V> for BTreeMap<&'a [u8], V> { + fn to_list(&self) -> Vec<(&&'a [u8], &V)> { + self.iter().map(|(k, v)| (k, v)).collect() + } + + fn lookup(&self, key: &[u8]) -> Option<&V> { + self.get(key) + } + + fn lookup_mut(&mut self, key: &[u8]) -> Option<&mut V> { + self.get_mut(key) + } + + fn insert(&mut self, key: &'a [u8], value: V) -> Option { + self.insert(key, value) + } + + fn remove(&mut self, key: &[u8]) -> Option { + self.remove(key) + } +} + +impl<'a, V> BDictAccess, V> for BTreeMap, V> { + fn to_list(&self) -> Vec<(&Cow<'a, [u8]>, &V)> { + self.iter().map(|(k, v)| (k, v)).collect() + } + + fn lookup(&self, key: &[u8]) -> Option<&V> { + self.get(key) + } + + fn lookup_mut(&mut self, key: &[u8]) -> Option<&mut V> { + self.get_mut(key) + } + + fn insert(&mut self, key: Cow<'a, [u8]>, value: V) -> Option { + self.insert(key, value) + } + + fn remove(&mut self, key: &[u8]) -> Option { + self.remove(key) + } +} diff --git a/contrib/bencode/src/access/list.rs b/contrib/bencode/src/access/list.rs new file mode 100644 index 00000000..840bffa1 --- /dev/null +++ b/contrib/bencode/src/access/list.rs @@ -0,0 +1,108 @@ +use std::ops::{Index, IndexMut}; + +/// Trait for working with generic list data structures. +pub trait BListAccess { + /// Get a list element at the given index. + fn get(&self, index: usize) -> Option<&V>; + + /// Get a mutable list element at the given index. + fn get_mut(&mut self, index: usize) -> Option<&mut V>; + + /// Remove a list element at the given index. + fn remove(&mut self, index: usize) -> Option; + + /// Insert a list element at the given index. + fn insert(&mut self, index: usize, item: V); + + /// Push an element to the back of the list. + fn push(&mut self, item: V); + + /// Get the length of the list. + fn len(&self) -> usize; + + fn is_empty(&self) -> bool; +} + +impl<'a, V: 'a> Index for &'a dyn BListAccess { + type Output = V; + + fn index(&self, index: usize) -> &V { + self.get(index).unwrap() + } +} + +impl<'a, V: 'a> Index for &'a mut dyn BListAccess { + type Output = V; + + fn index(&self, index: usize) -> &V { + self.get(index).unwrap() + } +} + +impl<'a, V: 'a> IndexMut for &'a mut dyn BListAccess { + fn index_mut(&mut self, index: usize) -> &mut V { + self.get_mut(index).unwrap() + } +} + +impl<'a, V: 'a> IntoIterator for &'a dyn BListAccess { + type Item = &'a V; + type IntoIter = BListIter<'a, V>; + + fn into_iter(self) -> BListIter<'a, V> { + BListIter { index: 0, access: self } + } +} + +pub struct BListIter<'a, V> { + index: usize, + access: &'a dyn BListAccess, +} + +impl<'a, V> Iterator for BListIter<'a, V> { + type Item = &'a V; + + fn next(&mut self) -> Option<&'a V> { + let opt_next = self.access.get(self.index); + + if opt_next.is_some() { + self.index += 1; + } + + opt_next + } +} + +impl BListAccess for Vec { + fn get(&self, index: usize) -> Option<&V> { + self[..].get(index) + } + + fn get_mut(&mut self, index: usize) -> Option<&mut V> { + self[..].get_mut(index) + } + + fn remove(&mut self, index: usize) -> Option { + if index >= self[..].len() { + None + } else { + Some(Vec::remove(self, index)) + } + } + + fn insert(&mut self, index: usize, item: V) { + Vec::insert(self, index, item); + } + + fn push(&mut self, item: V) { + Vec::push(self, item); + } + + fn len(&self) -> usize { + Vec::len(self) + } + + fn is_empty(&self) -> bool { + Vec::is_empty(self) + } +} diff --git a/contrib/bencode/src/access/mod.rs b/contrib/bencode/src/access/mod.rs new file mode 100644 index 00000000..f14b032d --- /dev/null +++ b/contrib/bencode/src/access/mod.rs @@ -0,0 +1,4 @@ +pub mod bencode; +pub mod convert; +pub mod dict; +pub mod list; diff --git a/contrib/bencode/src/cow.rs b/contrib/bencode/src/cow.rs new file mode 100644 index 00000000..0d38c751 --- /dev/null +++ b/contrib/bencode/src/cow.rs @@ -0,0 +1,44 @@ +use std::borrow::Cow; + +/// Trait for macros to convert owned/borrowed types to `Cow`. +/// +/// This is needed because `&str` and `String` do not have `From` +/// implements into `Cow<_, [u8]>`. One solution is to just call `AsRef<[u8]>` +/// before converting. However, then when a user specifies an owned type, +/// we will implicitly borrow that; this trait prevents that so that macro +/// behavior is intuitive, so that owned types stay owned. +pub trait BCowConvert<'a> { + fn convert(self) -> Cow<'a, [u8]>; +} + +// TODO: Enable when specialization lands. +/* +impl<'a, T> BCowConvert<'a> for T where T: AsRef<[u8]> + 'a { + fn convert(self) -> Cow<'a, [u8]> { + self.into() + } +}*/ + +impl<'a> BCowConvert<'a> for &'a [u8] { + fn convert(self) -> Cow<'a, [u8]> { + self.into() + } +} + +impl<'a> BCowConvert<'a> for &'a str { + fn convert(self) -> Cow<'a, [u8]> { + self.as_bytes().into() + } +} + +impl BCowConvert<'static> for String { + fn convert(self) -> Cow<'static, [u8]> { + self.into_bytes().into() + } +} + +impl BCowConvert<'static> for Vec { + fn convert(self) -> Cow<'static, [u8]> { + self.into() + } +} diff --git a/contrib/bencode/src/error.rs b/contrib/bencode/src/error.rs new file mode 100644 index 00000000..18ebe960 --- /dev/null +++ b/contrib/bencode/src/error.rs @@ -0,0 +1,101 @@ +use error_chain::error_chain; + +error_chain! { + types { + BencodeParseError, BencodeParseErrorKind, BencodeParseResultExt, BencodeParseResult; + } + + errors { + BytesEmpty { + pos: usize + } { + description("Incomplete Number Of Bytes") + display("Incomplete Number Of Bytes At {:?}", pos) + } + InvalidByte { + pos: usize + } { + description("Invalid Byte Found") + display("Invalid Byte Found At {:?}", pos) + } + InvalidIntNoDelimiter { + pos: usize + } { + description("Invalid Integer Found With No Delimiter") + display("Invalid Integer Found With No Delimiter At {:?}", pos) + } + InvalidIntNegativeZero { + pos: usize + } { + description("Invalid Integer Found As Negative Zero") + display("Invalid Integer Found As Negative Zero At {:?}", pos) + } + InvalidIntZeroPadding { + pos: usize + } { + description("Invalid Integer Found With Zero Padding") + display("Invalid Integer Found With Zero Padding At {:?}", pos) + } + InvalidIntParseError { + pos: usize + } { + description("Invalid Integer Found To Fail Parsing") + display("Invalid Integer Found To Fail Parsing At {:?}", pos) + } + InvalidKeyOrdering { + pos: usize, + key: Vec + } { + description("Invalid Dictionary Key Ordering Found") + display("Invalid Dictionary Key Ordering Found At {:?} For Key {:?}", pos, key) + } + InvalidKeyDuplicates { + pos: usize, + key: Vec + } { + description("Invalid Dictionary Duplicate Keys Found") + display("Invalid Dictionary Key Found At {:?} For Key {:?}", pos, key) + } + InvalidLengthNegative { + pos: usize + } { + description("Invalid Byte Length Found As Negative") + display("Invalid Byte Length Found As Negative At {:?}", pos) + } + InvalidLengthOverflow { + pos: usize + } { + description("Invalid Byte Length Found To Overflow Buffer Length") + display("Invalid Byte Length Found To Overflow Buffer Length At {:?}", pos) + } + InvalidRecursionExceeded { + pos: usize, + max: usize + } { + description("Invalid Recursion Limit Exceeded") + display("Invalid Recursion Limit Exceeded At {:?} For Limit {:?}", pos, max) + } + } +} + +error_chain! { + types { + BencodeConvertError, BencodeConvertErrorKind, BencodeConvertResultExt, BencodeConvertResult; + } + + errors { + MissingKey { + key: Vec + } { + description("Missing Key In Bencode") + display("Missing Key In Bencode For {:?}", key) + } + WrongType { + key: Vec, + expected_type: String + } { + description("Wrong Type In Bencode") + display("Wrong Type In Bencode For {:?} Expected Type {}", key, expected_type) + } + } +} diff --git a/contrib/bencode/src/lib.rs b/contrib/bencode/src/lib.rs new file mode 100644 index 00000000..103a3c37 --- /dev/null +++ b/contrib/bencode/src/lib.rs @@ -0,0 +1,143 @@ +//! Library for parsing and converting bencoded data. +//! +//! # Examples +//! +//! Decoding bencoded data: +//! +//! ```rust +//! extern crate bencode; +//! +//! use std::default::Default; +//! use bencode::{BencodeRef, BRefAccess, BDecodeOpt}; +//! +//! fn main() { +//! let data = b"d12:lucky_numberi7ee"; // cspell:disable-line +//! let bencode = BencodeRef::decode(data, BDecodeOpt::default()).unwrap(); +//! +//! assert_eq!(7, bencode.dict().unwrap().lookup("lucky_number".as_bytes()) +//! .unwrap().int().unwrap()); +//! } +//! ``` +//! +//! Encoding bencoded data: +//! +//! ```rust +//! #[macro_use] +//! extern crate bencode; +//! +//! fn main() { +//! let message = (ben_map!{ +//! "lucky_number" => ben_int!(7), +//! "lucky_string" => ben_bytes!("7") +//! }).encode(); +//! +//! let data = b"d12:lucky_numberi7e12:lucky_string1:7e"; // cspell:disable-line +//! assert_eq!(&data[..], &message[..]); +//! } +//! ``` + +mod access; +mod cow; +mod error; +mod mutable; +mod reference; + +/// Traits for implementation functionality. +pub mod inner { + pub use crate::cow::BCowConvert; +} + +/// Traits for extended functionality. +pub mod ext { + #[allow(clippy::module_name_repetitions)] + pub use crate::access::bencode::BRefAccessExt; + #[allow(clippy::module_name_repetitions)] + pub use crate::access::convert::BConvertExt; +} + +#[deprecated(since = "1.0.0", note = "use `MutKind` instead.")] +pub use crate::access::bencode::MutKind as BencodeMutKind; +#[deprecated(since = "1.0.0", note = "use `RefKind` instead.")] +pub use crate::access::bencode::RefKind as BencodeRefKind; +pub use crate::access::bencode::{BMutAccess, BRefAccess, MutKind, RefKind}; +pub use crate::access::convert::BConvert; +pub use crate::access::dict::BDictAccess; +pub use crate::access::list::BListAccess; +pub use crate::error::{ + BencodeConvertError, BencodeConvertErrorKind, BencodeConvertResult, BencodeParseError, BencodeParseErrorKind, + BencodeParseResult, +}; +pub use crate::mutable::bencode_mut::BencodeMut; +pub use crate::reference::bencode_ref::BencodeRef; +pub use crate::reference::decode_opt::BDecodeOpt; + +const BEN_END: u8 = b'e'; +const DICT_START: u8 = b'd'; +const LIST_START: u8 = b'l'; +const INT_START: u8 = b'i'; + +const BYTE_LEN_LOW: u8 = b'0'; +const BYTE_LEN_HIGH: u8 = b'9'; +const BYTE_LEN_END: u8 = b':'; + +/// Construct a `BencodeMut` map by supplying string references as keys and `BencodeMut` as values. +#[macro_export] +macro_rules! ben_map { +( $($key:expr => $val:expr),* ) => { + { + use bencode::{BMutAccess, BencodeMut}; + use bencode::inner::BCowConvert; + + let mut bencode_map = BencodeMut::new_dict(); + { + let map = bencode_map.dict_mut().unwrap(); + $( + map.insert(BCowConvert::convert($key), $val); + )* + } + + bencode_map + } + } +} + +/// Construct a `BencodeMut` list by supplying a list of `BencodeMut` values. +#[macro_export] +macro_rules! ben_list { + ( $($ben:expr),* ) => { + { + use bencode::{BencodeMut, BMutAccess}; + + let mut bencode_list = BencodeMut::new_list(); + { + let list = bencode_list.list_mut().unwrap(); + $( + list.push($ben); + )* + } + + bencode_list + } + } +} + +/// Construct `BencodeMut` bytes by supplying a type convertible to `Vec`. +#[macro_export] +macro_rules! ben_bytes { + ( $ben:expr ) => {{ + use bencode::inner::BCowConvert; + use bencode::BencodeMut; + + BencodeMut::new_bytes(BCowConvert::convert($ben)) + }}; +} + +/// Construct a `BencodeMut` integer by supplying an `i64`. +#[macro_export] +macro_rules! ben_int { + ( $ben:expr ) => {{ + use bencode::BencodeMut; + + BencodeMut::new_int($ben) + }}; +} diff --git a/contrib/bencode/src/mutable/bencode_mut.rs b/contrib/bencode/src/mutable/bencode_mut.rs new file mode 100644 index 00000000..a3f95dbb --- /dev/null +++ b/contrib/bencode/src/mutable/bencode_mut.rs @@ -0,0 +1,229 @@ +use std::borrow::Cow; +use std::collections::BTreeMap; +use std::str; + +use crate::access::bencode::{BMutAccess, BRefAccess, MutKind, RefKind}; +use crate::access::dict::BDictAccess; +use crate::access::list::BListAccess; +use crate::mutable::encode; + +/// Bencode object that holds references to the underlying data. +#[derive(Debug, Eq, PartialEq, Clone, Hash)] +pub enum Inner<'a> { + /// Bencode Integer. + Int(i64), + /// Bencode Bytes. + Bytes(Cow<'a, [u8]>), + /// Bencode List. + List(Vec>), + /// Bencode Dictionary. + Dict(BTreeMap, BencodeMut<'a>>), +} + +/// `BencodeMut` object that stores references to some data. +#[derive(Debug, Eq, PartialEq, Clone, Hash)] +pub struct BencodeMut<'a> { + inner: Inner<'a>, +} + +impl<'a> BencodeMut<'a> { + fn new(inner: Inner<'a>) -> BencodeMut<'a> { + BencodeMut { inner } + } + + /// Create a new `BencodeMut` representing an `i64`. + #[must_use] + pub fn new_int(value: i64) -> BencodeMut<'a> { + BencodeMut::new(Inner::Int(value)) + } + + /// Create a new `BencodeMut` representing a `[u8]`. + #[must_use] + pub fn new_bytes(value: Cow<'a, [u8]>) -> BencodeMut<'a> { + BencodeMut::new(Inner::Bytes(value)) + } + + /// Create a new `BencodeMut` representing a `BListAccess`. + #[must_use] + pub fn new_list() -> BencodeMut<'a> { + BencodeMut::new(Inner::List(Vec::new())) + } + + /// Create a new `BencodeMut` representing a `BDictAccess`. + #[must_use] + pub fn new_dict() -> BencodeMut<'a> { + BencodeMut::new(Inner::Dict(BTreeMap::new())) + } + + /// Encode the `BencodeMut` into a buffer representing the bencode. + #[must_use] + pub fn encode(&self) -> Vec { + let mut buffer = Vec::new(); + + encode::encode(self, &mut buffer); + + buffer + } +} + +impl<'a> BRefAccess for BencodeMut<'a> { + type BKey = Cow<'a, [u8]>; + type BType = BencodeMut<'a>; + + fn kind<'b>(&'b self) -> RefKind<'b, Cow<'a, [u8]>, BencodeMut<'a>> { + match self.inner { + Inner::Int(n) => RefKind::Int(n), + Inner::Bytes(ref n) => RefKind::Bytes(n), + Inner::List(ref n) => RefKind::List(n), + Inner::Dict(ref n) => RefKind::Dict(n), + } + } + + fn str(&self) -> Option<&str> { + let bytes = self.bytes()?; + + match str::from_utf8(bytes) { + Ok(n) => Some(n), + Err(_) => None, + } + } + + fn int(&self) -> Option { + match self.inner { + Inner::Int(n) => Some(n), + _ => None, + } + } + + fn bytes(&self) -> Option<&[u8]> { + match self.inner { + Inner::Bytes(ref n) => Some(n.as_ref()), + _ => None, + } + } + + fn list(&self) -> Option<&dyn BListAccess>> { + match self.inner { + Inner::List(ref n) => Some(n), + _ => None, + } + } + + fn dict(&self) -> Option<&dyn BDictAccess, BencodeMut<'a>>> { + match self.inner { + Inner::Dict(ref n) => Some(n), + _ => None, + } + } +} + +impl<'a> BMutAccess for BencodeMut<'a> { + fn kind_mut<'b>(&'b mut self) -> MutKind<'b, Cow<'a, [u8]>, BencodeMut<'a>> { + match self.inner { + Inner::Int(n) => MutKind::Int(n), + Inner::Bytes(ref mut n) => MutKind::Bytes((*n).as_ref()), + Inner::List(ref mut n) => MutKind::List(n), + Inner::Dict(ref mut n) => MutKind::Dict(n), + } + } + + fn list_mut(&mut self) -> Option<&mut dyn BListAccess>> { + match self.inner { + Inner::List(ref mut n) => Some(n), + _ => None, + } + } + + fn dict_mut(&mut self) -> Option<&mut dyn BDictAccess, BencodeMut<'a>>> { + match self.inner { + Inner::Dict(ref mut n) => Some(n), + _ => None, + } + } +} + +// impl<'a> From> for BencodeMut<'a> { +// fn from(value: BencodeRef<'a>) -> Self { +// let inner = match value.kind() { +// BencodeRefKind::Int(value) => InnerBencodeMut::Int(value), +// BencodeRefKind::Bytes(value) => InnerBencodeMut::Bytes(Cow::Owned(Vec::from(value))), +// BencodeRefKind::List(value) => { +// InnerBencodeMut::List(value.clone().into_iter().map(|b| BencodeMut::from(b.clone())).collect()) +// } +// BencodeRefKind::Dict(value) => InnerBencodeMut::Dict( +// value +// .to_list() +// .into_iter() +// .map(|(key, value)| (Cow::Owned(Vec::from(*key)), BencodeMut::from(value.clone()))) +// .collect(), +// ), +// }; +// BencodeMut { inner } +// } +// } + +#[cfg(test)] +mod test { + use crate::access::bencode::BMutAccess; + use crate::mutable::bencode_mut::BencodeMut; + + #[test] + fn positive_int_encode() { + let bencode_int = BencodeMut::new_int(-560); + + let int_bytes = b"i-560e"; // cspell:disable-line + assert_eq!(&int_bytes[..], &bencode_int.encode()[..]); + } + + #[test] + fn positive_bytes_encode() { + /* cspell:disable-next-line */ + let bencode_bytes = BencodeMut::new_bytes((&b"asdasd"[..]).into()); + + let bytes_bytes = b"6:asdasd"; // cspell:disable-line + assert_eq!(&bytes_bytes[..], &bencode_bytes.encode()[..]); + } + + #[test] + fn positive_empty_list_encode() { + let bencode_list = BencodeMut::new_list(); + + let list_bytes = b"le"; // cspell:disable-line + assert_eq!(&list_bytes[..], &bencode_list.encode()[..]); + } + + #[test] + fn positive_nonempty_list_encode() { + let mut bencode_list = BencodeMut::new_list(); + + { + let list_mut = bencode_list.list_mut().unwrap(); + list_mut.push(BencodeMut::new_int(56)); + } + + let list_bytes = b"li56ee"; // cspell:disable-line + assert_eq!(&list_bytes[..], &bencode_list.encode()[..]); + } + + #[test] + fn positive_empty_dict_encode() { + let bencode_dict = BencodeMut::new_dict(); + + let dict_bytes = b"de"; // cspell:disable-line + assert_eq!(&dict_bytes[..], &bencode_dict.encode()[..]); + } + + #[test] + fn positive_nonempty_dict_encode() { + let mut bencode_dict = BencodeMut::new_dict(); + + { + let dict_mut = bencode_dict.dict_mut().unwrap(); + /* cspell:disable-next-line */ + dict_mut.insert((&b"asd"[..]).into(), BencodeMut::new_bytes((&b"asdasd"[..]).into())); + } + + let dict_bytes = b"d3:asd6:asdasde"; // cspell:disable-line + assert_eq!(&dict_bytes[..], &bencode_dict.encode()[..]); + } +} diff --git a/contrib/bencode/src/mutable/encode.rs b/contrib/bencode/src/mutable/encode.rs new file mode 100644 index 00000000..811c3581 --- /dev/null +++ b/contrib/bencode/src/mutable/encode.rs @@ -0,0 +1,67 @@ +use std::iter::Extend; + +use crate::access::bencode::{BRefAccess, RefKind}; +use crate::access::dict::BDictAccess; +use crate::access::list::BListAccess; + +pub fn encode(val: T, bytes: &mut Vec) +where + T: BRefAccess, + T::BKey: AsRef<[u8]>, +{ + match val.kind() { + RefKind::Int(n) => encode_int(n, bytes), + RefKind::Bytes(n) => encode_bytes(n, bytes), + RefKind::List(n) => encode_list(n, bytes), + RefKind::Dict(n) => encode_dict(n, bytes), + } +} + +fn encode_int(val: i64, bytes: &mut Vec) { + bytes.push(crate::INT_START); + + bytes.extend(val.to_string().into_bytes()); + + bytes.push(crate::BEN_END); +} + +fn encode_bytes(list: &[u8], bytes: &mut Vec) { + bytes.extend(list.len().to_string().into_bytes()); + + bytes.push(crate::BYTE_LEN_END); + + bytes.extend(list.iter().copied()); +} + +fn encode_list(list: &dyn BListAccess, bytes: &mut Vec) +where + T: BRefAccess, + T::BKey: AsRef<[u8]>, +{ + bytes.push(crate::LIST_START); + + for i in list { + encode(i, bytes); + } + + bytes.push(crate::BEN_END); +} + +fn encode_dict(dict: &dyn BDictAccess, bytes: &mut Vec) +where + K: AsRef<[u8]>, + V: BRefAccess, + V::BKey: AsRef<[u8]>, +{ + // Need To Sort The Keys In The Map Before Encoding + let mut sort_dict = dict.to_list(); + sort_dict.sort_by(|&(a, _), &(b, _)| a.as_ref().cmp(b.as_ref())); + + bytes.push(crate::DICT_START); + // Iterate And Dictionary Encode The (String, Bencode) Pairs + for (key, value) in &sort_dict { + encode_bytes(key.as_ref(), bytes); + encode(value, bytes); + } + bytes.push(crate::BEN_END); +} diff --git a/contrib/bencode/src/mutable/mod.rs b/contrib/bencode/src/mutable/mod.rs new file mode 100644 index 00000000..329ee9f7 --- /dev/null +++ b/contrib/bencode/src/mutable/mod.rs @@ -0,0 +1,2 @@ +pub mod bencode_mut; +mod encode; diff --git a/contrib/bencode/src/reference/bencode_ref.rs b/contrib/bencode/src/reference/bencode_ref.rs new file mode 100644 index 00000000..760dd301 --- /dev/null +++ b/contrib/bencode/src/reference/bencode_ref.rs @@ -0,0 +1,265 @@ +use std::collections::BTreeMap; +use std::str; + +use crate::access::bencode::{BRefAccess, BRefAccessExt, RefKind}; +use crate::access::dict::BDictAccess; +use crate::access::list::BListAccess; +use crate::error::{BencodeParseError, BencodeParseErrorKind, BencodeParseResult}; +use crate::reference::decode; +use crate::reference::decode_opt::BDecodeOpt; + +/// Bencode object that holds references to the underlying data. +#[derive(Debug, Eq, PartialEq, Clone, Hash)] +pub enum Inner<'a> { + /// Bencode Integer. + Int(i64, &'a [u8]), + /// Bencode Bytes. + Bytes(&'a [u8], &'a [u8]), + /// Bencode List. + List(Vec>, &'a [u8]), + /// Bencode Dictionary. + Dict(BTreeMap<&'a [u8], BencodeRef<'a>>, &'a [u8]), +} + +impl<'a> From> for BencodeRef<'a> { + fn from(val: Inner<'a>) -> Self { + BencodeRef { inner: val } + } +} + +/// `BencodeRef` object that stores references to some buffer. +#[derive(Debug, Eq, PartialEq, Clone, Hash)] +pub struct BencodeRef<'a> { + inner: Inner<'a>, +} + +impl<'a> BencodeRef<'a> { + /// Decode the given bytes into a `BencodeRef` using the given decode options. + #[allow(clippy::missing_errors_doc)] + pub fn decode(bytes: &'a [u8], opts: BDecodeOpt) -> BencodeParseResult> { + // Apply try so any errors return before the eof check + let (bencode, end_pos) = decode::decode(bytes, 0, opts, 0)?; + + if end_pos != bytes.len() && opts.enforce_full_decode() { + return Err(BencodeParseError::from_kind(BencodeParseErrorKind::BytesEmpty { + pos: end_pos, + })); + } + + Ok(bencode) + } + + /// Get a byte slice of the current bencode byte representation. + #[must_use] + pub fn buffer(&self) -> &'a [u8] { + #[allow(clippy::match_same_arms)] + match self.inner { + Inner::Int(_, buffer) => buffer, + Inner::Bytes(_, buffer) => buffer, + Inner::List(_, buffer) => buffer, + Inner::Dict(_, buffer) => buffer, + } + } +} + +impl<'a> BRefAccess for BencodeRef<'a> { + type BKey = &'a [u8]; + type BType = BencodeRef<'a>; + + fn kind<'b>(&'b self) -> RefKind<'b, &'a [u8], BencodeRef<'a>> { + match self.inner { + Inner::Int(n, _) => RefKind::Int(n), + Inner::Bytes(n, _) => RefKind::Bytes(n), + Inner::List(ref n, _) => RefKind::List(n), + Inner::Dict(ref n, _) => RefKind::Dict(n), + } + } + + fn str(&self) -> Option<&str> { + self.str_ext() + } + + fn int(&self) -> Option { + match self.inner { + Inner::Int(n, _) => Some(n), + _ => None, + } + } + + fn bytes(&self) -> Option<&[u8]> { + self.bytes_ext() + } + + fn list(&self) -> Option<&dyn BListAccess>> { + match self.inner { + Inner::List(ref n, _) => Some(n), + _ => None, + } + } + + fn dict(&self) -> Option<&dyn BDictAccess<&'a [u8], BencodeRef<'a>>> { + match self.inner { + Inner::Dict(ref n, _) => Some(n), + _ => None, + } + } +} + +impl<'a> BRefAccessExt<'a> for BencodeRef<'a> { + fn str_ext(&self) -> Option<&'a str> { + let bytes = self.bytes_ext()?; + + match str::from_utf8(bytes) { + Ok(n) => Some(n), + Err(_) => None, + } + } + + fn bytes_ext(&self) -> Option<&'a [u8]> { + match self.inner { + Inner::Bytes(n, _) => Some(&n[0..]), + _ => None, + } + } +} + +#[cfg(test)] +mod tests { + use std::default::Default; + + use crate::access::bencode::BRefAccess; + use crate::reference::bencode_ref::BencodeRef; + use crate::reference::decode_opt::BDecodeOpt; + + #[test] + fn positive_int_buffer() { + let int_bytes = b"i-500e"; // cspell:disable-line + let bencode = BencodeRef::decode(&int_bytes[..], BDecodeOpt::default()).unwrap(); + + assert_eq!(int_bytes, bencode.buffer()); + } + + #[test] + fn positive_bytes_buffer() { + let bytes_bytes = b"3:asd"; // cspell:disable-line + let bencode = BencodeRef::decode(&bytes_bytes[..], BDecodeOpt::default()).unwrap(); + + assert_eq!(bytes_bytes, bencode.buffer()); + } + + #[test] + fn positive_list_buffer() { + let list_bytes = b"l3:asde"; // cspell:disable-line + let bencode = BencodeRef::decode(&list_bytes[..], BDecodeOpt::default()).unwrap(); + + assert_eq!(list_bytes, bencode.buffer()); + } + + #[test] + fn positive_dict_buffer() { + let dict_bytes = b"d3:asd3:asde"; // cspell:disable-line + let bencode = BencodeRef::decode(&dict_bytes[..], BDecodeOpt::default()).unwrap(); + + assert_eq!(dict_bytes, bencode.buffer()); + } + + #[test] + fn positive_list_nested_int_buffer() { + let nested_int_bytes = b"li-500ee"; // cspell:disable-line + let bencode = BencodeRef::decode(&nested_int_bytes[..], BDecodeOpt::default()).unwrap(); + + let bencode_list = bencode.list().unwrap(); + let bencode_int = bencode_list.get(0).unwrap(); + + let int_bytes = b"i-500e"; // cspell:disable-line + assert_eq!(int_bytes, bencode_int.buffer()); + } + + #[test] + fn positive_dict_nested_int_buffer() { + let nested_int_bytes = b"d3:asdi-500ee"; // cspell:disable-line + let bencode = BencodeRef::decode(&nested_int_bytes[..], BDecodeOpt::default()).unwrap(); + + let bencode_dict = bencode.dict().unwrap(); + /* cspell:disable-next-line */ + let bencode_int = bencode_dict.lookup(&b"asd"[..]).unwrap(); + + let int_bytes = b"i-500e"; // cspell:disable-line + assert_eq!(int_bytes, bencode_int.buffer()); + } + + #[test] + fn positive_list_nested_bytes_buffer() { + let nested_bytes_bytes = b"l3:asde"; // cspell:disable-line + let bencode = BencodeRef::decode(&nested_bytes_bytes[..], BDecodeOpt::default()).unwrap(); + + let bencode_list = bencode.list().unwrap(); + let bencode_bytes = bencode_list.get(0).unwrap(); + + let bytes_bytes = b"3:asd"; // cspell:disable-line + assert_eq!(bytes_bytes, bencode_bytes.buffer()); + } + + #[test] + fn positive_dict_nested_bytes_buffer() { + let nested_bytes_bytes = b"d3:asd3:asde"; // cspell:disable-line + let bencode = BencodeRef::decode(&nested_bytes_bytes[..], BDecodeOpt::default()).unwrap(); + + let bencode_dict = bencode.dict().unwrap(); + /* cspell:disable-next-line */ + let bencode_bytes = bencode_dict.lookup(&b"asd"[..]).unwrap(); + + let bytes_bytes = b"3:asd"; // cspell:disable-line + assert_eq!(bytes_bytes, bencode_bytes.buffer()); + } + + #[test] + fn positive_list_nested_list_buffer() { + let nested_list_bytes = b"ll3:asdee"; // cspell:disable-line + let bencode = BencodeRef::decode(&nested_list_bytes[..], BDecodeOpt::default()).unwrap(); + + let bencode_list = bencode.list().unwrap(); + let bencode_list = bencode_list.get(0).unwrap(); + + let list_bytes = b"l3:asde"; // cspell:disable-line + assert_eq!(list_bytes, bencode_list.buffer()); + } + + #[test] + fn positive_dict_nested_list_buffer() { + let nested_list_bytes = b"d3:asdl3:asdee"; // cspell:disable-line + let bencode = BencodeRef::decode(&nested_list_bytes[..], BDecodeOpt::default()).unwrap(); + + let bencode_dict = bencode.dict().unwrap(); + /* cspell:disable-next-line */ + let bencode_list = bencode_dict.lookup(&b"asd"[..]).unwrap(); + + let list_bytes = b"l3:asde"; // cspell:disable-line + assert_eq!(list_bytes, bencode_list.buffer()); + } + + #[test] + fn positive_list_nested_dict_buffer() { + let nested_dict_bytes = b"ld3:asd3:asdee"; // cspell:disable-line + let bencode = BencodeRef::decode(&nested_dict_bytes[..], BDecodeOpt::default()).unwrap(); + + let bencode_list = bencode.list().unwrap(); + let bencode_dict = bencode_list.get(0).unwrap(); + + let dict_bytes = b"d3:asd3:asde"; // cspell:disable-line + assert_eq!(dict_bytes, bencode_dict.buffer()); + } + + #[test] + fn positive_dict_nested_dict_buffer() { + let nested_dict_bytes = b"d3:asdd3:asd3:asdee"; // cspell:disable-line + let bencode = BencodeRef::decode(&nested_dict_bytes[..], BDecodeOpt::default()).unwrap(); + + let bencode_dict = bencode.dict().unwrap(); + /* cspell:disable-next-line */ + let bencode_dict = bencode_dict.lookup(&b"asd"[..]).unwrap(); + + let dict_bytes = b"d3:asd3:asde"; // cspell:disable-line + assert_eq!(dict_bytes, bencode_dict.buffer()); + } +} diff --git a/contrib/bencode/src/reference/decode.rs b/contrib/bencode/src/reference/decode.rs new file mode 100644 index 00000000..96ab6dfb --- /dev/null +++ b/contrib/bencode/src/reference/decode.rs @@ -0,0 +1,398 @@ +use std::collections::btree_map::Entry; +use std::collections::BTreeMap; +use std::str::{self}; + +use crate::error::{BencodeParseError, BencodeParseErrorKind, BencodeParseResult}; +use crate::reference::bencode_ref::{BencodeRef, Inner}; +use crate::reference::decode_opt::BDecodeOpt; + +pub fn decode(bytes: &[u8], pos: usize, opts: BDecodeOpt, depth: usize) -> BencodeParseResult<(BencodeRef<'_>, usize)> { + if depth >= opts.max_recursion() { + return Err(BencodeParseError::from_kind( + BencodeParseErrorKind::InvalidRecursionExceeded { pos, max: depth }, + )); + } + let curr_byte = peek_byte(bytes, pos)?; + + match curr_byte { + crate::INT_START => { + let (bencode, next_pos) = decode_int(bytes, pos + 1, crate::BEN_END)?; + Ok((Inner::Int(bencode, &bytes[pos..next_pos]).into(), next_pos)) + } + crate::LIST_START => { + let (bencode, next_pos) = decode_list(bytes, pos + 1, opts, depth)?; + Ok((Inner::List(bencode, &bytes[pos..next_pos]).into(), next_pos)) + } + crate::DICT_START => { + let (bencode, next_pos) = decode_dict(bytes, pos + 1, opts, depth)?; + Ok((Inner::Dict(bencode, &bytes[pos..next_pos]).into(), next_pos)) + } + crate::BYTE_LEN_LOW..=crate::BYTE_LEN_HIGH => { + let (bencode, next_pos) = decode_bytes(bytes, pos)?; + // Include the length digit, don't increment position + Ok((Inner::Bytes(bencode, &bytes[pos..next_pos]).into(), next_pos)) + } + _ => Err(BencodeParseError::from_kind(BencodeParseErrorKind::InvalidByte { pos })), + } +} + +fn decode_int(bytes: &[u8], pos: usize, delim: u8) -> BencodeParseResult<(i64, usize)> { + let (_, begin_decode) = bytes.split_at(pos); + + let Some(relative_end_pos) = begin_decode.iter().position(|n| *n == delim) else { + return Err(BencodeParseError::from_kind(BencodeParseErrorKind::InvalidIntNoDelimiter { + pos, + })); + }; + let int_byte_slice = &begin_decode[..relative_end_pos]; + + if int_byte_slice.len() > 1 { + // Negative zero is not allowed (this would not be caught when converting) + if int_byte_slice[0] == b'-' && int_byte_slice[1] == b'0' { + return Err(BencodeParseError::from_kind(BencodeParseErrorKind::InvalidIntNegativeZero { + pos, + })); + } + + // Zero padding is illegal, and unspecified for key lengths (we disallow both) + if int_byte_slice[0] == b'0' { + return Err(BencodeParseError::from_kind(BencodeParseErrorKind::InvalidIntZeroPadding { + pos, + })); + } + } + + let Ok(int_str) = str::from_utf8(int_byte_slice) else { + return Err(BencodeParseError::from_kind(BencodeParseErrorKind::InvalidIntParseError { + pos, + })); + }; + + // Position of end of integer type, next byte is the start of the next value + let absolute_end_pos = pos + relative_end_pos; + let next_pos = absolute_end_pos + 1; + match int_str.parse::() { + Ok(n) => Ok((n, next_pos)), + Err(_) => Err(BencodeParseError::from_kind(BencodeParseErrorKind::InvalidIntParseError { + pos, + })), + } +} + +fn decode_bytes(bytes: &[u8], pos: usize) -> BencodeParseResult<(&[u8], usize)> { + let (num_bytes, start_pos) = decode_int(bytes, pos, crate::BYTE_LEN_END)?; + + if num_bytes < 0 { + return Err(BencodeParseError::from_kind(BencodeParseErrorKind::InvalidLengthNegative { + pos, + })); + } + + // Should be safe to cast to usize (TODO: Check if cast would overflow to provide + // a more helpful error message, otherwise, parsing will probably fail with an + // unrelated message). + let num_bytes = + usize::try_from(num_bytes).map_err(|_| BencodeParseErrorKind::Msg(format!("input length is too long: {num_bytes}")))?; + + if num_bytes > bytes[start_pos..].len() { + return Err(BencodeParseError::from_kind(BencodeParseErrorKind::InvalidLengthOverflow { + pos, + })); + } + + let next_pos = start_pos + num_bytes; + Ok((&bytes[start_pos..next_pos], next_pos)) +} + +fn decode_list(bytes: &[u8], pos: usize, opts: BDecodeOpt, depth: usize) -> BencodeParseResult<(Vec>, usize)> { + let mut bencode_list = Vec::new(); + + let mut curr_pos = pos; + let mut curr_byte = peek_byte(bytes, curr_pos)?; + + while curr_byte != crate::BEN_END { + let (bencode, next_pos) = decode(bytes, curr_pos, opts, depth + 1)?; + + bencode_list.push(bencode); + + curr_pos = next_pos; + curr_byte = peek_byte(bytes, curr_pos)?; + } + + let next_pos = curr_pos + 1; + Ok((bencode_list, next_pos)) +} + +fn decode_dict( + bytes: &[u8], + pos: usize, + opts: BDecodeOpt, + depth: usize, +) -> BencodeParseResult<(BTreeMap<&[u8], BencodeRef<'_>>, usize)> { + let mut bencode_dict = BTreeMap::new(); + + let mut curr_pos = pos; + let mut curr_byte = peek_byte(bytes, curr_pos)?; + + while curr_byte != crate::BEN_END { + let (key_bytes, next_pos) = decode_bytes(bytes, curr_pos)?; + + // Spec says that the keys must be in alphabetical order + match (bencode_dict.keys().last(), opts.check_key_sort()) { + (Some(last_key), true) if key_bytes < *last_key => { + return Err(BencodeParseError::from_kind(BencodeParseErrorKind::InvalidKeyOrdering { + pos: curr_pos, + key: key_bytes.to_vec(), + })) + } + _ => (), + }; + curr_pos = next_pos; + + let (value, next_pos) = decode(bytes, curr_pos, opts, depth + 1)?; + match bencode_dict.entry(key_bytes) { + Entry::Vacant(n) => n.insert(value), + Entry::Occupied(_) => { + return Err(BencodeParseError::from_kind(BencodeParseErrorKind::InvalidKeyDuplicates { + pos: curr_pos, + key: key_bytes.to_vec(), + })) + } + }; + + curr_pos = next_pos; + curr_byte = peek_byte(bytes, curr_pos)?; + } + + let next_pos = curr_pos + 1; + Ok((bencode_dict, next_pos)) +} + +fn peek_byte(bytes: &[u8], pos: usize) -> BencodeParseResult { + bytes + .get(pos) + .copied() + .ok_or_else(|| BencodeParseError::from_kind(BencodeParseErrorKind::BytesEmpty { pos })) +} + +#[cfg(test)] +mod tests { + use std::default::Default; + + use crate::access::bencode::BRefAccess; + use crate::reference::bencode_ref::BencodeRef; + use crate::reference::decode_opt::BDecodeOpt; + + /* cSpell:disable */ + // Positive Cases + const GENERAL: &[u8] = b"d0:12:zero_len_key8:location17:udp://test.com:8011:nested dictd4:listli-500500eee6:numberi500500ee"; + const RECURSION: &[u8] = b"lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllleeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"; + const BYTES_UTF8: &[u8] = b"16:valid_utf8_bytes"; + const DICTIONARY: &[u8] = b"d9:test_dictd10:nested_key12:nested_value11:nested_listli500ei-500ei0eee8:test_key10:test_valuee"; + const LIST: &[u8] = b"l10:test_bytesi500ei0ei-500el12:nested_bytesed8:test_key10:test_valueee"; + const BYTES: &[u8] = b"5:\xC5\xE6\xBE\xE6\xF2"; + const BYTES_ZERO_LEN: &[u8] = b"0:"; + const INT: &[u8] = b"i500e"; + const INT_NEGATIVE: &[u8] = b"i-500e"; + const INT_ZERO: &[u8] = b"i0e"; + const PARTIAL: &[u8] = b"i0e_asd"; + + // Negative Cases + const BYTES_NEG_LEN: &[u8] = b"-4:test"; + const BYTES_EXTRA: &[u8] = b"l15:processed_bytese17:unprocessed_bytes"; + const BYTES_NOT_UTF8: &[u8] = b"5:\xC5\xE6\xBE\xE6\xF2"; + const INT_NAN: &[u8] = b"i500a500e"; + const INT_LEADING_ZERO: &[u8] = b"i0500e"; + const INT_DOUBLE_ZERO: &[u8] = b"i00e"; + const INT_NEGATIVE_ZERO: &[u8] = b"i-0e"; + const INT_DOUBLE_NEGATIVE: &[u8] = b"i--5e"; + const DICT_UNORDERED_KEYS: &[u8] = b"d5:z_key5:value5:a_key5:valuee"; + const DICT_DUP_KEYS_SAME_DATA: &[u8] = b"d5:a_keyi0e5:a_keyi0ee"; + const DICT_DUP_KEYS_DIFF_DATA: &[u8] = b"d5:a_keyi0e5:a_key7:a_valuee"; + /* cSpell:enable */ + + #[test] + fn positive_decode_general() { + let bencode = BencodeRef::decode(GENERAL, BDecodeOpt::default()).unwrap(); + + let ben_dict = bencode.dict().unwrap(); + assert_eq!(ben_dict.lookup("".as_bytes()).unwrap().str().unwrap(), "zero_len_key"); + assert_eq!( + ben_dict.lookup("location".as_bytes()).unwrap().str().unwrap(), + "udp://test.com:80" + ); + assert_eq!(ben_dict.lookup("number".as_bytes()).unwrap().int().unwrap(), 500_500_i64); + + let nested_dict = ben_dict.lookup("nested dict".as_bytes()).unwrap().dict().unwrap(); + let nested_list = nested_dict.lookup("list".as_bytes()).unwrap().list().unwrap(); + assert_eq!(nested_list[0].int().unwrap(), -500_500_i64); + } + + #[test] + fn positive_decode_recursion() { + BencodeRef::decode(RECURSION, BDecodeOpt::new(50, true, true)).unwrap_err(); + + // As long as we didn't overflow our call stack, we are good! + } + + #[test] + fn positive_decode_bytes_utf8() { + let bencode = BencodeRef::decode(BYTES_UTF8, BDecodeOpt::default()).unwrap(); + + assert_eq!(bencode.str().unwrap(), "valid_utf8_bytes"); + } + + #[test] + fn positive_decode_dict() { + let bencode = BencodeRef::decode(DICTIONARY, BDecodeOpt::default()).unwrap(); + let dict = bencode.dict().unwrap(); + assert_eq!(dict.lookup("test_key".as_bytes()).unwrap().str().unwrap(), "test_value"); + + let nested_dict = dict.lookup("test_dict".as_bytes()).unwrap().dict().unwrap(); + assert_eq!( + nested_dict.lookup("nested_key".as_bytes()).unwrap().str().unwrap(), + "nested_value" + ); + + let nested_list = nested_dict.lookup("nested_list".as_bytes()).unwrap().list().unwrap(); + assert_eq!(nested_list[0].int().unwrap(), 500i64); + assert_eq!(nested_list[1].int().unwrap(), -500i64); + assert_eq!(nested_list[2].int().unwrap(), 0i64); + } + + #[test] + fn positive_decode_list() { + let bencode = BencodeRef::decode(LIST, BDecodeOpt::default()).unwrap(); + let list = bencode.list().unwrap(); + + assert_eq!(list[0].str().unwrap(), "test_bytes"); + assert_eq!(list[1].int().unwrap(), 500i64); + assert_eq!(list[2].int().unwrap(), 0i64); + assert_eq!(list[3].int().unwrap(), -500i64); + + let nested_list = list[4].list().unwrap(); + assert_eq!(nested_list[0].str().unwrap(), "nested_bytes"); + + let nested_dict = list[5].dict().unwrap(); + assert_eq!( + nested_dict.lookup("test_key".as_bytes()).unwrap().str().unwrap(), + "test_value" + ); + } + + #[test] + fn positive_decode_bytes() { + let bytes = super::decode_bytes(BYTES, 0).unwrap().0; + assert_eq!(bytes.len(), 5); + assert_eq!(bytes[0] as char, 'Å'); + assert_eq!(bytes[1] as char, 'æ'); + assert_eq!(bytes[2] as char, '¾'); + assert_eq!(bytes[3] as char, 'æ'); + assert_eq!(bytes[4] as char, 'ò'); + } + + #[test] + fn positive_decode_bytes_zero_len() { + let bytes = super::decode_bytes(BYTES_ZERO_LEN, 0).unwrap().0; + assert_eq!(bytes.len(), 0); + } + + #[test] + fn positive_decode_int() { + let int_value = super::decode_int(INT, 1, crate::BEN_END).unwrap().0; + assert_eq!(int_value, 500i64); + } + + #[test] + fn positive_decode_int_negative() { + let int_value = super::decode_int(INT_NEGATIVE, 1, crate::BEN_END).unwrap().0; + assert_eq!(int_value, -500i64); + } + + #[test] + fn positive_decode_int_zero() { + let int_value = super::decode_int(INT_ZERO, 1, crate::BEN_END).unwrap().0; + assert_eq!(int_value, 0i64); + } + + #[test] + fn positive_decode_partial() { + let bencode = BencodeRef::decode(PARTIAL, BDecodeOpt::new(2, true, false)).unwrap(); + + assert_ne!(PARTIAL.len(), bencode.buffer().len()); + assert_eq!(3, bencode.buffer().len()); + } + + #[test] + fn positive_decode_dict_unordered_keys() { + BencodeRef::decode(DICT_UNORDERED_KEYS, BDecodeOpt::default()).unwrap(); + } + + #[test] + #[should_panic] + fn negative_decode_bytes_neg_len() { + BencodeRef::decode(BYTES_NEG_LEN, BDecodeOpt::default()).unwrap(); + } + + #[test] + #[should_panic] + fn negative_decode_bytes_extra() { + BencodeRef::decode(BYTES_EXTRA, BDecodeOpt::default()).unwrap(); + } + + #[test] + #[should_panic] + fn negative_decode_bytes_not_utf8() { + let bencode = BencodeRef::decode(BYTES_NOT_UTF8, BDecodeOpt::default()).unwrap(); + + bencode.str().unwrap(); + } + + #[test] + #[should_panic] + fn negative_decode_int_nan() { + super::decode_int(INT_NAN, 1, crate::BEN_END).unwrap(); + } + + #[test] + #[should_panic] + fn negative_decode_int_leading_zero() { + super::decode_int(INT_LEADING_ZERO, 1, crate::BEN_END).unwrap(); + } + + #[test] + #[should_panic] + fn negative_decode_int_double_zero() { + super::decode_int(INT_DOUBLE_ZERO, 1, crate::BEN_END).unwrap(); + } + + #[test] + #[should_panic] + fn negative_decode_int_negative_zero() { + super::decode_int(INT_NEGATIVE_ZERO, 1, crate::BEN_END).unwrap(); + } + + #[test] + #[should_panic] + fn negative_decode_int_double_negative() { + super::decode_int(INT_DOUBLE_NEGATIVE, 1, crate::BEN_END).unwrap(); + } + + #[test] + #[should_panic] + fn negative_decode_dict_unordered_keys() { + BencodeRef::decode(DICT_UNORDERED_KEYS, BDecodeOpt::new(5, true, true)).unwrap(); + } + + #[test] + #[should_panic] + fn negative_decode_dict_dup_keys_same_data() { + BencodeRef::decode(DICT_DUP_KEYS_SAME_DATA, BDecodeOpt::default()).unwrap(); + } + + #[test] + #[should_panic] + fn negative_decode_dict_dup_keys_diff_data() { + BencodeRef::decode(DICT_DUP_KEYS_DIFF_DATA, BDecodeOpt::default()).unwrap(); + } +} diff --git a/contrib/bencode/src/reference/decode_opt.rs b/contrib/bencode/src/reference/decode_opt.rs new file mode 100644 index 00000000..ac94d031 --- /dev/null +++ b/contrib/bencode/src/reference/decode_opt.rs @@ -0,0 +1,55 @@ +use std::default::Default; + +const DEFAULT_MAX_RECURSION: usize = 50; +const DEFAULT_CHECK_KEY_SORT: bool = false; +const DEFAULT_ENFORCE_FULL_DECODE: bool = true; + +/// Stores decoding options for modifying decode behavior. +#[derive(Copy, Clone)] +#[allow(clippy::module_name_repetitions)] +pub struct BDecodeOpt { + max_recursion: usize, + check_key_sort: bool, + enforce_full_decode: bool, +} + +impl BDecodeOpt { + /// Create a new `BDecodeOpt` object. + #[must_use] + pub fn new(max_recursion: usize, check_key_sort: bool, enforce_full_decode: bool) -> BDecodeOpt { + BDecodeOpt { + max_recursion, + check_key_sort, + enforce_full_decode, + } + } + + /// Maximum limit allowed when decoding bencode. + #[must_use] + pub fn max_recursion(&self) -> usize { + self.max_recursion + } + + /// Whether or not an error should be thrown for out of order dictionary keys. + #[must_use] + pub fn check_key_sort(&self) -> bool { + self.check_key_sort + } + + /// Whether or not we enforce that the decoded bencode must make up all of the input + /// bytes or not. + /// + /// It may be useful to disable this if for example, the input bencode is prepended to + /// some payload and you would like to disassociate it. In this case, to find where the + /// rest of the payload starts that wasn't decoded, get the bencode buffer, and call len(). + #[must_use] + pub fn enforce_full_decode(&self) -> bool { + self.enforce_full_decode + } +} + +impl Default for BDecodeOpt { + fn default() -> BDecodeOpt { + BDecodeOpt::new(DEFAULT_MAX_RECURSION, DEFAULT_CHECK_KEY_SORT, DEFAULT_ENFORCE_FULL_DECODE) + } +} diff --git a/contrib/bencode/src/reference/mod.rs b/contrib/bencode/src/reference/mod.rs new file mode 100644 index 00000000..6a0ae6e4 --- /dev/null +++ b/contrib/bencode/src/reference/mod.rs @@ -0,0 +1,3 @@ +pub mod bencode_ref; +pub mod decode; +pub mod decode_opt; diff --git a/contrib/bencode/test/mod.rs b/contrib/bencode/test/mod.rs new file mode 100644 index 00000000..c1454967 --- /dev/null +++ b/contrib/bencode/test/mod.rs @@ -0,0 +1,18 @@ +use bencode::{ben_bytes, ben_int, ben_list, ben_map}; + +#[test] +fn positive_ben_map_macro() { + let result = (ben_map! { + "key" => ben_bytes!("value") + }) + .encode(); + + assert_eq!("d3:key5:valuee".as_bytes(), &result[..]); // cspell:disable-line +} + +#[test] +fn positive_ben_list_macro() { + let result = (ben_list!(ben_int!(5))).encode(); + + assert_eq!("li5ee".as_bytes(), &result[..]); // cspell:disable-line +} diff --git a/src/servers/http/v1/responses/announce.rs b/src/servers/http/v1/responses/announce.rs index 0cd62578..e7b64522 100644 --- a/src/servers/http/v1/responses/announce.rs +++ b/src/servers/http/v1/responses/announce.rs @@ -7,7 +7,7 @@ use std::panic::Location; use axum::http::StatusCode; use axum::response::{IntoResponse, Response}; -use bip_bencode::{ben_bytes, ben_int, ben_list, ben_map, BMutAccess, BencodeMut}; +use bencode::{ben_bytes, ben_int, ben_list, ben_map, BMutAccess, BencodeMut}; use serde::{self, Deserialize, Serialize}; use thiserror::Error; diff --git a/src/servers/http/v1/responses/scrape.rs b/src/servers/http/v1/responses/scrape.rs index 6610f9dc..c2f09959 100644 --- a/src/servers/http/v1/responses/scrape.rs +++ b/src/servers/http/v1/responses/scrape.rs @@ -5,7 +5,7 @@ use std::borrow::Cow; use axum::http::StatusCode; use axum::response::{IntoResponse, Response}; -use bip_bencode::{ben_int, ben_map, BMutAccess}; +use bencode::{ben_int, ben_map, BMutAccess}; use crate::tracker::ScrapeData;