diff --git a/.gitignore b/.gitignore index 01efd20..555c018 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ *.wasm target/ -Cargo.lock .DS_Store services/ws-wasm-agent/pkg/ services/ws-server/static/models/ diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..f2ee374 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,4242 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "actix" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de7fa236829ba0841304542f7614c42b80fca007455315c45c785ccfa873a85b" +dependencies = [ + "actix-macros", + "actix-rt", + "actix_derive", + "bitflags", + "bytes", + "crossbeam-channel", + "futures-core", + "futures-sink", + "futures-task", + "futures-util", + "log", + "once_cell", + "parking_lot", + "pin-project-lite", + "smallvec", + "tokio", + "tokio-util", +] + +[[package]] +name = "actix-codec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-sink", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-files" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8c4f30e3272d7c345f88ae0aac3848507ef5ba871f9cc2a41c8085a0f0523b" +dependencies = [ + "actix-http", + "actix-service", + "actix-utils", + "actix-web", + "bitflags", + "bytes", + "derive_more", + "futures-core", + "http-range", + "log", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "v_htmlescape", +] + +[[package]] +name = "actix-http" +version = "3.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93acb4a42f64936f9b8cae4a433b237599dd6eb6ed06124eb67132ef8cc90662" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-tls", + "actix-utils", + "base64", + "bitflags", + "brotli", + "bytes", + "bytestring", + "derive_more", + "encoding_rs", + "flate2", + "foldhash", + "futures-core", + "h2", + "http 0.2.12", + "httparse", + "httpdate", + "itoa", + "language-tags", + "local-channel", + "mime", + "percent-encoding", + "pin-project-lite", + "rand 0.10.1", + "sha1", + "smallvec", + "tokio", + "tokio-util", + "tracing", + "zstd", +] + +[[package]] +name = "actix-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "actix-router" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14f8c75c51892f18d9c46150c5ac7beb81c95f78c8b83a634d49f4ca32551fe7" +dependencies = [ + "bytestring", + "cfg-if", + "http 0.2.12", + "regex", + "regex-lite", + "serde", + "tracing", +] + +[[package]] +name = "actix-rt" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92589714878ca59a7626ea19734f0e07a6a875197eec751bb5d3f99e64998c63" +dependencies = [ + "actix-macros", + "futures-core", + "tokio", +] + +[[package]] +name = "actix-server" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a65064ea4a457eaf07f2fba30b4c695bf43b721790e9530d26cb6f9019ff7502" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "futures-util", + "mio", + "socket2 0.5.10", + "tokio", + "tracing", +] + +[[package]] +name = "actix-service" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e46f36bf0e5af44bdc4bdb36fbbd421aa98c79a9bce724e1edeb3894e10dc7f" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "actix-tls" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6176099de3f58fbddac916a7f8c6db297e021d706e7a6b99947785fee14abe9f" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "impl-more", + "pin-project-lite", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-utils" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +dependencies = [ + "local-waker", + "pin-project-lite", +] + +[[package]] +name = "actix-web" +version = "4.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff87453bc3b56e9b2b23c1cc0b1be8797184accf51d2abe0f8a33ec275d316bf" +dependencies = [ + "actix-codec", + "actix-http", + "actix-macros", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-tls", + "actix-utils", + "actix-web-codegen", + "bytes", + "bytestring", + "cfg-if", + "cookie", + "derive_more", + "encoding_rs", + "foldhash", + "futures-core", + "futures-util", + "impl-more", + "itoa", + "language-tags", + "log", + "mime", + "once_cell", + "pin-project-lite", + "regex", + "regex-lite", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "socket2 0.6.3", + "time", + "tracing", + "url", +] + +[[package]] +name = "actix-web-actors" +version = "4.3.1+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98c5300b38fd004fe7d2a964f9a90813fdbe8a81fed500587e78b1b71c6f980" +dependencies = [ + "actix", + "actix-codec", + "actix-http", + "actix-web", + "bytes", + "bytestring", + "futures-core", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "actix-web-codegen" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8" +dependencies = [ + "actix-router", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "actix_derive" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6ac1e58cded18cb28ddc17143c4dea5345b3ad575e14f32f66e4054a56eb271" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "asn1-rs" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "aws-lc-rs" +version = "1.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "block-buffer" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "brotli" +version = "8.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "bytestring" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "113b4343b5f6617e7ad401ced8de3cc8b012e73a594347c307b90db3e9271289" +dependencies = [ + "bytes", +] + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cc" +version = "1.2.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] + +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "clap" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "const-hex" +version = "1.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "531185e432bb31db1ecda541e9e7ab21468d4d844ad7505e0546a49b4945d49b" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "proptest", + "serde_core", +] + +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cookie" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[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 = "crossterm" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +dependencies = [ + "bitflags", + "crossterm_winapi", + "parking_lot", + "rustix 0.38.44", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "crypto-common" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "data-encoding" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" + +[[package]] +name = "der-parser" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", + "unicode-xid", +] + +[[package]] +name = "digest" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "edge-toolkit" +version = "0.1.0" +dependencies = [ + "base64", + "lets_find_up", + "log", + "rstest", + "secrecy", + "serde", + "serde-env", + "serde-inline-default", + "serde_default", + "serde_json", + "serde_yaml", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "et-cli" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "edge-toolkit", + "serde", + "serde_yaml", + "tempfile", + "toml", +] + +[[package]] +name = "et-modules-service" +version = "0.1.0" +dependencies = [ + "actix-files", + "actix-rt", + "actix-web", + "edge-toolkit", + "serde_json", + "tracing", +] + +[[package]] +name = "et-onnx" +version = "0.1.0" +dependencies = [ + "clap", + "onnx-extractor", +] + +[[package]] +name = "et-storage-service" +version = "0.1.0" +dependencies = [ + "actix-files", + "actix-web", + "edge-toolkit", + "futures-util", + "tokio", + "tracing", +] + +[[package]] +name = "et-web" +version = "0.1.0" +dependencies = [ + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "et-ws-audio1" +version = "0.1.0" +dependencies = [ + "et-web", + "et-ws-wasm-agent", + "js-sys", + "serde", + "serde-wasm-bindgen", + "serde_json", + "tracing", + "tracing-wasm", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test", + "web-sys", +] + +[[package]] +name = "et-ws-bluetooth" +version = "0.1.0" +dependencies = [ + "et-ws-wasm-agent", + "js-sys", + "serde", + "serde-wasm-bindgen", + "serde_json", + "tracing", + "tracing-wasm", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test", + "web-sys", +] + +[[package]] +name = "et-ws-comm1" +version = "0.1.0" +dependencies = [ + "edge-toolkit", + "et-ws-wasm-agent", + "js-sys", + "serde", + "serde-wasm-bindgen", + "serde_json", + "tracing", + "tracing-wasm", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test", + "web-sys", +] + +[[package]] +name = "et-ws-data1" +version = "0.1.0" +dependencies = [ + "edge-toolkit", + "et-ws-wasm-agent", + "js-sys", + "serde", + "serde-wasm-bindgen", + "serde_json", + "tracing", + "tracing-wasm", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test", + "web-sys", +] + +[[package]] +name = "et-ws-face-detection" +version = "0.1.0" +dependencies = [ + "et-web", + "et-ws-wasm-agent", + "js-sys", + "serde", + "serde-wasm-bindgen", + "serde_json", + "tracing", + "tracing-wasm", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test", + "web-sys", +] + +[[package]] +name = "et-ws-geolocation" +version = "0.1.0" +dependencies = [ + "et-ws-wasm-agent", + "js-sys", + "serde", + "serde-wasm-bindgen", + "serde_json", + "tracing", + "tracing-wasm", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test", + "web-sys", +] + +[[package]] +name = "et-ws-graphics-info" +version = "0.1.0" +dependencies = [ + "et-ws-wasm-agent", + "js-sys", + "serde", + "serde-wasm-bindgen", + "serde_json", + "tracing", + "tracing-wasm", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test", + "web-sys", +] + +[[package]] +name = "et-ws-har1" +version = "0.1.0" +dependencies = [ + "et-web", + "et-ws-wasm-agent", + "js-sys", + "serde", + "serde-wasm-bindgen", + "serde_json", + "tracing", + "tracing-wasm", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test", + "web-sys", +] + +[[package]] +name = "et-ws-nfc" +version = "0.1.0" +dependencies = [ + "et-ws-wasm-agent", + "js-sys", + "serde", + "serde-wasm-bindgen", + "serde_json", + "tracing", + "tracing-wasm", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test", + "web-sys", +] + +[[package]] +name = "et-ws-sensor1" +version = "0.1.0" +dependencies = [ + "et-web", + "et-ws-wasm-agent", + "js-sys", + "serde", + "serde-wasm-bindgen", + "serde_json", + "tracing", + "tracing-wasm", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test", + "web-sys", +] + +[[package]] +name = "et-ws-server" +version = "0.1.0" +dependencies = [ + "actix", + "actix-rt", + "actix-web", + "chrono", + "clap", + "edge-toolkit", + "et-modules-service", + "et-storage-service", + "et-ws-service", + "futures-util", + "hostname", + "local-ip-address", + "log", + "opentelemetry", + "opentelemetry-appender-tracing", + "opentelemetry-otlp", + "opentelemetry_sdk", + "qr2term", + "rcgen", + "regex", + "rustls", + "secrecy", + "serde", + "serde-env", + "serde-inline-default", + "serde_default", + "serde_json", + "serde_yaml", + "tokio", + "tracing", + "tracing-actix-web", + "tracing-log", + "tracing-opentelemetry", + "tracing-subscriber", + "uuid", +] + +[[package]] +name = "et-ws-service" +version = "0.1.0" +dependencies = [ + "actix", + "actix-web", + "actix-web-actors", + "chrono", + "edge-toolkit", + "opentelemetry", + "serde", + "serde_json", + "serde_yaml", + "tracing", + "uuid", +] + +[[package]] +name = "et-ws-speech-recognition" +version = "0.1.0" +dependencies = [ + "et-ws-wasm-agent", + "js-sys", + "serde", + "serde-wasm-bindgen", + "serde_json", + "tracing", + "tracing-wasm", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test", + "web-sys", +] + +[[package]] +name = "et-ws-video1" +version = "0.1.0" +dependencies = [ + "et-web", + "et-ws-wasm-agent", + "js-sys", + "serde", + "serde-wasm-bindgen", + "serde_json", + "tracing", + "tracing-wasm", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test", + "web-sys", +] + +[[package]] +name = "et-ws-wasm-agent" +version = "0.1.0" +dependencies = [ + "chrono", + "edge-toolkit", + "js-sys", + "serde", + "serde_json", + "tracing", + "tracing-wasm", + "wasm-bindgen", + "wasm-bindgen-test", + "web-sys", +] + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +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 = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", + "wasip2", + "wasip3", +] + +[[package]] +name = "getset" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf0fc11e47561d47397154977bc219f4cf809b2974facc3ccb3b89e2436f912" +dependencies = [ + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "h2" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hostname" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617aaa3557aef3810a6369d0a99fac8a080891b68bd9f9812a1eeda0c0730cbd" +dependencies = [ + "cfg-if", + "libc", + "windows-link", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.4.0", +] + +[[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 1.4.0", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "http-range" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" + +[[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 = "hybrid-array" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3944cf8cf766b40e2a1a333ee5e9b563f854d5fa49d6a8ca2764e97c6eddb214" +dependencies = [ + "typenum", +] + +[[package]] +name = "hyper" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http 1.4.0", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http 1.4.0", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2 0.6.3", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "impl-more" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.0", + "serde", + "serde_core", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "iri-string" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "lets_find_up" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8052b3d5cfa8bae8af3b44aae11a43e9fa48ce0ae477c4a39733a8deff34059" + +[[package]] +name = "libc" +version = "0.2.185" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "local-channel" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" +dependencies = [ + "futures-core", + "futures-sink", + "local-waker", +] + +[[package]] +name = "local-ip-address" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7b0187df4e614e42405b49511b82ff7a1774fbd9a816060ee465067847cac22" +dependencies = [ + "libc", + "neli", + "windows-sys 0.61.2", +] + +[[package]] +name = "local-waker" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "memmap2" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +dependencies = [ + "libc", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minicov" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4869b6a491569605d66d3952bcdf03df789e5b536e5f0cf7758a7f08a55ae24d" +dependencies = [ + "cc", + "walkdir", +] + +[[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.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "multimap" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" + +[[package]] +name = "mutually_exclusive_features" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94e1e6445d314f972ff7395df2de295fe51b71821694f0b0e1e79c4f12c8577" + +[[package]] +name = "neli" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22f9786d56d972959e1408b6a93be6af13b9c1392036c5c1fafa08a1b0c6ee87" +dependencies = [ + "bitflags", + "byteorder", + "derive_builder", + "getset", + "libc", + "log", + "neli-proc-macros", + "parking_lot", +] + +[[package]] +name = "neli-proc-macros" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05d8d08c6e98f20a62417478ebf7be8e1425ec9acecc6f63e22da633f6b71609" +dependencies = [ + "either", + "proc-macro2", + "quote", + "serde", + "syn", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "oid-registry" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" +dependencies = [ + "asn1-rs", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "onnx-extractor" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dcef162d82101e4b8a54631b983d5aee84f7657a113c62a322b4e5de46a417c" +dependencies = [ + "memmap2", + "prost", + "prost-build", +] + +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + +[[package]] +name = "opentelemetry" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b84bcd6ae87133e903af7ef497404dda70c60d0ea14895fc8a5e6722754fc2a0" +dependencies = [ + "futures-core", + "futures-sink", + "js-sys", + "pin-project-lite", + "thiserror", + "tracing", +] + +[[package]] +name = "opentelemetry-appender-tracing" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef6a1ac5ca3accf562b8c306fa8483c85f4390f768185ab775f242f7fe8fdcc2" +dependencies = [ + "opentelemetry", + "tracing", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "opentelemetry-http" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a6d09a73194e6b66df7c8f1b680f156d916a1a942abf2de06823dd02b7855d" +dependencies = [ + "async-trait", + "bytes", + "http 1.4.0", + "opentelemetry", + "reqwest", +] + +[[package]] +name = "opentelemetry-otlp" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f69cd6acbb9af919df949cd1ec9e5e7fdc2ef15d234b6b795aaa525cc02f71f" +dependencies = [ + "http 1.4.0", + "opentelemetry", + "opentelemetry-http", + "opentelemetry-proto", + "opentelemetry_sdk", + "prost", + "reqwest", + "serde_json", + "thiserror", +] + +[[package]] +name = "opentelemetry-proto" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7175df06de5eaee9909d4805a3d07e28bb752c34cab57fa9cff549da596b30f" +dependencies = [ + "base64", + "const-hex", + "opentelemetry", + "opentelemetry_sdk", + "prost", + "serde", + "serde_json", + "tonic", + "tonic-prost", +] + +[[package]] +name = "opentelemetry_sdk" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ae4f5991976fd48df6d843de219ca6d31b01daaab2dad5af2badeded372bd" +dependencies = [ + "futures-channel", + "futures-executor", + "futures-util", + "opentelemetry", + "percent-encoding", + "rand 0.9.4", + "thiserror", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64", + "serde_core", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "petgraph" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" +dependencies = [ + "fixedbitset", + "hashbrown 0.15.5", + "indexmap", +] + +[[package]] +name = "pin-project" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit 0.25.11+spec-1.1.0", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" +dependencies = [ + "bitflags", + "num-traits", + "rand 0.9.4", + "rand_chacha", + "rand_xorshift", + "regex-syntax", + "unarray", +] + +[[package]] +name = "prost" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7" +dependencies = [ + "heck", + "itertools", + "log", + "multimap", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7" +dependencies = [ + "prost", +] + +[[package]] +name = "pyproject-to-package-json" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", + "toml", +] + +[[package]] +name = "qr2term" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6867c60b38e9747a079a19614dbb5981a53f21b9a56c265f3bfdf6011a50a957" +dependencies = [ + "crossterm", + "qrcode", +] + +[[package]] +name = "qrcode" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d68782463e408eb1e668cf6152704bd856c78c5b6417adaee3203d8f4c1fc9ec" + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha", + "rand_core 0.9.5", +] + +[[package]] +name = "rand" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" +dependencies = [ + "chacha20", + "getrandom 0.4.2", + "rand_core 0.10.1", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core 0.9.5", +] + +[[package]] +name = "rcgen" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10b99e0098aa4082912d4c649628623db6aba77335e4f4569ff5083a6448b32e" +dependencies = [ + "pem", + "ring", + "rustls-pki-types", + "time", + "x509-parser", + "yasna", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-lite" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "relative-path" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http 1.4.0", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rstest" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5a3193c063baaa2a95a33f03035c8a72b83d97a54916055ba22d35ed3839d49" +dependencies = [ + "futures-timer", + "futures-util", + "rstest_macros", +] + +[[package]] +name = "rstest_macros" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c845311f0ff7951c5506121a9ad75aec44d083c31583b2ea5a30bcb0b0abba0" +dependencies = [ + "cfg-if", + "glob", + "proc-macro-crate", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version", + "syn", + "unicode-ident", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.12.1", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "secrecy" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" +dependencies = [ + "serde", + "zeroize", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-env" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d29a4f822b7a7e7eada47da087a9d29d9b266dba45edc375fdc700965860d7" +dependencies = [ + "serde", +] + +[[package]] +name = "serde-inline-default" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf03b7a5281cfd4013bc788d057b0f09fc634397d83bc8973c955bd4f32727a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_default" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "486b028b311aaaea83e0ba65a3e6e3cbef381e74e9d0bd6263faefd1fb503c1d" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sha1" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest", +] + +[[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 = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix 1.1.4", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "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.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.52.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.6.3", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime 0.6.11", + "toml_write", + "winnow 0.7.15", +] + +[[package]] +name = "toml_edit" +version = "0.25.11+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" +dependencies = [ + "indexmap", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "winnow 1.0.2", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow 1.0.2", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "tonic" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fec7c61a0695dc1887c1b53952990f3ad2e3a31453e1f49f10e75424943a93ec" +dependencies = [ + "async-trait", + "base64", + "bytes", + "http 1.4.0", + "http-body", + "http-body-util", + "percent-encoding", + "pin-project", + "sync_wrapper", + "tokio-stream", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-prost" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a55376a0bbaa4975a3f10d009ad763d8f4108f067c7c2e74f3001fb49778d309" +dependencies = [ + "bytes", + "prost", + "tonic", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http 1.4.0", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[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.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-actix-web" +version = "0.7.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ca6b15407f9bfcb35f82d0e79e603e1629ece4e91cc6d9e58f890c184dd20af" +dependencies = [ + "actix-web", + "mutually_exclusive_features", + "opentelemetry", + "pin-project", + "tracing", + "tracing-opentelemetry", + "uuid", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-opentelemetry" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac28f2d093c6c477eaa76b23525478f38de514fa9aeb1285738d4b97a9552fc" +dependencies = [ + "js-sys", + "opentelemetry", + "smallvec", + "tracing", + "tracing-core", + "tracing-log", + "tracing-subscriber", + "web-time", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "tracing-wasm" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4575c663a174420fa2d78f4108ff68f65bf2fbb7dd89f33749b6e826b3626e07" +dependencies = [ + "tracing", + "tracing-subscriber", + "wasm-bindgen", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "v_htmlescape" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e8257fbc510f0a46eb602c10215901938b5c2a7d5e70fc11483b1d3c9b5b18c" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen 0.57.1", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-bindgen-test" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb55e2540ad1c56eec35fd63e2aea15f83b11ce487fd2de9ad11578dfc047ea" +dependencies = [ + "async-trait", + "cast", + "js-sys", + "libm", + "minicov", + "nu-ansi-term", + "num-traits", + "oorandom", + "serde", + "serde_json", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", + "wasm-bindgen-test-shared", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf0ca1bd612b988616bac1ab34c4e4290ef18f7148a1d8b7f31c150080e9295" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "wasm-bindgen-test-shared" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cda5ecc67248c48d3e705d3e03e00af905769b78b9d2a1678b663b8b9d4472" + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[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.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[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-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "x509-parser" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "ring", + "rusticata-macros", + "thiserror", + "time", +] + +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + +[[package]] +name = "yoke" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml index b2edd5d..fbc2d2c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,9 @@ members = [ "services/ws-modules/sensor1", "services/ws-modules/speech-recognition", "services/ws-modules/video1", + "services/modules", + "services/storage", + "services/ws", "services/ws-server", "services/ws-wasm-agent", "utilities/cli", diff --git a/libs/edge-toolkit/Cargo.toml b/libs/edge-toolkit/Cargo.toml index 44f44ea..d849967 100644 --- a/libs/edge-toolkit/Cargo.toml +++ b/libs/edge-toolkit/Cargo.toml @@ -13,6 +13,7 @@ log.workspace = true secrecy.workspace = true serde.workspace = true serde-env.workspace = true +serde-inline-default.workspace = true serde_default.workspace = true serde_json.workspace = true serde_yaml.workspace = true diff --git a/libs/edge-toolkit/src/lib.rs b/libs/edge-toolkit/src/lib.rs index 9add5e2..7f91195 100644 --- a/libs/edge-toolkit/src/lib.rs +++ b/libs/edge-toolkit/src/lib.rs @@ -4,3 +4,4 @@ pub mod config; pub mod input; pub mod ports; pub mod ws; +pub mod ws_server; diff --git a/libs/edge-toolkit/src/ws_server.rs b/libs/edge-toolkit/src/ws_server.rs new file mode 100644 index 0000000..78ae80d --- /dev/null +++ b/libs/edge-toolkit/src/ws_server.rs @@ -0,0 +1,246 @@ +use std::collections::BTreeMap; +use std::path::PathBuf; +use std::sync::{Arc, Mutex}; + +use serde::{Deserialize, Serialize}; +use serde_default::DefaultFromSerde; +use serde_inline_default::serde_inline_default; + +use crate::config::{OtlpConfig, default_modules_folders}; +use crate::ws::{AgentConnectionState, AgentSummary, ConnectStatus}; + +/// Default storage directory. +#[must_use] +pub fn default_storage_folder() -> PathBuf { + let project_root = crate::config::get_project_root(); + project_root.join("services/ws-server/storage") +} + +/// Modules config. +#[serde_inline_default] +#[derive(Clone, Debug, DefaultFromSerde, Deserialize)] +pub struct ModulesConfig { + #[serde(default = "default_modules_folders")] + pub paths: Vec, + #[serde_inline_default(String::from("et-ws-server-static"))] + pub root: String, +} + +/// Storage config. +#[derive(Clone, Debug, DefaultFromSerde, Deserialize)] +pub struct StorageConfig { + #[serde(default = "default_storage_folder")] + pub path: PathBuf, +} + +/// Application config shared across ws-server services. +#[derive(Clone, Debug, DefaultFromSerde, Deserialize)] +pub struct Config { + /// OpenTelemetry config. + #[serde(default)] + pub otlp: Option, + /// Modules config. + #[serde(default)] + pub modules: ModulesConfig, + /// Storage config. + #[serde(default)] + pub storage: StorageConfig, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PendingDirectMessage { + pub message_id: String, + pub from_agent_id: String, + pub server_received_at: String, + pub message: serde_json::Value, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct AgentRecord { + pub state: AgentConnectionState, + pub last_known_ip: Option, + #[serde(skip)] + pub session: Option, + #[serde(default)] + pub pending_direct_messages: BTreeMap, +} + +#[derive(Clone)] +pub struct AgentRegistry { + pub agents: Arc>>>, +} + +impl Default for AgentRegistry { + fn default() -> Self { + Self { + agents: Arc::new(Mutex::new(BTreeMap::new())), + } + } +} + +impl AgentRegistry { + pub fn load(path: &std::path::Path) -> std::io::Result { + if !path.exists() { + log::warn!("Registry file {:?} does not exist, starting with empty registry", path); + return Ok(Self::default()); + } + let yaml = std::fs::read_to_string(path)?; + let agents: BTreeMap> = serde_yaml::from_str(&yaml).map_err(std::io::Error::other)?; + log::info!("Loaded {} agents from registry {:?}", agents.len(), path); + Ok(Self { + agents: Arc::new(Mutex::new(agents)), + }) + } +} + +impl AgentRegistry { + pub fn save(&self, path: &std::path::Path) -> std::io::Result<()> { + let agents = self.agents.lock().expect("agent registry lock poisoned"); + let yaml = serde_yaml::to_string(&*agents).map_err(std::io::Error::other)?; + std::fs::write(path, yaml)?; + log::info!("Agent registry saved to {:?}", path); + Ok(()) + } + + pub fn connect_agent( + &self, + requested_id: Option, + new_id: String, + client_ip: &str, + session: S, + ) -> (String, ConnectStatus) { + let mut agents = self.agents.lock().expect("agent registry lock poisoned"); + + if let Some(requested_id) = requested_id + && let Some(record) = agents.get_mut(&requested_id) + { + record.state = AgentConnectionState::Connected; + record.last_known_ip = Some(client_ip.to_string()); + record.session = Some(session); + return (requested_id, ConnectStatus::Reconnected); + } + + agents.insert( + new_id.clone(), + AgentRecord { + state: AgentConnectionState::Connected, + last_known_ip: Some(client_ip.to_string()), + session: Some(session), + pending_direct_messages: BTreeMap::new(), + }, + ); + (new_id, ConnectStatus::Assigned) + } + + pub fn mark_disconnected(&self, agent_id: &str) { + let mut agents = self.agents.lock().expect("agent registry lock poisoned"); + if let Some(record) = agents.get_mut(agent_id) { + record.state = AgentConnectionState::Disconnected; + record.session = None; + } + } + + pub fn list_agents(&self) -> Vec { + let agents = self.agents.lock().expect("agent registry lock poisoned"); + let mut summaries = agents + .iter() + .map(|(agent_id, record)| AgentSummary { + agent_id: agent_id.clone(), + state: record.state.clone(), + last_known_ip: record.last_known_ip.clone(), + }) + .collect::>(); + summaries.sort_by(|left, right| left.agent_id.cmp(&right.agent_id)); + summaries + } + + pub fn queue_direct( + &self, + message_id: String, + from_agent_id: &str, + to_agent_id: &str, + server_received_at: String, + message: serde_json::Value, + ) -> (PendingDirectMessage, Option) { + let mut agents = self.agents.lock().expect("agent registry lock poisoned"); + let recipient = agents + .get_mut(to_agent_id) + .expect("queue_direct called for unknown target agent"); + + let pending = PendingDirectMessage { + message_id, + from_agent_id: from_agent_id.to_string(), + server_received_at, + message, + }; + recipient + .pending_direct_messages + .insert(from_agent_id.to_string(), pending); + + let session = recipient.session.clone(); + let pending = recipient + .pending_direct_messages + .get(from_agent_id) + .expect("pending direct message was just inserted") + .clone(); + (pending, session) + } + + pub fn pending_messages_for(&self, agent_id: &str) -> Vec { + let agents = self.agents.lock().expect("agent registry lock poisoned"); + agents + .get(agent_id) + .map(|record| { + let mut pending = record.pending_direct_messages.values().cloned().collect::>(); + pending.sort_by(|left, right| left.message_id.cmp(&right.message_id)); + pending + }) + .unwrap_or_default() + } + + /// Returns `(message_id, sender_session, sender_agent_id)` on success, or an error detail string. + pub fn acknowledge_message( + &self, + recipient_agent_id: &str, + message_id: &str, + ) -> Result<(String, Option, String), String> { + let mut agents = self.agents.lock().expect("agent registry lock poisoned"); + let Some(recipient) = agents.get_mut(recipient_agent_id) else { + return Err(format!("unknown acknowledging agent {}", recipient_agent_id)); + }; + + let Some(sender_agent_id) = recipient + .pending_direct_messages + .iter() + .find_map(|(id, p)| (p.message_id == message_id).then(|| id.clone())) + else { + return Err("no pending message to acknowledge".to_string()); + }; + + let pending = recipient + .pending_direct_messages + .remove(&sender_agent_id) + .expect("pending direct message disappeared during acknowledgement"); + let sender_session = agents.get(&sender_agent_id).and_then(|r| r.session.clone()); + + Ok((pending.message_id, sender_session, sender_agent_id)) + } + + pub fn connected_sessions(&self, excluding_agent_id: &str) -> Vec<(String, S)> { + let agents = self.agents.lock().expect("agent registry lock poisoned"); + agents + .iter() + .filter_map(|(agent_id, record)| { + if agent_id == excluding_agent_id { + return None; + } + record.session.clone().map(|s| (agent_id.clone(), s)) + }) + .collect() + } + + pub fn agent_session(&self, agent_id: &str) -> Option { + let agents = self.agents.lock().expect("agent registry lock poisoned"); + agents.get(agent_id).and_then(|r| r.session.clone()) + } +} diff --git a/services/modules/Cargo.toml b/services/modules/Cargo.toml new file mode 100644 index 0000000..dc75f95 --- /dev/null +++ b/services/modules/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "et-modules-service" +version = "0.1.0" +edition.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +actix-files = "0.6" +actix-web = "4" +edge-toolkit = { path = "../../libs/edge-toolkit" } +serde_json.workspace = true +tracing.workspace = true + +[dev-dependencies] +actix-rt = "2" diff --git a/services/modules/src/lib.rs b/services/modules/src/lib.rs new file mode 100644 index 0000000..b45e178 --- /dev/null +++ b/services/modules/src/lib.rs @@ -0,0 +1,87 @@ +use std::path::PathBuf; + +use actix_files::Files; +use actix_web::{HttpResponse, web}; +use edge_toolkit::ws_server::{Config, ModulesConfig}; + +fn read_package_name(package_json: &std::path::Path) -> Option { + let content = std::fs::read_to_string(package_json).ok()?; + let v: serde_json::Value = serde_json::from_str(&content).ok()?; + v.get("name")?.as_str().map(str::to_string) +} + +/// Scan all configured module paths and return a sorted list of (name, pkg_dir) pairs. +pub fn list_modules(config: &ModulesConfig) -> Vec<(String, PathBuf)> { + let mut modules: Vec<(String, PathBuf)> = Vec::new(); + for path in &config.paths { + let pkg_dir = path.join("pkg"); + if pkg_dir.is_dir() { + let name = read_package_name(&pkg_dir.join("package.json")) + .or_else(|| path.file_name().and_then(|n| n.to_str()).map(str::to_string)); + if let Some(name) = name { + modules.push((name, pkg_dir)); + } + } else if path.join("package.json").is_file() { + let name = read_package_name(&path.join("package.json")) + .or_else(|| path.file_name().and_then(|n| n.to_str()).map(str::to_string)); + if let Some(name) = name { + modules.push((name, path.clone())); + } + } else if let Ok(entries) = std::fs::read_dir(path) { + for entry in entries.flatten() { + if let Ok(file_type) = entry.file_type() + && file_type.is_dir() + && !config.paths.contains(&entry.path()) + { + let entry_path = entry.path(); + let pkg_dir = entry_path.join("pkg"); + if pkg_dir.is_dir() { + let name = read_package_name(&pkg_dir.join("package.json")) + .or_else(|| entry.file_name().to_str().map(str::to_string)); + if let Some(name) = name { + modules.push((name, pkg_dir)); + } + } else if entry_path.join("package.json").is_file() { + let name = read_package_name(&entry_path.join("package.json")) + .or_else(|| entry.file_name().to_str().map(str::to_string)); + if let Some(name) = name { + modules.push((name, entry_path)); + } + } + } + } + } + } + modules.sort_by(|a, b| a.0.cmp(&b.0)); + modules +} + +async fn list_modules_handler(config: web::Data) -> HttpResponse { + let names: Vec = list_modules(&config.modules) + .into_iter() + .map(|(name, _)| name) + .collect(); + HttpResponse::Ok().json(names) +} + +/// Register `GET /modules/` (JSON list), `GET /modules/{name}/...` (static files), +/// and `GET /` (root module). +pub fn configure(cfg: &mut web::ServiceConfig, config: &Config) { + let modules = list_modules(&config.modules); + + let root_module_dir = modules + .iter() + .find(|(name, _)| name == &config.modules.root) + .map(|(_, path)| path.clone()) + .unwrap_or_else(|| panic!("Root module '{}' not found", config.modules.root)); + + cfg.route("/modules/", web::get().to(list_modules_handler)); + for (name, pkg_dir) in &modules { + cfg.service(Files::new(&format!("/modules/{name}"), pkg_dir)); + } + cfg.service( + Files::new("/", root_module_dir) + .index_file("index.html") + .prefer_utf8(true), + ); +} diff --git a/services/ws-server/tests/api_modules.rs b/services/modules/tests/api_modules.rs similarity index 56% rename from services/ws-server/tests/api_modules.rs rename to services/modules/tests/api_modules.rs index 4acaf00..3d44d13 100644 --- a/services/ws-server/tests/api_modules.rs +++ b/services/modules/tests/api_modules.rs @@ -1,19 +1,21 @@ use actix_web::{App, test, web}; -use et_ws_server::config::Config; -use et_ws_server::{AgentRegistry, configure_app}; +use edge_toolkit::ws_server::{AgentRegistry, Config}; +use et_modules_service::configure; #[actix_rt::test] -async fn list_modules() { - let agent_registry = web::Data::new(AgentRegistry::default()); +async fn list_modules_api() { + let config = Config::default(); + let app = test::init_service( + App::new() + .app_data(web::Data::new(AgentRegistry::<()>::default())) + .app_data(web::Data::new(config.clone())) + .configure(|cfg| configure(cfg, &config)), + ) + .await; - let app = - test::init_service(App::new().configure(|cfg| configure_app(cfg, agent_registry.clone(), Config::default()))) - .await; - - let req = test::TestRequest::get().uri("/api/modules").to_request(); + let req = test::TestRequest::get().uri("/modules/").to_request(); let resp: Vec = test::call_and_read_body_json(&app, req).await; - // We expect at least the modules we know exist assert!(resp.contains(&"et-ws-server-static".to_string())); assert!(resp.contains(&"et-ws-wasm-agent".to_string())); assert!(resp.contains(&"et-ws-comm1".to_string())); diff --git a/services/storage/Cargo.toml b/services/storage/Cargo.toml new file mode 100644 index 0000000..f959502 --- /dev/null +++ b/services/storage/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "et-storage-service" +version = "0.1.0" +edition.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +actix-files = "0.6" +actix-web = "4" +edge-toolkit = { path = "../../libs/edge-toolkit" } +futures-util = "0.3" +tokio = { version = "1", features = ["full"] } +tracing.workspace = true diff --git a/services/storage/src/lib.rs b/services/storage/src/lib.rs new file mode 100644 index 0000000..06758aa --- /dev/null +++ b/services/storage/src/lib.rs @@ -0,0 +1,59 @@ +use std::path::PathBuf; + +use actix_files::Files; +use actix_web::{Error, HttpRequest, HttpResponse, web}; +use edge_toolkit::ws_server::{AgentRegistry, Config}; +use futures_util::StreamExt; +use tracing::info; + +pub async fn agent_put_file( + req: HttpRequest, + mut payload: web::Payload, + registry: web::Data>, + config: web::Data, +) -> Result { + let agent_id: String = req.match_info().query("agent_id").parse().unwrap(); + let filename: PathBuf = req + .match_info() + .query("filename") + .parse() + .map_err(|_| actix_web::error::ErrorBadRequest("invalid filename"))?; + + { + let agents = registry.agents.lock().expect("lock poisoned"); + if !agents.contains_key(&agent_id) { + return Err(actix_web::error::ErrorNotFound("agent not found")); + } + } + + if filename.components().count() != 1 { + return Err(actix_web::error::ErrorBadRequest("invalid filename")); + } + + let storage_dir = &config.storage.path; + let agent_dir = storage_dir.join(&agent_id); + std::fs::create_dir_all(&agent_dir)?; + + let path = agent_dir.join(&filename); + info!("Agent {} storing file: {:?}", agent_id, path); + + let mut file = tokio::fs::File::create(path).await?; + while let Some(chunk) = payload.next().await { + let chunk = chunk?; + tokio::io::copy(&mut &chunk[..], &mut file).await?; + } + + Ok(HttpResponse::Ok().finish()) +} + +/// Register `PUT /storage/{agent_id}/{filename}` and `GET /storage/...` (static file serving). +pub fn configure(cfg: &mut web::ServiceConfig, config: &Config) { + let storage_dir = config.storage.path.clone(); + cfg.route("/storage/{agent_id}/{filename}", web::put().to(agent_put_file::)) + .service( + Files::new("/storage", storage_dir) + .show_files_listing() + .use_etag(true) + .use_last_modified(true), + ); +} diff --git a/services/ws-modules/java-data1/.gitignore b/services/ws-modules/java-data1/.gitignore deleted file mode 100644 index 2f7896d..0000000 --- a/services/ws-modules/java-data1/.gitignore +++ /dev/null @@ -1 +0,0 @@ -target/ diff --git a/services/ws-server/Cargo.toml b/services/ws-server/Cargo.toml index 7bdb425..10fd6dd 100644 --- a/services/ws-server/Cargo.toml +++ b/services/ws-server/Cargo.toml @@ -7,18 +7,18 @@ repository.workspace = true [dependencies] actix = "0.13" -actix-files = "0.6" actix-rt = "2" actix-web = { version = "4", features = ["rustls-0_23"] } -actix-web-actors = "4" chrono.workspace = true clap.workspace = true edge-toolkit = { path = "../../libs/edge-toolkit" } +et-modules-service = { path = "../modules" } +et-storage-service = { path = "../storage" } +et-ws-service = { path = "../ws" } futures-util = "0.3" hostname = "0.4" local-ip-address = "0.6" log.workspace = true -onnx-extractor.workspace = true opentelemetry = "0.31" opentelemetry-appender-tracing = "0.31" opentelemetry-otlp = { version = "0.31", default-features = false, features = [ diff --git a/services/ws-server/src/config.rs b/services/ws-server/src/config.rs index 97b7d7d..ebbf7d9 100644 --- a/services/ws-server/src/config.rs +++ b/services/ws-server/src/config.rs @@ -1,44 +1 @@ -use std::path::PathBuf; - -use edge_toolkit::config::{OtlpConfig, default_modules_folders}; -use serde::Deserialize; -use serde_default::DefaultFromSerde; -use serde_inline_default::serde_inline_default; - -/// Default modules directory. -#[must_use] -pub fn default_storage_folder() -> std::path::PathBuf { - let project_root = edge_toolkit::config::get_project_root(); - project_root.join("services/ws-server/storage") -} - -/// Modules config. -#[serde_inline_default] -#[derive(Clone, Debug, DefaultFromSerde, Deserialize)] -pub struct ModulesConfig { - #[serde(default = "default_modules_folders")] - pub paths: Vec, - #[serde_inline_default(String::from("et-ws-server-static"))] - pub root: String, -} - -/// Storage config. -#[derive(Clone, Debug, DefaultFromSerde, Deserialize)] -pub struct StorageConfig { - #[serde(default = "default_storage_folder")] - pub path: PathBuf, -} - -/// Application environment variables and config. -#[derive(Clone, Debug, DefaultFromSerde, Deserialize)] -pub struct Config { - /// OpenTelemetry config. - #[serde(default)] - pub otlp: Option, - /// Modules config. - #[serde(default)] - pub modules: ModulesConfig, - /// Storage config. - #[serde(default)] - pub storage: StorageConfig, -} +pub use edge_toolkit::ws_server::{Config, ModulesConfig, StorageConfig}; diff --git a/services/ws-server/src/lib.rs b/services/ws-server/src/lib.rs index 0fc9f8d..235c868 100644 --- a/services/ws-server/src/lib.rs +++ b/services/ws-server/src/lib.rs @@ -1,764 +1,11 @@ pub mod config; -use std::collections::BTreeMap; use std::path::{Path, PathBuf}; -use std::sync::{Arc, Mutex}; -use std::time::{Duration, Instant}; -use actix::{Actor, ActorContext, Addr, AsyncContext, Handler, Message, StreamHandler}; -use actix_files::Files; -use actix_web::{Error, HttpRequest, HttpResponse, web}; -use actix_web_actors::ws; -use chrono::Utc; -use edge_toolkit::ws::{ - AgentConnectionState, AgentSummary, ConnectStatus, MessageDeliveryStatus, MessageScope, WsMessage, -}; -use futures_util::StreamExt; -use opentelemetry::{ - global, - trace::{Span, Tracer}, -}; -use serde::{Deserialize, Serialize}; -use tracing::{error, info, warn}; -use uuid::Uuid; +use actix_web::{HttpResponse, web}; +pub use et_ws_service::{WebSocketActor, WsAgentRegistry}; -use crate::config::{Config, ModulesConfig}; - -/// Maximum time the server allows a websocket connection to remain idle before closing it. -/// This should remain comfortably higher than the client's `Alive` message interval. -pub const CONNECTION_TIMEOUT: Duration = Duration::from_secs(15); -/// How often the server checks whether a websocket connection has exceeded `CONNECTION_TIMEOUT`. -/// This is only the check cadence, not the allowed idle duration. -pub const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(1); - -#[derive(Message)] -#[rtype(result = "()")] -pub struct ServerEnvelope { - pub message: WsMessage, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct PendingDirectMessage { - pub message_id: String, - pub from_agent_id: String, - pub server_received_at: String, - pub message: serde_json::Value, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct AgentRecord { - pub state: AgentConnectionState, - pub last_known_ip: Option, - #[serde(skip)] - pub session: Option>, - #[serde(default)] - pub pending_direct_messages: BTreeMap, -} - -#[derive(Clone, Default)] -pub struct AgentRegistry { - pub agents: Arc>>, -} - -pub enum DirectSendResult { - Delivered { - pending: PendingDirectMessage, - recipient_addr: Addr, - }, - Queued { - pending: PendingDirectMessage, - }, -} - -pub enum AckResult { - Acknowledged { - message_id: String, - sender_addr: Option>, - sender_agent_id: String, - recipient_agent_id: String, - }, - Invalid { - detail: String, - }, -} - -impl AgentRegistry { - pub fn save(&self, path: &Path) -> std::io::Result<()> { - let agents = self.agents.lock().expect("agent registry lock poisoned"); - let yaml = serde_yaml::to_string(&*agents).map_err(std::io::Error::other)?; - std::fs::write(path, yaml)?; - info!("Agent registry saved to {:?}", path); - Ok(()) - } - - pub fn load(path: &Path) -> std::io::Result { - if !path.exists() { - warn!("Registry file {:?} does not exist, starting with empty registry", path); - return Ok(Self::default()); - } - let yaml = std::fs::read_to_string(path)?; - let agents: BTreeMap = serde_yaml::from_str(&yaml).map_err(std::io::Error::other)?; - info!("Loaded {} agents from registry {:?}", agents.len(), path); - Ok(Self { - agents: Arc::new(Mutex::new(agents)), - }) - } - - pub fn connect_agent( - &self, - requested_id: Option, - client_ip: &str, - session: Addr, - ) -> (String, ConnectStatus) { - let mut agents = self.agents.lock().expect("agent registry lock poisoned"); - - if let Some(requested_id) = requested_id - && let Some(record) = agents.get_mut(&requested_id) - { - record.state = AgentConnectionState::Connected; - record.last_known_ip = Some(client_ip.to_string()); - record.session = Some(session); - return (requested_id, ConnectStatus::Reconnected); - } - - let assigned_id = Uuid::now_v7().to_string(); - agents.insert( - assigned_id.clone(), - AgentRecord { - state: AgentConnectionState::Connected, - last_known_ip: Some(client_ip.to_string()), - session: Some(session), - pending_direct_messages: BTreeMap::new(), - }, - ); - (assigned_id, ConnectStatus::Assigned) - } - - pub fn mark_disconnected(&self, agent_id: &str) { - let mut agents = self.agents.lock().expect("agent registry lock poisoned"); - if let Some(record) = agents.get_mut(agent_id) { - record.state = AgentConnectionState::Disconnected; - record.session = None; - } - } - - pub fn list_agents(&self) -> Vec { - let agents = self.agents.lock().expect("agent registry lock poisoned"); - let mut summaries = agents - .iter() - .map(|(agent_id, record)| AgentSummary { - agent_id: agent_id.clone(), - state: record.state.clone(), - last_known_ip: record.last_known_ip.clone(), - }) - .collect::>(); - summaries.sort_by(|left, right| left.agent_id.cmp(&right.agent_id)); - summaries - } - - pub fn queue_or_deliver_direct( - &self, - from_agent_id: &str, - to_agent_id: &str, - server_received_at: String, - message: serde_json::Value, - ) -> DirectSendResult { - let mut agents = self.agents.lock().expect("agent registry lock poisoned"); - let recipient = agents - .get_mut(to_agent_id) - .expect("queue_or_deliver_direct called for unknown target agent"); - - let pending = PendingDirectMessage { - message_id: Uuid::now_v7().to_string(), - from_agent_id: from_agent_id.to_string(), - server_received_at, - message, - }; - recipient - .pending_direct_messages - .insert(from_agent_id.to_string(), pending); - - if let Some(recipient_addr) = recipient.session.clone() { - DirectSendResult::Delivered { - pending: recipient - .pending_direct_messages - .get(from_agent_id) - .expect("pending direct message was just inserted") - .clone(), - recipient_addr, - } - } else { - DirectSendResult::Queued { - pending: recipient - .pending_direct_messages - .get(from_agent_id) - .expect("pending direct message was just inserted") - .clone(), - } - } - } - - pub fn pending_messages_for(&self, agent_id: &str) -> Vec { - let agents = self.agents.lock().expect("agent registry lock poisoned"); - agents - .get(agent_id) - .map(|record| { - let mut pending = record.pending_direct_messages.values().cloned().collect::>(); - pending.sort_by(|left, right| left.message_id.cmp(&right.message_id)); - pending - }) - .unwrap_or_default() - } - - pub fn acknowledge_message(&self, recipient_agent_id: &str, message_id: &str) -> AckResult { - let mut agents = self.agents.lock().expect("agent registry lock poisoned"); - let Some(recipient) = agents.get_mut(recipient_agent_id) else { - return AckResult::Invalid { - detail: format!("unknown acknowledging agent {}", recipient_agent_id), - }; - }; - - let Some(sender_agent_id) = recipient - .pending_direct_messages - .iter() - .find_map(|(sender_agent_id, pending)| (pending.message_id == message_id).then(|| sender_agent_id.clone())) - else { - return AckResult::Invalid { - detail: "no pending message to acknowledge".to_string(), - }; - }; - - let pending = recipient - .pending_direct_messages - .remove(&sender_agent_id) - .expect("pending direct message disappeared during acknowledgement"); - let sender_addr = agents.get(&sender_agent_id).and_then(|record| record.session.clone()); - - AckResult::Acknowledged { - message_id: pending.message_id, - sender_addr, - sender_agent_id, - recipient_agent_id: recipient_agent_id.to_string(), - } - } - - pub fn connected_recipient_addrs(&self, excluding_agent_id: &str) -> Vec<(String, Addr)> { - let agents = self.agents.lock().expect("agent registry lock poisoned"); - agents - .iter() - .filter_map(|(agent_id, record)| { - if agent_id == excluding_agent_id { - return None; - } - record.session.clone().map(|addr| (agent_id.clone(), addr)) - }) - .collect() - } -} - -/// WebSocket actor for handling connections. -pub struct WebSocketActor { - pub agent_id: Option, - pub last_activity: Instant, - pub client_ip: String, - pub registry: AgentRegistry, -} - -impl WebSocketActor { - pub fn new(registry: AgentRegistry, client_ip: String) -> Self { - info!("New WebSocket actor created for client IP {}", client_ip); - Self { - agent_id: None, - last_activity: Instant::now(), - client_ip, - registry, - } - } - - pub fn current_agent_id(&self) -> &str { - self.agent_id.as_deref().unwrap_or("unassigned") - } - - pub fn assigned_agent_id(&self) -> Option<&str> { - self.agent_id.as_deref() - } - - pub fn mark_activity(&mut self) { - self.last_activity = Instant::now(); - } - - pub fn start_heartbeat(&self, ctx: &mut ws::WebsocketContext) { - ctx.run_interval(HEARTBEAT_INTERVAL, |act, ctx| { - let idle_for = Instant::now().saturating_duration_since(act.last_activity); - if idle_for > CONNECTION_TIMEOUT { - warn!( - "WebSocket connection timed out for client {} after {:?} of inactivity", - act.current_agent_id(), - idle_for - ); - ctx.close(Some(ws::CloseReason { - code: ws::CloseCode::Policy, - description: Some(format!( - "connection timed out after {:?} of inactivity", - CONNECTION_TIMEOUT - )), - })); - ctx.stop(); - } - }); - } - - fn assign_or_reconnect_agent( - &mut self, - requested_id: Option, - session: Addr, - ) -> (String, ConnectStatus) { - let (assigned_id, status) = self.registry.connect_agent(requested_id, &self.client_ip, session); - self.agent_id = Some(assigned_id.clone()); - (assigned_id, status) - } - - fn send_json(ctx: &mut ws::WebsocketContext, response: &WsMessage) { - match serde_json::to_string(response) { - Ok(json) => { - ctx.text(json); - let tracer = global::tracer("ws-server"); - let mut sent_span = tracer.start("ws.message.sent"); - sent_span.end(); - } - Err(error) => { - error!("Failed to serialize websocket response: {}", error); - } - } - } - - fn send_status( - ctx: &mut ws::WebsocketContext, - message_id: Option, - status: MessageDeliveryStatus, - detail: impl Into, - ) { - Self::send_json( - ctx, - &WsMessage::MessageStatus { - message_id, - status, - detail: detail.into(), - }, - ); - } - - fn send_invalid(ctx: &mut ws::WebsocketContext, message_id: Option, detail: impl Into) { - Self::send_json( - ctx, - &WsMessage::Invalid { - message_id, - detail: detail.into(), - }, - ); - } - - fn deliver_pending_messages(&self, ctx: &mut ws::WebsocketContext) { - let Some(agent_id) = self.assigned_agent_id() else { - return; - }; - let pending_messages = self.registry.pending_messages_for(agent_id); - if pending_messages.is_empty() { - return; - } - for pending in pending_messages { - info!( - "Delivering pending message {} to agent {} from {}", - pending.message_id, agent_id, pending.from_agent_id - ); - let message = WsMessage::AgentMessage { - message_id: pending.message_id, - from_agent_id: pending.from_agent_id, - scope: MessageScope::Direct, - server_received_at: pending.server_received_at, - message: pending.message, - }; - Self::send_json(ctx, &message); - } - } -} - -impl Actor for WebSocketActor { - type Context = ws::WebsocketContext; - - fn started(&mut self, ctx: &mut Self::Context) { - self.start_heartbeat(ctx); - info!( - "WebSocket connection established for client IP {} with agent {}", - self.client_ip, - self.current_agent_id() - ); - let tracer = global::tracer("ws-server"); - let mut span = tracer.start("ws.connect"); - span.end(); - } - - fn stopped(&mut self, _ctx: &mut Self::Context) { - if let Some(agent_id) = self.agent_id.as_deref() { - self.registry.mark_disconnected(agent_id); - info!("Agent {} disconnected; last known IP {}", agent_id, self.client_ip); - } else { - info!( - "WebSocket connection closed before agent assignment for client IP {}", - self.client_ip - ); - } - } -} - -impl Handler for WebSocketActor { - type Result = (); - - fn handle(&mut self, msg: ServerEnvelope, ctx: &mut Self::Context) -> Self::Result { - Self::send_json(ctx, &msg.message); - } -} - -impl StreamHandler> for WebSocketActor { - fn handle(&mut self, msg: Result, ctx: &mut Self::Context) { - match msg { - Ok(ws::Message::Ping(ping)) => { - self.mark_activity(); - ctx.pong(&ping); - } - Ok(ws::Message::Pong(_)) => { - self.mark_activity(); - } - Ok(ws::Message::Text(text)) => { - self.mark_activity(); - let tracer = global::tracer("ws-server"); - let mut span = tracer.start("ws.message.received"); - info!("Received message from client {}: {:?}", self.current_agent_id(), text); - - if let Ok(msg) = serde_json::from_str::(&text) { - match msg { - WsMessage::Connect { agent_id } => { - let requested_id = agent_id.clone(); - info!( - "Connect message: requested_agent_id={:?} client_ip={}", - requested_id, self.client_ip - ); - let (assigned_id, status) = self.assign_or_reconnect_agent(agent_id, ctx.address()); - info!( - "Agent {} status {:?}connected from IP {}", - assigned_id, status, self.client_ip - ); - Self::send_json( - ctx, - &WsMessage::ConnectAck { - agent_id: assigned_id, - status: status.clone(), - }, - ); - info!( - "WebSocket connection ready for client {} with status {:?}", - self.current_agent_id(), - status - ); - self.deliver_pending_messages(ctx); - } - WsMessage::Alive { timestamp } => { - info!("Alive message from client {} at {}", self.current_agent_id(), timestamp); - Self::send_json( - ctx, - &WsMessage::Response { - message: format!("Alive message received at {}", Utc::now().to_rfc3339()), - }, - ); - } - WsMessage::ListAgents => { - let agents = self.registry.list_agents(); - info!( - "Agent {} requested list_agents; returning {} agents", - self.current_agent_id(), - agents.len() - ); - Self::send_json(ctx, &WsMessage::ListAgentsResponse { agents }); - } - WsMessage::SendAgentMessage { to_agent_id, message } => { - let Some(from_agent_id) = self.assigned_agent_id().map(str::to_string) else { - Self::send_invalid(ctx, None, "agent must connect before sending messages"); - span.end(); - return; - }; - - if from_agent_id == to_agent_id { - Self::send_invalid(ctx, None, "agent cannot send a direct message to itself"); - span.end(); - return; - } - - if !self - .registry - .list_agents() - .iter() - .any(|agent| agent.agent_id == to_agent_id) - { - Self::send_invalid(ctx, None, format!("unknown target agent {}", to_agent_id)); - span.end(); - return; - } - - let server_received_at = Utc::now().to_rfc3339(); - match self.registry.queue_or_deliver_direct( - &from_agent_id, - &to_agent_id, - server_received_at.clone(), - message, - ) { - DirectSendResult::Delivered { - pending, - recipient_addr, - } => { - let message_id = pending.message_id.clone(); - info!( - "Direct message {} delivered from {} to {}", - message_id, from_agent_id, to_agent_id - ); - recipient_addr.do_send(ServerEnvelope { - message: WsMessage::AgentMessage { - message_id: message_id.clone(), - from_agent_id: from_agent_id.clone(), - scope: MessageScope::Direct, - server_received_at: pending.server_received_at, - message: pending.message, - }, - }); - Self::send_status( - ctx, - Some(message_id), - MessageDeliveryStatus::Delivered, - format!("message delivered to agent {}", to_agent_id), - ); - } - DirectSendResult::Queued { pending } => { - let message_id = pending.message_id; - info!( - "Direct message {} queued from {} to disconnected agent {}", - message_id, from_agent_id, to_agent_id - ); - Self::send_status( - ctx, - Some(message_id), - MessageDeliveryStatus::Queued, - format!("message queued for agent {}", to_agent_id), - ); - } - } - } - WsMessage::BroadcastMessage { message } => { - let Some(from_agent_id) = self.assigned_agent_id().map(str::to_string) else { - Self::send_invalid(ctx, None, "agent must connect before broadcasting messages"); - span.end(); - return; - }; - - let recipients = self.registry.connected_recipient_addrs(&from_agent_id); - let message_id = Uuid::now_v7().to_string(); - let server_received_at = Utc::now().to_rfc3339(); - for (recipient_id, recipient_addr) in &recipients { - info!( - "Broadcast message {} from {} to {}", - message_id, from_agent_id, recipient_id - ); - recipient_addr.do_send(ServerEnvelope { - message: WsMessage::AgentMessage { - message_id: message_id.clone(), - from_agent_id: from_agent_id.clone(), - scope: MessageScope::Broadcast, - server_received_at: server_received_at.clone(), - message: message.clone(), - }, - }); - } - Self::send_status( - ctx, - Some(message_id), - MessageDeliveryStatus::Broadcast, - format!("broadcast sent to {} connected agents", recipients.len()), - ); - } - WsMessage::MessageAck { message_id } => { - let Some(recipient_agent_id) = self.assigned_agent_id().map(str::to_string) else { - Self::send_invalid(ctx, None, "agent must connect before acknowledging messages"); - span.end(); - return; - }; - - match self.registry.acknowledge_message(&recipient_agent_id, &message_id) { - AckResult::Acknowledged { - message_id, - sender_addr, - sender_agent_id, - recipient_agent_id, - } => { - info!( - "Agent {} acknowledged direct message {} from {}", - recipient_agent_id, message_id, sender_agent_id - ); - Self::send_status( - ctx, - Some(message_id.clone()), - MessageDeliveryStatus::Acknowledged, - "message acknowledged", - ); - if let Some(sender_addr) = sender_addr { - sender_addr.do_send(ServerEnvelope { - message: WsMessage::MessageStatus { - message_id: Some(message_id), - status: MessageDeliveryStatus::Acknowledged, - detail: format!("agent {} acknowledged receipt", recipient_agent_id), - }, - }); - } - } - AckResult::Invalid { detail } => { - warn!("Invalid ack from {} for {}: {}", recipient_agent_id, message_id, detail); - Self::send_invalid(ctx, Some(message_id), detail); - } - } - } - WsMessage::ClientEvent { - capability, - action, - details, - } => { - if capability == "video_cv" && action == "inference" { - let detected_class = details - .get("detected_class") - .and_then(|value| value.as_str()) - .unwrap_or("unknown"); - let confidence = details - .get("confidence") - .and_then(|value| value.as_f64()) - .unwrap_or_default(); - let processed_at = details - .get("processed_at") - .and_then(|value| value.as_str()) - .unwrap_or("unknown"); - info!( - "Video inference received from {}: class={} confidence={:.4} processed_at={}", - self.current_agent_id(), - detected_class, - confidence, - processed_at - ); - } - info!( - "Client event from {}: capability={} action={} details={}", - self.current_agent_id(), - capability, - action, - details - ); - } - WsMessage::StoreFile { filename } => { - let Some(agent_id) = self.assigned_agent_id() else { - Self::send_invalid(ctx, None, "agent must connect before storing files"); - span.end(); - return; - }; - let url = format!("/storage/{}/{}", agent_id, filename); - info!("Agent {} requested storage URL for {}: {}", agent_id, filename, url); - Self::send_json( - ctx, - &WsMessage::Response { - message: format!("PUT to {}", url), - }, - ); - } - WsMessage::FetchFile { agent_id, filename } => { - let url = format!("/storage/{}/{}", agent_id, filename); - info!( - "Agent {} requested fetch URL for {}/{}", - self.current_agent_id(), - agent_id, - filename - ); - Self::send_json( - ctx, - &WsMessage::Response { - message: format!("GET from {}", url), - }, - ); - } - WsMessage::ConnectAck { .. } - | WsMessage::ListAgentsResponse { .. } - | WsMessage::AgentMessage { .. } - | WsMessage::MessageStatus { .. } - | WsMessage::Invalid { .. } - | WsMessage::Response { .. } => { - warn!( - "Unexpected server-originated message from client {}", - self.current_agent_id() - ); - } - } - } else { - warn!( - "Received unrecognized message from client {}: {}", - self.current_agent_id(), - text - ); - } - span.end(); - } - Ok(ws::Message::Close(reason)) => { - self.mark_activity(); - info!( - "WebSocket close request from client: {} reason: {:?}", - self.current_agent_id(), - reason - ); - let tracer = global::tracer("ws-server"); - let mut span = tracer.start("ws.disconnect"); - span.end(); - ctx.close(reason); - ctx.stop(); - } - Ok(ws::Message::Binary(_)) | Ok(ws::Message::Continuation(_)) | Ok(ws::Message::Nop) => { - self.mark_activity(); - } - Err(e) => { - error!("WebSocket error for client {}: {:?}", self.current_agent_id(), e); - let tracer = global::tracer("ws-server"); - let mut span = tracer.start("ws.error"); - span.end(); - } - } - } -} - -/// WebSocket endpoint handler. -pub async fn ws_handler( - req: HttpRequest, - stream: web::Payload, - registry: web::Data, -) -> Result { - let tracer = global::tracer("ws-server"); - let mut span = tracer.start("ws.connect"); - - let client_ip = req - .peer_addr() - .map(|addr| addr.ip().to_string()) - .or_else(|| { - req.connection_info() - .realip_remote_addr() - .and_then(|addr| addr.split(':').next().map(str::to_string)) - }) - .unwrap_or_else(|| "unknown".to_string()); - - let actor = WebSocketActor::new(registry.get_ref().clone(), client_ip); - let result = ws::start(actor, &req, stream); - - span.end(); - result -} +use crate::config::Config; pub fn browser_static_dir() -> PathBuf { Path::new(".").join("static") @@ -775,149 +22,14 @@ pub async fn health() -> HttpResponse { })) } -/// Read the `name` field from a `package.json` file, returning `None` on any error. -fn read_package_name(package_json: &std::path::Path) -> Option { - let content = std::fs::read_to_string(package_json).ok()?; - let v: serde_json::Value = serde_json::from_str(&content).ok()?; - v.get("name")?.as_str().map(str::to_string) -} - -/// Scan all configured module paths and return a sorted list of (name, pkg_dir) pairs. -/// -/// Each entry is a module whose `/pkg/` subdirectory exists, or whose directory -/// directly contains a `package.json`. -pub fn list_modules(config: &ModulesConfig) -> Vec<(String, PathBuf)> { - let mut modules: Vec<(String, PathBuf)> = Vec::new(); - for path in &config.paths { - let pkg_dir = path.join("pkg"); - if pkg_dir.is_dir() { - // This path is itself a single module (e.g. ws-wasm-agent). - let name = read_package_name(&pkg_dir.join("package.json")) - .or_else(|| path.file_name().and_then(|n| n.to_str()).map(str::to_string)); - if let Some(name) = name { - modules.push((name, pkg_dir)); - } - } else if path.join("package.json").is_file() { - // This path directly contains a package.json. - let name = read_package_name(&path.join("package.json")) - .or_else(|| path.file_name().and_then(|n| n.to_str()).map(str::to_string)); - if let Some(name) = name { - modules.push((name, path.clone())); - } - } else if let Ok(entries) = std::fs::read_dir(path) { - // This path is a directory of modules (e.g. ws-modules). - for entry in entries.flatten() { - if let Ok(file_type) = entry.file_type() - && file_type.is_dir() - && !config.paths.contains(&entry.path()) - { - let entry_path = entry.path(); - let pkg_dir = entry_path.join("pkg"); - if pkg_dir.is_dir() { - let name = read_package_name(&pkg_dir.join("package.json")) - .or_else(|| entry.file_name().to_str().map(str::to_string)); - if let Some(name) = name { - modules.push((name, pkg_dir)); - } - } else if entry_path.join("package.json").is_file() { - let name = read_package_name(&entry_path.join("package.json")) - .or_else(|| entry.file_name().to_str().map(str::to_string)); - if let Some(name) = name { - modules.push((name, entry_path)); - } - } - } - } - } - } - modules.sort_by(|a, b| a.0.cmp(&b.0)); - modules -} - -async fn api_list_modules(config: web::Data) -> HttpResponse { - let names: Vec = list_modules(&config.modules) - .into_iter() - .map(|(name, _)| name) - .collect(); - HttpResponse::Ok().json(names) -} - -pub async fn agent_put_file( - req: HttpRequest, - mut payload: web::Payload, - registry: web::Data, - config: web::Data, -) -> Result { - let agent_id: String = req.match_info().query("agent_id").parse().unwrap(); - let filename: PathBuf = req - .match_info() - .query("filename") - .parse() - .map_err(|_| actix_web::error::ErrorBadRequest("invalid filename"))?; - - { - let agents = registry.agents.lock().expect("lock poisoned"); - if !agents.contains_key(&agent_id) { - return Err(actix_web::error::ErrorNotFound("agent not found")); - } - } - - if filename.components().count() != 1 { - return Err(actix_web::error::ErrorBadRequest("invalid filename")); - } - - let storage_dir = &config.storage.path; - let agent_dir = storage_dir.join(&agent_id); - std::fs::create_dir_all(&agent_dir)?; - - let path = agent_dir.join(&filename); - info!("Agent {} storing file: {:?}", agent_id, path); - - let mut file = tokio::fs::File::create(path).await?; - while let Some(chunk) = payload.next().await { - let chunk = chunk?; - tokio::io::copy(&mut &chunk[..], &mut file).await?; - } - - Ok(HttpResponse::Ok().finish()) -} - -pub fn configure_app(cfg: &mut web::ServiceConfig, agent_registry: web::Data, config: Config) { - let modules = list_modules(&config.modules); - let storage_dir = config.storage.path.clone(); - - let root_module_dir: PathBuf = modules - .iter() - .find(|(name, _)| name == &config.modules.root) - .map(|(_, path)| path.clone()) - .unwrap_or_else(|| { - panic!( - "Root module '{}' not found among configured modules paths", - config.modules.root - ); - }); - +pub fn configure_app(cfg: &mut web::ServiceConfig, agent_registry: web::Data, config: Config) { cfg.app_data(agent_registry) - .app_data(web::Data::new(config)) + .app_data(web::Data::new(config.clone())) .route("/favicon.ico", web::get().to(no_content)) - .route("/health", web::get().to(health)) - .route("/api/modules", web::get().to(api_list_modules)) - .route("/ws", web::get().to(ws_handler)) - .route("/storage/{agent_id}/{filename}", web::put().to(agent_put_file)) - .service( - Files::new("/storage", storage_dir) - .show_files_listing() - .use_etag(true) - .use_last_modified(true), - ); - - for (name, pkg_dir) in &modules { - cfg.service(Files::new(&format!("/modules/{name}"), pkg_dir)); - } + .route("/health", web::get().to(health)); - cfg.service( - Files::new("/", root_module_dir) - .index_file("index.html") - .prefer_utf8(true), - ); + et_ws_service::configure(cfg, &config); + et_storage_service::configure::>(cfg, &config); + // Must be last: registers a catch-all Files::new("/", ...) for the root module. + et_modules_service::configure(cfg, &config); } diff --git a/services/ws-server/src/main.rs b/services/ws-server/src/main.rs index 9a17c4f..12b8868 100644 --- a/services/ws-server/src/main.rs +++ b/services/ws-server/src/main.rs @@ -3,8 +3,10 @@ use std::path::PathBuf; use actix_web::middleware::{DefaultHeaders, Logger}; use actix_web::{App, HttpServer, web}; use clap::Parser; +use et_modules_service::list_modules; use et_ws_server::config::Config; -use et_ws_server::{AgentRegistry, browser_static_dir, configure_app, list_modules}; +use et_ws_server::{browser_static_dir, configure_app}; +use et_ws_service::load_registry; use tracing::{error, info}; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; @@ -80,7 +82,7 @@ async fn main() -> std::io::Result<()> { info!("Serving browser assets from {:?}", browser_static_dir()); info!("HTTPS uses an in-memory self-signed localhost certificate for development"); - let agent_registry = web::Data::new(AgentRegistry::load(&args.agent_registry)?); + let agent_registry = web::Data::new(load_registry(&args.agent_registry)?); let registry_clone = agent_registry.clone(); let registry_path = args.agent_registry.clone(); diff --git a/services/ws-server/static/app.js b/services/ws-server/static/app.js index bb8f439..da5f7e2 100644 --- a/services/ws-server/static/app.js +++ b/services/ws-server/static/app.js @@ -30,8 +30,8 @@ const describeError = (error) => ( const WORKFLOW_MODULES = new Map(); const populateModuleDropdown = async () => { - append("Discovering modules via /api/modules..."); - const resp = await fetch("/api/modules"); + append("Discovering modules via /modules..."); + const resp = await fetch("/modules/"); if (!resp.ok) { append(`Failed to fetch module list from server: ${resp.status} ${resp.statusText}`); return; diff --git a/services/ws/Cargo.toml b/services/ws/Cargo.toml new file mode 100644 index 0000000..fa65fa7 --- /dev/null +++ b/services/ws/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "et-ws-service" +version = "0.1.0" +edition.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +actix = "0.13" +actix-web = "4" +actix-web-actors = "4" +chrono = { version = "0.4", features = ["serde"] } +edge-toolkit = { path = "../../libs/edge-toolkit" } +opentelemetry = "0.31" +serde.workspace = true +serde_json.workspace = true +serde_yaml = "0.9" +tracing.workspace = true +uuid.workspace = true diff --git a/services/ws/src/lib.rs b/services/ws/src/lib.rs new file mode 100644 index 0000000..9756f85 --- /dev/null +++ b/services/ws/src/lib.rs @@ -0,0 +1,567 @@ +use std::collections::BTreeMap; +use std::sync::{Arc, Mutex}; +use std::time::{Duration, Instant}; + +use actix::{Actor, ActorContext, Addr, AsyncContext, Handler, Message, StreamHandler}; +use actix_web::{Error, HttpRequest, HttpResponse, web}; +use actix_web_actors::ws; +use chrono::Utc; +use edge_toolkit::ws::{ConnectStatus, MessageDeliveryStatus, MessageScope, WsMessage}; +use edge_toolkit::ws_server::{AgentRecord, AgentRegistry, PendingDirectMessage}; +use opentelemetry::{ + global, + trace::{Span, Tracer}, +}; +use tracing::{error, info, warn}; +use uuid::Uuid; + +pub const CONNECTION_TIMEOUT: Duration = Duration::from_secs(15); +pub const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(1); + +pub type WsAgentRegistry = AgentRegistry>; + +/// Load a registry from disk. Sessions are not persisted, so they are initialised to `None`. +pub fn load_registry(path: &std::path::Path) -> std::io::Result { + use edge_toolkit::ws::AgentConnectionState; + if !path.exists() { + warn!("Registry file {:?} does not exist, starting with empty registry", path); + return Ok(WsAgentRegistry::default()); + } + let yaml = std::fs::read_to_string(path)?; + // Deserialize using a session-less record type, then convert. + #[derive(serde::Deserialize)] + struct BareRecord { + state: AgentConnectionState, + last_known_ip: Option, + #[serde(default)] + pending_direct_messages: BTreeMap, + } + let bare: BTreeMap = serde_yaml::from_str(&yaml).map_err(std::io::Error::other)?; + let agents = bare + .into_iter() + .map(|(id, r)| { + ( + id, + AgentRecord { + state: r.state, + last_known_ip: r.last_known_ip, + session: None, + pending_direct_messages: r.pending_direct_messages, + }, + ) + }) + .collect(); + info!("Loaded registry from {:?}", path); + Ok(WsAgentRegistry { + agents: Arc::new(Mutex::new(agents)), + }) +} + +#[derive(Message)] +#[rtype(result = "()")] +pub struct ServerEnvelope { + pub message: WsMessage, +} + +pub struct WebSocketActor { + pub agent_id: Option, + pub last_activity: Instant, + pub client_ip: String, + pub registry: WsAgentRegistry, +} + +impl WebSocketActor { + pub fn new(registry: WsAgentRegistry, client_ip: String) -> Self { + info!("New WebSocket actor created for client IP {}", client_ip); + Self { + agent_id: None, + last_activity: Instant::now(), + client_ip, + registry, + } + } + + pub fn current_agent_id(&self) -> &str { + self.agent_id.as_deref().unwrap_or("unassigned") + } + + pub fn assigned_agent_id(&self) -> Option<&str> { + self.agent_id.as_deref() + } + + pub fn mark_activity(&mut self) { + self.last_activity = Instant::now(); + } + + pub fn start_heartbeat(&self, ctx: &mut ws::WebsocketContext) { + ctx.run_interval(HEARTBEAT_INTERVAL, |act, ctx| { + let idle_for = Instant::now().saturating_duration_since(act.last_activity); + if idle_for > CONNECTION_TIMEOUT { + warn!( + "WebSocket connection timed out for client {} after {:?} of inactivity", + act.current_agent_id(), + idle_for + ); + ctx.close(Some(ws::CloseReason { + code: ws::CloseCode::Policy, + description: Some(format!( + "connection timed out after {:?} of inactivity", + CONNECTION_TIMEOUT + )), + })); + ctx.stop(); + } + }); + } + + fn assign_or_reconnect_agent( + &mut self, + requested_id: Option, + session: Addr, + ) -> (String, ConnectStatus) { + let new_id = Uuid::now_v7().to_string(); + let (assigned_id, status) = self + .registry + .connect_agent(requested_id, new_id, &self.client_ip, session); + self.agent_id = Some(assigned_id.clone()); + (assigned_id, status) + } + + fn send_json(ctx: &mut ws::WebsocketContext, response: &WsMessage) { + match serde_json::to_string(response) { + Ok(json) => { + ctx.text(json); + let tracer = global::tracer("ws-server"); + let mut sent_span = tracer.start("ws.message.sent"); + sent_span.end(); + } + Err(error) => { + error!("Failed to serialize websocket response: {}", error); + } + } + } + + fn send_status( + ctx: &mut ws::WebsocketContext, + message_id: Option, + status: MessageDeliveryStatus, + detail: impl Into, + ) { + Self::send_json( + ctx, + &WsMessage::MessageStatus { + message_id, + status, + detail: detail.into(), + }, + ); + } + + fn send_invalid(ctx: &mut ws::WebsocketContext, message_id: Option, detail: impl Into) { + Self::send_json( + ctx, + &WsMessage::Invalid { + message_id, + detail: detail.into(), + }, + ); + } + + fn deliver_pending_messages(&self, ctx: &mut ws::WebsocketContext) { + let Some(agent_id) = self.assigned_agent_id() else { + return; + }; + for pending in self.registry.pending_messages_for(agent_id) { + info!( + "Delivering pending message {} to agent {} from {}", + pending.message_id, agent_id, pending.from_agent_id + ); + Self::send_json( + ctx, + &WsMessage::AgentMessage { + message_id: pending.message_id, + from_agent_id: pending.from_agent_id, + scope: MessageScope::Direct, + server_received_at: pending.server_received_at, + message: pending.message, + }, + ); + } + } + + fn handle_send_direct( + &self, + ctx: &mut ws::WebsocketContext, + span: &mut impl opentelemetry::trace::Span, + from_agent_id: String, + to_agent_id: String, + message: serde_json::Value, + ) { + let server_received_at = Utc::now().to_rfc3339(); + let (pending, recipient_session) = self.registry.queue_direct( + Uuid::now_v7().to_string(), + &from_agent_id, + &to_agent_id, + server_received_at, + message, + ); + let message_id = pending.message_id.clone(); + + if let Some(recipient_addr) = recipient_session { + info!( + "Direct message {} delivered from {} to {}", + message_id, from_agent_id, to_agent_id + ); + recipient_addr.do_send(ServerEnvelope { + message: WsMessage::AgentMessage { + message_id: message_id.clone(), + from_agent_id, + scope: MessageScope::Direct, + server_received_at: pending.server_received_at, + message: pending.message, + }, + }); + Self::send_status( + ctx, + Some(message_id), + MessageDeliveryStatus::Delivered, + format!("message delivered to agent {}", to_agent_id), + ); + } else { + info!( + "Direct message {} queued from {} to disconnected agent {}", + message_id, from_agent_id, to_agent_id + ); + Self::send_status( + ctx, + Some(message_id), + MessageDeliveryStatus::Queued, + format!("message queued for agent {}", to_agent_id), + ); + } + span.end(); + } +} + +impl Actor for WebSocketActor { + type Context = ws::WebsocketContext; + + fn started(&mut self, ctx: &mut Self::Context) { + self.start_heartbeat(ctx); + info!( + "WebSocket connection established for client IP {} with agent {}", + self.client_ip, + self.current_agent_id() + ); + let tracer = global::tracer("ws-server"); + let mut span = tracer.start("ws.connect"); + span.end(); + } + + fn stopped(&mut self, _ctx: &mut Self::Context) { + if let Some(agent_id) = self.agent_id.as_deref() { + self.registry.mark_disconnected(agent_id); + info!("Agent {} disconnected; last known IP {}", agent_id, self.client_ip); + } else { + info!( + "WebSocket connection closed before agent assignment for client IP {}", + self.client_ip + ); + } + } +} + +impl Handler for WebSocketActor { + type Result = (); + + fn handle(&mut self, msg: ServerEnvelope, ctx: &mut Self::Context) -> Self::Result { + Self::send_json(ctx, &msg.message); + } +} + +impl StreamHandler> for WebSocketActor { + fn handle(&mut self, msg: Result, ctx: &mut Self::Context) { + match msg { + Ok(ws::Message::Ping(ping)) => { + self.mark_activity(); + ctx.pong(&ping); + } + Ok(ws::Message::Pong(_)) => { + self.mark_activity(); + } + Ok(ws::Message::Text(text)) => { + self.mark_activity(); + let tracer = global::tracer("ws-server"); + let mut span = tracer.start("ws.message.received"); + info!("Received message from client {}: {:?}", self.current_agent_id(), text); + + if let Ok(msg) = serde_json::from_str::(&text) { + match msg { + WsMessage::Connect { agent_id } => { + let requested_id = agent_id.clone(); + info!( + "Connect message: requested_agent_id={:?} client_ip={}", + requested_id, self.client_ip + ); + let (assigned_id, status) = self.assign_or_reconnect_agent(agent_id, ctx.address()); + info!( + "Agent {} status {:?}connected from IP {}", + assigned_id, status, self.client_ip + ); + Self::send_json( + ctx, + &WsMessage::ConnectAck { + agent_id: assigned_id, + status: status.clone(), + }, + ); + info!( + "WebSocket connection ready for client {} with status {:?}", + self.current_agent_id(), + status + ); + self.deliver_pending_messages(ctx); + } + WsMessage::Alive { timestamp } => { + info!("Alive message from client {} at {}", self.current_agent_id(), timestamp); + Self::send_json( + ctx, + &WsMessage::Response { + message: format!("Alive message received at {}", Utc::now().to_rfc3339()), + }, + ); + } + WsMessage::ListAgents => { + let agents = self.registry.list_agents(); + info!( + "Agent {} requested list_agents; returning {} agents", + self.current_agent_id(), + agents.len() + ); + Self::send_json(ctx, &WsMessage::ListAgentsResponse { agents }); + } + WsMessage::SendAgentMessage { to_agent_id, message } => { + let Some(from_agent_id) = self.assigned_agent_id().map(str::to_string) else { + Self::send_invalid(ctx, None, "agent must connect before sending messages"); + span.end(); + return; + }; + + if from_agent_id == to_agent_id { + Self::send_invalid(ctx, None, "agent cannot send a direct message to itself"); + span.end(); + return; + } + + if !self.registry.list_agents().iter().any(|a| a.agent_id == to_agent_id) { + Self::send_invalid(ctx, None, format!("unknown target agent {}", to_agent_id)); + span.end(); + return; + } + + self.handle_send_direct(ctx, &mut span, from_agent_id, to_agent_id, message); + return; + } + WsMessage::BroadcastMessage { message } => { + let Some(from_agent_id) = self.assigned_agent_id().map(str::to_string) else { + Self::send_invalid(ctx, None, "agent must connect before broadcasting messages"); + span.end(); + return; + }; + + let recipients = self.registry.connected_sessions(&from_agent_id); + let message_id = Uuid::now_v7().to_string(); + let server_received_at = Utc::now().to_rfc3339(); + for (recipient_id, recipient_addr) in &recipients { + info!( + "Broadcast message {} from {} to {}", + message_id, from_agent_id, recipient_id + ); + recipient_addr.do_send(ServerEnvelope { + message: WsMessage::AgentMessage { + message_id: message_id.clone(), + from_agent_id: from_agent_id.clone(), + scope: MessageScope::Broadcast, + server_received_at: server_received_at.clone(), + message: message.clone(), + }, + }); + } + Self::send_status( + ctx, + Some(message_id), + MessageDeliveryStatus::Broadcast, + format!("broadcast sent to {} connected agents", recipients.len()), + ); + } + WsMessage::MessageAck { message_id } => { + let Some(recipient_agent_id) = self.assigned_agent_id().map(str::to_string) else { + Self::send_invalid(ctx, None, "agent must connect before acknowledging messages"); + span.end(); + return; + }; + + match self.registry.acknowledge_message(&recipient_agent_id, &message_id) { + Ok((message_id, sender_session, sender_agent_id)) => { + info!( + "Agent {} acknowledged direct message {} from {}", + recipient_agent_id, message_id, sender_agent_id + ); + Self::send_status( + ctx, + Some(message_id.clone()), + MessageDeliveryStatus::Acknowledged, + "message acknowledged", + ); + if let Some(sender_addr) = sender_session { + sender_addr.do_send(ServerEnvelope { + message: WsMessage::MessageStatus { + message_id: Some(message_id), + status: MessageDeliveryStatus::Acknowledged, + detail: format!("agent {} acknowledged receipt", recipient_agent_id), + }, + }); + } + } + Err(detail) => { + warn!("Invalid ack from {} for {}: {}", recipient_agent_id, message_id, detail); + Self::send_invalid(ctx, Some(message_id), detail); + } + } + } + WsMessage::ClientEvent { + capability, + action, + details, + } => { + if capability == "video_cv" && action == "inference" { + let detected_class = details + .get("detected_class") + .and_then(|v| v.as_str()) + .unwrap_or("unknown"); + let confidence = details.get("confidence").and_then(|v| v.as_f64()).unwrap_or_default(); + let processed_at = details + .get("processed_at") + .and_then(|v| v.as_str()) + .unwrap_or("unknown"); + info!( + "Video inference received from {}: class={} confidence={:.4} processed_at={}", + self.current_agent_id(), + detected_class, + confidence, + processed_at + ); + } + info!( + "Client event from {}: capability={} action={} details={}", + self.current_agent_id(), + capability, + action, + details + ); + } + WsMessage::StoreFile { filename } => { + let Some(agent_id) = self.assigned_agent_id() else { + Self::send_invalid(ctx, None, "agent must connect before storing files"); + span.end(); + return; + }; + let url = format!("/storage/{}/{}", agent_id, filename); + info!("Agent {} requested storage URL for {}: {}", agent_id, filename, url); + Self::send_json( + ctx, + &WsMessage::Response { + message: format!("PUT to {}", url), + }, + ); + } + WsMessage::FetchFile { agent_id, filename } => { + let url = format!("/storage/{}/{}", agent_id, filename); + info!( + "Agent {} requested fetch URL for {}/{}", + self.current_agent_id(), + agent_id, + filename + ); + Self::send_json( + ctx, + &WsMessage::Response { + message: format!("GET from {}", url), + }, + ); + } + WsMessage::ConnectAck { .. } + | WsMessage::ListAgentsResponse { .. } + | WsMessage::AgentMessage { .. } + | WsMessage::MessageStatus { .. } + | WsMessage::Invalid { .. } + | WsMessage::Response { .. } => { + warn!( + "Unexpected server-originated message from client {}", + self.current_agent_id() + ); + } + } + } else { + warn!( + "Received unrecognized message from client {}: {}", + self.current_agent_id(), + text + ); + } + span.end(); + } + Ok(ws::Message::Close(reason)) => { + self.mark_activity(); + info!( + "WebSocket close request from client: {} reason: {:?}", + self.current_agent_id(), + reason + ); + let tracer = global::tracer("ws-server"); + let mut span = tracer.start("ws.disconnect"); + span.end(); + ctx.close(reason); + ctx.stop(); + } + Ok(ws::Message::Binary(_)) | Ok(ws::Message::Continuation(_)) | Ok(ws::Message::Nop) => { + self.mark_activity(); + } + Err(e) => { + error!("WebSocket error for client {}: {:?}", self.current_agent_id(), e); + let tracer = global::tracer("ws-server"); + let mut span = tracer.start("ws.error"); + span.end(); + } + } + } +} + +pub async fn ws_handler( + req: HttpRequest, + stream: web::Payload, + registry: web::Data, +) -> Result { + let tracer = global::tracer("ws-server"); + let mut span = tracer.start("ws.connect"); + + let client_ip = req + .peer_addr() + .map(|addr| addr.ip().to_string()) + .or_else(|| { + req.connection_info() + .realip_remote_addr() + .and_then(|addr| addr.split(':').next().map(str::to_string)) + }) + .unwrap_or_else(|| "unknown".to_string()); + + let actor = WebSocketActor::new(registry.get_ref().clone(), client_ip); + let result = ws::start(actor, &req, stream); + + span.end(); + result +} + +pub fn configure(cfg: &mut web::ServiceConfig, _config: &edge_toolkit::ws_server::Config) { + cfg.route("/ws", web::get().to(ws_handler)); +}