From 306eb86d74bb54e9e5aba61dba83346de6e77c4f Mon Sep 17 00:00:00 2001 From: ae Date: Tue, 24 Sep 2024 00:14:53 +0300 Subject: [PATCH] chore: commit history pruned --- .gitignore | 7 + .pre-commit-config.yaml | 8 + Cargo.lock | 1499 ++++++++++++++++++++++++++++++++++ Cargo.toml | 24 + LICENSE | 21 + README.md | 43 + docs/contego-dark.png | Bin 0 -> 12216 bytes docs/contego-light.png | Bin 0 -> 11923 bytes src/client.rs | 148 ++++ src/crypto.rs | 111 +++ src/lib.rs | 6 + src/main.rs | 115 +++ src/parser.rs | 94 +++ src/server.rs | 200 +++++ src/sockets.rs | 103 +++ src/util.rs | 132 +++ tests/data/.placeholder | 0 tests/output/.placeholder | 0 tests/sockets_integration.rs | 101 +++ 19 files changed, 2612 insertions(+) create mode 100644 .gitignore create mode 100644 .pre-commit-config.yaml create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 docs/contego-dark.png create mode 100644 docs/contego-light.png create mode 100644 src/client.rs create mode 100644 src/crypto.rs create mode 100755 src/lib.rs create mode 100755 src/main.rs create mode 100644 src/parser.rs create mode 100644 src/server.rs create mode 100644 src/sockets.rs create mode 100644 src/util.rs create mode 100644 tests/data/.placeholder create mode 100644 tests/output/.placeholder create mode 100644 tests/sockets_integration.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1ff84a6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +/target + +tests/data/** +tests/output/** + +!tests/output/.placeholder +!tests/data/.placeholder diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..6433eff --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,8 @@ +repos: + - repo: https://github.com/doublify/pre-commit-rust + rev: v1.0 + hooks: + - id: fmt + args: ["--verbose", "--"] + - id: cargo-check + - id: clippy diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..8757347 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1499 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "aho-corasick" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea" + +[[package]] +name = "anstyle-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "anstyle-wincon" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd" +dependencies = [ + "anstyle", + "windows-sys 0.48.0", +] + +[[package]] +name = "async-stream" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.30", +] + +[[package]] +name = "async-trait" +version = "0.1.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.30", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "clap" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a13b88d2c62ff462f88e4a121f17a82c1af05693a2f192b5c38d14de73c19f6" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.30", +] + +[[package]] +name = "clap_lex" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "contego" +version = "0.4.0" +dependencies = [ + "aes-gcm", + "base64", + "clap", + "env_logger", + "log", + "ntest", + "rand", + "sha256", + "tokio", + "tokio-test", + "ureq", + "x25519-dalek", +] + +[[package]] +name = "cpufeatures" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "curve25519-dalek" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "subtle", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "env_logger" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "flate2" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "form_urlencoded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "ghash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "gimli" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" + +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "idna" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + +[[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 0.48.0", +] + +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + +[[package]] +name = "lock_api" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "memchr" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.48.0", +] + +[[package]] +name = "ntest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da8ec6d2b73d45307e926f5af46809768581044384637af6b3f3fe7c3c88f512" +dependencies = [ + "ntest_test_cases", + "ntest_timeout", +] + +[[package]] +name = "ntest_test_cases" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be7d33be719c6f4d09e64e27c1ef4e73485dc4cc1f4d22201f89860a7fe22e22" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ntest_timeout" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "066b468120587a402f0b47d8f80035c921f6a46f8209efd0632a89a16f5188a4" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.48.5", +] + +[[package]] +name = "percent-encoding" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "polyval" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha", + "rand_core 0.5.1", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.10", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "regex" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin 0.5.2", + "untrusted 0.7.1", + "web-sys", + "winapi", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.10", + "libc", + "spin 0.9.8", + "untrusted 0.9.0", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustix" +version = "0.38.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +dependencies = [ + "bitflags 2.4.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.21.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fecbfb7b1444f477b345853b1fce097a2c6fb637b2bfb87e6bc5db0f043fae4" +dependencies = [ + "log", + "ring 0.17.8", + "rustls-webpki 0.101.7", + "sct", +] + +[[package]] +name = "rustls-webpki" +version = "0.100.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e98ff011474fa39949b7e5c0428f9b4937eda7da7848bbb947786b7be0b27dab" +dependencies = [ + "ring 0.16.20", + "untrusted 0.7.1", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring 0.17.8", + "untrusted 0.9.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring 0.16.20", + "untrusted 0.7.1", +] + +[[package]] +name = "sha2" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha256" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7895c8ae88588ccead14ff438b939b0c569cd619116f14b4d13fdff7b8333386" +dependencies = [ + "async-trait", + "bytes", + "hex", + "sha2", + "tokio", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "smallvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" + +[[package]] +name = "socket2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ddc1f908d32ec46858c2d3b3daa00cc35bf4b6841ce4355c7bb3eedf2283a68" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.30", +] + +[[package]] +name = "tokio-stream" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-test" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89b3cbabd3ae862100094ae433e1def582cf86451b4e9bf83aa7ac1d8a7d719" +dependencies = [ + "async-stream", + "bytes", + "futures-core", + "tokio", + "tokio-stream", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" + +[[package]] +name = "toml_edit" +version = "0.19.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + +[[package]] +name = "unicode-ident" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "ureq" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b11c96ac7ee530603dcdf68ed1557050f374ce55a5a07193ebf8cbc9f8927e9" +dependencies = [ + "base64", + "flate2", + "log", + "once_cell", + "rustls", + "rustls-webpki 0.100.2", + "url", + "webpki-roots", +] + +[[package]] +name = "url" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.30", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.30", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "web-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b03058f88386e5ff5310d9111d53f48b17d732b401aeb83a8d5190f2ac459338" +dependencies = [ + "rustls-webpki 0.100.2", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.4", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +dependencies = [ + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" + +[[package]] +name = "winnow" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" +dependencies = [ + "memchr", +] + +[[package]] +name = "x25519-dalek" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2392b6b94a576b4e2bf3c5b2757d63f10ada8020a2e4d08ac849ebcf6ea8e077" +dependencies = [ + "curve25519-dalek", + "rand_core 0.5.1", + "zeroize", +] + +[[package]] +name = "zeroize" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.30", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..1a87a52 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "contego" +version = "0.4.0" +authors = ["ae"] +description = "Cryptographically secure file transfer CLI tool " +repository = "https://umbrella.haus/ae/contego" +license = "MIT" +edition = "2021" + +[dependencies] +tokio = { version = "1.28.1", features = ["full"] } +rand = "0.7.0" +x25519-dalek = "1.2.0" +aes-gcm = "0.10.3" +base64 = "0.21.2" +sha256 = "1.1.3" +ureq = "2.6.2" +clap = { version = "4.3.0", features = ["derive"] } +log = "0.4.17" +env_logger = "0.10.0" + +[dev-dependencies] +tokio-test = "0.4.2" +ntest = "0.9.0" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..aaba73a --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 ae + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..0a47628 --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ +
+ + + + +
+ +## Cryptographic specifications + +The initial key exchange is performed with elliptic-curve Diffie-Hellman. General data exchange is encrypted with AES-GCM. During regular communication payloads are Base64 encoded before being encrypted to prevent delimiter conflicts. SHA-256 hashes of files are compared to ensure data integrity. + +## Usage + +Build the optimized binary with `cargo build --release`. + +### Server + +``` +Usage: contego host [OPTIONS] --key <--source |--files ...> + +Options: + -k, --key Access key + -s, --source Path to a source file (alternative to --files) + -f, --files ... Paths to shareable files (alternative to --source) + -p, --port Host port [default: 8080] + -6, --ipv6 IPv6 instead of IPv4 + -c, --chunksize Transmit chunksize in bytes [default: 8192] + -l, --local Host locally + -h, --help Print help + +``` + +### Client + +``` +Usage: contego connect --addr --out --key + +Options: + -a, --addr IP address of the instance + -o, --out Path to an output folder + -k, --key Access key + -h, --help Print help +``` diff --git a/docs/contego-dark.png b/docs/contego-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..d53b3f3326ae6b3144132bce0f7546a8a845559c GIT binary patch literal 12216 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 nW1xNnk`eX)dH# Self { + Self { addr, key, output } + } + + pub async fn connection(&self) -> Result<(), Box> { + info!("Trying to connect to the server at {}", self.addr); + + let mut socket = TcpStream::connect(self.addr).await?; + + debug!("Connected to the TCP socket at {}", self.addr); + + let mut handler = SocketHandler::new(&mut socket); + let crypto = Crypto::new(&mut handler, true).await?; + handler.set_crypto(crypto); + + info!("Encrypted connection to {} established", self.addr); + + if !self.authorize(&mut handler).await? { + error!( + "Authorization failed due to an invalid access key '{}'", + self.key + ); + return Ok(()); + } + + let metadata = self.metadata(&mut handler).await?; + self.requests(&mut handler, metadata).await?; + + debug!("Connection sequence done, shutting down"); + + Ok(()) + } + + async fn authorize( + &self, + handler: &mut SocketHandler<'_>, + ) -> Result> { + debug!("Starting authorization"); + + let msg = self.key.as_bytes().to_vec(); + handler.send(&msg).await?; + + let buf = handler.recv().await?; + let msg = String::from_utf8(buf)?; + let msg = msg.trim(); + + if msg == "DISCONNECT" { + return Ok(false); + } + + debug!("Authorization successfully done"); + + Ok(true) + } + + async fn metadata( + &self, + handler: &mut SocketHandler<'_>, + ) -> Result, Box> { + debug!("Starting to receive metadata"); + + let buf = handler.recv().await?; + let amt = String::from_utf8(buf.clone())?.parse::()?; + handler.send(&buf).await?; // confirmation + + debug!("Confirmed metadata amount ({})", amt); + + let mut metadata = Vec::new(); + + while metadata.len() < amt { + let buf = handler.recv().await?; + let data = String::from_utf8(buf)?; + + let split = data.split(':').collect::>(); + let name = split[0].trim().to_string(); + let size = split[1].trim().parse::()?; + let hash = split[2].trim().to_string(); + + debug!("Metadata of file '{}' received successfully", name); + + let info = FileInfo::new(name, size, hash); + + metadata.push(info); + } + + Ok(metadata) + } + + async fn requests( + &self, + handler: &mut SocketHandler<'_>, + metadata: Vec, + ) -> Result<(), Box> { + info!("Starting to send requests"); + + for file in metadata { + let (mut handle, path) = new_file(self.output.clone(), &file.name).await?; + let msg = file.hash.as_bytes().to_vec(); + handler.send(&msg).await?; + + info!("Requesting file '{}'", file.hash); + + let mut remaining = file.size; + + while remaining != 0 { + let buf = handler.recv().await?; + handle.write_all(&buf).await?; + handle.flush().await?; + remaining -= buf.len() as u64; + + debug!("File '{}': {} bytes remaining", file.hash, remaining); + } + + let check_hash = crypto::try_hash(&path).unwrap(); + let msg = check_hash.as_bytes().to_vec(); + handler.send(&msg).await?; + + if check_hash != file.hash { + return Err("Unsuccessful file transfer, hashes don't match".into()); + } + + info!("File '{}' successfully transferred", file.hash); + } + + info!("All requests successfully done"); + + Ok(()) + } +} diff --git a/src/crypto.rs b/src/crypto.rs new file mode 100644 index 0000000..e9596ea --- /dev/null +++ b/src/crypto.rs @@ -0,0 +1,111 @@ +use std::{error::Error, path::Path}; + +use aes_gcm::{ + aead::{consts::U12, Aead}, + aes::Aes256, + Aes256Gcm, AesGcm, KeyInit, Nonce, +}; +use log::debug; +use rand::{rngs::OsRng, RngCore}; +use x25519_dalek::{EphemeralSecret, PublicKey, SharedSecret}; + +use crate::sockets::SocketHandler; + +const AES_NONCE_SIZE: usize = 12; +const DH_PBK_SIZE: usize = 32; + +#[derive(Clone)] +pub struct Crypto { + cipher: AesGcm, + rng: OsRng, +} + +impl Crypto { + pub async fn new( + handler: &mut SocketHandler<'_>, + go_first: bool, + ) -> Result> { + let secret = Self::ecdh(handler, go_first).await?; + let cipher = Aes256Gcm::new(secret.as_bytes().into()); + let rng = OsRng; + + Ok(Self { cipher, rng }) + } + + async fn ecdh( + handler: &mut SocketHandler<'_>, + go_first: bool, + ) -> Result> { + debug!("Starting ECDH key exchange"); + + let buf: Vec; + let own_sec = EphemeralSecret::new(OsRng); + let own_pbk = PublicKey::from(&own_sec); + let mut msg = own_pbk.as_bytes().to_vec(); + + msg.push(b':'); // manual delimiter + + if go_first { + handler.send_raw(&msg).await?; + buf = handler.recv_raw(DH_PBK_SIZE).await?; + } else { + buf = handler.recv_raw(DH_PBK_SIZE).await?; + handler.send_raw(&msg).await?; + } + + debug!("Calculating PPK from the shared secret"); + + let slice: [u8; DH_PBK_SIZE] = buf[..DH_PBK_SIZE].try_into()?; + let recv_pbk = PublicKey::from(slice); + let pvk = own_sec.diffie_hellman(&recv_pbk); + + debug!("PPK successfully generated"); + + Ok(pvk) + } + + fn nonce(&mut self) -> Nonce { + debug!("Generating new unique nonce (AEAD)"); + + let mut nonce = Nonce::default(); + self.rng.fill_bytes(&mut nonce); + + nonce + } + + pub async fn encrypt(&mut self, data: &[u8]) -> Result, Box> { + debug!("Encrypting {} bytes payload", data.len()); + + let nonce = self.nonce(); + let encrypted = match self.cipher.encrypt(&nonce, data.as_ref()) { + Ok(data) => data, + Err(e) => return Err(format!("Encryption failed: {}", e).into()), + }; + + let mut data = nonce.to_vec(); + data.extend_from_slice(&encrypted); + + Ok(data) + } + + pub async fn decrypt(&self, data: &[u8]) -> Result, Box> { + debug!("Decrypting {} bytes payload", data.len()); + + let (nonce_bytes, data) = data.split_at(AES_NONCE_SIZE); + let nonce = Nonce::from_slice(nonce_bytes); + let decrypted = match self.cipher.decrypt(nonce, data.as_ref()) { + Ok(data) => data, + Err(e) => return Err(format!("Decryption failed: {}", e).into()), + }; + + Ok(decrypted) + } +} + +pub fn try_hash(path: &Path) -> Result> { + debug!("Calculating SHA hash"); + + let hash = sha256::try_digest(path)?; + + Ok(hash) +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100755 index 0000000..4e16913 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,6 @@ +pub mod client; +pub mod crypto; +pub mod parser; +pub mod server; +pub mod sockets; +pub mod util; diff --git a/src/main.rs b/src/main.rs new file mode 100755 index 0000000..ef8827b --- /dev/null +++ b/src/main.rs @@ -0,0 +1,115 @@ +use std::{error::Error, net::SocketAddr, path::PathBuf}; + +use clap::{command, ArgGroup, Parser, Subcommand}; + +use contego::{ + client::Client, + parser::{addr_parser, dirpath_parser, filepath_parser}, + server::Server, + util::{ascii, filepaths, metadata, Ip}, +}; +use env_logger::Env; +use log::{error, info}; +use tokio::{signal, sync::mpsc}; + +#[derive(Debug, Parser)] +#[command(about, version)] +struct Cli { + #[command(subcommand)] + command: Commands, +} + +#[derive(Debug, Subcommand)] +enum Commands { + #[clap(group(ArgGroup::new("input").required(true).args(&["source", "files"])))] + Host { + /// Access key + #[clap(short = 'k', long)] + key: String, + /// Path to a source file (alternative to --files) + #[clap(short = 's', long, value_parser = filepath_parser, conflicts_with = "files", group = "input")] + source: Option, + /// Paths to shareable files (alternative to --source) + #[clap(short = 'f', long, num_args = 1.., value_parser = filepath_parser, conflicts_with = "source", group = "input")] + files: Option>, + /// Host port + #[clap(short = 'p', long, default_value_t = 8080)] + port: u16, + /// IPv6 instead of IPv4 + #[clap(short = '6', long, default_value_t = false)] + ipv6: bool, + /// Transmit chunksize in bytes + #[clap(short = 'c', long, default_value_t = 8192)] + chunksize: usize, + /// Host locally + #[clap(short = 'l', long, default_value_t = false)] + local: bool, + }, + Connect { + /// IP address of the instance + #[clap(short = 'a', long, value_parser = addr_parser)] + addr: SocketAddr, + /// Path to an output folder + #[clap(short = 'o', long, value_parser = dirpath_parser)] + out: PathBuf, + /// Access key + #[clap(short = 'k', long)] + key: String, + }, +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + ascii(); + + env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); + let cli = Cli::parse(); + + match cli.command { + Commands::Host { + port, + ipv6, + source, + files, + chunksize, + local, + key, + } => { + let (tx, rx) = mpsc::channel::<()>(1); + + let paths = filepaths(source, files)?; + let (metadata, index) = metadata(&paths).await?; + let (display_addr, bind_addr) = match (local, ipv6) { + (true, _) => Ip::Local.fetch(port)?, + (false, true) => Ip::V6.fetch(port)?, + (false, false) => Ip::V4.fetch(port)?, + }; + + let server = Server::new(display_addr, key, chunksize, metadata, index); + + tokio::spawn(async move { + match server.start(rx, &bind_addr).await { + Ok(_) => {} + Err(e) => error!("Error during server execution: {}", e), + }; + }); + + match signal::ctrl_c().await { + Ok(_) => { + tx.send(()).await?; + info!("Captured Ctrl+C, shutting down"); + } + Err(_) => error!("Failed to listen for a Ctrl+C event"), + }; + } + Commands::Connect { addr, out, key } => { + let client = Client::new(addr, key, out); + match client.connection().await { + Ok(_) => {} + Err(e) => error!("Error during client execution: {}", e), + }; + } + }; + + Ok(()) +} diff --git a/src/parser.rs b/src/parser.rs new file mode 100644 index 0000000..6c2c6b1 --- /dev/null +++ b/src/parser.rs @@ -0,0 +1,94 @@ +use std::{ + env, + io::{Error, ErrorKind::NotFound}, + net::{AddrParseError, SocketAddr}, + path::PathBuf, +}; + +use log::debug; + +pub fn addr_parser(addr: &str) -> Result { + let addr = addr + .parse::() + .expect("Failed to parse IP address"); + + Ok(addr) +} + +pub fn filepath_parser(path: &str) -> Result { + debug!("Validating filepath '{}'", path); + + let home = env::var("HOME").unwrap(); + let path = path + .replace('~', &home) + .parse::() + .expect("Failed to parse path"); + + if path.exists() && path.is_file() { + Ok(path) + } else { + Err(Error::new(NotFound, "File not found")) + } +} + +pub fn dirpath_parser(path: &str) -> Result { + debug!("Validating dirpath '{}'", path); + + let path = path.parse::().expect("Failed to parse path"); + + if path.exists() && path.is_dir() { + Ok(path) + } else { + Err(Error::new(NotFound, "Directory not found")) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn valid_ip() { + use std::net::Ipv6Addr; + + let ipv4 = "10.1.2.3:8888"; + let ipv6 = "[2001:db8::1]:8888"; + + let parsed_ipv4 = addr_parser(ipv4).unwrap(); + let parsed_ipv6 = addr_parser(ipv6).unwrap(); + + assert_eq!(parsed_ipv4, SocketAddr::from(([10, 1, 2, 3], 8888))); + assert_eq!( + parsed_ipv6, + SocketAddr::from((Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), 8888)) + ); + } + + #[test] + #[should_panic] + fn short_ip() { + let ip = "10.1.2:8888"; + addr_parser(ip).unwrap(); + } + + #[test] + #[should_panic] + fn long_ip() { + let ip = "[2001:0db8:ac10:fe01:0000:0000:0000:0000:0000]:8888"; + addr_parser(ip).unwrap(); + } + + #[test] + #[should_panic] + fn ipv6_no_brackets() { + let ip = "2001:db8::1:8888"; + addr_parser(ip).unwrap(); + } + + #[test] + #[should_panic] + fn ip_missing_port() { + let ip = "10.1.2.3"; + addr_parser(ip).unwrap(); + } +} diff --git a/src/server.rs b/src/server.rs new file mode 100644 index 0000000..af5b61e --- /dev/null +++ b/src/server.rs @@ -0,0 +1,200 @@ +use std::{collections::HashMap, error::Error, net::SocketAddr, path::PathBuf, sync::Arc}; + +use log::{debug, error, info}; +use tokio::{ + fs::File, + io::AsyncReadExt, + net::{TcpListener, TcpStream}, + sync::mpsc, +}; + +use crate::{crypto::Crypto, sockets::SocketHandler, util::FileInfo}; + +#[derive(Clone)] +pub struct Server { + addr: SocketAddr, + key: String, + chunksize: usize, + metadata: Vec, + index: HashMap, +} + +impl Server { + pub fn new( + addr: SocketAddr, + key: String, + chunksize: usize, + metadata: Vec, + index: HashMap, + ) -> Arc { + Arc::new(Self { + addr, + key, + chunksize, + metadata, + index, + }) + } + + pub async fn start( + self: Arc, + mut kill: mpsc::Receiver<()>, + bind_addr: &SocketAddr, + ) -> Result<(), Box> { + tokio::select! { + _ = self.listen(bind_addr) => Ok(()), + _ = kill.recv() => Ok(()), + } + } + + async fn listen( + self: Arc, + bind_addr: &SocketAddr, + ) -> Result<(), Box> { + let listener = TcpListener::bind(bind_addr).await?; + + info!("Listening on {} - Access key: {}", self.addr, self.key); + + loop { + let this_self = self.clone(); + let (mut socket, addr) = listener.accept().await?; + + info!("New client connected: {}", addr); + + match tokio::spawn(async move { this_self.connection(&mut socket, &addr).await }).await + { + Ok(_) => info!("Client disconnected: {}", addr), + Err(e) => error!("Fatal error in connection {}: {}", addr, e), + }; + } + } + + async fn connection( + &self, + socket: &mut TcpStream, + addr: &SocketAddr, + ) -> Result<(), Box> { + let mut handler = SocketHandler::new(socket); + let crypto = Crypto::new(&mut handler, false).await?; + handler.set_crypto(crypto); + + debug!("({}): Connection established", addr); + + if !self.authorize(&mut handler, addr).await? { + info!("({}): Invalid access key", addr); + return Ok(()); + } + + self.metadata(&mut handler, addr).await?; + self.requests(&mut handler, addr).await?; + + Ok(()) + } + + async fn authorize( + &self, + handler: &mut SocketHandler<'_>, + addr: &SocketAddr, + ) -> Result> { + debug!("({}): Starting authorization", addr); + + let buf = handler.recv().await?; + let key = String::from_utf8(buf)?; + + let is_valid: bool; + let res_msg: Vec; + + if key != self.key { + is_valid = false; + res_msg = b"DISCONNECT".to_vec(); + } else { + is_valid = true; + res_msg = b"VALID".to_vec(); + } + + handler.send(&res_msg).await?; + + debug!("({}): Authorization finished", addr); + + Ok(is_valid) + } + + async fn metadata( + &self, + handler: &mut SocketHandler<'_>, + addr: &SocketAddr, + ) -> Result<(), Box> { + debug!("({}): Starting to send metadata", addr); + + let amt = self.metadata.len(); + let msg = amt.to_string().as_bytes().to_vec(); + + handler.send(&msg).await?; + + let buf = handler.recv().await?; + let res_amt = String::from_utf8(buf)?.trim().parse::()?; + + if res_amt != amt { + return Err("Broken message sequence during metadata exchange".into()); + } + + debug!("({}): Metadata amount confirmed successfully", addr); + + for file in &self.metadata { + let msg = format!("{}:{}:{}", file.name, file.size, file.hash) + .as_bytes() + .to_vec(); + handler.send(&msg).await?; + + debug!("({}): Sent metadata of file '{}'", addr, file.hash); + } + + Ok(()) + } + + async fn requests( + &self, + handler: &mut SocketHandler<'_>, + addr: &SocketAddr, + ) -> Result<(), Box> { + debug!("({}): Waiting for file requests", addr); + + loop { + let buf = handler.recv().await?; + let hash = String::from_utf8(buf)?; + let hash = hash.trim(); + + if hash == "DISCONNECT" { + break; + } + + debug!("({}): Received request for file '{}'", addr, hash); + + let mut file = File::open(self.index[hash].clone()).await?; + let mut remaining = file.metadata().await?.len(); + let mut sendbuf = vec![0u8; self.chunksize]; + + debug!("({}): Sending bytes of '{}'", addr, hash); + + while remaining != 0 { + let n = file.read(&mut sendbuf).await?; + handler.send(&sendbuf[..n]).await?; + remaining -= n as u64; + + debug!("({}): {} bytes remaining", addr, remaining); + } + + let buf = handler.recv().await?; + let confirmation = String::from_utf8(buf)?; + let confirmation = confirmation.trim(); + + if confirmation != hash { + return Err("Unsuccessful file transfer, hashes don't match".into()); + } + + debug!("({}): File '{}' successfully transferred", addr, hash); + } + + Ok(()) + } +} diff --git a/src/sockets.rs b/src/sockets.rs new file mode 100644 index 0000000..ad01e3e --- /dev/null +++ b/src/sockets.rs @@ -0,0 +1,103 @@ +use std::error::Error; + +use base64::{engine::general_purpose, Engine}; +use log::debug; +use tokio::{ + io::{AsyncBufReadExt, AsyncWriteExt, BufReader, BufWriter}, + net::{ + tcp::{ReadHalf, WriteHalf}, + TcpStream, + }, +}; + +use crate::crypto::Crypto; + +pub struct SocketHandler<'a> { + writer: BufWriter>, + reader: BufReader>, + crypto: Option, +} + +impl<'a> SocketHandler<'a> { + pub fn new(socket: &'a mut TcpStream) -> Self { + let (reader, writer) = socket.split(); + let reader = BufReader::new(reader); + let writer = BufWriter::new(writer); + + Self { + writer, + reader, + crypto: None, + } + } + + pub fn set_crypto(&mut self, crypto: Crypto) { + // setting up AES cipher requires DH key exchange in plaintext, + // meaning crypto can't be initialized at the same time as the socket handler + debug!("Cryptography module initialized to the connection"); + self.crypto = Some(crypto); + } + + pub async fn send(&mut self, data: &[u8]) -> Result<(), Box> { + let data = match &mut self.crypto { + Some(c) => c.encrypt(data).await?, + None => data.to_vec(), // syntactic sugar, never actually called + }; + + let mut encoded = general_purpose::STANDARD_NO_PAD + .encode(data) + .as_bytes() + .to_vec(); + encoded.push(b':'); + + self.send_raw(&encoded).await?; + + Ok(()) + } + + pub async fn send_raw(&mut self, data: &[u8]) -> Result<(), Box> { + self.writer.write_all(data).await?; + self.writer.flush().await?; + + debug!("Sent {} bytes to the socket", data.len()); + + Ok(()) + } + + pub async fn recv(&mut self) -> Result, Box> { + let mut buf = self.recv_raw(1).await?; + buf.pop(); + buf = general_purpose::STANDARD_NO_PAD.decode(&buf)?.to_vec(); + + let data = match &self.crypto { + Some(c) => c.decrypt(&buf).await?, + None => buf, + }; + + Ok(data) + } + + pub async fn recv_raw( + &mut self, + min_limit: usize, + ) -> Result, Box> { + let mut buf = Vec::new(); + + while buf.len() <= min_limit { + let n = self.reader.read_until(b':', &mut buf).await?; + + if n == 0 { + return Err("Received 0 bytes from the socket".into()); + } + } + + /* + TODO: use min_limit to check whether read_until has reached EOF before reading all the necessary bytes + (e.g. regarding ecdh key exchange) --> loop and read until buf.len() == min_limit + */ + + debug!("Received {} bytes from the socket", buf.len()); + + Ok(buf) + } +} diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..9fdd279 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,132 @@ +use std::{collections::HashMap, env, error::Error, fs, net::SocketAddr, path::PathBuf}; + +use log::{debug, info}; +use tokio::{fs::File, io::BufWriter}; + +use crate::crypto; + +const PUBLIC_IPV4: &str = "https://ipinfo.io/ip"; +const PUBLIC_IPV6: &str = "https://ipv6.icanhazip.com"; + +#[derive(PartialEq, Eq)] +pub enum Ip { + V4, + V6, + Local, +} + +impl Ip { + pub fn fetch(self, port: u16) -> Result<(SocketAddr, SocketAddr), Box> { + let addr = match self { + Ip::V4 => PUBLIC_IPV4, + Ip::V6 => PUBLIC_IPV6, + Ip::Local => { + let addr_str = format!("127.0.0.1:{}", port); + let addr = addr_str.parse::()?; + return Ok((addr, addr)); + } + }; + + info!("Fetching IP information from {}", addr); + + let res = format!("{}:{}", ureq::get(addr).call()?.into_string()?.trim(), port); + let display_addr = res.parse::()?; + let bind_addr = format!("0.0.0.0:{}", port).parse::()?; + + debug!("IP: {}", res); + + Ok((display_addr, bind_addr)) + } +} + +#[derive(Clone)] +pub struct FileInfo { + pub name: String, + pub size: u64, + pub hash: String, +} + +impl FileInfo { + pub fn new(name: String, size: u64, hash: String) -> Self { + Self { name, size, hash } + } +} + +pub fn filepaths( + source: Option, + files: Option>, +) -> Result, Box> { + info!("Collecting filepaths"); + + let mut paths = Vec::new(); + + if let Some(source) = source { + let home = env::var("HOME")?; + let content = fs::read_to_string(source)?; + paths = content + .lines() + .into_iter() + .map(|p| PathBuf::from(p.replace('~', &home))) + .collect(); + } else if let Some(files) = files { + paths = files; + } + + debug!("Filepaths collection finished (total: {})", paths.len()); + + Ok(paths) +} + +pub async fn metadata( + files: &Vec, +) -> Result<(Vec, HashMap), Box> { + info!("Collecting metadata"); + + let mut metadata = Vec::new(); + let mut index = HashMap::new(); + + for path in files { + debug!("Collecting '{}' metadata", path.to_str().unwrap()); + + let split = path.to_str().unwrap().split('/').collect::>(); + let name = split[split.len() - 1].to_string(); + let handle = File::open(path).await?; + let size = handle.metadata().await?.len(); + let hash = crypto::try_hash(path)?; + + if size > 0 { + let info = FileInfo::new(name, size, hash.clone()); + metadata.push(info); + index.insert(hash, path.clone()); + } + } + + debug!( + "Metadata collection successfully done (total: {})", + metadata.len() + ); + + Ok((metadata, index)) +} + +pub async fn new_file( + mut path: PathBuf, + name: &str, +) -> Result<(BufWriter, PathBuf), Box> { + debug!("New file handle for '{}'", name); + + path.push(name); + let handle = File::create(&path).await?; + + Ok((BufWriter::new(handle), path)) +} + +pub fn ascii() { + let ascii = " __ + _________ ____ / /____ ____ _____ + / ___/ __ \\/ __ \\/ __/ _ \\/ __ `/ __ \\ +/ /__/ /_/ / / / / /_/ __/ /_/ / /_/ / +\\___/\\____/_/ /_/\\__/\\___/\\__, /\\____/ + /____/ "; + println!("{}\n", ascii); +} diff --git a/tests/data/.placeholder b/tests/data/.placeholder new file mode 100644 index 0000000..e69de29 diff --git a/tests/output/.placeholder b/tests/output/.placeholder new file mode 100644 index 0000000..e69de29 diff --git a/tests/sockets_integration.rs b/tests/sockets_integration.rs new file mode 100644 index 0000000..b013620 --- /dev/null +++ b/tests/sockets_integration.rs @@ -0,0 +1,101 @@ +use std::{ + fs::{self, File}, + io::{BufWriter, Write}, + path::PathBuf, + str::FromStr, +}; + +use contego::{ + client::Client, + server::Server, + util::{metadata, Ip}, +}; +use env_logger::Env; +use log::debug; +use ntest::timeout; +use rand::{distributions::Alphanumeric, thread_rng, Rng}; +use tokio::{fs::read_to_string, sync::mpsc}; + +#[tokio::test] +#[timeout(2000)] +/// Ensures backend communications integrity & the ability to handle individual requests. +async fn sockets_integration() { + env_logger::Builder::from_env(Env::default().default_filter_or("debug")) + .is_test(true) + .try_init() + .unwrap(); + + debug!("Initializing and starting the test"); + + let (testdata, paths) = testdata(); + let (metadata, index) = metadata(&paths).await.unwrap(); + + let (display_addr, bind_addr) = Ip::Local.fetch(8080).unwrap(); + let outdir = PathBuf::from("./tests/output/"); + let key = String::from("testkey"); + let c_key = key.clone(); + + let (tx, rx) = mpsc::channel::<()>(1); + + let server_handle = tokio::spawn(async move { + debug!("Initializing the asynchronous server task"); + let server = Server::new(display_addr, key, 8192, metadata, index); + debug!("Starting to listen to incoming connections"); + server.start(rx, &bind_addr).await.unwrap(); + }); + + let client_handle = tokio::spawn(async move { + debug!("Initializing the asynchronous client task"); + let client = Client::new(display_addr, c_key, outdir); + debug!("Connecting to the server"); + client.connection().await.unwrap(); + }); + + client_handle.await.unwrap(); + tx.send(()).await.unwrap(); + server_handle.await.unwrap(); + + debug!("Checking for file integrity"); + + for file in testdata { + let path = String::from("./tests/output/") + file.0; + let recv_content = read_to_string(path).await.unwrap(); + + assert_eq!( + recv_content, file.1, + "Output '{}' doesn't match the input '{}'", + recv_content, file.1 + ); + + fs::remove_file(String::from("./tests/output/") + file.0).unwrap(); + fs::remove_file(String::from("./tests/data/") + file.0).unwrap(); + + debug!("File '{}' checked and removed successfully", file.0); + } +} + +fn testdata() -> (Vec<(&'static str, String)>, Vec) { + let mut paths = Vec::new(); + let testdata = vec![ + ("1.txt", generate_data()), + ("2.txt", generate_data()), + ("3.txt", generate_data()), + ]; + + for file in &testdata { + let filepath = PathBuf::from_str("./tests/data/").unwrap().join(file.0); + let mut writer = BufWriter::new(File::create(filepath.clone()).unwrap()); + paths.push(filepath); + writer.write_all(file.1.as_bytes()).unwrap(); + } + + (testdata, paths) +} + +fn generate_data() -> String { + thread_rng() + .sample_iter(&Alphanumeric) + .take(30) + .map(char::from) + .collect::() +}