diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..bff29e6 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +rustflags = ["--cfg", "tokio_unstable"] diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..e931c34 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "rust-analyzer.showUnlinkedFileNotification": false +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 8de0704..88e6f66 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "aho-corasick" version = "1.1.3" @@ -11,6 +17,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + [[package]] name = "argp" version = "0.4.0" @@ -71,6 +83,23 @@ dependencies = [ "syn", ] +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.5.0" @@ -101,6 +130,61 @@ dependencies = [ "fs_extra", ] +[[package]] +name = "axum" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18ed336352031311f4e0b4dd2ff392d4fbb370777c9d18d7fc9d7359f73871" +dependencies = [ + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "sync_wrapper", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59446ce19cd142f8833f856eb31f3eb097812d1479ab224f54d72428ca21ea22" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bindgen" version = "0.72.1" @@ -138,6 +222,12 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.10.1" @@ -191,6 +281,70 @@ dependencies = [ "cc", ] +[[package]] +name = "console-api" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8599749b6667e2f0c910c1d0dff6901163ff698a52d5a39720f61b5be4b20d3" +dependencies = [ + "futures-core", + "prost", + "prost-types", + "tonic", + "tonic-prost", + "tracing-core", +] + +[[package]] +name = "console-subscriber" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb4915b7d8dd960457a1b6c380114c2944f728e7c65294ab247ae6b6f1f37592" +dependencies = [ + "console-api", + "crossbeam-channel", + "crossbeam-utils", + "futures-task", + "hdrhistogram", + "humantime", + "hyper-util", + "prost", + "prost-types", + "serde", + "serde_json", + "thread_local", + "tokio", + "tokio-stream", + "tonic", + "tracing", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + [[package]] name = "data-encoding" version = "2.9.0" @@ -243,6 +397,12 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + [[package]] name = "fast-tlsh" version = "0.1.10" @@ -264,6 +424,22 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" +[[package]] +name = "flate2" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foreign-types" version = "0.3.2" @@ -285,6 +461,58 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-macro", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "getopts" version = "0.2.24" @@ -323,6 +551,44 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +[[package]] +name = "h2" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" + +[[package]] +name = "hdrhistogram" +version = "7.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d" +dependencies = [ + "base64 0.21.7", + "byteorder", + "flate2", + "nom", + "num-traits", +] + [[package]] name = "hex-simd" version = "0.8.0" @@ -333,6 +599,125 @@ dependencies = [ "vsimd", ] +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humantime" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" + +[[package]] +name = "hyper" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "libc", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "indexmap" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "itertools" version = "0.13.0" @@ -386,18 +771,49 @@ version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + [[package]] name = "memchr" version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "mio" version = "1.1.0" @@ -415,11 +831,16 @@ version = "0.1.0" dependencies = [ "argp", "aws-lc-rs", + "console-subscriber", "fast-tlsh", + "futures-util", + "memchr", + "regex", "sslrelay", "static_cell", "tokio", "tokio-rustls", + "tokio-util", "x509-parser", ] @@ -526,12 +947,44 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pin-project-lite" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "pkg-config" version = "0.3.32" @@ -569,6 +1022,38 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prost" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7231bd9b3d3d33c86b58adbac74b5ec0ad9f496b19d22801d773636feaa95f3d" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9b4db3d6da204ed77bb26ba83b6122a73aeb2e87e25fbf7ad2e84c4ccbf8f72" +dependencies = [ + "prost", +] + [[package]] name = "pulldown-cmark" version = "0.9.6" @@ -690,6 +1175,12 @@ dependencies = [ "untrusted 0.9.0", ] +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + [[package]] name = "serde" version = "1.0.228" @@ -697,6 +1188,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", + "serde_derive", ] [[package]] @@ -719,12 +1211,52 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + [[package]] name = "socket2" version = "0.6.1" @@ -774,6 +1306,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + [[package]] name = "synstructure" version = "0.13.2" @@ -805,6 +1343,15 @@ dependencies = [ "syn", ] +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + [[package]] name = "time" version = "0.3.44" @@ -848,6 +1395,7 @@ dependencies = [ "pin-project-lite", "socket2", "tokio-macros", + "tracing", "windows-sys 0.61.2", ] @@ -872,6 +1420,154 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tonic" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb7613188ce9f7df5bfe185db26c5814347d110db17920415cf2fbcad85e7203" +dependencies = [ + "async-trait", + "axum", + "base64 0.22.1", + "bytes", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "socket2", + "sync_wrapper", + "tokio", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-prost" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66bd50ad6ce1252d87ef024b3d64fe4c3cf54a86fb9ef4c631fdd0ded7aeaa67" +dependencies = [ + "bytes", + "prost", + "tonic", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "indexmap", + "pin-project-lite", + "slab", + "sync_wrapper", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +dependencies = [ + "matchers", + "once_cell", + "regex-automata", + "sharded-slab", + "thread_local", + "tracing", + "tracing-core", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "unicase" version = "2.8.1" @@ -902,6 +1598,12 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "vcpkg" version = "0.2.15" @@ -920,6 +1622,15 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml index 44307c3..52929e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,9 +6,14 @@ edition = "2024" [dependencies] argp = "0.4.0" aws-lc-rs = "1.14.1" -tlsh = { package = "fast-tlsh", version = "0.1.10", features = ["easy-functions"] } +console-subscriber = "0.5.0" +futures-util = "0.3.31" +memchr = "2.7.6" +regex = "1.12.2" sslrelay = { path = "../sslrelay-lib" } static_cell = "2.1.1" -tokio = { version = "1.48.0", features = ["io-util", "macros", "net", "rt", "rt-multi-thread", "sync"]} +tlsh = { package = "fast-tlsh", version = "0.1.10", features = ["easy-functions"] } +tokio = { version = "1.48.0", features = ["io-util", "macros", "net", "rt", "rt-multi-thread", "sync", "time", "tracing"]} tokio-rustls = "0.26.4" +tokio-util = { version = "0.7.6", features = ["codec"] } x509-parser = "0.18.0" diff --git a/README.md b/README.md index eec4982..bebd67c 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,12 @@ It relies on the server name extension in TLS (SNI). Requests without server nam For experimental purpose. Do not use on an open network where security matters. +## Build + +```bash +RUSTFLAGS="--cfg tokio_unstable" cargo build --release +``` + ## Record file format The record file is a list of records. Each record follows this format: diff --git a/src/client.rs b/src/client.rs index f3bfc79..c2aef79 100644 --- a/src/client.rs +++ b/src/client.rs @@ -3,11 +3,15 @@ use crate::{ record::{Direction, Records}, }; -use std::{net::ToSocketAddrs, sync::Arc}; +use futures_util::StreamExt; +use std::{ + net::ToSocketAddrs, + sync::{Arc, atomic::AtomicU32}, +}; use tokio::{ - io::{AsyncReadExt, AsyncWriteExt}, + io::AsyncWriteExt, net::TcpStream, - sync::oneshot, + sync::{Semaphore, oneshot}, }; use tokio_rustls::{ TlsConnector, @@ -17,6 +21,7 @@ use tokio_rustls::{ pki_types::ServerName, }, }; +use tokio_util::codec::Framed; #[derive(Debug)] struct DummyCertVerifier; @@ -82,6 +87,11 @@ pub async fn play( repeat: u32, ) { sync_receiver.await.unwrap(); + // Semaphore used to limit the number of concurrent clients. + // Its handle is released when the task panics. + let limiter = Arc::new(Semaphore::new(32)); + let counter = Arc::new(AtomicU32::new(0)); + let total = records.len() * repeat as usize; let mut handles = Vec::new(); let connect_to = connect_to.to_socket_addrs().unwrap().next().unwrap(); match tls_mode { @@ -92,58 +102,159 @@ pub async fn play( .with_custom_certificate_verifier(Arc::new(DummyCertVerifier)) .with_no_client_auth(), ); - for (_id, (server_name, records)) in records.iter() { + for (id, (server_name, records)) in records.iter() { let connector = TlsConnector::from(config.clone()); + let counter = counter.clone(); + let limiter = limiter.clone(); handles.push(tokio::spawn(async move { + let limiter = limiter.acquire().await.unwrap(); let server_name = ServerName::try_from(String::from_utf8(server_name.clone()).unwrap()) .unwrap(); for _i in 0..repeat { let stream = TcpStream::connect(connect_to).await.unwrap(); - let mut stream = connector + let stream = connector .connect(server_name.clone(), stream) .await .unwrap(); + let mut stream = Framed::new(stream, crate::http::HttpCodec {}); for (direction, data) in records { match direction { Direction::ClientToServer => { - stream.write_all(data).await.unwrap(); + println!("[CLT] ({id}) >> {}", data.len()); + stream.get_mut().write_all(data).await.unwrap(); } Direction::ServerToClient => { - let mut buf = Vec::new(); - stream.read_buf(&mut buf).await.ok(); + println!("[CLT] ({id}) << {}", data.len()); + // let mut buf = Vec::new(); + // stream.read_buf(&mut buf).await.ok(); + //let mut buf = vec![0; data.len().saturating_sub(50).max(1)]; + //let resp = stream.next().await.unwrap().unwrap(); + let resp = tokio::time::timeout( + std::time::Duration::from_millis(500), + stream.next(), + ) + .await + .unwrap() + .unwrap() + .unwrap(); + dbg!(resp.len()); + //crate::http::decode_http(&mut buf, &mut stream).await; } } } - stream.shutdown().await.unwrap(); + stream.get_mut().shutdown().await.unwrap(); + let cnt = counter.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + println!("Client: {} / {}", cnt + 1, total); } + drop(limiter); })); + //tokio::time::sleep(std::time::Duration::from_millis(500)).await; } } TlsMode::None | TlsMode::Server => { - for (_id, (_server_name, records)) in records.iter() { + for (id, (_server_name, records)) in records.iter() { + /*if *id != 33 { + continue + }*/ + let counter = counter.clone(); + let limiter = limiter.clone(); handles.push(tokio::spawn(async move { + dbg!(limiter.available_permits()); + let limiter = limiter.acquire().await.unwrap(); + //let mut buf = Vec::new(); for _i in 0..repeat { - let mut stream = TcpStream::connect(connect_to).await.unwrap(); + dbg!(); + let stream = TcpStream::connect(connect_to).await.unwrap(); + let mut stream = Framed::new(stream, crate::http::HttpCodec {}); + /*let mut skip_recv = false; for (direction, data) in records { match direction { Direction::ClientToServer => { + skip_recv = false; + println!("[CLT] ({id}) >> {}", data.len()); stream.write_all(data).await.unwrap(); } Direction::ServerToClient => { - let mut buf = Vec::new(); - stream.read_buf(&mut buf).await.ok(); + if skip_recv { + continue; + } + println!("[CLT] ({id}) << {}", data.len()); + //let mut buf = Vec::new(); + //stream.read_buf(&mut buf).await.ok(); + //let mut buf = vec![0; data.len().saturating_sub(50).max(1)]; + let mut buf = vec![0; data.len()]; + match tokio::time::timeout( + std::time::Duration::from_millis(500), + stream.readable(), + ) + .await + { + Ok(r) => { + r.unwrap(); + } + Err(_) => { + println!("[CLT] timeout recv ({id})"); + break; + } + } + // TODO utiliser crate::http ici + match tokio::time::timeout( + std::time::Duration::from_millis(500), + stream.read_exact(&mut buf), + ) + .await + { + Ok(r) => { + r.unwrap(); + } + Err(_) => { + println!("[CLT] skip recv ({id})"); + skip_recv = true; + } + } + } + } + }*/ + for (direction, data) in records { + match direction { + Direction::ClientToServer => { + println!("[CLT] ({id}) >> {}", data.len()); + stream.get_mut().write_all(data).await.unwrap(); + } + Direction::ServerToClient => { + println!("[CLT] ({id}) << {}", data.len()); + //let mut buf = Vec::new(); + //stream.read_buf(&mut buf).await.ok(); + //let mut buf = vec![0; data.len().saturating_sub(50).max(1)]; + let resp = tokio::time::timeout( + std::time::Duration::from_millis(500), + stream.next(), + ) + .await + .unwrap() + .unwrap() + .unwrap(); + //let resp = stream.next().await.unwrap().unwrap(); + dbg!(resp.len()); + //crate::http::decode_http(&mut buf, &mut stream).await; + //buf.clear(); } } } - stream.shutdown().await.unwrap(); + dbg!(); + stream.get_mut().shutdown().await.unwrap(); + let cnt = counter.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + println!("Client: {} / {}", cnt + 1, total); } + drop(limiter); })); + //tokio::time::sleep(std::time::Duration::from_millis(500)).await; } } } for handle in handles { handle.await.unwrap(); } - //std::process::exit(0); + std::process::exit(0); } diff --git a/src/http.rs b/src/http.rs new file mode 100644 index 0000000..b868c9e --- /dev/null +++ b/src/http.rs @@ -0,0 +1,120 @@ +use regex::bytes::Regex; +use std::sync::LazyLock; +use tokio::io::AsyncReadExt; +use tokio_util::codec::{Decoder, Encoder}; + +static REGEX_CONTENT_LENGTH: LazyLock = + LazyLock::new(|| Regex::new(r#"[cC]ontent-[lL]ength: *(\d+)\r\n"#).unwrap()); + +pub async fn _decode_http(buf: &mut Vec, stream: &mut R) { + dbg!(); + loop { + dbg!(); + if let Some(mut end_index) = memchr::memmem::find(buf, b"\r\n\r\n") { + end_index += 4; + if let Some(captures) = REGEX_CONTENT_LENGTH.captures(buf) { + if let Some(content_length) = captures.get(1) { + // Read body + let content_length: usize = str::from_utf8(content_length.as_bytes()) + .unwrap() + .parse() + .unwrap(); + while buf.len() < end_index + content_length { + dbg!(); + match tokio::time::timeout( + std::time::Duration::from_millis(500), + stream.read_buf(buf), + ) + .await + { + Ok(Ok(_n)) => {} + Ok(Err(e)) => { + println!("[http] error reading: {e:?}"); + break; + } + Err(_e) => { + // timeout + break; + } + } + } + break; + } else { + // Erroneous Content-Type + break; + } + } else { + // Header ended without Content-Type => no body + break; + } + } + match tokio::time::timeout(std::time::Duration::from_millis(500), stream.read_buf(buf)) + .await + { + Ok(Ok(n)) => { + println!("[http] read {n}"); + } + Ok(Err(e)) => { + println!("[http] error reading: {e:?}"); + break; + } + Err(_e) => { + // timeout + break; + } + } + } +} + +pub struct HttpCodec {} + +impl Decoder for HttpCodec { + type Item = Vec; + type Error = std::io::Error; + fn decode( + &mut self, + src: &mut tokio_util::bytes::BytesMut, + ) -> Result, Self::Error> { + if let Some(mut end_index) = memchr::memmem::find(src, b"\r\n\r\n") { + end_index += 4; + if let Some(captures) = REGEX_CONTENT_LENGTH.captures(src) { + if let Some(content_length) = captures.get(1) { + // Read body + let content_length: usize = str::from_utf8(content_length.as_bytes()) + .unwrap() + .parse() + .unwrap(); + if src.len() >= end_index + content_length { + //dbg!(content_length); + let out = src.to_vec(); + src.clear(); + Ok(Some(out)) + } else { + Ok(None) + } + } else { + // Invalid Content-Length + Err(std::io::ErrorKind::InvalidData.into()) + } + } else { + // Header ended without Content-Type => no body + let out = src.to_vec(); + src.clear(); + Ok(Some(out)) + } + } else { + Ok(None) + } + } +} + +impl Encoder> for HttpCodec { + type Error = std::io::Error; + fn encode( + &mut self, + _item: Vec, + _dst: &mut tokio_util::bytes::BytesMut, + ) -> Result<(), Self::Error> { + Ok(()) + } +} diff --git a/src/main.rs b/src/main.rs index 9ddb88c..c70603a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ #![feature(let_chains)] mod client; +mod http; mod record; mod server; @@ -53,6 +54,9 @@ struct OptPlay { /// Repeat N times #[argp(option, short = 'r', default = "1")] repeat: u32, + /// Only play this record + #[argp(option)] + record: Option, } /// Print records @@ -93,7 +97,13 @@ async fn main() { _ => panic!("TLS mode must be one of none,client,server,both."), }; let records = RECORDS.init(record::read_record_file(&opt.record_file)); + + if let Some(only_record) = subopt.record { + records.retain(|id, _| *id == only_record); + } + let (sync_sender, sync_receiver) = oneshot::channel(); + console_subscriber::init(); let client = tokio::spawn(client::play( records, tls_mode, diff --git a/src/record.rs b/src/record.rs index 7604e54..fe27379 100644 --- a/src/record.rs +++ b/src/record.rs @@ -212,7 +212,7 @@ pub fn print_records(records: &Records, print_packets: bool) { } } if print_packets { - let data = if data.len() >= 256 { + let data = if data.len() >= 256 && *direction == Direction::ServerToClient { &data[0..256] } else { data.as_slice() diff --git a/src/server.rs b/src/server.rs index 073f855..5f89da5 100644 --- a/src/server.rs +++ b/src/server.rs @@ -3,12 +3,9 @@ use crate::{ record::{Direction, Records}, }; +use futures_util::stream::StreamExt; use std::{collections::HashMap, sync::Arc}; -use tokio::{ - io::{AsyncReadExt, AsyncWriteExt}, - net::TcpListener, - sync::oneshot, -}; +use tokio::{io::AsyncWriteExt, net::TcpListener, sync::oneshot}; use tokio_rustls::rustls::{ pki_types::{ CertificateDer, PrivateKeyDer, @@ -17,6 +14,7 @@ use tokio_rustls::rustls::{ server::ResolvesServerCertUsingSni, sign::CertifiedKey, }; +use tokio_util::codec::Framed; use x509_parser::prelude::GeneralName; pub async fn play( @@ -27,7 +25,7 @@ pub async fn play( sync_sender: oneshot::Sender<()>, ) { let mut response_map = HashMap::new(); - for (_id, (server_name, records)) in records.iter() { + for (id, (server_name, records)) in records.iter() { let mut hash = None; let mut responses = Vec::new(); for (direction, data) in records { @@ -36,7 +34,7 @@ pub async fn play( if let Some(hash) = hash && !responses.is_empty() { - response_map.insert((server_name.to_vec(), hash), responses); + response_map.insert((server_name.to_vec(), hash), (id, responses)); responses = Vec::new(); } hash = Some( @@ -52,17 +50,21 @@ pub async fn play( if let Some(hash) = hash && !responses.is_empty() { - response_map.insert((server_name.to_vec(), hash), responses); + response_map.insert((server_name.to_vec(), hash), (id, responses)); } } + let response_map = Arc::new(response_map); + match tls_mode { TlsMode::Both | TlsMode::Server => { let mut resolver = ResolvesServerCertUsingSni::new(); let config = tokio_rustls::rustls::ServerConfig::builder() .with_no_client_auth() .with_cert_resolver(Arc::new(ResolvesServerCertUsingSni::new())); - for file in std::fs::read_dir(cert_path).unwrap() { + for file in std::fs::read_dir(cert_path).unwrap_or_else(|e| { + panic!("Cannot read certificate directory `{cert_path}`: {e:?}") + }) { match file { Ok(file) => { if file.file_name().as_encoded_bytes().ends_with(b".crt") { @@ -77,7 +79,7 @@ pub async fn play( let (_rem, cert) = x509_parser::parse_x509_certificate(&data).unwrap(); if !cert.is_ca() { - println!("File: {:?}", file.file_name()); + //println!("File: {:?}", file.file_name()); let mut key_path = file.path().to_path_buf(); key_path.pop(); let file_name = @@ -110,7 +112,7 @@ pub async fn play( .iter() { if let GeneralName::DNSName(name) = name { - resolver.add(dbg!(name), cert_key.clone()).unwrap(); + resolver.add(name, cert_key.clone()).unwrap(); } } } @@ -130,9 +132,7 @@ pub async fn play( .with_cert_resolver(Arc::new(resolver)), ); - //let acceptor = tokio_rustls::TlsAcceptor::from(Arc::new(config)); let listener = TcpListener::bind(listen_addr).await.unwrap(); - let response_map = Arc::new(response_map); sync_sender.send(()).unwrap(); loop { let config = config.clone(); @@ -143,28 +143,12 @@ pub async fn play( ); //let acceptor = acceptor.clone(); let response_map = response_map.clone(); - let fut = async move { - /*let mut server_name = None; - let mut stream = acceptor - .accept_with(stream, |conn| { - server_name = conn.server_name().map(String::from) - }) - .await - .unwrap(); - let server_name = server_name.unwrap();*/ + /*let fut = async move { let accepted = acceptor.await.unwrap(); let server_name = accepted.client_hello().server_name().unwrap().to_string(); let mut stream = accepted.into_stream(config).await.unwrap(); let mut req = Vec::new(); - // TODO if there is a body - while !req.ends_with(b"\r\n\r\n") { - if stream.read_buf(&mut req).await.unwrap() == 0 { - break; - } - } - if let Ok(req) = str::from_utf8(&req) { - println!("{req}"); - } + http::decode_http(&mut req, &mut stream).await; let req_hash = tlsh::hash_buf(&req) .map_or_else(|_| req.clone(), |h| h.to_string().into_bytes()); let mut best = None; @@ -183,15 +167,54 @@ pub async fn play( } } if let Some((hash, _diff)) = best { - let responses = response_map + let (id, responses) = response_map .get(&(server_name.as_bytes().to_vec(), hash.clone())) .unwrap(); for &res in responses { + println!("[SRV] response for ({}): {} bytes", id, res.len()); stream.write_all(res).await.unwrap(); stream.flush().await.unwrap(); } } else { - eprintln!("No response found for SNI=`{server_name}`"); + println!("No response found for SNI=`{server_name}`"); + } + stream.shutdown().await.unwrap(); + };*/ + let fut = async move { + let accepted = acceptor.await.unwrap(); + let server_name = accepted.client_hello().server_name().unwrap().to_string(); + let stream = accepted.into_stream(config).await.unwrap(); + let mut stream = Framed::new(stream, crate::http::HttpCodec {}); + let req = stream.next().await.unwrap().unwrap(); + let req_hash = tlsh::hash_buf(&req) + .map_or_else(|_| req.clone(), |h| h.to_string().into_bytes()); + let mut best = None; + for (i_server_name, hash) in response_map.keys() { + if i_server_name != server_name.as_bytes() { + continue; + } + let diff = compare(&req_hash, hash); + if let Some((best_hash, best_diff)) = &mut best { + if diff < *best_diff { + *best_hash = hash; + *best_diff = diff; + } + } else { + best = Some((hash, diff)); + } + } + let stream = stream.get_mut(); + if let Some((hash, _diff)) = best { + let (id, responses) = response_map + .get(&(server_name.as_bytes().to_vec(), hash.clone())) + .unwrap(); + for &res in responses { + println!("[SRV] response for ({}): {} bytes", id, res.len()); + stream.write_all(res).await.unwrap(); + stream.flush().await.unwrap(); + } + } else { + println!("No response found for SNI=`{server_name}`"); } stream.shutdown().await.unwrap(); }; @@ -201,7 +224,88 @@ pub async fn play( } } TlsMode::None | TlsMode::Client => { - // TODO + let listener = TcpListener::bind(listen_addr).await.unwrap_or_else(|e| { + println!("Server: Cannot listen: {e:?}"); + std::process::exit(1) + }); + sync_sender.send(()).unwrap(); + loop { + let (stream, _peer_addr) = listener.accept().await.unwrap(); + let response_map = response_map.clone(); + /*let fut = async move { + println!("[SRV] New task"); + let mut req = Vec::new(); + http::decode_http(&mut req, &mut stream).await; + let req_hash = tlsh::hash_buf(&req) + .map_or_else(|_| req.clone(), |h| h.to_string().into_bytes()); + let mut best = None; + for (i_server_name, hash) in response_map.keys() { + let diff = compare(&req_hash, hash); + if let Some((best_server_name, best_hash, best_diff)) = &mut best { + if diff < *best_diff { + *best_server_name = i_server_name; + *best_hash = hash; + *best_diff = diff; + } + } else { + best = Some((i_server_name, hash, diff)); + } + } + if let Some((server_name, hash, _diff)) = best { + let (id, responses) = response_map + .get(&(server_name.clone(), hash.clone())) + .unwrap(); + for &res in responses { + println!("[SRV] response for ({}): {} bytes", id, res.len()); + stream.write_all(res).await.unwrap(); + stream.flush().await.unwrap(); + } + } else { + println!("[SRV] No response found"); + } + //println!("Server shutdown"); + stream.shutdown().await.unwrap(); + };*/ + let fut = async move { + println!("[SRV] New task"); + let mut stream = Framed::new(stream, crate::http::HttpCodec {}); + let req = stream.next().await.unwrap().unwrap(); + let req_hash = tlsh::hash_buf(&req) + .map_or_else(|_| req.clone(), |h| h.to_string().into_bytes()); + let mut best = None; + for (i_server_name, hash) in response_map.keys() { + let diff = compare(&req_hash, hash); + if let Some((best_server_name, best_hash, best_diff)) = &mut best { + if diff < *best_diff { + *best_server_name = i_server_name; + *best_hash = hash; + *best_diff = diff; + } + } else { + best = Some((i_server_name, hash, diff)); + } + } + let stream = stream.get_mut(); + if let Some((server_name, hash, _diff)) = best { + let (id, responses) = response_map + .get(&(server_name.clone(), hash.clone())) + .unwrap(); + for &res in responses { + println!("[SRV] response for ({}): {} bytes", id, res.len()); + stream.write_all(res).await.unwrap(); + stream.flush().await.unwrap(); + } + } else { + println!("[SRV] No response found"); + } + //println!("Server shutdown"); + stream.shutdown().await.unwrap(); + }; + // Using a variable for the future allows it to be detected by tokio-console + tokio::spawn(async move { + fut.await; + }); + } } } }