Implement the telemetry streaming gRPC server
Added Cargo.lock file for cross compile for BMC platform.
Full gRPC server is from main.rs to telemetry_server.rs, then down to
telemetry_source_manager_api.rs. mtls.rs is at client authentication
stage if secure connection is enabled.
server_config.textproto is a sample server configuration, see the proto
voyager_server_config.proto for detail.
Passed unit test locally: https://paste.googleplex.com/6117540085104640
Google-Bug-Id: 366492368
Change-Id: Iefa74bd0c20e5e1354bc35caab416a7cc0bb88c6
Signed-off-by: Yongbing Chen <yongbingchen@google.com>
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..03ebd8c
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,3023 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "addr2line"
+version = "0.24.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler2"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "android-tzdata"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
+
+[[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 = "0.6.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is_terminal_polyfill",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8"
+dependencies = [
+ "anstyle",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.89"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6"
+
+[[package]]
+name = "asn1-rs"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048"
+dependencies = [
+ "asn1-rs-derive",
+ "asn1-rs-impl",
+ "displaydoc",
+ "nom",
+ "num-traits",
+ "rusticata-macros",
+ "thiserror",
+ "time",
+]
+
+[[package]]
+name = "asn1-rs-derive"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.79",
+ "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 2.0.79",
+]
+
+[[package]]
+name = "async-broadcast"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c48ccdbf6ca6b121e0f586cbc0e73ae440e56c67c30fa0873b4e110d9c26d2b"
+dependencies = [
+ "event-listener 2.5.3",
+ "futures-core",
+]
+
+[[package]]
+name = "async-channel"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a"
+dependencies = [
+ "concurrent-queue",
+ "event-listener-strategy",
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-io"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af"
+dependencies = [
+ "async-lock 2.8.0",
+ "autocfg",
+ "cfg-if",
+ "concurrent-queue",
+ "futures-lite 1.13.0",
+ "log",
+ "parking",
+ "polling 2.8.0",
+ "rustix 0.37.27",
+ "slab",
+ "socket2 0.4.10",
+ "waker-fn",
+]
+
+[[package]]
+name = "async-io"
+version = "2.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8"
+dependencies = [
+ "async-lock 3.4.0",
+ "cfg-if",
+ "concurrent-queue",
+ "futures-io",
+ "futures-lite 2.3.0",
+ "parking",
+ "polling 3.7.3",
+ "rustix 0.38.37",
+ "slab",
+ "tracing",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "async-lock"
+version = "2.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b"
+dependencies = [
+ "event-listener 2.5.3",
+]
+
+[[package]]
+name = "async-lock"
+version = "3.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18"
+dependencies = [
+ "event-listener 5.3.1",
+ "event-listener-strategy",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-process"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea6438ba0a08d81529c69b36700fa2f95837bfe3e776ab39cde9c14d9149da88"
+dependencies = [
+ "async-io 1.13.0",
+ "async-lock 2.8.0",
+ "async-signal",
+ "blocking",
+ "cfg-if",
+ "event-listener 3.1.0",
+ "futures-lite 1.13.0",
+ "rustix 0.38.37",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "async-recursion"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.79",
+]
+
+[[package]]
+name = "async-signal"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3"
+dependencies = [
+ "async-io 2.3.4",
+ "async-lock 3.4.0",
+ "atomic-waker",
+ "cfg-if",
+ "futures-core",
+ "futures-io",
+ "rustix 0.38.37",
+ "signal-hook-registry",
+ "slab",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "async-stream"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476"
+dependencies = [
+ "async-stream-impl",
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-stream-impl"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.79",
+]
+
+[[package]]
+name = "async-task"
+version = "4.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
+
+[[package]]
+name = "async-trait"
+version = "0.1.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.79",
+]
+
+[[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.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
+
+[[package]]
+name = "axum"
+version = "0.6.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf"
+dependencies = [
+ "async-trait",
+ "axum-core 0.3.4",
+ "bitflags 1.3.2",
+ "bytes",
+ "futures-util",
+ "http 0.2.12",
+ "http-body 0.4.6",
+ "hyper 0.14.30",
+ "itoa",
+ "matchit",
+ "memchr",
+ "mime",
+ "percent-encoding",
+ "pin-project-lite",
+ "rustversion",
+ "serde",
+ "sync_wrapper 0.1.2",
+ "tower 0.4.13",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
+name = "axum"
+version = "0.7.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "504e3947307ac8326a5437504c517c4b56716c9d98fac0028c2acc7ca47d70ae"
+dependencies = [
+ "async-trait",
+ "axum-core 0.4.5",
+ "bytes",
+ "futures-util",
+ "http 1.1.0",
+ "http-body 1.0.1",
+ "http-body-util",
+ "hyper 1.4.1",
+ "hyper-util",
+ "itoa",
+ "matchit",
+ "memchr",
+ "mime",
+ "percent-encoding",
+ "pin-project-lite",
+ "rustversion",
+ "serde",
+ "serde_json",
+ "serde_path_to_error",
+ "serde_urlencoded",
+ "sync_wrapper 1.0.1",
+ "tokio",
+ "tower 0.5.1",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "axum-core"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c"
+dependencies = [
+ "async-trait",
+ "bytes",
+ "futures-util",
+ "http 0.2.12",
+ "http-body 0.4.6",
+ "mime",
+ "rustversion",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
+name = "axum-core"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199"
+dependencies = [
+ "async-trait",
+ "bytes",
+ "futures-util",
+ "http 1.1.0",
+ "http-body 1.0.1",
+ "http-body-util",
+ "mime",
+ "pin-project-lite",
+ "rustversion",
+ "sync_wrapper 1.0.1",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "backtrace"
+version = "0.3.74"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
+dependencies = [
+ "addr2line",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "base64"
+version = "0.21.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "blocking"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea"
+dependencies = [
+ "async-channel",
+ "async-task",
+ "futures-io",
+ "futures-lite 2.3.0",
+ "piper",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "bytes"
+version = "1.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3"
+
+[[package]]
+name = "cc"
+version = "1.1.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "812acba72f0a070b003d3697490d2b55b837230ae7c6c6497f05cc2ddbb8d938"
+dependencies = [
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chrono"
+version = "0.4.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
+dependencies = [
+ "android-tzdata",
+ "iana-time-zone",
+ "js-sys",
+ "num-traits",
+ "wasm-bindgen",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "clap"
+version = "4.5.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7be5744db7978a28d9df86a214130d106a89ce49644cbc4e3f0c22c3fba30615"
+dependencies = [
+ "clap_builder",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.5.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5fbc17d3ef8278f55b282b2a2e75ae6f6c7d4bb70ed3d0382375104bfafdb4b"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
+
+[[package]]
+name = "colorchoice"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
+
+[[package]]
+name = "concurrent-queue"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[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.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
+
+[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "dashmap"
+version = "5.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
+dependencies = [
+ "cfg-if",
+ "hashbrown 0.14.5",
+ "lock_api",
+ "once_cell",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "data-encoding"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2"
+
+[[package]]
+name = "der-parser"
+version = "9.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553"
+dependencies = [
+ "asn1-rs",
+ "displaydoc",
+ "nom",
+ "num-bigint",
+ "num-traits",
+ "rusticata-macros",
+]
+
+[[package]]
+name = "deranged"
+version = "0.3.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
+dependencies = [
+ "powerfmt",
+]
+
+[[package]]
+name = "derivative"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+]
+
+[[package]]
+name = "displaydoc"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.79",
+]
+
+[[package]]
+name = "either"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
+
+[[package]]
+name = "enumflags2"
+version = "0.7.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d"
+dependencies = [
+ "enumflags2_derive",
+ "serde",
+]
+
+[[package]]
+name = "enumflags2_derive"
+version = "0.7.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.79",
+]
+
+[[package]]
+name = "env_logger"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580"
+dependencies = [
+ "humantime",
+ "is-terminal",
+ "log",
+ "regex",
+ "termcolor",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
+
+[[package]]
+name = "errno"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "event-listener"
+version = "2.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
+
+[[package]]
+name = "event-listener"
+version = "3.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2"
+dependencies = [
+ "concurrent-queue",
+ "parking",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "event-listener"
+version = "5.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba"
+dependencies = [
+ "concurrent-queue",
+ "parking",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "event-listener-strategy"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1"
+dependencies = [
+ "event-listener 5.3.1",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "fastrand"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
+dependencies = [
+ "instant",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6"
+
+[[package]]
+name = "fixedbitset"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "futures"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
+
+[[package]]
+name = "futures-lite"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce"
+dependencies = [
+ "fastrand 1.9.0",
+ "futures-core",
+ "futures-io",
+ "memchr",
+ "parking",
+ "pin-project-lite",
+ "waker-fn",
+]
+
+[[package]]
+name = "futures-lite"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "futures-macro"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.79",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
+
+[[package]]
+name = "futures-task"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
+
+[[package]]
+name = "futures-util"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "gimli"
+version = "0.31.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64"
+
+[[package]]
+name = "h2"
+version = "0.3.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8"
+dependencies = [
+ "bytes",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "futures-util",
+ "http 0.2.12",
+ "indexmap 2.6.0",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+
+[[package]]
+name = "hashbrown"
+version = "0.14.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
+
+[[package]]
+name = "hashbrown"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb"
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "hermit-abi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
+
+[[package]]
+name = "hermit-abi"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
+
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[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.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
+dependencies = [
+ "bytes",
+ "http 0.2.12",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "http-body"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
+dependencies = [
+ "bytes",
+ "http 1.1.0",
+]
+
+[[package]]
+name = "http-body-util"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f"
+dependencies = [
+ "bytes",
+ "futures-util",
+ "http 1.1.0",
+ "http-body 1.0.1",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "httparse"
+version = "1.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946"
+
+[[package]]
+name = "httpdate"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
+
+[[package]]
+name = "humantime"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
+
+[[package]]
+name = "hyper"
+version = "0.14.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http 0.2.12",
+ "http-body 0.4.6",
+ "httparse",
+ "httpdate",
+ "itoa",
+ "pin-project-lite",
+ "socket2 0.5.7",
+ "tokio",
+ "tower-service",
+ "tracing",
+ "want",
+]
+
+[[package]]
+name = "hyper"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-util",
+ "http 1.1.0",
+ "http-body 1.0.1",
+ "httparse",
+ "httpdate",
+ "itoa",
+ "pin-project-lite",
+ "smallvec",
+ "tokio",
+]
+
+[[package]]
+name = "hyper-timeout"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1"
+dependencies = [
+ "hyper 0.14.30",
+ "pin-project-lite",
+ "tokio",
+ "tokio-io-timeout",
+]
+
+[[package]]
+name = "hyper-util"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b"
+dependencies = [
+ "bytes",
+ "futures-util",
+ "http 1.1.0",
+ "http-body 1.0.1",
+ "hyper 1.4.1",
+ "pin-project-lite",
+ "tokio",
+ "tower-service",
+]
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.61"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "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 = "idna"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "indexmap"
+version = "1.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
+dependencies = [
+ "autocfg",
+ "hashbrown 0.12.3",
+]
+
+[[package]]
+name = "indexmap"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
+dependencies = [
+ "equivalent",
+ "hashbrown 0.15.0",
+]
+
+[[package]]
+name = "instant"
+version = "0.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "io-lifetimes"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
+dependencies = [
+ "hermit-abi 0.3.9",
+ "libc",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "io-uring"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "595a0399f411a508feb2ec1e970a4a30c249351e30208960d58298de8660b0e5"
+dependencies = [
+ "bitflags 1.3.2",
+ "libc",
+]
+
+[[package]]
+name = "is-terminal"
+version = "0.4.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b"
+dependencies = [
+ "hermit-abi 0.4.0",
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "is_terminal_polyfill"
+version = "1.70.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
+
+[[package]]
+name = "itertools"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
+
+[[package]]
+name = "js-sys"
+version = "0.3.70"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "jsonpath_lib"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eaa63191d68230cccb81c5aa23abd53ed64d83337cacbb25a7b8c7979523774f"
+dependencies = [
+ "log",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+
+[[package]]
+name = "libc"
+version = "0.2.159"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
+
+[[package]]
+name = "lock_api"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
+
+[[package]]
+name = "matchit"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
+
+[[package]]
+name = "memchr"
+version = "2.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
+
+[[package]]
+name = "memoffset"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "memoffset"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "mime"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
+
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1"
+dependencies = [
+ "adler2",
+]
+
+[[package]]
+name = "mio"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec"
+dependencies = [
+ "hermit-abi 0.3.9",
+ "libc",
+ "wasi",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "multimap"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03"
+
+[[package]]
+name = "nix"
+version = "0.26.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b"
+dependencies = [
+ "bitflags 1.3.2",
+ "cfg-if",
+ "libc",
+ "memoffset 0.7.1",
+]
+
+[[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 = "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.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
+
+[[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",
+]
+
+[[package]]
+name = "object"
+version = "0.36.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "oid-registry"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9"
+dependencies = [
+ "asn1-rs",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1"
+dependencies = [
+ "portable-atomic",
+]
+
+[[package]]
+name = "ordered-stream"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "parking"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
+
+[[package]]
+name = "parking_lot"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
+
+[[package]]
+name = "petgraph"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db"
+dependencies = [
+ "fixedbitset",
+ "indexmap 2.6.0",
+]
+
+[[package]]
+name = "pin-project"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.79",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "piper"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066"
+dependencies = [
+ "atomic-waker",
+ "fastrand 2.1.1",
+ "futures-io",
+]
+
+[[package]]
+name = "polling"
+version = "2.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce"
+dependencies = [
+ "autocfg",
+ "bitflags 1.3.2",
+ "cfg-if",
+ "concurrent-queue",
+ "libc",
+ "log",
+ "pin-project-lite",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "polling"
+version = "3.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511"
+dependencies = [
+ "cfg-if",
+ "concurrent-queue",
+ "hermit-abi 0.4.0",
+ "pin-project-lite",
+ "rustix 0.38.37",
+ "tracing",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "portable-atomic"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2"
+
+[[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.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
+dependencies = [
+ "zerocopy",
+]
+
+[[package]]
+name = "prettyplease"
+version = "0.2.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba"
+dependencies = [
+ "proc-macro2",
+ "syn 2.0.79",
+]
+
+[[package]]
+name = "proc-macro-crate"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
+dependencies = [
+ "once_cell",
+ "toml_edit",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.86"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "prost"
+version = "0.12.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29"
+dependencies = [
+ "bytes",
+ "prost-derive",
+]
+
+[[package]]
+name = "prost-build"
+version = "0.12.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4"
+dependencies = [
+ "bytes",
+ "heck",
+ "itertools",
+ "log",
+ "multimap",
+ "once_cell",
+ "petgraph",
+ "prettyplease",
+ "prost",
+ "prost-types",
+ "regex",
+ "syn 2.0.79",
+ "tempfile",
+]
+
+[[package]]
+name = "prost-derive"
+version = "0.12.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1"
+dependencies = [
+ "anyhow",
+ "itertools",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.79",
+]
+
+[[package]]
+name = "prost-types"
+version = "0.12.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0"
+dependencies = [
+ "prost",
+]
+
+[[package]]
+name = "protobuf"
+version = "3.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bcc343da15609eaecd65f8aa76df8dc4209d325131d8219358c0aaaebab0bf6"
+dependencies = [
+ "once_cell",
+ "protobuf-support",
+ "thiserror",
+]
+
+[[package]]
+name = "protobuf-support"
+version = "3.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0766e3675a627c327e4b3964582594b0e8741305d628a98a5de75a1d15f99b9"
+dependencies = [
+ "thiserror",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "redfish-codegen"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0ebb5fc1dfff92674e3b12765504f244373d9bfcedd85e5dddfe14275faf4f5"
+dependencies = [
+ "derivative",
+ "redfish-macros",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "redfish-macros"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57f59ccb0a00b682728c1ad04a3575cf450bacc2b675f23f61cc2163a61ac47c"
+dependencies = [
+ "lazy_static",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f"
+dependencies = [
+ "bitflags 2.6.0",
+]
+
+[[package]]
+name = "regex"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
+
+[[package]]
+name = "ring"
+version = "0.17.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d"
+dependencies = [
+ "cc",
+ "cfg-if",
+ "getrandom",
+ "libc",
+ "spin",
+ "untrusted",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
+
+[[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.37.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2"
+dependencies = [
+ "bitflags 1.3.2",
+ "errno",
+ "io-lifetimes",
+ "libc",
+ "linux-raw-sys 0.3.8",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "rustix"
+version = "0.38.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811"
+dependencies = [
+ "bitflags 2.6.0",
+ "errno",
+ "libc",
+ "linux-raw-sys 0.4.14",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "rustls"
+version = "0.22.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432"
+dependencies = [
+ "log",
+ "ring",
+ "rustls-pki-types",
+ "rustls-webpki",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-pemfile"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
+dependencies = [
+ "rustls-pki-types",
+]
+
+[[package]]
+name = "rustls-pki-types"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e696e35370c65c9c541198af4543ccd580cf17fc25d8e05c5a242b202488c55"
+
+[[package]]
+name = "rustls-webpki"
+version = "0.102.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9"
+dependencies = [
+ "ring",
+ "rustls-pki-types",
+ "untrusted",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6"
+
+[[package]]
+name = "ryu"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "serde"
+version = "1.0.210"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.210"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.79",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.128"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8"
+dependencies = [
+ "indexmap 2.6.0",
+ "itoa",
+ "memchr",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_path_to_error"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6"
+dependencies = [
+ "itoa",
+ "serde",
+]
+
+[[package]]
+name = "serde_qs"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0431a35568651e363364210c91983c1da5eb29404d9f0928b67d4ebcfa7d330c"
+dependencies = [
+ "percent-encoding",
+ "serde",
+ "thiserror",
+]
+
+[[package]]
+name = "serde_repr"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.79",
+]
+
+[[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 = "sha1"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[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.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
+
+[[package]]
+name = "socket2"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "socket2"
+version = "0.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "spin"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "streaming_telemetry_server"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "async-stream",
+ "async-trait",
+ "axum 0.7.7",
+ "chrono",
+ "clap",
+ "dashmap",
+ "env_logger",
+ "futures",
+ "futures-util",
+ "jsonpath_lib",
+ "lazy_static",
+ "log",
+ "prost",
+ "prost-types",
+ "protobuf",
+ "rand",
+ "redfish-codegen",
+ "regex",
+ "rustls",
+ "rustls-pemfile",
+ "rustls-pki-types",
+ "serde",
+ "serde_json",
+ "serde_qs",
+ "tokio",
+ "tokio-rustls",
+ "tokio-stream",
+ "tokio-uring",
+ "tonic",
+ "tonic-build",
+ "url",
+ "uuid",
+ "x509-parser",
+ "zbus",
+ "zbus_names",
+]
+
+[[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 = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.79"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "sync_wrapper"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
+
+[[package]]
+name = "sync_wrapper"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394"
+
+[[package]]
+name = "synstructure"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.79",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b"
+dependencies = [
+ "cfg-if",
+ "fastrand 2.1.1",
+ "once_cell",
+ "rustix 0.38.37",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.64"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.64"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.79",
+]
+
+[[package]]
+name = "time"
+version = "0.3.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
+dependencies = [
+ "deranged",
+ "itoa",
+ "num-conv",
+ "powerfmt",
+ "serde",
+ "time-core",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
+
+[[package]]
+name = "time-macros"
+version = "0.2.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf"
+dependencies = [
+ "num-conv",
+ "time-core",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
+[[package]]
+name = "tokio"
+version = "1.40.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998"
+dependencies = [
+ "backtrace",
+ "bytes",
+ "libc",
+ "mio",
+ "parking_lot",
+ "pin-project-lite",
+ "signal-hook-registry",
+ "socket2 0.5.7",
+ "tokio-macros",
+ "tracing",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "tokio-io-timeout"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf"
+dependencies = [
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.79",
+]
+
+[[package]]
+name = "tokio-rustls"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f"
+dependencies = [
+ "rustls",
+ "rustls-pki-types",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-stream"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-uring"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "748482e3e13584a34664a710168ad5068e8cb1d968aa4ffa887e83ca6dd27967"
+dependencies = [
+ "futures-util",
+ "io-uring",
+ "libc",
+ "slab",
+ "socket2 0.4.10",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.7.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
+
+[[package]]
+name = "toml_edit"
+version = "0.19.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
+dependencies = [
+ "indexmap 2.6.0",
+ "toml_datetime",
+ "winnow",
+]
+
+[[package]]
+name = "tonic"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76c4eb7a4e9ef9d4763600161f12f5070b92a578e1b634db88a6887844c91a13"
+dependencies = [
+ "async-stream",
+ "async-trait",
+ "axum 0.6.20",
+ "base64",
+ "bytes",
+ "h2",
+ "http 0.2.12",
+ "http-body 0.4.6",
+ "hyper 0.14.30",
+ "hyper-timeout",
+ "percent-encoding",
+ "pin-project",
+ "prost",
+ "rustls-pemfile",
+ "rustls-pki-types",
+ "tokio",
+ "tokio-rustls",
+ "tokio-stream",
+ "tower 0.4.13",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "tonic-build"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be4ef6dd70a610078cb4e338a0f79d06bc759ff1b22d2120c2ff02ae264ba9c2"
+dependencies = [
+ "prettyplease",
+ "proc-macro2",
+ "prost-build",
+ "quote",
+ "syn 2.0.79",
+]
+
+[[package]]
+name = "tower"
+version = "0.4.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
+dependencies = [
+ "futures-core",
+ "futures-util",
+ "indexmap 1.9.3",
+ "pin-project",
+ "pin-project-lite",
+ "rand",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "tower"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f"
+dependencies = [
+ "futures-core",
+ "futures-util",
+ "pin-project-lite",
+ "sync_wrapper 0.1.2",
+ "tokio",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "tower-layer"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
+
+[[package]]
+name = "tower-service"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
+
+[[package]]
+name = "tracing"
+version = "0.1.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
+dependencies = [
+ "log",
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.79",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
+dependencies = [
+ "once_cell",
+]
+
+[[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.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
+
+[[package]]
+name = "uds_windows"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9"
+dependencies = [
+ "memoffset 0.9.1",
+ "tempfile",
+ "winapi",
+]
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956"
+dependencies = [
+ "tinyvec",
+]
+
+[[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.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+]
+
+[[package]]
+name = "utf8parse"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
+
+[[package]]
+name = "uuid"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314"
+dependencies = [
+ "getrandom",
+ "rand",
+ "uuid-macro-internal",
+]
+
+[[package]]
+name = "uuid-macro-internal"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee1cd046f83ea2c4e920d6ee9f7c3537ef928d75dce5d84a87c2c5d6b3999a3a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.79",
+]
+
+[[package]]
+name = "version_check"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
+[[package]]
+name = "waker-fn"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7"
+
+[[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.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.93"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.93"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.79",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.93"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.93"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.79",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.93"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484"
+
+[[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.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
+dependencies = [
+ "windows-sys 0.59.0",
+]
+
+[[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.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.6",
+ "windows_aarch64_msvc 0.52.6",
+ "windows_i686_gnu 0.52.6",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc 0.52.6",
+ "windows_x86_64_gnu 0.52.6",
+ "windows_x86_64_gnullvm 0.52.6",
+ "windows_x86_64_msvc 0.52.6",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.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.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[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.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[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.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[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.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[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.5.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "x509-parser"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69"
+dependencies = [
+ "asn1-rs",
+ "data-encoding",
+ "der-parser",
+ "lazy_static",
+ "nom",
+ "oid-registry",
+ "rusticata-macros",
+ "thiserror",
+ "time",
+]
+
+[[package]]
+name = "xdg-home"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6"
+dependencies = [
+ "libc",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "zbus"
+version = "3.15.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "675d170b632a6ad49804c8cf2105d7c31eddd3312555cffd4b740e08e97c25e6"
+dependencies = [
+ "async-broadcast",
+ "async-process",
+ "async-recursion",
+ "async-trait",
+ "byteorder",
+ "derivative",
+ "enumflags2",
+ "event-listener 2.5.3",
+ "futures-core",
+ "futures-sink",
+ "futures-util",
+ "hex",
+ "nix",
+ "once_cell",
+ "ordered-stream",
+ "rand",
+ "serde",
+ "serde_repr",
+ "sha1",
+ "static_assertions",
+ "tokio",
+ "tracing",
+ "uds_windows",
+ "winapi",
+ "xdg-home",
+ "zbus_macros",
+ "zbus_names",
+ "zvariant",
+]
+
+[[package]]
+name = "zbus_macros"
+version = "3.15.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7131497b0f887e8061b430c530240063d33bf9455fa34438f388a245da69e0a5"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "syn 1.0.109",
+ "zvariant_utils",
+]
+
+[[package]]
+name = "zbus_names"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "437d738d3750bed6ca9b8d423ccc7a8eb284f6b1d6d4e225a0e4e6258d864c8d"
+dependencies = [
+ "serde",
+ "static_assertions",
+ "zvariant",
+]
+
+[[package]]
+name = "zerocopy"
+version = "0.7.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
+dependencies = [
+ "byteorder",
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.7.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.79",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
+
+[[package]]
+name = "zvariant"
+version = "3.15.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4eef2be88ba09b358d3b58aca6e41cd853631d44787f319a1383ca83424fb2db"
+dependencies = [
+ "byteorder",
+ "enumflags2",
+ "libc",
+ "serde",
+ "static_assertions",
+ "zvariant_derive",
+]
+
+[[package]]
+name = "zvariant_derive"
+version = "3.15.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37c24dc0bed72f5f90d1f8bb5b07228cbf63b3c6e9f82d82559d4bae666e7ed9"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+ "zvariant_utils",
+]
+
+[[package]]
+name = "zvariant_utils"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7234f0d811589db492d16893e3f21e8e2fd282e6d01b0cddee310322062cc200"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..6f55645
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,4 @@
+[workspace]
+members = [
+ "streaming_telemetry",
+]
diff --git a/streaming_telemetry/Cargo.toml b/streaming_telemetry/Cargo.toml
new file mode 100644
index 0000000..49f7f97
--- /dev/null
+++ b/streaming_telemetry/Cargo.toml
@@ -0,0 +1,88 @@
+[package]
+name = "streaming_telemetry_server"
+version = "0.1.0"
+edition = "2021"
+repository = "https://gbmc.googlesource.com/streaming-telemetry-server"
+rust-version = "1.70"
+
+[[bin]]
+name = "streaming_telemetry_server"
+path = "src/main.rs"
+
+[[bin]]
+name = "streaming_telemetry_client"
+path = "src/telemetry_client.rs"
+required-features = ["build-client"]
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[features]
+default = ["mtls"]
+mtls = ["tokio-rustls", "rustls", "rustls-pemfile", "rustls-pki-types", "x509-parser"]
+# enable this feature for comparing sensor telemetry result with Redfish query result,
+# compile client with "cargo build --bin telemetry_client --release --features=client_debug"
+# then open ssh tunnel "ssh -L 8088:localhost:80 root@yscsz15-nfd11" before run client
+# client_debug = ["reqwest"]
+build-client = ["async-stream"]
+load-sources-from-server-config = []
+
+[dependencies]
+tonic = { version = "0.11.0", features = ["tls", "transport"] }
+prost = "0.12.3"
+prost-types = "0.12.3"
+tokio = { version = "1.36.0", features = ["full"] }
+tokio-stream = "0.1.15"
+axum = "0.7.4"
+jsonpath_lib = "0.3.0"
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
+serde_qs = "0.12.0"
+url = "2.5.0"
+regex = "1.10.3"
+dashmap = "5.5.3"
+futures = "0.3.30"
+futures-util = "0.3.30"
+anyhow = "1.0.81"
+zbus = { version = "3.15", default-features = false, features = ["tokio"] }
+zbus_names = { version = "2.6.0" }
+chrono = "0.4.37"
+clap = { version = "4.0" }
+async-trait = "0.1.80"
+tokio-uring = "0.5.0"
+lazy_static = "1.4.0"
+log = "0.4.21"
+protobuf = "=3.5.1"
+redfish-codegen = "0.3.1"
+env_logger = "0.10.2"
+
+# Conditional dependencies
+tokio-rustls = { version = "=0.25.0", optional = true }
+# set rustls default-features to false, to let it depends on ring instead of aws-lc-rs as crypto library
+rustls = { version = "=0.22.4", default-features = false, optional = true }
+rustls-pemfile = { version = "2.1.2", optional = true }
+rustls-pki-types = { version = "1.5.0", optional = true }
+x509-parser = { version = "0.16.0", optional = true }
+# For test client only
+# reqwest = { version = "0.12.7", optional = true }
+async-stream = { version = "0.3.5", optional = true }
+
+[dev-dependencies]
+rand = "0.8.5"
+[lib]
+doctest = false
+
+[dependencies.uuid]
+version = "1.8.0"
+features = [
+ "v4", # Lets you generate random UUIDs
+ "fast-rng", # Use a faster (but still sufficiently random) RNG
+ "macro-diagnostics", # Enable better diagnostics for compile-time UUIDs
+]
+[build-dependencies]
+tonic-build = "0.11.0"
+
+[profile.release]
+strip = true # Automatically strip symbols from the binary.
+opt-level = "z" # Optimize for size.
+lto = true
+panic = 'abort'
diff --git a/streaming_telemetry/src/grpc/mod.rs b/streaming_telemetry/src/grpc/mod.rs
new file mode 100644
index 0000000..facc22f
--- /dev/null
+++ b/streaming_telemetry/src/grpc/mod.rs
@@ -0,0 +1,3 @@
+pub mod server_config;
+pub mod telemetry_server;
+pub mod third_party_voyager;
diff --git a/streaming_telemetry/src/grpc/telemetry_server.rs b/streaming_telemetry/src/grpc/telemetry_server.rs
new file mode 100644
index 0000000..75574e2
--- /dev/null
+++ b/streaming_telemetry/src/grpc/telemetry_server.rs
@@ -0,0 +1,638 @@
+//! BMC Telemetry Service implementation voyager telemetry gRPC.
+//!
+//! This module provides a gRPC-based telemetry service for Baseboard Management Controllers (BMCs).
+//! It handles subscription requests, processes telemetry data, and streams updates to clients.
+
+use crate::app_state::AppState;
+use futures::StreamExt;
+use std::collections::HashMap;
+use std::pin::Pin;
+use std::sync::Arc;
+use std::time::Duration;
+use tokio::sync::mpsc;
+use tokio_stream::wrappers::ReceiverStream;
+use tokio_stream::Stream;
+use tonic::{Code, Request, Response, Status};
+
+use crate::grpc::third_party_voyager::{
+ machine_telemetry_server::MachineTelemetry, request_fqp, DataPoint, FqpType,
+ Request as TelemetryRequest, RequestFqp, SetRequest, TypedStruct, TypedValue, Update,
+};
+use crate::handlers::xpath::get_xpath_urls;
+use crate::telemetry_source_manager::telemetry_source_manager::{SubscriptionType, TypedTelemetry};
+use crate::telemetry_source_manager::telemetry_source_manager_api::handle_subscribe_inner;
+
+use super::third_party_voyager;
+
+use crate::grpc::server_config::voyager_server_config::ServerConfig as ProtobufServerConfig;
+use prost::Message;
+use protobuf::text_format::parse_from_str;
+use protobuf::Message as ProtobufMessage;
+use third_party_voyager::ServerConfig as ProstServerConfig;
+use third_party_voyager::{typed_value, Threshold, Thresholds};
+
+/// Represents the BMC Telemetry Service.
+pub struct BmcTelemetryService {
+ /// Shared application state.
+ pub state: Arc<AppState>,
+ /// Server configuration, shared and protected by a read-write lock.
+ pub server_config: Arc<tokio::sync::RwLock<ProstServerConfig>>,
+}
+
+/// Loads the server configuration from a textproto file.
+///
+/// # Arguments
+///
+/// * `file_path` - The path to the configuration file in textproto format, which contains a
+/// ServerConfig message defined in voyager_server_config proto file.
+///
+/// # Returns
+///
+/// Returns a `Result` containing the parsed `ServerConfig` or an error.
+pub fn load_server_config(
+ file_path: &str,
+) -> Result<ProstServerConfig, Box<dyn std::error::Error>> {
+ let content = std::fs::read_to_string(file_path)?;
+
+ // Use protobuf creat to parse a textproto string to protobuf crate format ServerConfig object..
+ let config: ProtobufServerConfig = parse_from_str(&content)?;
+
+ // Encode the protobuf crate format ServerConfig object into binary format
+ let mut binary_data = Vec::new();
+ config
+ .write_to_vec(&mut binary_data)
+ .expect("Failed to encode ProtobufServerConfig object to binary");
+
+ // Use this trick to convert protobuf crate format message to prost crate format message
+ let config = ProstServerConfig::decode(&*binary_data)
+ .expect("Failed to decode binary data into ProstServerConfig");
+ println!("Loaded ServerConfig: {:#?}", config);
+
+ Ok(config)
+}
+
+/// Retrieves the double value from a `TypedValue`.
+///
+/// # Arguments
+///
+/// * `typed_value` - The `TypedValue` to extract the double from.
+///
+/// # Returns
+///
+/// An `Option<f64>` containing the double value if present.
+fn get_double_value(typed_value: &TypedValue) -> Option<f64> {
+ typed_value.value.as_ref().and_then(|value| {
+ if let typed_value::Value::DoubleVal(v) = value {
+ Some(*v)
+ } else {
+ None
+ }
+ })
+}
+
+/// Determines the appropriate threshold configuration based on a reading.
+///
+/// # Arguments
+///
+/// * `reading` - The current sensor reading.
+/// * `config` - The threshold configuration.
+///
+/// # Returns
+///
+/// An `Option` containing a reference to the appropriate `Threshold`, if any.
+pub fn get_threshold_config(reading: f64, config: &Thresholds) -> Option<&Threshold> {
+ let thresholds = &config.threshold;
+
+ if thresholds.is_empty() {
+ return None;
+ }
+
+ let mut current_index = 0;
+
+ while current_index < thresholds.len() {
+ // SAFETY: already checked for out-of-range access
+ let threshold = &thresholds[current_index];
+ let cross_above = threshold
+ .cross_above_value
+ .as_ref()
+ .and_then(get_double_value);
+ let cross_below = threshold
+ .cross_below_value
+ .as_ref()
+ .and_then(get_double_value);
+
+ match (cross_above, cross_below) {
+ (Some(above), Some(below)) => {
+ if reading > above && current_index < thresholds.len() - 1 {
+ current_index += 1;
+ } else if reading < below && current_index > 0 {
+ current_index -= 1;
+ } else {
+ return Some(threshold);
+ }
+ }
+ (Some(above), None) => {
+ if reading > above && current_index < thresholds.len() - 1 {
+ current_index += 1;
+ } else {
+ return Some(threshold);
+ }
+ }
+ (None, Some(below)) => {
+ if reading < below && current_index > 0 {
+ current_index -= 1;
+ } else {
+ return Some(threshold);
+ }
+ }
+ (None, None) => return Some(threshold),
+ }
+ }
+
+ // If we've gone through all thresholds, return the last one
+ thresholds.last()
+}
+
+/// Creates a `DataPoint` from an `TypedTelemetry<f64>`.
+///
+/// # Arguments
+///
+/// * `event` - The `TypedTelemetry<f64>` to convert.
+/// * `predecessor` - The predecessor of 'event' in same batch.
+///
+/// # Returns
+///
+/// A `Result` containing the created `DataPoint` or an error.
+fn create_sensor_data_point(
+ event: &TypedTelemetry<f64>,
+ predecessor: Option<&TypedTelemetry<f64>>,
+) -> Result<DataPoint, anyhow::Error> {
+ let timestamp_ns = event
+ .timestamp
+ .duration_since(std::time::UNIX_EPOCH)
+ .unwrap_or_default()
+ .as_nanos() as u64;
+
+ let mut fields = HashMap::new();
+ let mut changed = false;
+
+ if predecessor.map_or(true, |p| p.source_name != event.source_name) {
+ fields.insert(
+ "@odata.id".to_string(),
+ TypedValue {
+ value: Some(typed_value::Value::StringVal(event.source_name.clone())),
+ },
+ );
+ changed = true;
+ }
+
+ if predecessor.map_or(true, |p| p.value != event.value) {
+ fields.insert(
+ "SensorValue".to_string(),
+ TypedValue {
+ value: Some(typed_value::Value::DoubleVal(event.value)),
+ },
+ );
+ changed = true;
+ }
+
+ if changed || predecessor.is_none() {
+ fields.insert(
+ "Status.Health".to_string(),
+ TypedValue {
+ value: Some(typed_value::Value::StringVal("OK".to_owned())),
+ },
+ );
+ }
+
+ Ok(DataPoint {
+ timestamp_ns,
+ data: Some(third_party_voyager::data_point::Data::KeyValue(
+ TypedStruct { fields },
+ )),
+ ..Default::default()
+ })
+}
+
+/// Creates and manages a stream of telemetry updates.
+///
+/// # Arguments
+///
+/// * `state` - The shared application state.
+/// * `req_id` - The request ID.
+/// * `subscription_type` - The subscription type parameters.
+/// * `urls` - The Redfish URLs of telemetry events.
+/// * `server_config` - The server configuration.
+/// * `tx` - The channel sender for updates.
+///
+/// # Returns
+///
+/// A `Result` indicating success or containing a `Status` error.
+async fn create_response_stream(
+ state: Arc<AppState>,
+ req_id: String,
+ subscription_type: SubscriptionType,
+ urls: Vec<String>,
+ server_config: Arc<tokio::sync::RwLock<ProstServerConfig>>,
+ tx: mpsc::Sender<Result<Update, Status>>,
+) -> Result<(), Status> {
+ let mut rx_stream =
+ handle_subscribe_inner(state.clone(), subscription_type, urls, Some(server_config)).await;
+
+ while let Some(batch_events) = rx_stream.next().await {
+ let mut current_update: Option<Update> = None;
+ let mut predecessor: Option<TypedTelemetry<f64>> = None;
+ for event in batch_events {
+ let data_point =
+ create_sensor_data_point(&event, predecessor.as_ref()).map_err(|e| {
+ Status::internal(format!("Failed to create sensor data point: {}", e))
+ })?;
+
+ if let Some(update) = &mut current_update {
+ update.data_points.push(data_point);
+ } else {
+ current_update = Some(Update {
+ req_id: req_id.clone(),
+ data_points: vec![data_point],
+ ..Default::default()
+ });
+ }
+ predecessor = Some(event);
+ }
+
+ if let Some(update) = current_update.take() {
+ if let Err(e) = tx.send(Ok(update)).await {
+ eprintln!("Error sending update: {:?}", e);
+ return Err(Status::internal("Failed to send update"));
+ }
+ }
+ }
+ Ok(())
+}
+
+/// Helper function, creates subscription parameters from a `RequestFqp`.
+///
+/// # Arguments
+///
+/// * `req_fqp` - The `RequestFqp` to create parameters from.
+///
+/// # Returns
+///
+/// A `SubscriptionType` struct containing the subscription parameters on success or Status on
+/// error.
+fn get_subscription_type(req_fqp: &RequestFqp) -> Result<SubscriptionType, Status> {
+ let subscription_type = match req_fqp.mode {
+ 1 => SubscriptionType::OnChange,
+ 2 => SubscriptionType::Periodical(
+ Duration::from_nanos(req_fqp.sample_frequency_expect_ns),
+ Duration::from_nanos(req_fqp.export_frequency_ns),
+ ),
+ 3 => SubscriptionType::Periodical(
+ // TODO: get default polling parameters from server_config
+ Duration::from_secs(1),
+ Duration::from_secs(10),
+ ),
+ _ => return Err(Status::invalid_argument("Unsupported sampling mode")),
+ };
+ Ok(subscription_type)
+}
+
+/// Retrieves selected FQPs and their associated thresholds from the server configuration.
+///
+/// # Arguments
+///
+/// * `server_config` - The server configuration.
+/// * `req_config_group` - The requested configuration group name.
+///
+/// # Returns
+///
+/// A vector of tuples containing the selected FQPs and their thresholds.
+async fn get_selected_fqps(
+ server_config: &Arc<tokio::sync::RwLock<ProstServerConfig>>,
+ req_config_group: &str,
+) -> Vec<(String, Option<Threshold>)> {
+ let server_config = server_config.read().await;
+ let mut result = Vec::new();
+
+ // Step 1: Get the ConfigGroup from the top-level map
+ if let Some(config_group) = server_config.cfg_groups.get(req_config_group) {
+ // Step 2: Iterate through req_fqp_names in the ConfigGroup
+ for req_fqp_name in &config_group.req_fqp_names {
+ // Step 3: Get the ReqFqpConfig from the second-level map
+ if let Some(req_fqp_config) = server_config.req_fqp_configs.get(req_fqp_name) {
+ // Step 4: Iterate through RequestFqp in ReqFqpConfig
+ for req_fqp in &req_fqp_config.req_fqp {
+ if let Some(fqp) = &req_fqp.fqp {
+ let specifier = fqp.specifier.clone();
+
+ // Step 5: Get the matching Threshold from the threshold_config map
+ let threshold =
+ if let Some(request_fqp::Config::ThresholdConfig(threshold_config)) =
+ &req_fqp.config
+ {
+ server_config
+ .threshold_config
+ .get(&req_fqp.req_fqp_name)
+ .and_then(|thresholds| {
+ thresholds
+ .threshold
+ .iter()
+ .find(|t| t.name == *threshold_config)
+ .cloned()
+ })
+ } else {
+ None
+ };
+
+ result.push((specifier, threshold));
+ }
+ }
+ }
+ }
+ }
+
+ result
+}
+
+/// Handles requests for a specific configuration group.
+///
+/// # Arguments
+///
+/// * `state` - The shared application state.
+/// * `server_config` - The server configuration.
+/// * `req` - The telemetry request.
+/// * `tx` - The channel sender for updates.
+///
+/// # Returns
+///
+/// A `Result` indicating success or containing a `Status` error.
+async fn handle_config_group(
+ state: Arc<AppState>,
+ server_config: Arc<tokio::sync::RwLock<ProstServerConfig>>,
+ req: TelemetryRequest,
+ tx: mpsc::Sender<Result<Update, Status>>,
+) -> Result<(), Status> {
+ let selected = get_selected_fqps(&server_config, &req.req_config_group).await;
+ println!(
+ "handle_config_group {} selected Fqp and Theshold: {:#?}",
+ &req.req_config_group, selected
+ );
+ let urls = selected.iter().map(|(string, _)| string.clone()).collect();
+ let subscription_type = SubscriptionType::Periodical(
+ // TODO: get default polling parameters from server_config
+ Duration::from_secs(1),
+ Duration::from_secs(10),
+ );
+ create_response_stream(
+ state,
+ req.req_id,
+ subscription_type,
+ urls,
+ server_config,
+ tx,
+ )
+ .await
+}
+
+/// Handles a single FQP (Fully Qualified Path) request.
+///
+/// # Arguments
+///
+/// * `state` - The shared application state.
+/// * `server_config` - The server configuration.
+/// * `req_fqp` - The FQP request.
+/// * `req_id` - The request ID.
+/// * `tx` - The channel sender for updates.
+///
+/// # Returns
+///
+/// A `Result` indicating success or containing a `Status` error.
+async fn handle_fqp(
+ state: Arc<AppState>,
+ server_config: Arc<tokio::sync::RwLock<ProstServerConfig>>,
+ req_fqp: &RequestFqp,
+ req_id: String,
+ tx: mpsc::Sender<Result<Update, Status>>,
+) -> Result<(), Status> {
+ let fqp = match &req_fqp.fqp {
+ Some(fqp) => fqp,
+ None => return Err(Status::invalid_argument("Address not specified")),
+ };
+ let identifiers: HashMap<String, Vec<String>> = fqp
+ .identifiers
+ .iter()
+ .map(|(k, v)| (k.clone(), vec![v.clone()]))
+ .collect();
+
+ // TODO: need adapt to proto encoded filters
+
+ let segments: Vec<&str> = fqp.specifier.split('/').collect();
+ let urls = match get_xpath_urls(&state, &identifiers, &segments).await {
+ Ok(urls) => urls,
+ Err(_) => return Err(Status::internal("Failed to get XPath URLs")),
+ };
+
+ let subscription_type = get_subscription_type(req_fqp)?;
+ create_response_stream(state, req_id, subscription_type, urls, server_config, tx).await
+}
+
+/// Handles FqpType::RedfishResource request, when Fqp.specifier represents an odata.type,
+/// and the Fqp.specifier is used to select all sensors when it's "*Sensor".
+///
+/// # Arguments
+///
+/// * `state` - The shared application state.
+/// * `server_config` - The server configuration.
+/// * `req_fqp` - The FQP request.
+/// * `req_id` - The request ID.
+/// * `tx` - The channel sender for updates.
+///
+/// # Returns
+///
+/// A `Result` indicating success or containing a `Status` error.
+async fn handle_sensor_request(
+ state: Arc<AppState>,
+ server_config: Arc<tokio::sync::RwLock<ProstServerConfig>>,
+ req_fqp: &RequestFqp,
+ req_id: String,
+ tx: mpsc::Sender<Result<Update, Status>>,
+) -> Result<(), Status> {
+ // Use fixed identifiers
+ let identifiers: HashMap<String, Vec<String>> = HashMap::from([
+ ("ChassisId".to_string(), vec!["*".to_string()]),
+ ("SensorId".to_string(), vec!["*".to_string()]),
+ ]);
+ let segments: Vec<&str> = "/redfish/v1/Chassis/{ChassisId}/Sensors/{SensorId}"
+ .split('/')
+ .collect();
+
+ let urls = match get_xpath_urls(&state, &identifiers, &segments).await {
+ Ok(urls) => urls,
+ Err(_) => return Err(Status::internal("Failed to get XPath URLs")),
+ };
+
+ let subscription_type = get_subscription_type(req_fqp)?;
+ create_response_stream(state, req_id, subscription_type, urls, server_config, tx).await
+}
+
+#[tonic::async_trait]
+impl MachineTelemetry for BmcTelemetryService {
+ type SubscribeV2Stream = ReceiverStream<Result<Update, Status>>;
+ type SubscribeStream = Pin<Box<dyn Stream<Item = Result<Update, Status>> + Send + 'static>>;
+
+ /// Handles subscription requests and sets up a stream of telemetry updates.
+ ///
+ /// # Arguments
+ ///
+ /// * `request` - The incoming subscription request.
+ ///
+ /// # Returns
+ ///
+ /// A `Result` containing a `Response` with the subscription stream or a `Status` error.
+ async fn subscribe_v2(
+ &self,
+ request: Request<tonic::Streaming<TelemetryRequest>>,
+ ) -> Result<Response<Self::SubscribeV2Stream>, Status> {
+ let mut stream = request.into_inner();
+ let state = self.state.clone();
+ let server_config = self.server_config.clone();
+
+ let (tx, rx) = mpsc::channel(16); // Adjust channel size as needed
+
+ tokio::spawn(async move {
+ while let Some(req) = match stream.message().await {
+ Ok(req) => req,
+ Err(e) => {
+ eprintln!("Error receiving stream message: {:?}", e);
+ return;
+ }
+ } {
+ if !req.req_config_group.is_empty() {
+ // Client subscribe by a server config name, like
+ // TelemetryRequest {
+ // req_id: "req_repairability".into(),
+ // req_config_group: "repairability_basic_cfg_group".into(),
+ // ..Default::default()
+ // };
+ if let Err(e) =
+ handle_config_group(state.clone(), server_config.clone(), req, tx.clone())
+ .await
+ {
+ eprintln!("Handler error: {:?}", e);
+ }
+ continue;
+ }
+ for req_fqp in &req.req_fqp {
+ if let Some(fqp) = &req_fqp.fqp {
+ println!("fqp: {:?}", fqp);
+ match FqpType::try_from(fqp.r#type) {
+ Ok(fqp_type) => {
+ match fqp_type {
+ FqpType::NotSet => {
+ // Client subscribe by a Fqp, with wildcard match to select
+ // all sensors on all chassis:
+ // Fqp {
+ // specifier: "/redfish/v1/Chassis/{ChassisId}/Sensors/{SensorId}".into(),
+ // identifiers: HashMap::from([
+ // ("ChassisId".into(), "*".into()),
+ // ("SensorId".into(), "*".into()),
+ // ]),
+ // r#type: FqpType::NotSet as i32,
+ // ..Default::default()
+ // };
+ if let Err(e) = handle_fqp(
+ state.clone(),
+ server_config.clone(),
+ req_fqp,
+ req.req_id.clone(),
+ tx.clone(),
+ )
+ .await
+ {
+ eprintln!("Handler error: {:?}", e);
+ }
+ }
+ FqpType::RedfishResource => {
+ // Client subscribe by a Fqp, with Redfish odata.type to
+ // select all resouces has that type:
+ // Fqp {
+ // specifier: ""#Sensor.v1_2_0.Sensor"".into(),
+ // r#type: FqpType::RedfishResource as i32,
+ // ..Default::default()
+ // };
+ if fqp.specifier.contains("Sensor") {
+ if let Err(e) = handle_sensor_request(
+ state.clone(),
+ server_config.clone(),
+ req_fqp,
+ req.req_id.clone(),
+ tx.clone(),
+ )
+ .await
+ {
+ eprintln!("Handler error: {:?}", e);
+ }
+ } else {
+ eprintln!("Subscribe by RedfishResource for {} not implemented", fqp.specifier);
+ }
+ }
+ }
+ }
+ Err(e) => {
+ eprintln!("Invalid FqpType value: {}. Error: {:?}", fqp.r#type, e);
+ continue;
+ }
+ }
+ }
+ }
+ }
+ });
+
+ Ok(Response::new(ReceiverStream::new(rx)))
+ }
+
+ async fn get(&self, _: Request<TelemetryRequest>) -> Result<Response<Update>, Status> {
+ Err(Status::new(
+ Code::Internal,
+ "gRPC method not implemented".to_string(),
+ ))
+ }
+
+ async fn put(&self, _: Request<SetRequest>) -> Result<Response<Update>, Status> {
+ Err(Status::new(
+ Code::Internal,
+ "gRPC method not implemented".to_string(),
+ ))
+ }
+
+ async fn post(&self, _: Request<SetRequest>) -> Result<Response<Update>, Status> {
+ Err(Status::new(
+ Code::Internal,
+ "gRPC method not implemented".to_string(),
+ ))
+ }
+
+ async fn patch(&self, _: Request<SetRequest>) -> Result<Response<Update>, Status> {
+ Err(Status::new(
+ Code::Internal,
+ "gRPC method not implemented".to_string(),
+ ))
+ }
+
+ async fn delete(&self, _: Request<SetRequest>) -> Result<Response<Update>, Status> {
+ Err(Status::new(
+ Code::Internal,
+ "gRPC method not implemented".to_string(),
+ ))
+ }
+
+ async fn subscribe(
+ &self,
+ _: Request<TelemetryRequest>,
+ ) -> Result<Response<Self::SubscribeStream>, Status> {
+ Err(Status::new(
+ Code::Internal,
+ "gRPC method not implemented".to_string(),
+ ))
+ }
+}
diff --git a/streaming_telemetry/src/lib.rs b/streaming_telemetry/src/lib.rs
new file mode 100644
index 0000000..4f40c59
--- /dev/null
+++ b/streaming_telemetry/src/lib.rs
@@ -0,0 +1,7 @@
+pub mod app_state;
+pub mod composite_query;
+pub mod dbus_client;
+pub mod grpc;
+pub mod handlers;
+pub mod mtls;
+pub mod telemetry_source_manager;
diff --git a/streaming_telemetry/src/main.rs b/streaming_telemetry/src/main.rs
new file mode 100644
index 0000000..f9f09fc
--- /dev/null
+++ b/streaming_telemetry/src/main.rs
@@ -0,0 +1,138 @@
+mod app_state;
+mod composite_query;
+mod dbus_client;
+mod grpc;
+mod handlers;
+#[cfg(feature = "mtls")]
+mod mtls;
+mod telemetry_source_manager;
+use crate::grpc::telemetry_server::{load_server_config, BmcTelemetryService};
+use crate::grpc::third_party_voyager::machine_telemetry_server::MachineTelemetryServer;
+
+use crate::app_state::AppState;
+#[cfg(feature = "mtls")]
+use crate::mtls::mtls::run_secure_server;
+use clap::{arg, Command};
+use handlers::chassis::ChassisState;
+use std::sync::Arc;
+use telemetry_source_manager::telemetry_source_manager_api::create_telemetry_source_manager;
+
+fn cli() -> Command {
+ Command::new("my_server")
+ .about("Starts a server with the specified configuration")
+ .arg(
+ arg!(-c --config <CONFIG> "Streaming telemetry server config textproto file")
+ .default_value("server_config.textproto")
+ .value_parser(clap::value_parser!(String)),
+ )
+ .arg(
+ arg!(-i --insecure "Disables SSL certificate verification")
+ .required(false)
+ .default_value("false")
+ .action(clap::ArgAction::SetTrue),
+ )
+ .arg(
+ arg!(-k --key <KEY> "Server's private key file name")
+ .required_if_eq("insecure", "false")
+ .value_parser(clap::value_parser!(String)),
+ )
+ .arg(
+ arg!(-c --cert <CERT> "Server's certificate file name")
+ .required_if_eq("insecure", "false")
+ .value_parser(clap::value_parser!(String)),
+ )
+ .arg(
+ arg!(-a --cacert <CACERT> "CA certificate chain file name")
+ .required_if_eq("insecure", "false")
+ .value_parser(clap::value_parser!(String)),
+ )
+ .arg(
+ arg!(-p --policy <POLICY> "Client authentication policy file name")
+ .required_if_eq("insecure", "false")
+ .value_parser(clap::value_parser!(String)),
+ )
+ .arg(
+ arg!(-s --crls <CRLS> "Certificate Revocation List file name")
+ .required(false)
+ .value_parser(clap::value_parser!(String)),
+ )
+ .arg(
+ arg!(-P --port <PORT> "Server's listening port number")
+ .required(true)
+ .value_parser(clap::value_parser!(u16)),
+ )
+ .arg(
+ arg!(-e --emconfig <EMCONFIG> "EntityManager cofnig files separated by comma")
+ .required(true)
+ .value_parser(clap::value_parser!(String)),
+ )
+}
+
+async fn run_insecure_server(
+ port: u16,
+ grpc: MachineTelemetryServer<BmcTelemetryService>,
+) -> Result<(), Box<dyn std::error::Error>> {
+ let addr = format!("[::]:{port}").parse()?;
+ tonic::transport::Server::builder()
+ .add_service(grpc)
+ .serve(addr)
+ .await?;
+
+ Ok(())
+}
+
+#[tokio::main]
+async fn main() -> Result<(), Box<dyn std::error::Error>> {
+ env_logger::init();
+
+ let matches = cli().get_matches();
+
+ let port = matches
+ .get_one::<u16>("port")
+ .expect("required argument")
+ .to_owned();
+
+ let server_config = matches.get_one::<String>("config").unwrap();
+ let server_config = load_server_config(server_config).unwrap();
+ // The server_config will not be read at data path, means when data streaming started, the
+ // application will not try to access it anymore
+ let server_config = Arc::new(tokio::sync::RwLock::new(server_config));
+
+ let em_config = matches.get_one::<String>("emconfig").unwrap();
+ let em_config: Vec<String> = em_config.split(',').map(|s| s.trim().to_string()).collect();
+
+ let chassis_state = ChassisState::default();
+ let telemetry_source_manager =
+ create_telemetry_source_manager(1_000, 10_000, server_config.clone(), em_config).await?;
+ let state = Arc::new(AppState {
+ chassis_state,
+ telemetry_source_manager,
+ });
+
+ let grpc = MachineTelemetryServer::new(BmcTelemetryService {
+ state,
+ server_config,
+ });
+
+ if matches.get_flag("insecure") {
+ println!("Running in insecure mode on port {}...", port);
+ run_insecure_server(port, grpc).await
+ } else {
+ #[cfg(feature = "mtls")]
+ {
+ let key = matches.get_one::<String>("key").unwrap();
+ let cert = matches.get_one::<String>("cert").unwrap();
+ let cacert = matches.get_one::<String>("cacert").unwrap();
+ let policy = matches.get_one::<String>("policy").unwrap();
+ let crls = matches.get_one::<String>("crls").map(|s| s.as_str());
+
+ println!("Starting secure server on port {}...", port);
+ println!("Using key file: {}", key);
+ println!("Using certificate file: {}", cert);
+ println!("Using CA certificate file: {}", cacert);
+ println!("Using policy file: {}", policy);
+ run_secure_server(port, key, cert, cacert, policy, crls, grpc).await?;
+ }
+ Ok(())
+ }
+}
diff --git a/streaming_telemetry/src/mtls/mod.rs b/streaming_telemetry/src/mtls/mod.rs
new file mode 100644
index 0000000..9ef166d
--- /dev/null
+++ b/streaming_telemetry/src/mtls/mod.rs
@@ -0,0 +1,3 @@
+#![allow(clippy::module_inception)]
+#[cfg(feature = "mtls")]
+pub mod mtls;
diff --git a/streaming_telemetry/src/mtls/mtls.rs b/streaming_telemetry/src/mtls/mtls.rs
new file mode 100644
index 0000000..117e9ef
--- /dev/null
+++ b/streaming_telemetry/src/mtls/mtls.rs
@@ -0,0 +1,469 @@
+//! Secure gRPC server implementation with custom TLS configuration and client certificate verification.
+//!
+//! This module provides functionality to run a secure gRPC server with mutual TLS authentication,
+//! certificate revocation checking, and custom client certificate validation using MTLS.
+
+use crate::grpc::telemetry_server::BmcTelemetryService;
+use crate::grpc::third_party_voyager::machine_telemetry_server::MachineTelemetryServer;
+use rustls::{
+ client::danger::HandshakeSignatureValid, pki_types::UnixTime,
+ server::danger::ClientCertVerifier, DigitallySignedStruct, DistinguishedName, Error,
+ RootCertStore, SignatureScheme,
+};
+use rustls_pki_types::{CertificateDer, PrivateKeyDer};
+use std::fs::File;
+use std::pin::Pin;
+use std::sync::Arc;
+use std::task::{Context, Poll};
+use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
+use x509_parser::prelude::*;
+
+/// Represents connection information for MTLS.
+#[derive(Clone)]
+struct MtlsConnectInfo {
+ #[allow(dead_code)]
+ remote_addr: std::net::SocketAddr,
+}
+
+/// A wrapper around a TLS stream that includes connection information.
+struct TlsStreamWithAddr {
+ stream: tokio_rustls::server::TlsStream<tokio::net::TcpStream>,
+ connect_info: MtlsConnectInfo,
+}
+
+// Implement AsyncRead, AsyncWrite, and tonic::transport::server::Connected traits for TlsStreamWithAddr
+impl AsyncRead for TlsStreamWithAddr {
+ fn poll_read(
+ mut self: Pin<&mut Self>,
+ cx: &mut Context<'_>,
+ buf: &mut ReadBuf<'_>,
+ ) -> Poll<std::io::Result<()>> {
+ Pin::new(&mut self.stream).poll_read(cx, buf)
+ }
+}
+
+impl AsyncWrite for TlsStreamWithAddr {
+ fn poll_write(
+ mut self: Pin<&mut Self>,
+ cx: &mut Context<'_>,
+ buf: &[u8],
+ ) -> Poll<std::io::Result<usize>> {
+ Pin::new(&mut self.stream).poll_write(cx, buf)
+ }
+
+ fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
+ Pin::new(&mut self.stream).poll_flush(cx)
+ }
+
+ fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
+ Pin::new(&mut self.stream).poll_shutdown(cx)
+ }
+}
+
+impl tonic::transport::server::Connected for TlsStreamWithAddr {
+ type ConnectInfo = MtlsConnectInfo;
+
+ fn connect_info(&self) -> Self::ConnectInfo {
+ self.connect_info.clone()
+ }
+}
+
+/// Custom client certificate verifier that includes CRL checking and MTLS validation.
+#[derive(Debug)]
+struct CustomClientCertVerifier {
+ inner: Arc<dyn ClientCertVerifier>,
+ crls: Option<Vec<u8>>,
+}
+
+impl CustomClientCertVerifier {
+ /// Creates a new CustomClientCertVerifier.
+ ///
+ /// SAFETY: unwrap in this function is allowed, should only be called in server init
+ ///
+ /// # Arguments
+ ///
+ /// * `root_cert_store` - The root certificate store for verifying client certificates.
+ /// * `crls_dir` - Optional directory containing Certificate Revocation Lists.
+ fn new(root_cert_store: RootCertStore, crls_dir: Option<&str>) -> Self {
+ let client_verifier =
+ rustls::server::WebPkiClientVerifier::builder(Arc::new(root_cert_store))
+ .build()
+ .unwrap();
+
+ let crls = match crls_dir {
+ Some(dir) => {
+ let mut all_crls = Vec::new();
+ match std::fs::read_dir(dir) {
+ Ok(entries) => {
+ for entry in entries.flatten() {
+ if let Ok(file_type) = entry.file_type() {
+ if file_type.is_file() {
+ match std::fs::read(entry.path()) {
+ Ok(data) => all_crls.extend(data),
+ Err(e) => println!(
+ "WARNING! Failed to read CRL file {:?}: {}",
+ entry.path(),
+ e
+ ),
+ }
+ }
+ }
+ }
+ if all_crls.is_empty() {
+ println!("WARNING! No CRL files were successfully read from {}", dir);
+ None
+ } else {
+ Some(all_crls)
+ }
+ }
+ Err(e) => {
+ println!("WARNING! Failed to read CRL directory {}: {}", dir, e);
+ None
+ }
+ }
+ }
+ None => None,
+ };
+
+ CustomClientCertVerifier {
+ inner: client_verifier,
+ crls,
+ }
+ }
+
+ /// Checks if a certificate is revoked using the loaded CRLs.
+ ///
+ /// # Arguments
+ ///
+ /// * `certificate` - The X509 certificate to check.
+ ///
+ /// # Returns
+ ///
+ /// `true` if the certificate is revoked, `false` otherwise.
+ fn check_crl(&self, certificate: &X509Certificate) -> bool {
+ if let Some(data) = &self.crls {
+ // Determine if the data is DER or PEM format
+ // SAFETY: starts_with has out-of-range check
+ let der_data: Vec<u8> = if data.starts_with(&[0x30, 0x82]) {
+ // Data is likely in DER format
+ data.clone()
+ } else {
+ // Data is likely in PEM format, try to parse it
+ match parse_x509_pem(data) {
+ Ok((_, pem)) => pem.contents.clone(),
+ Err(_) => {
+ println!("Could not decode the PEM file");
+ return false; // If parsing fails, return false
+ }
+ }
+ };
+
+ match parse_x509_crl(&der_data) {
+ Ok((_, crl)) => {
+ // Check if the certificate's serial number is in the list of revoked certificates
+ crl.iter_revoked_certificates().any(|rc| {
+ println!("revoked serial {:?}", rc.raw_serial());
+ println!(
+ "certificate serial {:?}, is revoked {:?}",
+ certificate.tbs_certificate.raw_serial(),
+ rc.raw_serial() == certificate.tbs_certificate.raw_serial()
+ );
+ rc.raw_serial() == certificate.tbs_certificate.raw_serial()
+ })
+ }
+ Err(_) => {
+ println!("Could not decode DER data");
+ false // If parsing fails, return false
+ }
+ }
+ } else {
+ false // If no CRL data is present, return false
+ }
+ }
+}
+
+/// Verifies a client certificate using MTLS.
+///
+/// # Arguments
+///
+/// * `peer_dns_names` - DNS names from the client certificate.
+/// * `peer_uri_names` - URI names from the client certificate.
+/// * `root_subject_str` - Root subject string for validation.
+///
+/// # Returns
+///
+/// A result indicating whether the client certificate is valid.
+fn mtls_verify_client_cert(
+ _peer_dns_names: Vec<String>,
+ _peer_uri_names: Vec<String>,
+ _root_subject_str: &str,
+) -> Result<rustls::server::danger::ClientCertVerified, rustls::Error> {
+ // TODO: do authentication based on client cert's URI, DNS, SANs
+ println!("MTLS client validation passed");
+ Ok(rustls::server::danger::ClientCertVerified::assertion())
+}
+
+// Implement ClientCertVerifier for CustomClientCertVerifier
+impl ClientCertVerifier for CustomClientCertVerifier {
+ fn root_hint_subjects(&self) -> &[DistinguishedName] {
+ self.inner.root_hint_subjects()
+ }
+
+ fn verify_client_cert(
+ &self,
+ end_entity: &CertificateDer<'_>,
+ intermediates: &[CertificateDer<'_>],
+ now: UnixTime,
+ ) -> Result<rustls::server::danger::ClientCertVerified, rustls::Error> {
+ // Verify the client certificate chain
+ self.inner
+ .verify_client_cert(end_entity, intermediates, now)?;
+
+ let root_subject_str = "".to_string();
+ let subject = self.inner.root_hint_subjects().to_owned();
+ for name in &subject {
+ match parse_distinguished_name(name) {
+ // TODO: set the name_string to root_subject_str
+ Ok(name_string) => println!("Distinguished Name: {}", name_string),
+ Err(e) => eprintln!("Failed to parse distinguished name: {}", e),
+ }
+ }
+
+ // Parse the DER-encoded certificate
+ let (_rem, cert) = match X509Certificate::from_der(end_entity) {
+ Ok((rem, cert)) => {
+ if !rem.is_empty() {
+ return Err(rustls::Error::General(
+ "Certificate parser did not consume all input".to_string(),
+ ));
+ }
+ (rem, cert)
+ }
+ Err(e) => {
+ return Err(rustls::Error::General(format!(
+ "Error parsing certificate: {}",
+ e
+ )))
+ }
+ };
+
+ // Check if the certificate is revoked
+ if self.check_crl(&cert) {
+ return Err(rustls::Error::General(
+ "Client certificate revoked".to_string(),
+ ));
+ }
+
+ let sans = extract_sans(&cert);
+ match sans {
+ Ok((dns_names, uris)) => {
+ println!("DNS Names: {:?}", dns_names);
+ println!("URIs: {:?}", uris);
+ mtls_verify_client_cert(dns_names, uris, &root_subject_str)
+ }
+ Err(e) => {
+ println!("Error extracting SANs, but continuing: {}", e);
+ Err(rustls::Error::General(
+ "ValidatePeer failed to extract SANs".to_string(),
+ ))
+ }
+ }
+ }
+
+ fn verify_tls12_signature(
+ &self,
+ message: &[u8],
+ cert: &CertificateDer<'_>,
+ dss: &DigitallySignedStruct,
+ ) -> Result<HandshakeSignatureValid, Error> {
+ self.inner.verify_tls12_signature(message, cert, dss)
+ }
+
+ fn verify_tls13_signature(
+ &self,
+ message: &[u8],
+ cert: &CertificateDer<'_>,
+ dss: &DigitallySignedStruct,
+ ) -> Result<HandshakeSignatureValid, Error> {
+ self.inner.verify_tls13_signature(message, cert, dss)
+ }
+
+ fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
+ self.inner.supported_verify_schemes()
+ }
+}
+
+/// Extracts Subject Alternative Names (SANs) from an X509 certificate.
+///
+/// # Arguments
+///
+/// * `cert` - The X509 certificate to extract SANs from.
+///
+/// # Returns
+///
+/// A tuple containing vectors of DNS names and URIs.
+fn extract_sans(
+ cert: &X509Certificate,
+) -> Result<(Vec<String>, Vec<String>), Box<dyn std::error::Error>> {
+ // Extract SANs
+ let sans = cert.tbs_certificate.subject_alternative_name()?;
+ let sans = sans.ok_or("No SAN extension found")?;
+ let dns_names = sans
+ .value
+ .general_names
+ .iter()
+ .filter_map(|name| {
+ if let GeneralName::DNSName(dns) = name {
+ Some(dns.to_string())
+ } else {
+ None
+ }
+ })
+ .collect();
+
+ let uris = sans
+ .value
+ .general_names
+ .iter()
+ .filter_map(|name| {
+ if let GeneralName::URI(uri) = name {
+ Some(uri.to_string())
+ } else {
+ None
+ }
+ })
+ .collect();
+
+ Ok((dns_names, uris))
+}
+
+/// Parses a DistinguishedName into a string representation.
+///
+/// # Arguments
+///
+/// * `dn` - The DistinguishedName to parse.
+///
+/// # Returns
+///
+/// A string representation of the DistinguishedName.
+fn parse_distinguished_name(dn: &DistinguishedName) -> Result<String, Box<dyn std::error::Error>> {
+ // Attempt to parse the DistinguishedName as an X509Name
+ let parsed_name = X509Name::from_der(dn.as_ref())?;
+ Ok(parsed_name.1.to_string())
+}
+
+/// Loads certificates from a PEM file.
+///
+/// # Arguments
+///
+/// * `file_name` - The name of the file containing the certificates.
+///
+/// # Returns
+///
+/// A vector of loaded certificates.
+pub fn load_cert(file_name: &str) -> std::io::Result<Vec<CertificateDer<'static>>> {
+ let file = File::open(file_name)?;
+ let mut reader = std::io::BufReader::new(file);
+ rustls_pemfile::certs(&mut reader).collect()
+}
+
+/// Loads a private key from a PEM file.
+///
+/// SAFETY: unwrap in this function is allowed
+///
+/// # Arguments
+///
+/// * `file_name` - The name of the file containing the private key.
+///
+/// # Returns
+///
+/// The loaded private key.
+fn load_key(file_name: &str) -> std::io::Result<PrivateKeyDer<'static>> {
+ let file = File::open(file_name)?;
+ let mut reader = std::io::BufReader::new(file);
+ rustls_pemfile::private_key(&mut reader).map(|key| key.unwrap())
+}
+
+/// Runs the secure gRPC server.
+///
+/// SAFETY: unwrap in this function is allowed
+///
+/// # Arguments
+///
+/// * `port` - The port number to listen on.
+/// * `key` - Path to the server's private key file.
+/// * `cert` - Path to the server's certificate file.
+/// * `cacert` - Path to the CA certificate file for verifying client certificates.
+/// * `policy` - Path to the MTLS policy file.
+/// * `crls` - Optional path to the directory containing CRLs.
+/// * `grpc` - The gRPC service to serve.
+///
+/// # Returns
+///
+/// A result indicating whether the server ran successfully.
+pub async fn run_secure_server(
+ port: u16,
+ key: &str,
+ cert: &str,
+ cacert: &str,
+ _policy: &str,
+ crls: Option<&str>,
+ grpc: MachineTelemetryServer<BmcTelemetryService>,
+) -> Result<(), Box<dyn std::error::Error>> {
+ // Load server's private key and certificate
+ let cert = load_cert(cert)?;
+ let key = load_key(key)?;
+
+ // Load root CA certificate to verify clients
+ let client_ca_cert: Vec<CertificateDer<'static>> = load_cert(cacert)?;
+ let mut root_cert_store = rustls::RootCertStore::empty();
+ for ca_cert in client_ca_cert {
+ let _ = root_cert_store.add(ca_cert);
+ }
+
+ let client_verifier = Arc::new(CustomClientCertVerifier::new(root_cert_store, crls));
+ let mut tls_config = rustls::ServerConfig::builder()
+ .with_client_cert_verifier(client_verifier)
+ .with_single_cert(cert, key)
+ .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?;
+ tls_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec(), b"http/1.0".to_vec()];
+
+ let tls_config = Arc::new(tls_config);
+
+ let acceptor = tokio_rustls::TlsAcceptor::from(tls_config);
+
+ let addr = format!("[::]:{port}");
+ let addr: std::net::SocketAddr = addr.parse().unwrap();
+ let listener = tokio::net::TcpListener::bind(addr).await?;
+ println!("Server listening on {}", addr);
+
+ let incoming_tls_stream = futures_util::stream::unfold(listener, |listener| async {
+ let (socket, remote_addr) = match listener.accept().await {
+ Ok(conn) => conn,
+ Err(_) => return None,
+ };
+ match acceptor.accept(socket).await {
+ Ok(tls_stream) => {
+ let connect_info = MtlsConnectInfo { remote_addr };
+ Some((
+ Ok(TlsStreamWithAddr {
+ stream: tls_stream,
+ connect_info,
+ }),
+ listener,
+ ))
+ }
+ Err(_) => Some((
+ Err(std::io::Error::new(std::io::ErrorKind::Other, "TLS Error")),
+ listener,
+ )),
+ }
+ });
+
+ tonic::transport::Server::builder()
+ .add_service(grpc)
+ .serve_with_incoming(incoming_tls_stream)
+ .await?;
+
+ Ok(())
+}
diff --git a/streaming_telemetry/src/telemetry_client.rs b/streaming_telemetry/src/telemetry_client.rs
new file mode 100644
index 0000000..ef0c227
--- /dev/null
+++ b/streaming_telemetry/src/telemetry_client.rs
@@ -0,0 +1,249 @@
+use anyhow::{anyhow, Result};
+use chrono::{DateTime, NaiveDateTime, Utc};
+use clap::{arg, Command};
+use futures_util::stream::StreamExt;
+#[cfg(feature = "client_debug")]
+use redfish_codegen::models::sensor::v1_7_0::Sensor as SensorModel;
+#[cfg(feature = "client_debug")]
+use reqwest::Client;
+#[cfg(feature = "client_debug")]
+use serde_json::Value;
+#[cfg(feature = "client_debug")]
+use std::collections::HashMap;
+use std::fs::File;
+use std::io::Read;
+use tonic::transport::{Certificate, Channel, ClientTlsConfig, Identity};
+use tonic::Request;
+
+mod app_state;
+mod composite_query;
+mod dbus_client;
+mod grpc;
+mod handlers;
+mod telemetry_source_manager;
+
+#[cfg(feature = "client_debug")]
+use crate::grpc::third_party_voyager::{Fqp, FqpType, RequestFqp};
+
+use crate::grpc::third_party_voyager::{
+ data_point, machine_telemetry_client::MachineTelemetryClient, typed_value,
+ Request as TelemetryRequest, Update,
+};
+
+fn format_timestamp(timestamp_ns: u64) -> String {
+ // Convert nanoseconds to seconds and nanoseconds
+ let seconds = (timestamp_ns / 1_000_000_000) as i64;
+ let nanoseconds = (timestamp_ns % 1_000_000_000) as u32;
+
+ // Create a NaiveDateTime from the timestamp
+ let naive = NaiveDateTime::from_timestamp_opt(seconds, nanoseconds).expect("Invalid timestamp");
+
+ // Convert to DateTime<Utc>
+ let datetime: DateTime<Utc> = DateTime::from_utc(naive, Utc);
+
+ // Format the datetime
+ datetime.format("%Y:%m:%d:%H:%M:%S.%3f").to_string()
+}
+
+async fn print_datapoint(update: &Update) -> Result<()> {
+ for (index, datapoint) in update.data_points.iter().enumerate() {
+ if let Some(data_point::Data::KeyValue(typed_struct)) = &datapoint.data {
+ print!("{index}th datapoint, ");
+ match typed_struct
+ .fields
+ .get("@odata.id")
+ .and_then(|v| v.value.as_ref())
+ {
+ Some(typed_value::Value::StringVal(id)) => print!("@odata.id: {}, ", id),
+ None => print!("@odata.id not changed, "),
+ _ => print!("@odata.id invalid type, "),
+ }
+
+ match typed_struct
+ .fields
+ .get("SensorValue")
+ .and_then(|v| v.value.as_ref())
+ {
+ Some(typed_value::Value::DoubleVal(value)) => print!("value: {}, ", value),
+ None => print!("value not changed, "),
+ _ => print!("value invalid type, "),
+ }
+
+ #[cfg(feature = "client_debug")]
+ {
+ // Fetch data from local server
+ let url = format!("http://localhost:8088{}", odata_id);
+ let client = Client::new();
+ let response = client.get(&url).send().await?;
+ let body = response.text().await?;
+ let sensor: SensorModel = serde_json::from_str(&body)?;
+
+ if let (Some(reading), Some(min), Some(max)) = (
+ sensor.reading,
+ sensor.reading_range_min,
+ sensor.reading_range_max,
+ ) {
+ let corrected_reading = reading.max(min).min(max);
+ println!(" Fetched Reading: {}", corrected_reading);
+
+ // Compare readings
+ let difference = (sensor_value - corrected_reading).abs();
+ let threshold = corrected_reading * 0.05; // 5% of the reading
+ if difference > threshold {
+ eprintln!("Error: sensor {odata_id} value {sensor_value} is out of 5% range of fetched Reading {corrected_reading}");
+ return Err(anyhow!("Value not matching Redfish query result"));
+ }
+ } else {
+ return Err(anyhow!("Failed to get Reading from fetched data"));
+ }
+ }
+ }
+ println!("timestamp: {}", format_timestamp(datapoint.timestamp_ns));
+ }
+ println!();
+
+ Ok(())
+}
+
+fn cli() -> Command {
+ Command::new("my_client")
+ .about("Starts a client with the specified configuration")
+ .arg(
+ arg!(-i --insecure "Disables SSL certificate verification")
+ .required(false)
+ .default_value("false")
+ .action(clap::ArgAction::SetTrue),
+ )
+ .arg(
+ arg!(-k --key <KEY> "Server's private key file name")
+ .required_if_eq("insecure", "false")
+ .value_parser(clap::value_parser!(String)),
+ )
+ .arg(
+ arg!(-c --cert <CERT> "Server's certificate file name")
+ .required_if_eq("insecure", "false")
+ .value_parser(clap::value_parser!(String)),
+ )
+ .arg(
+ arg!(-a --cacert <CACERT> "CA certificate chain file name")
+ .required_if_eq("insecure", "false")
+ .value_parser(clap::value_parser!(String)),
+ )
+ .arg(
+ arg!(-p --policy <POLICY> "Client authentication policy file name")
+ .required_if_eq("insecure", "false")
+ .value_parser(clap::value_parser!(String)),
+ )
+ .arg(
+ arg!(-s --server_dns <SERVERNDS> "gRPC server's certificate DNS name, extract from command:
+ $ openssl x509 -noout -text -in test-realm-a-node-jybls11-nfd01.prod.google.com-cert.pem | grep DNS
+ DNS:jybls11-nfd01.prod.google.com, DNS:borg-test.node.test-realm-a.prod.spiffe.goog")
+ .required_if_eq("insecure", "false")
+ .value_parser(clap::value_parser!(String)),
+ )
+ .arg(
+ arg!(-P --port <PORT> "Server's listening port to connect")
+ .required(true)
+ .value_parser(clap::value_parser!(u16)),
+ )
+}
+
+fn load_cert(file_name: &str) -> Result<Vec<u8>, std::io::Error> {
+ let mut file = File::open(file_name)?;
+ let mut buffer = Vec::new();
+ file.read_to_end(&mut buffer)?;
+ Ok(buffer)
+}
+
+fn load_key(path: &str) -> Result<Vec<u8>, std::io::Error> {
+ load_cert(path) // Since they are both loaded the same way
+}
+
+#[tokio::main]
+async fn main() -> Result<(), Box<dyn std::error::Error>> {
+ // TODO: disable log due to https://github.com/extrawurst/gitui/issues/2094
+ // env_logger::init_from_env(
+ // env_logger::Env::default().default_filter_or("info,tonic=debug,hyper=debug,rustls=debug"),
+ // );
+
+ let matches = cli().get_matches();
+ let insecure = matches.get_flag("insecure");
+ let port = matches
+ .get_one::<u16>("port")
+ .expect("required argument")
+ .to_owned();
+
+ let url = if insecure {
+ format!("http://localhost:{port}")
+ } else {
+ format!("https://localhost:{port}")
+ };
+ let channel = if insecure {
+ Channel::builder(url.parse()?).connect().await?
+ } else {
+ #[cfg(feature = "mtls")]
+ {
+ let key = matches.get_one::<String>("key").unwrap();
+ let cert = matches.get_one::<String>("cert").unwrap();
+ let cacert = matches.get_one::<String>("cacert").unwrap();
+ let server_dns = matches.get_one::<String>("server_dns").unwrap();
+
+ // Load client's certificate and private key
+ let cert = load_cert(cert)?;
+ let key = load_key(key)?;
+ let client_identity = Identity::from_pem(cert, key);
+
+ // Load CA certificate to verify the server
+ let server_ca_cert = load_cert(cacert)?;
+ let server_ca_cert = Certificate::from_pem(server_ca_cert);
+
+ let tls_config = ClientTlsConfig::new()
+ .identity(client_identity)
+ .ca_certificate(server_ca_cert)
+ .domain_name(server_dns);
+
+ Channel::builder(url.parse()?)
+ .tls_config(tls_config)?
+ .connect()
+ .await?
+ }
+ #[cfg(not(feature = "mtls"))]
+ {
+ return Err("TLS support is not enabled".into());
+ }
+ };
+
+ let mut client = MachineTelemetryClient::new(channel);
+
+ let outbound = async_stream::stream! {
+ let requests = vec![
+ TelemetryRequest {
+ req_id: "req_repairability".into(),
+ req_config_group: "repairability_basic_cfg_group".into(),
+ ..Default::default()
+ },
+ ];
+
+ for request in requests {
+ yield request;
+ }
+ };
+
+ // Make the call to SubscribeV2
+ let response = client.subscribe_v2(Request::new(outbound)).await?;
+
+ // Handle the stream of responses
+ let mut inbound = response.into_inner();
+ while let Some(update) = inbound.next().await {
+ match update {
+ Ok(update) => {
+ #[cfg(feature = "client_debug")]
+ println!("Received update: {:?}", update);
+ let _ = print_datapoint(&update).await;
+ }
+ Err(e) => eprintln!("Error receiving update: {:?}", e),
+ }
+ }
+
+ Ok(())
+}
diff --git a/streaming_telemetry/src/telemetry_source_manager/telemetry_source_manager_api.rs b/streaming_telemetry/src/telemetry_source_manager/telemetry_source_manager_api.rs
new file mode 100644
index 0000000..66571fa
--- /dev/null
+++ b/streaming_telemetry/src/telemetry_source_manager/telemetry_source_manager_api.rs
@@ -0,0 +1,351 @@
+//! This module provides functionality for creating and managing a sensor database,
+//! handling sensor events, and subscribing to sensor updates in an OpenBMC environment.
+
+use anyhow::{Context, Result};
+use std::collections::HashMap;
+use std::sync::Arc;
+use std::time::Duration;
+
+use crate::app_state::AppState;
+use crate::grpc::telemetry_server::get_threshold_config;
+use crate::grpc::third_party_voyager::ServerConfig as ProstServerConfig;
+use crate::grpc::third_party_voyager::Thresholds;
+use crate::telemetry_source_manager::dbus_sensors::dbus_sensors::create_dbus_sensor;
+use crate::telemetry_source_manager::i2c_sensors::create_i2c_sensor;
+use crate::telemetry_source_manager::sensor_configs::{extract_sensor_frus, extract_sensors};
+use crate::telemetry_source_manager::telemetry_source_manager::{
+ SubscriptionType, Telemetry, TelemetrySourceManager, TypedTelemetry,
+};
+use log::{error, info, warn};
+use redfish_codegen::models::odata_v4;
+
+/// Creates a telemetry_source_manager by reading sensor configurations from Entity Manager config files.
+///
+/// # Arguments
+///
+/// * `sampling_interval` - The default sampling interval for sensors.
+/// * `reporting_interval` - The default reporting interval for sensors.
+/// * `_server_config` - An optional server configuration.
+///
+/// # Returns
+///
+/// An `Arc<TelemetrySourceManager>` if successful, or an error if the operation fails.
+pub async fn create_telemetry_source_manager(
+ sampling_interval: u64,
+ reporting_interval: u64,
+ _server_config: Arc<tokio::sync::RwLock<ProstServerConfig>>,
+ em_configs: Vec<String>,
+) -> Result<Arc<TelemetrySourceManager>, Box<dyn std::error::Error>> {
+ let telemetry_source_manager = TelemetrySourceManager::new();
+
+ // Extract all real sensors from Entity Manager config files
+
+ let mut all_sensors = Vec::new();
+ for file_path in &em_configs {
+ let path = std::path::PathBuf::from(file_path);
+ if path.is_file() && path.extension().and_then(|s| s.to_str()) == Some("json") {
+ all_sensors.extend(extract_sensors(path.to_str().unwrap_or_default()).await?);
+ } else {
+ eprintln!("Skipping non-JSON file: {:?}", path);
+ }
+ }
+
+ // Extract all sensor's FRU info from EM's config files
+ let mut all_sensor_frus = HashMap::new();
+ for file_path in &em_configs {
+ let path = std::path::PathBuf::from(file_path);
+ if path.is_file() && path.extension().and_then(|s| s.to_str()) == Some("json") {
+ all_sensor_frus.extend(extract_sensor_frus(path.to_str().unwrap_or_default())?);
+ }
+ }
+
+ // Create TelemetrySource objects from parsed configurations, add them to the telemetry_source_manager.
+ for sensor_config in all_sensors {
+ if let (Some(_bus), Some(_address)) =
+ (sensor_config.Bus.as_ref(), sensor_config.Address.as_ref())
+ {
+ #[allow(unused_mut)]
+ #[allow(unused_assignments)]
+ let mut add_this_sensor = true;
+ #[cfg(feature = "load-sources-from-server-config")]
+ {
+ let config_map = _server_config.read().await;
+ add_this_sensor = config_map
+ .threshold_config
+ .keys()
+ .any(|key| key.contains(&sensor_config.Name));
+ }
+
+ if add_this_sensor {
+ if let Ok(sensor) = create_i2c_sensor(
+ &sensor_config,
+ &all_sensor_frus,
+ sampling_interval,
+ reporting_interval,
+ )
+ .await
+ {
+ println!("Adding I2C sensor {}", &sensor_config.Name);
+ telemetry_source_manager.add_source::<f64>(sensor).await?;
+ } else {
+ warn!("no sysfs_path found for sensor {:?}", sensor_config);
+ }
+ } else {
+ println!(
+ "Skip I2C sensor {} that is not in server_config",
+ &sensor_config.Name
+ );
+ }
+ } else if sensor_config.sensor_type == "PLDM" {
+ if let Ok(sensor) = create_dbus_sensor(
+ &sensor_config,
+ &all_sensor_frus,
+ sampling_interval,
+ reporting_interval,
+ )
+ .await
+ {
+ info!("Adding PLDM sensor {}", sensor_config.Name);
+ telemetry_source_manager.add_source::<f64>(sensor).await?;
+ }
+ }
+ }
+
+ Ok(telemetry_source_manager)
+}
+
+/// Extracts the sensor name from a Redfish sensor ID.
+///
+/// # Arguments
+///
+/// * `sensor_name` - The Redfish sensor ID.
+///
+/// # Returns
+///
+/// A `String` containing the extracted sensor name, or an error if the extraction fails.
+fn get_sensor_name(sensor_name: &odata_v4::Id) -> Result<String, Box<dyn std::error::Error>> {
+ let sensor_name = sensor_name.0.split('/').last().unwrap_or_default();
+ let parts: Vec<&str> = sensor_name.split('_').collect();
+
+ if parts.is_empty() {
+ return Err(anyhow::anyhow!("Invalid Redfish sensor name pattern").into());
+ }
+
+ // FIXME! make "fanpwm_fan1_pwm" or "fan_pwm_fan1_pwm" to "/xyz/openbmc_project/sensors/fan_pwm/fan1_pwm"
+ if parts[0].starts_with("fan") {
+ // Handle fan sensor names
+ if parts.len() < 2 {
+ return Err(anyhow::anyhow!("Invalid fan sensor name pattern").into());
+ }
+ // Take the last two segments
+ Ok(parts[parts.len() - 2..].join("_"))
+ } else {
+ // Handle non-fan sensor names
+ if parts.len() < 2 {
+ return Err(anyhow::anyhow!("Invalid sensor name pattern").into());
+ }
+ Ok(parts[1..].join("_"))
+ }
+}
+
+/// Send sensor values to gRPC subscriber.
+///
+/// # Arguments
+///
+/// * `sensor_name_id` - The Redfish sensor ID.
+/// * `telemetries` - The telemetries from subscription to telemetry_source_manager.
+/// * `tx` - The transmit half of a channel for sending `TypedTelemetry<f64>` updates.
+///
+/// # Returns
+///
+/// `Ok(())` if successful, or an error if the operation fails.
+async fn send_sensor_values(
+ sensor_name_id: &odata_v4::Id,
+ telemetries: Vec<Box<dyn Telemetry + Send>>,
+ tx: &tokio::sync::mpsc::Sender<Vec<TypedTelemetry<f64>>>,
+) -> Result<()> {
+ let event_responses: Vec<TypedTelemetry<f64>> = telemetries
+ .into_iter()
+ .filter_map(|telemetry| {
+ telemetry
+ .as_any()
+ .downcast_ref::<TypedTelemetry<f64>>()
+ .map(|tt| {
+ let mut new_tt = tt.clone();
+ new_tt.source_name.clone_from(&sensor_name_id.0);
+ new_tt
+ })
+ })
+ .collect();
+
+ if !event_responses.is_empty() {
+ tx.send(event_responses)
+ .await
+ .context("Failed to send event responses")?;
+ }
+
+ Ok(())
+}
+
+/// Polls a single sensor and sends updates through a channel.
+///
+/// # Arguments
+///
+/// * `sensor_name_id` - The Redfish odata_id of the sensor.
+/// * `subscription_type` - The subscription parameters.
+/// * `event_source_config` - The Thresholds associated with this sensor.
+/// * `tx` - The transmit half of a channel for sending `TypedTelemetry<f64>` updates.
+/// * `telemetry_source_manager` - The `TelemetrySourceManager` containing sensor information.
+///
+/// # Returns
+///
+/// `Ok(())` if successful, or an error if the operation fails.
+async fn poll_one_sensor(
+ sensor_name_id: &odata_v4::Id,
+ subscription_type: SubscriptionType,
+ event_source_config: Option<Thresholds>,
+ tx: tokio::sync::mpsc::Sender<Vec<TypedTelemetry<f64>>>,
+ telemetry_source_manager: Arc<TelemetrySourceManager>,
+) -> Result<(), Box<dyn std::error::Error>> {
+ let sensor_name = get_sensor_name(sensor_name_id)?;
+ let sensors = telemetry_source_manager.query_sources_by_names(&[&sensor_name])?;
+ if sensors.len() != 1 {
+ error!("query_sources_by_names failed for {sensor_name}");
+ return Err(anyhow::anyhow!("Invalid query_sources_by_names result").into());
+ }
+ match subscription_type {
+ SubscriptionType::Periodical(sampling_interval, reporting_interval) => {
+ let mut current_sample_rate = sampling_interval;
+ let mut current_reporting_rate = reporting_interval;
+ if let Some(config) = &event_source_config {
+ // Threshold mode
+ loop {
+ #[cfg(debug_assertions)]
+ println!(
+ "Sensor {} interval_duration {:?}, reporting_duration {:?}",
+ sensor_name, current_sample_rate, current_reporting_rate
+ );
+
+ let mut subscription = telemetry_source_manager.subscribe_telemetries::<f64>(
+ &sensor_name,
+ SubscriptionType::Periodical(current_sample_rate, current_reporting_rate),
+ )?;
+
+ while let Some(telemetries) = subscription.telemetry_receiver.recv().await {
+ let last_value = telemetries
+ .last()
+ .and_then(|t| t.as_any().downcast_ref::<TypedTelemetry<f64>>())
+ .map(|t| t.value);
+
+ send_sensor_values(sensor_name_id, telemetries, &tx).await?;
+
+ if let Some(last_value) = last_value {
+ let threshold_config = get_threshold_config(last_value, config);
+ if let Some(threshold_config) = threshold_config {
+ let new_sampling_interval =
+ if threshold_config.sample_frequency_expect_ns > 0 {
+ Duration::from_nanos(
+ threshold_config.sample_frequency_expect_ns,
+ )
+ } else {
+ Duration::from_secs(1)
+ };
+ let new_reporting_interval =
+ if threshold_config.export_frequency_ns > 0 {
+ Duration::from_nanos(threshold_config.export_frequency_ns)
+ } else {
+ Duration::from_secs(10)
+ };
+
+ if new_sampling_interval != current_sample_rate {
+ #[cfg(debug_assertions)]
+ println!(
+ "Sensor {} sample rate changed from {:?} to {:?}",
+ sensor_name, current_sample_rate, new_sampling_interval
+ );
+ current_sample_rate = new_sampling_interval;
+ current_reporting_rate = new_reporting_interval;
+ break; // This will exit the inner while loop and create a new subscription
+ }
+ }
+ }
+ }
+ }
+ } else {
+ // Normal Periodic mode
+ let mut subscription = telemetry_source_manager.subscribe_telemetries::<f64>(
+ &sensor_name,
+ SubscriptionType::Periodical(sampling_interval, reporting_interval),
+ )?;
+
+ while let Some(telemetries) = subscription.telemetry_receiver.recv().await {
+ send_sensor_values(sensor_name_id, telemetries, &tx).await?;
+ }
+ }
+ }
+ SubscriptionType::OnChange => {
+ let mut subscription = telemetry_source_manager
+ .subscribe_telemetries::<f64>(&sensor_name, SubscriptionType::OnChange)?;
+
+ while let Some(telemetries) = subscription.telemetry_receiver.recv().await {
+ send_sensor_values(sensor_name_id, telemetries, &tx).await?;
+ }
+ }
+ // TODO: Add support for other SubscriptionType
+ #[allow(unreachable_patterns)]
+ _ => {
+ return Err(anyhow::anyhow!("Must give a subscribe type").into());
+ }
+ }
+ Ok(())
+}
+
+/// Handles sensor subscription requests and returns a stream of batch `TypedTelemetry` objects.
+///
+/// # Arguments
+///
+/// * `state` - The application state.
+/// * `subscription_type` - The subscription parameters.
+/// * `urls` - The Redfish URLs to identify sensors.
+/// * `server_config` - An optional server configuration.
+///
+/// # Returns
+///
+/// A `ReceiverStream` of `Vec<TypedTelemetry<f64>>` objects.
+pub async fn handle_subscribe_inner(
+ state: Arc<AppState>,
+ subscription_type: SubscriptionType,
+ urls: Vec<String>,
+ server_config: Option<Arc<tokio::sync::RwLock<ProstServerConfig>>>,
+) -> tokio_stream::wrappers::ReceiverStream<Vec<TypedTelemetry<f64>>> {
+ let (tx, rx) = tokio::sync::mpsc::channel(16);
+ for url in urls {
+ // Extract sensor name from odata_id
+ let sensor_name = url.split('/').last().unwrap_or_default().to_string();
+
+ let sensor_name_id = odata_v4::Id(url.to_owned());
+
+ let config = if let Some(ref config_map) = server_config {
+ let config_map = config_map.read().await;
+ config_map.threshold_config.get(&sensor_name).cloned()
+ } else {
+ None
+ };
+
+ let tx_clone = tx.clone();
+ let telemetry_source_manager = state.telemetry_source_manager.clone();
+ let subscription_type_clone = subscription_type.clone();
+ tokio::spawn(async move {
+ let e = poll_one_sensor(
+ &sensor_name_id,
+ subscription_type_clone,
+ config,
+ tx_clone,
+ telemetry_source_manager,
+ )
+ .await;
+ println!("{:?}", e);
+ });
+ }
+ tokio_stream::wrappers::ReceiverStream::new(rx)
+}
diff --git a/test/server_config.textproto b/test/server_config.textproto
new file mode 100644
index 0000000..df1a298
--- /dev/null
+++ b/test/server_config.textproto
@@ -0,0 +1,531 @@
+# Top-level configuration groups
+cfg_groups {
+ key: "repairability_basic_cfg_group"
+ value {
+ req_fqp_names: "temperature"
+ req_fqp_names: "fan"
+ }
+}
+
+# Second-level FQP configurations
+req_fqp_configs {
+ key: "fan"
+ value {
+ req_fqp {
+ fqp {
+ specifier: "/redfish/v1/Chassis/MyBMC/Sensors/fantach_fan0_tach"
+ type: FQP_TYPE_REDFISH_RESOURCE
+ }
+ priority: FQP_PRI_HIG
+ mode: SAMPLING_MODE_THRESHOLD
+ encoding: ENCODING_PROTO_KEYVALUE
+ variant_fields_only: true
+ threshold_config: "normal"
+ req_fqp_name: "fantach_fan0_tach"
+ }
+ req_fqp {
+ fqp {
+ specifier: "/redfish/v1/Chassis/MyBMC/Sensors/fantach_fan1_tach"
+ type: FQP_TYPE_REDFISH_RESOURCE
+ }
+ priority: FQP_PRI_HIG
+ mode: SAMPLING_MODE_THRESHOLD
+ encoding: ENCODING_PROTO_KEYVALUE
+ variant_fields_only: true
+ threshold_config: "normal"
+ req_fqp_name: "fantach_fan1_tach"
+ }
+ req_fqp {
+ fqp {
+ specifier: "/redfish/v1/Chassis/MyBMC/Sensors/fantach_fan2_tach"
+ type: FQP_TYPE_REDFISH_RESOURCE
+ }
+ priority: FQP_PRI_HIG
+ mode: SAMPLING_MODE_THRESHOLD
+ encoding: ENCODING_PROTO_KEYVALUE
+ variant_fields_only: true
+ threshold_config: "normal"
+ req_fqp_name: "fantach_fan2_tach"
+ }
+ req_fqp {
+ fqp {
+ specifier: "/redfish/v1/Chassis/MyBMC/Sensors/fantach_fan3_tach"
+ type: FQP_TYPE_REDFISH_RESOURCE
+ }
+ priority: FQP_PRI_HIG
+ mode: SAMPLING_MODE_THRESHOLD
+ encoding: ENCODING_PROTO_KEYVALUE
+ variant_fields_only: true
+ threshold_config: "normal"
+ req_fqp_name: "fantach_fan3_tach"
+ }
+ req_fqp {
+ fqp {
+ specifier: "/redfish/v1/Chassis/MyPSU/Sensors/fantach_fan4_tach"
+ type: FQP_TYPE_REDFISH_RESOURCE
+ }
+ priority: FQP_PRI_HIG
+ mode: SAMPLING_MODE_THRESHOLD
+ encoding: ENCODING_PROTO_KEYVALUE
+ variant_fields_only: true
+ threshold_config: "normal"
+ req_fqp_name: "fantach_fan4_tach"
+ }
+ req_fqp {
+ fqp {
+ specifier: "/redfish/v1/Chassis/MyPSU/Sensors/fantach_fan5_tach"
+ type: FQP_TYPE_REDFISH_RESOURCE
+ }
+ priority: FQP_PRI_HIG
+ mode: SAMPLING_MODE_THRESHOLD
+ encoding: ENCODING_PROTO_KEYVALUE
+ variant_fields_only: true
+ threshold_config: "normal"
+ req_fqp_name: "fantach_fan5_tach"
+ }
+ }
+}
+
+req_fqp_configs {
+ key: "temperature"
+ value {
+ req_fqp {
+ fqp {
+ specifier: "/redfish/v1/Chassis/MyBMC/Sensors/temperature_CPU0_NBM_T"
+ type: FQP_TYPE_REDFISH_RESOURCE
+ }
+ priority: FQP_PRI_MED
+ mode: SAMPLING_MODE_THRESHOLD
+ encoding: ENCODING_PROTO_KEYVALUE
+ variant_fields_only: true
+ threshold_config: "normal"
+ req_fqp_name: "temperature_CPU0_NBM_T"
+ }
+ req_fqp {
+ fqp {
+ specifier: "/redfish/v1/Chassis/MyBMC/Sensors/temperature_CPU1_NBM_T"
+ type: FQP_TYPE_REDFISH_RESOURCE
+ }
+ priority: FQP_PRI_MED
+ mode: SAMPLING_MODE_THRESHOLD
+ encoding: ENCODING_PROTO_KEYVALUE
+ variant_fields_only: true
+ threshold_config: "normal"
+ req_fqp_name: "temperature_CPU1_NBM_T"
+ }
+ req_fqp {
+ fqp {
+ specifier: "/redfish/v1/Chassis/MyPSU/Sensors/temperature_BRICK1_P12V_T"
+ type: FQP_TYPE_REDFISH_RESOURCE
+ }
+ priority: FQP_PRI_HIG
+ mode: SAMPLING_MODE_THRESHOLD
+ encoding: ENCODING_PROTO_KEYVALUE
+ variant_fields_only: true
+ threshold_config: "normal"
+ req_fqp_name: "temperature_BRICK1_P12V_T"
+ }
+ }
+}
+
+# Threshold configurations
+threshold_config {
+ key: "fantach_fan4_tach"
+ value {
+ # Set pwm to 30 to enter this range (8192)
+ threshold {
+ index: 0 # lower_critical
+ sample_frequency_expect_ns: 5000000 # 5ms
+ export_frequency_ns: 500000000 # 500ms
+ cross_above_value { double_val: 8300 }
+ name: "lower_critical"
+ }
+ # Set pwm to 40 to enter this range (10000)
+ threshold {
+ index: 1 # lower_caution
+ sample_frequency_expect_ns: 50000000 # 50ms
+ export_frequency_ns: 5000000000 # 5s
+ cross_above_value { double_val: 10500 }
+ cross_below_value { double_val: 8300 }
+ name: "lower_caution"
+ }
+ # Set pwm to 50 to enter this range (11700)
+ threshold {
+ index: 2 # normal
+ sample_frequency_expect_ns: 1000000000 # 1s
+ export_frequency_ns: 10000000000 # 10s
+ cross_above_value { double_val: 14000 }
+ cross_below_value { double_val: 10500 }
+ name: "normal"
+ }
+ # Set pwm to 60 to enter this range (13653)
+ threshold {
+ index: 3 # upper_caution
+ sample_frequency_expect_ns: 50000000 # 50ms
+ export_frequency_ns: 5000000000 # 5s
+ cross_above_value { double_val: 15500 }
+ cross_below_value { double_val: 14000 }
+ name: "upper_caution"
+ }
+ # Set pwm to 80 to enter this range (19275)
+ threshold {
+ index: 4 # upper_critical
+ sample_frequency_expect_ns: 5000000 # 5ms
+ export_frequency_ns: 500000000 # 500ms
+ cross_below_value { double_val: 15500 }
+ name: "upper_critical"
+ }
+ }
+}
+
+threshold_config {
+ key: "fantach_fan0_tach"
+ value {
+ threshold {
+ index: 0 # lower_critical
+ sample_frequency_expect_ns: 5000000 # 5ms
+ export_frequency_ns: 500000000 # 500ms
+ cross_above_value { double_val: 3275 }
+ name: "lower_critical"
+ }
+ threshold {
+ index: 1 # lower_caution
+ sample_frequency_expect_ns: 50000000 # 50ms
+ export_frequency_ns: 5000000000 # 5s
+ cross_above_value { double_val: 4000 }
+ cross_below_value { double_val: 3275 }
+ name: "lower_caution"
+ }
+ threshold {
+ index: 2 # normal
+ sample_frequency_expect_ns: 1000000000 # 1s
+ export_frequency_ns: 10000000000 # 10s
+ cross_above_value { double_val: 5560 }
+ cross_below_value { double_val: 4000 }
+ name: "normal"
+ }
+ threshold {
+ index: 3 # upper_caution
+ sample_frequency_expect_ns: 50000000 # 50ms
+ export_frequency_ns: 5000000000 # 5s
+ cross_above_value { double_val: 6150 }
+ cross_below_value { double_val: 5560 }
+ name: "upper_caution"
+ }
+ threshold {
+ index: 4 # upper_critical
+ sample_frequency_expect_ns: 5000000 # 5ms
+ export_frequency_ns: 500000000 # 500ms
+ cross_below_value { double_val: 6150 }
+ name: "upper_critical"
+ }
+ }
+}
+
+threshold_config {
+ key: "fantach_fan1_tach"
+ value {
+ threshold {
+ index: 0 # lower_critical
+ sample_frequency_expect_ns: 5000000 # 5ms
+ export_frequency_ns: 500000000 # 500ms
+ cross_above_value { double_val: 3260 }
+ name: "lower_critical"
+ }
+ threshold {
+ index: 1 # lower_caution
+ sample_frequency_expect_ns: 50000000 # 50ms
+ export_frequency_ns: 5000000000 # 5s
+ cross_above_value { double_val: 3980 }
+ cross_below_value { double_val: 3260 }
+ name: "lower_caution"
+ }
+ threshold {
+ index: 2 # normal
+ sample_frequency_expect_ns: 1000000000 # 1s
+ export_frequency_ns: 10000000000 # 10s
+ cross_above_value { double_val: 5540 }
+ cross_below_value { double_val: 3980 }
+ name: "normal"
+ }
+ threshold {
+ index: 3 # upper_caution
+ sample_frequency_expect_ns: 50000000 # 50ms
+ export_frequency_ns: 5000000000 # 5s
+ cross_above_value { double_val: 6120 }
+ cross_below_value { double_val: 5540 }
+ name: "upper_caution"
+ }
+ threshold {
+ index: 4 # upper_critical
+ sample_frequency_expect_ns: 5000000 # 5ms
+ export_frequency_ns: 500000000 # 500ms
+ cross_below_value { double_val: 6120 }
+ name: "upper_critical"
+ }
+ }
+}
+
+threshold_config {
+ key: "fantach_fan2_tach"
+ value {
+ threshold {
+ index: 0 # lower_critical
+ sample_frequency_expect_ns: 5000000 # 5ms
+ export_frequency_ns: 500000000 # 500ms
+ cross_above_value { double_val: 7710 }
+ name: "lower_critical"
+ }
+ threshold {
+ index: 1 # lower_caution
+ sample_frequency_expect_ns: 50000000 # 50ms
+ export_frequency_ns: 5000000000 # 5s
+ cross_above_value { double_val: 9420 }
+ cross_below_value { double_val: 7710 }
+ name: "lower_caution"
+ }
+ threshold {
+ index: 2 # normal
+ sample_frequency_expect_ns: 1000000000 # 1s
+ export_frequency_ns: 10000000000 # 10s
+ cross_above_value { double_val: 13110 }
+ cross_below_value { double_val: 9420 }
+ name: "normal"
+ }
+ threshold {
+ index: 3 # upper_caution
+ sample_frequency_expect_ns: 50000000 # 50ms
+ export_frequency_ns: 5000000000 # 5s
+ cross_above_value { double_val: 14500 }
+ cross_below_value { double_val: 13110 }
+ name: "upper_caution"
+ }
+ threshold {
+ index: 4 # upper_critical
+ sample_frequency_expect_ns: 5000000 # 5ms
+ export_frequency_ns: 500000000 # 500ms
+ cross_below_value { double_val: 14500 }
+ name: "upper_critical"
+ }
+ }
+}
+
+threshold_config {
+ key: "fantach_fan3_tach"
+ value {
+ threshold {
+ index: 0 # lower_critical
+ sample_frequency_expect_ns: 5000000 # 5ms
+ export_frequency_ns: 500000000 # 500ms
+ cross_above_value { double_val: 7710 }
+ name: "lower_critical"
+ }
+ threshold {
+ index: 1 # lower_caution
+ sample_frequency_expect_ns: 50000000 # 50ms
+ export_frequency_ns: 5000000000 # 5s
+ cross_above_value { double_val: 9420 }
+ cross_below_value { double_val: 7710 }
+ name: "lower_caution"
+ }
+ threshold {
+ index: 2 # normal
+ sample_frequency_expect_ns: 1000000000 # 1s
+ export_frequency_ns: 10000000000 # 10s
+ cross_above_value { double_val: 13110 }
+ cross_below_value { double_val: 9420 }
+ name: "normal"
+ }
+ threshold {
+ index: 3 # upper_caution
+ sample_frequency_expect_ns: 50000000 # 50ms
+ export_frequency_ns: 5000000000 # 5s
+ cross_above_value { double_val: 14500 }
+ cross_below_value { double_val: 13110 }
+ name: "upper_caution"
+ }
+ threshold {
+ index: 4 # upper_critical
+ sample_frequency_expect_ns: 5000000 # 5ms
+ export_frequency_ns: 500000000 # 500ms
+ cross_below_value { double_val: 14500 }
+ name: "upper_critical"
+ }
+ }
+}
+
+threshold_config {
+ key: "fantach_fan5_tach"
+ value {
+ threshold {
+ index: 0 # lower_critical
+ sample_frequency_expect_ns: 5000000 # 5ms
+ export_frequency_ns: 500000000 # 500ms
+ cross_above_value { double_val: 16520 }
+ name: "lower_critical"
+ }
+ threshold {
+ index: 1 # lower_caution
+ sample_frequency_expect_ns: 50000000 # 50ms
+ export_frequency_ns: 5000000000 # 5s
+ cross_above_value { double_val: 20180 }
+ cross_below_value { double_val: 16520 }
+ name: "lower_caution"
+ }
+ threshold {
+ index: 2 # normal
+ sample_frequency_expect_ns: 1000000000 # 1s
+ export_frequency_ns: 10000000000 # 10s
+ cross_above_value { double_val: 28090 }
+ cross_below_value { double_val: 20180 }
+ name: "normal"
+ }
+ threshold {
+ index: 3 # upper_caution
+ sample_frequency_expect_ns: 50000000 # 50ms
+ export_frequency_ns: 5000000000 # 5s
+ cross_above_value { double_val: 31090 }
+ cross_below_value { double_val: 28090 }
+ name: "upper_caution"
+ }
+ threshold {
+ index: 4 # upper_critical
+ sample_frequency_expect_ns: 5000000 # 5ms
+ export_frequency_ns: 500000000 # 500ms
+ cross_below_value { double_val: 31090 }
+ name: "upper_critical"
+ }
+ }
+}
+
+threshold_config {
+ key: "temperature_CPU0_NBM_T"
+ value {
+ threshold {
+ index: 0 # normal
+ sample_frequency_expect_ns: 1000000000 # 1s
+ export_frequency_ns: 10000000000 # 10s
+ cross_above_value { double_val: 70.0 }
+ name: "normal"
+ }
+ threshold {
+ index: 1 # caution
+ sample_frequency_expect_ns: 50000000 # 50ms
+ export_frequency_ns: 5000000000 # 5s
+ cross_above_value { double_val: 80.0 }
+ cross_below_value { double_val: 70.0 }
+ name: "caution"
+ }
+ threshold {
+ index: 2 # warning
+ sample_frequency_expect_ns: 50000000 # 50ms
+ export_frequency_ns: 5000000000 # 5s
+ cross_above_value { double_val: 100.0 }
+ cross_below_value { double_val: 80.0 }
+ name: "warning"
+ }
+ threshold {
+ index: 3 # critical
+ sample_frequency_expect_ns: 5000000 # 5ms
+ export_frequency_ns: 500000000 # 500ms
+ cross_above_value { double_val: 105.0 }
+ cross_below_value { double_val: 100.0 }
+ name: "critical"
+ }
+ threshold {
+ index: 4 # fatal
+ sample_frequency_expect_ns: 5000000 # 5ms
+ export_frequency_ns: 500000000 # 500ms
+ cross_above_value { double_val: 105.0 }
+ name: "fatal"
+ }
+ }
+}
+
+threshold_config {
+ key: "temperature_CPU1_NBM_T"
+ value {
+ threshold {
+ index: 0 # normal
+ sample_frequency_expect_ns: 1000000000 # 1s
+ export_frequency_ns: 10000000000 # 10s
+ cross_above_value { double_val: 70.0 }
+ name: "normal"
+ }
+ threshold {
+ index: 1 # caution
+ sample_frequency_expect_ns: 50000000 # 50ms
+ export_frequency_ns: 5000000000 # 5s
+ cross_above_value { double_val: 80.0 }
+ cross_below_value { double_val: 70.0 }
+ name: "caution"
+ }
+ threshold {
+ index: 2 # warning
+ sample_frequency_expect_ns: 50000000 # 50ms
+ export_frequency_ns: 5000000000 # 5s
+ cross_above_value { double_val: 100.0 }
+ cross_below_value { double_val: 80.0 }
+ name: "warning"
+ }
+ threshold {
+ index: 3 # critical
+ sample_frequency_expect_ns: 5000000 # 5ms
+ export_frequency_ns: 500000000 # 500ms
+ cross_above_value { double_val: 105.0 }
+ cross_below_value { double_val: 100.0 }
+ name: "critical"
+ }
+ threshold {
+ index: 4 # fatal
+ sample_frequency_expect_ns: 5000000 # 5ms
+ export_frequency_ns: 500000000 # 500ms
+ cross_above_value { double_val: 105.0 }
+ name: "fatal"
+ }
+ }
+}
+
+threshold_config {
+ key: "temperature_BRICK1_P12V_T"
+ value {
+ threshold {
+ index: 0 # normal
+ sample_frequency_expect_ns: 1000000000 # 1s
+ export_frequency_ns: 10000000000 # 10s
+ cross_above_value { double_val: 70.0 }
+ name: "normal"
+ }
+ threshold {
+ index: 1 # caution
+ sample_frequency_expect_ns: 50000000 # 50ms
+ export_frequency_ns: 5000000000 # 5s
+ cross_above_value { double_val: 85.0 }
+ cross_below_value { double_val: 70.0 }
+ name: "caution"
+ }
+ threshold {
+ index: 2 # warning
+ sample_frequency_expect_ns: 50000000 # 50ms
+ export_frequency_ns: 5000000000 # 5s
+ cross_above_value { double_val: 108.0 }
+ cross_below_value { double_val: 85.0 }
+ name: "warning"
+ }
+ threshold {
+ index: 3 # critical
+ sample_frequency_expect_ns: 5000000 # 5ms
+ export_frequency_ns: 500000000 # 500ms
+ cross_above_value { double_val: 113.0 }
+ cross_below_value { double_val: 108.0 }
+ name: "critical"
+ }
+ threshold {
+ index: 4 # fatal
+ sample_frequency_expect_ns: 5000000 # 5ms
+ export_frequency_ns: 500000000 # 500ms
+ cross_above_value { double_val: 113.0 }
+ name: "fatal"
+ }
+ }
+}