cast-sender-0.2.0/.cargo_vcs_info.json0000644000000001360000000000100132430ustar { "git": { "sha1": "bb7fe0449879bc35d5d2140883e5550ae58264e7" }, "path_in_vcs": "" }cast-sender-0.2.0/.github/workflows/build.yml000064400000000000000000000005621046102023000172550ustar 00000000000000on: push: branches: [main] pull_request: name: build jobs: check: name: check runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true - uses: actions-rs/cargo@v1 with: command: check cast-sender-0.2.0/.github/workflows/clippy.yml000064400000000000000000000006771046102023000174650ustar 00000000000000on: push: branches: [main] pull_request: name: clippy jobs: clippy: name: clippy runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true - run: rustup component add clippy - uses: actions-rs/cargo@v1 with: command: clippy args: -- -D warnings cast-sender-0.2.0/.github/workflows/codespell.yml000064400000000000000000000004641046102023000201310ustar 00000000000000 on: push: branches: [main] pull_request: name: codespell jobs: codespell: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 - uses: codespell-project/actions-codespell@master with: check_filenames: true ignore_words_list: "crate,ser,inout" cast-sender-0.2.0/.github/workflows/rustfmt.yml000064400000000000000000000006751046102023000176670ustar 00000000000000on: push: branches: [main] pull_request: name: rustfmt jobs: fmt: name: Rustfmt runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true - run: rustup component add rustfmt - uses: actions-rs/cargo@v1 with: command: fmt args: --all -- --check cast-sender-0.2.0/.gitignore000064400000000000000000000000221046102023000140150ustar 00000000000000target Cargo.lock cast-sender-0.2.0/Cargo.lock0000644000001212320000000000100112170ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[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 = "anyhow" version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[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 0.2.14", ] [[package]] name = "async-executor" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7ebdfa2ebdab6b1760375fa7d6f382b9f486eac35fc994625a00e89280bdbb7" dependencies = [ "async-task", "concurrent-queue", "fastrand 2.1.1", "futures-lite 2.3.0", "slab", ] [[package]] name = "async-fs" version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" dependencies = [ "async-lock 3.4.0", "blocking", "futures-lite 2.3.0", ] [[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", "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.35", "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 0.2.14", ] [[package]] name = "async-native-tls" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9343dc5acf07e79ff82d0c37899f079db3534d99f189a1837c8e549c99405bec" dependencies = [ "futures-util", "native-tls", "thiserror", "url", ] [[package]] name = "async-net" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" dependencies = [ "async-io 2.3.4", "blocking", "futures-lite 2.3.0", ] [[package]] name = "async-process" version = "2.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8a07789659a4d385b79b18b9127fc27e1a59e1e89117c78c5ea3b806f016374" dependencies = [ "async-channel", "async-io 2.3.4", "async-lock 3.4.0", "async-signal", "async-task", "blocking", "cfg-if", "event-listener 5.3.1", "futures-lite 2.3.0", "rustix 0.38.35", "tracing", "windows-sys 0.59.0", ] [[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.35", "signal-hook-registry", "slab", "windows-sys 0.59.0", ] [[package]] name = "async-task" version = "4.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[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.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[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 = "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 = "bytes" version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" [[package]] name = "cast-sender" version = "0.2.0" dependencies = [ "async-channel", "async-native-tls", "async-net", "bitflags 2.6.0", "derive_builder", "futures-util", "log", "macro_rules_attribute", "prost", "serde", "serde_derive", "serde_json", "serde_with", "smol", "smol-macros", "smol-timeout", "strum", "strum_macros", "thiserror", ] [[package]] name = "cc" version = "1.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6" 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", "num-traits", "serde", "windows-targets 0.52.6", ] [[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" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "crossbeam-utils" version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "darling" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ "darling_core", "darling_macro", ] [[package]] name = "darling_core" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", "syn", ] [[package]] name = "darling_macro" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", "syn", ] [[package]] name = "deranged" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", "serde", ] [[package]] name = "derive_builder" version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd33f37ee6a119146a1781d3356a7c26028f83d779b2e04ecd45fdc75c76877b" dependencies = [ "derive_builder_macro", ] [[package]] name = "derive_builder_core" version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7431fa049613920234f22c47fdc33e6cf3ee83067091ea4277a3f8c4587aae38" dependencies = [ "darling", "proc-macro2", "quote", "syn", ] [[package]] name = "derive_builder_macro" version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4abae7035bf79b9877b779505d8cf3749285b80c43941eda66604841889451dc" dependencies = [ "derive_builder_core", "syn", ] [[package]] name = "either" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[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 = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" dependencies = [ "concurrent-queue", "parking", "pin-project-lite 0.2.14", ] [[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 0.2.14", ] [[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 = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foreign-types" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ "foreign-types-shared", ] [[package]] name = "foreign-types-shared" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[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-core" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[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 0.2.14", "waker-fn", ] [[package]] name = "futures-lite" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" dependencies = [ "fastrand 2.1.1", "futures-core", "futures-io", "parking", "pin-project-lite 0.2.14", ] [[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", ] [[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-core", "futures-io", "futures-macro", "futures-task", "memchr", "pin-project-lite 0.2.14", "pin-utils", "slab", ] [[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 = "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 = "iana-time-zone" version = "0.1.60" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" 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 = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[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", "serde", ] [[package]] name = "indexmap" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" dependencies = [ "equivalent", "hashbrown 0.14.5", "serde", ] [[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 = "itertools" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" 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 = "libc" version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[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 = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "macro_rules_attribute" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a82271f7bc033d84bbca59a3ce3e4159938cb08a9c3aebbe54d215131518a13" dependencies = [ "macro_rules_attribute-proc_macro", "paste", ] [[package]] name = "macro_rules_attribute-proc_macro" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8dd856d451cc0da70e2ef2ce95a18e39a93b7558bedf10201ad28503f918568" [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "native-tls" version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" dependencies = [ "libc", "log", "openssl", "openssl-probe", "openssl-sys", "schannel", "security-framework", "security-framework-sys", "tempfile", ] [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "openssl" version = "0.10.66" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" dependencies = [ "bitflags 2.6.0", "cfg-if", "foreign-types", "libc", "once_cell", "openssl-macros", "openssl-sys", ] [[package]] name = "openssl-macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "openssl-probe" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" version = "0.9.103" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" dependencies = [ "cc", "libc", "pkg-config", "vcpkg", ] [[package]] name = "parking" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" [[package]] name = "paste" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project-lite" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" [[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 = "pkg-config" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[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 0.2.14", "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 0.2.14", "rustix 0.38.35", "tracing", "windows-sys 0.59.0", ] [[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[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.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13db3d3fde688c61e2446b4d843bc27a7e8af269a69440c0308021dc92333cc" dependencies = [ "bytes", "prost-derive", ] [[package]] name = "prost-derive" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18bec9b0adc4eba778b33684b7ba3e7137789434769ee3ce3930463ef904cfca" dependencies = [ "anyhow", "itertools", "proc-macro2", "quote", "syn", ] [[package]] name = "quote" version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] [[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.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f" dependencies = [ "bitflags 2.6.0", "errno", "libc", "linux-raw-sys 0.4.14", "windows-sys 0.52.0", ] [[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 = "schannel" version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "security-framework" version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags 2.6.0", "core-foundation", "core-foundation-sys", "libc", "security-framework-sys", ] [[package]] name = "security-framework-sys" version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "serde" version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.127" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" dependencies = [ "itoa", "memchr", "ryu", "serde", ] [[package]] name = "serde_with" version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857" dependencies = [ "base64", "chrono", "hex", "indexmap 1.9.3", "indexmap 2.4.0", "serde", "serde_derive", "serde_json", "serde_with_macros", "time", ] [[package]] name = "serde_with_macros" version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350" dependencies = [ "darling", "proc-macro2", "quote", "syn", ] [[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 = "smol" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aad24f41392790e6ac67f4f4cd871da61f7d758e07b5622431e491e897d9c8a7" dependencies = [ "async-channel", "async-executor", "async-fs", "async-io 2.3.4", "async-lock 3.4.0", "async-net", "async-process", "blocking", "futures-lite 2.3.0", ] [[package]] name = "smol-macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfcaedb62e0475a6898988138995ec7b1e5d116167a72bb12c7b59d0649fbbc2" dependencies = [ "async-executor", "async-io 2.3.4", "async-lock 3.4.0", "event-listener 5.3.1", "futures-lite 2.3.0", ] [[package]] name = "smol-timeout" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "847d777e2c6c166bad26264479e80a9820f3d364fcb4a0e23cd57bbfa8e94961" dependencies = [ "async-io 1.13.0", "pin-project-lite 0.1.12", ] [[package]] name = "socket2" version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" dependencies = [ "libc", "winapi", ] [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" [[package]] name = "strum_macros" version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ "heck", "proc-macro2", "quote", "rustversion", "syn", ] [[package]] name = "syn" version = "2.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "tempfile" version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" dependencies = [ "cfg-if", "fastrand 2.1.1", "once_cell", "rustix 0.38.35", "windows-sys 0.59.0", ] [[package]] name = "thiserror" version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", "syn", ] [[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 = "tracing" version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "pin-project-lite 0.2.14", "tracing-core", ] [[package]] name = "tracing-core" version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" [[package]] name = "unicode-bidi" version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] [[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 = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "waker-fn" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" [[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", "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", "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-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" cast-sender-0.2.0/Cargo.toml0000644000000040240000000000100112410ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "cast-sender" version = "0.2.0" authors = ["Felix Häcker "] build = false autobins = false autoexamples = false autotests = false autobenches = false description = "Fully asynchronous implementation of the Google Cast CASTV2 protocol, allowing communication with receivers such as Chromecast or Google TV devices." documentation = "https://docs.rs/cast_sender" readme = "README.md" keywords = [ "chromecast", "cast", "castv2", ] categories = [ "network-programming", "multimedia", "api-bindings", ] license = "MIT" repository = "https://github.com/haecker-felix/cast-sender" [lib] name = "cast_sender" path = "src/lib.rs" [[example]] name = "web_radio" path = "examples/web_radio.rs" [dependencies.async-channel] version = "2.3" [dependencies.async-native-tls] version = "0.5" [dependencies.async-net] version = "2.0" [dependencies.bitflags] version = "2.6" [dependencies.derive_builder] version = "0.20.1" [dependencies.futures-util] version = "0.3" [dependencies.log] version = "0.4" [dependencies.prost] version = "0.13" [dependencies.serde] version = "1.0" [dependencies.serde_derive] version = "1.0" [dependencies.serde_json] version = "1.0" [dependencies.serde_with] version = "3.9" [dependencies.smol] version = "2.0" [dependencies.smol-timeout] version = "0.6" [dependencies.strum] version = "0.26" [dependencies.strum_macros] version = "0.26" [dependencies.thiserror] version = "1.0" [dev-dependencies.macro_rules_attribute] version = "0.2" [dev-dependencies.smol-macros] version = "0.1" cast-sender-0.2.0/Cargo.toml.orig000064400000000000000000000016301046102023000147220ustar 00000000000000[package] name = "cast-sender" version = "0.2.0" edition = "2021" authors = ["Felix Häcker "] license = "MIT" repository = "https://github.com/haecker-felix/cast-sender" keywords = ["chromecast", "cast", "castv2"] description = "Fully asynchronous implementation of the Google Cast CASTV2 protocol, allowing communication with receivers such as Chromecast or Google TV devices." documentation = "https://docs.rs/cast_sender" categories = ["network-programming", "multimedia", "api-bindings"] [dependencies] async-channel = "2.3" async-native-tls = "0.5" async-net = "2.0" bitflags = "2.6" derive_builder = "0.20.1" futures-util = "0.3" log = "0.4" prost = "0.13" serde = "1.0" serde_derive = "1.0" serde_json = "1.0" serde_with = "3.9" smol = "2.0" smol-timeout = "0.6" strum = "0.26" strum_macros = "0.26" thiserror = "1.0" [dev-dependencies] smol-macros = "0.1" macro_rules_attribute = "0.2"cast-sender-0.2.0/LICENSE000064400000000000000000000020561046102023000130430ustar 00000000000000MIT License Copyright (c) 2024 Felix Häcker Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. cast-sender-0.2.0/README.md000064400000000000000000000017711046102023000133200ustar 00000000000000# cast-sender [![Crates.io Total Downloads](https://img.shields.io/crates/d/cast-sender)](https://crates.io/crates/cast-sender) [![docs.rs](https://img.shields.io/docsrs/cast-sender)](https://docs.rs/cast-sender) [![GitHub License](https://img.shields.io/github/license/haecker-felix/cast-sender)](./LICENSE) [![GitHub Build]( https://img.shields.io/github/actions/workflow/status/haecker-felix/cast-sender/build.yml)](https://github.com/haecker-felix/cast-sender/actions) Fully asynchronous implementation of the Google Cast CASTV2 protocol, allowing communication with receivers such as Chromecast or Google TV devices. **Example Usage** - [Simple web radio example](./examples/web_radio.rs) **References / Sources** - [Google Cast Reference](https://developers.google.com/cast/docs/reference/web_receiver/cast.framework.messages) - [JavaScript castv2 Implementation](https://github.com/thibauts/node-castv2) - [The Chromecast Protocol - A Brief Look](https://hackernoon.com/the-chromecast-protocol-a-brief-look)cast-sender-0.2.0/examples/web_radio.rs000064400000000000000000000023071046102023000161540ustar 00000000000000extern crate cast_sender; use macro_rules_attribute::apply; use smol_macros::main; use cast_sender::namespace::media::{ MediaInformationBuilder, MusicTrackMediaMetadataBuilder, StreamType, }; use cast_sender::{AppId, Error, ImageBuilder, MediaController, Receiver}; #[apply(main!)] async fn main() -> Result<(), Error> { let receiver = Receiver::new(); receiver.connect("192.168.178.50").await?; let app = receiver.launch_app(AppId::DefaultMediaReceiver).await?; let media_controller = MediaController::new(app.clone(), receiver.clone())?; let metadata = MusicTrackMediaMetadataBuilder::default() .title("BBC World Service") .images(vec![ImageBuilder::default() .url("https://sounds.files.bbci.co.uk/3.7.0/networks/bbc_world_service/colour_default.svg") .build() .unwrap()]) .build() .unwrap(); let media_info = MediaInformationBuilder::default() .content_id("http://stream.live.vc.bbcmedia.co.uk/bbc_world_service") .stream_type(StreamType::Live) .content_type("audio/*") .metadata(metadata) .build() .unwrap(); media_controller.load(media_info).await?; Ok(()) } cast-sender-0.2.0/proto/README.md000064400000000000000000000002101046102023000144460ustar 00000000000000Source https://source.chromium.org/chromium/chromium/src/+/main:third_party/openscreen/src/cast/common/channel/proto/cast_channel.protocast-sender-0.2.0/proto/authority_keys.proto000064400000000000000000000005671046102023000173560ustar 00000000000000// Copyright 2019 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. syntax = "proto2"; option optimize_for = LITE_RUNTIME; package openscreen.cast.proto; message AuthorityKeys { message Key { required bytes fingerprint = 1; required bytes public_key = 2; } repeated Key keys = 1; } cast-sender-0.2.0/proto/cast_channel.proto000064400000000000000000000072651046102023000167170ustar 00000000000000// Copyright 2019 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. syntax = "proto2"; option optimize_for = LITE_RUNTIME; package openscreen.cast.proto; message CastMessage { // Always pass a version of the protocol for future compatibility // requirements. enum ProtocolVersion { CASTV2_1_0 = 0; CASTV2_1_1 = 1; // message chunking support (deprecated). CASTV2_1_2 = 2; // reworked message chunking. CASTV2_1_3 = 3; // binary payload over utf8. } required ProtocolVersion protocol_version = 1; // source and destination ids identify the origin and destination of the // message. They are used to route messages between endpoints that share a // device-to-device channel. // // For messages between applications: // - The sender application id is a unique identifier generated on behalf of // the sender application. // - The receiver id is always the the session id for the application. // // For messages to or from the sender or receiver platform, the special ids // 'sender-0' and 'receiver-0' can be used. // // For messages intended for all endpoints using a given channel, the // wildcard destination_id '*' can be used. required string source_id = 2; required string destination_id = 3; // This is the core multiplexing key. All messages are sent on a namespace // and endpoints sharing a channel listen on one or more namespaces. The // namespace defines the protocol and semantics of the message. required string namespace = 4; // Encoding and payload info follows. // What type of data do we have in this message. enum PayloadType { STRING = 0; BINARY = 1; } required PayloadType payload_type = 5; // Depending on payload_type, exactly one of the following optional fields // will always be set. optional string payload_utf8 = 6; optional bytes payload_binary = 7; // --- Begin new 1.1 fields. // Flag indicating whether there are more chunks to follow for this message. // If the flag is false or is not present, then this is the last (or only) // chunk of the message. optional bool continued = 8; // If this is a chunk of a larger message, and the remaining length of the // message payload (the sum of the lengths of the payloads of the remaining // chunks) is known, this field will indicate that length. For a given // chunked message, this field should either be present in all of the chunks, // or in none of them. optional uint32 remaining_length = 9; } enum SignatureAlgorithm { UNSPECIFIED = 0; RSASSA_PKCS1v15 = 1; RSASSA_PSS = 2; } enum HashAlgorithm { SHA1 = 0; SHA256 = 1; } // Messages for authentication protocol between a sender and a receiver. message AuthChallenge { optional SignatureAlgorithm signature_algorithm = 1 [default = RSASSA_PKCS1v15]; optional bytes sender_nonce = 2; optional HashAlgorithm hash_algorithm = 3 [default = SHA1]; } message AuthResponse { required bytes signature = 1; required bytes client_auth_certificate = 2; repeated bytes intermediate_certificate = 3; optional SignatureAlgorithm signature_algorithm = 4 [default = RSASSA_PKCS1v15]; optional bytes sender_nonce = 5; optional HashAlgorithm hash_algorithm = 6 [default = SHA1]; optional bytes crl = 7; } message AuthError { enum ErrorType { INTERNAL_ERROR = 0; NO_TLS = 1; // The underlying connection is not TLS SIGNATURE_ALGORITHM_UNAVAILABLE = 2; } required ErrorType error_type = 1; } message DeviceAuthMessage { // Request fields optional AuthChallenge challenge = 1; // Response fields optional AuthResponse response = 2; optional AuthError error = 3; } cast-sender-0.2.0/src/app.rs000064400000000000000000000033261046102023000137540ustar 00000000000000use std::str::FromStr; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use strum_macros::{Display, EnumString}; use crate::namespace::NamespaceUrn; #[derive(Serialize, Deserialize, Clone, Debug, Default)] #[serde(rename_all = "camelCase")] pub struct App { pub app_id: AppId, pub app_type: String, pub display_name: String, pub icon_url: String, pub is_idle_screen: bool, pub launched_from_cloud: bool, pub namespaces: Vec, pub session_id: String, pub status_text: String, pub transport_id: String, pub universal_app_id: String, } impl App { pub fn receiver() -> Self { Self { transport_id: "receiver-0".into(), namespaces: vec![ NamespaceUrn::Connection, NamespaceUrn::Heartbeat, NamespaceUrn::Receiver, NamespaceUrn::DeviceAuth, ], ..Default::default() } } } #[derive(EnumString, Display, Debug, Clone, Default, PartialEq, Eq)] pub enum AppId { #[default] #[strum(serialize = "CC1AD845")] DefaultMediaReceiver, #[strum(serialize = "E8C28D3C")] Backdrop, #[strum(serialize = "233637DE")] YouTube, #[strum(default)] Custom(String), } impl<'de> Deserialize<'de> for AppId { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { let id = String::deserialize(deserializer)?; AppId::from_str(&id).map_err(serde::de::Error::custom) } } impl Serialize for AppId { fn serialize(&self, serializer: S) -> Result where S: Serializer, { self.to_string().serialize(serializer) } } cast-sender-0.2.0/src/client.rs000064400000000000000000000107361046102023000144550ustar 00000000000000use std::net::SocketAddr; use std::sync::Arc; use async_native_tls::{TlsConnector, TlsStream}; use async_net::TcpStream; use futures_util::io::{ReadHalf, WriteHalf}; use futures_util::{AsyncReadExt, AsyncWriteExt}; use prost::Message; use smol::lock::Mutex; use super::proto; use super::{namespace::NamespaceUrn, Error, Payload}; #[derive(Debug, Clone)] pub struct Response { pub source_id: String, pub destination_id: String, // Probably not strictly necessary, since the namespace can be derived // using the payload, but this may not have any guarantee of correctness, // since the namespace may differ from the deserialized enum variant. pub namespace: NamespaceUrn, pub payload: Payload, // Part of the payload pub request_id: Option, } #[derive(Debug, Clone)] pub struct Client { read_stream: Arc>>>, write_stream: Arc>>>, } impl Client { pub async fn connect(addr: &str) -> Result { let addr = SocketAddr::new(addr.parse()?, 8009); // Casts devices are using self signed certs let tls_connector = TlsConnector::new().danger_accept_invalid_certs(true); let tcp_stream = TcpStream::connect(&addr).await?; let tls_stream = tls_connector .connect(addr.to_string(), tcp_stream.clone()) .await?; let (read_stream, write_stream) = tls_stream.split(); Ok(Self { read_stream: Arc::new(Mutex::new(read_stream)), write_stream: Arc::new(Mutex::new(write_stream)), }) } pub async fn receive(&self) -> Result { let mut read_stream = self.read_stream.lock().await; // The first package is a u32 specifying the packet length.... let mut buf: [u8; 4] = [0; 4]; read_stream.read_exact(&mut buf).await?; let len = u32::from_be_bytes(buf); // ... then get the actual package with the specified length let mut buf: Vec = vec![0; len as usize]; read_stream.read_exact(&mut buf).await?; let msg: proto::CastMessage = proto::CastMessage::decode(&buf[..])?; let ns: NamespaceUrn = msg.namespace.parse().unwrap(); let mut pl: PayloadData = serde_json::from_str(msg.payload_utf8())?; if let Payload::Custom(u) = &mut pl.data { u.namespace = ns.clone(); }; debug!( "[RECV] {} -> {} | Namespace: {:?} | Request: {:?}", msg.source_id, msg.destination_id, ns, pl.request_id ); debug!(" {:#?}", pl); Ok(Response { source_id: msg.source_id, destination_id: msg.destination_id, namespace: ns, payload: pl.data, request_id: pl.request_id, }) } pub async fn send>( &self, destination_id: String, payload: P, request_id: Option, ) -> Result<(), Error> { let payload: Payload = payload.into(); let payload_data = PayloadData { request_id, data: payload.clone(), }; let payload_json = serde_json::to_string(&payload_data).unwrap(); let msg = proto::CastMessage { protocol_version: proto::cast_message::ProtocolVersion::Castv210.into(), source_id: "sender-0".into(), destination_id, namespace: payload.namespace().to_string(), payload_type: proto::cast_message::PayloadType::String.into(), payload_utf8: Some(payload_json.clone()), payload_binary: None, continued: None, remaining_length: None, }; debug!( "[SEND] {} -> {} | Namespace: {:?} | Request: {:?}", msg.source_id, msg.destination_id, payload.namespace(), request_id, ); debug!(" {}", payload_json); let mut write_stream = self.write_stream.lock().await; let len: u32 = msg.encoded_len().try_into().unwrap(); // First send package length write_stream.write_all(&len.to_be_bytes()).await?; // Then the actual package write_stream.write_all(&msg.encode_to_vec()).await?; Ok(()) } } #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] struct PayloadData { #[serde(skip_serializing_if = "Option::is_none")] request_id: Option, #[serde(flatten)] data: Payload, } cast-sender-0.2.0/src/error.rs000064400000000000000000000025431046102023000143250ustar 00000000000000use thiserror::Error; use crate::namespace::media::ErrorReason; use super::Response; #[derive(Error, Debug)] pub enum Error { #[error("Input/Output error: {0}")] Io(#[from] std::io::Error), #[error("Input/Output error: {0}")] AddrParseError(#[from] std::net::AddrParseError), #[error("TLS error: {0}")] Tls(#[from] async_native_tls::Error), #[error("Decode error: {0}")] Decode(#[from] prost::DecodeError), #[error("Deserialize error: {0}")] Deserialize(#[from] serde_json::Error), #[error("Receive error: {0}")] Receive(#[from] async_channel::RecvError), #[error("Send error: {0}")] Send(#[from] async_channel::SendError), #[error("Did not receive request response")] ResponseTimeout, #[error("Not connected with receiver")] NoConnection, #[error("Unable to launch app: {0}")] LaunchError(String), #[error("Unsupported Namespace")] UnsupportedNamespace, #[error("Media Channel Error: {0}")] MediaError(MediaError), #[error("Did not receive a matching response")] NoResponse, } #[derive(Error, Debug)] pub enum MediaError { #[error("Invalid Request")] InvalidRequest(ErrorReason), #[error("Invalid Player State")] InvalidPlayerState, #[error("Load Failed")] LoadFailed, #[error("Load Cancelled")] LoadCancelled, } cast-sender-0.2.0/src/lib.rs000064400000000000000000000007361046102023000137440ustar 00000000000000#[macro_use] extern crate log; #[macro_use] extern crate serde_derive; #[macro_use] extern crate serde_with; #[macro_use] extern crate derive_builder; pub mod namespace; mod proto; mod app; mod client; mod error; mod media_controller; mod payload; mod receiver; mod shared; pub use app::{App, AppId}; pub use client::{Client, Response}; pub use error::Error; pub use media_controller::MediaController; pub use payload::Payload; pub use receiver::Receiver; pub use shared::*; cast-sender-0.2.0/src/media_controller.rs000064400000000000000000000065111046102023000165150ustar 00000000000000use std::sync::Arc; use smol::lock::Mutex; use crate::namespace::media::*; use crate::{App, Error, Payload, Receiver, Response}; #[derive(Clone, Debug)] pub struct MediaController { app: App, receiver: Receiver, media_session_id: Arc>, } impl MediaController { pub fn new(app: App, receiver: Receiver) -> Result { if !app .namespaces .contains(&crate::namespace::NamespaceUrn::Media) { return Err(Error::UnsupportedNamespace); } Ok(Self { app, receiver, media_session_id: Arc::default(), }) } pub async fn load(&self, media: MediaInformation) -> Result<(), Error> { let response = self .receiver .send_request( &self.app, Media::Load(LoadRequestData { media, autoplay: Some(true), ..Default::default() }), ) .await?; Self::handle_error(&response)?; if let Payload::Media(Media::MediaStatus(response_data)) = response.payload { *self.media_session_id.lock().await = response_data.first().media_session_id; Ok(()) } else { Err(Error::NoResponse) } } pub async fn start(&self) -> Result<(), Error> { let response = self .receiver .send_request( &self.app, Media::Play(RequestData { media_session_id: Some(*self.media_session_id.lock().await), }), ) .await?; Self::handle_error(&response)?; Ok(()) } pub async fn stop(&self) -> Result<(), Error> { let response = self .receiver .send_request( &self.app, Media::Stop(RequestData { media_session_id: Some(*self.media_session_id.lock().await), }), ) .await?; Self::handle_error(&response)?; Ok(()) } pub async fn pause(&self) -> Result<(), Error> { let response = self .receiver .send_request( &self.app, Media::Pause(RequestData { media_session_id: Some(*self.media_session_id.lock().await), }), ) .await?; Self::handle_error(&response)?; Ok(()) } fn handle_error(response: &Response) -> Result<(), Error> { if let Payload::Media(Media::InvalidRequest(err)) = &response.payload { return Err(Error::MediaError(crate::error::MediaError::InvalidRequest( err.reason.clone(), ))); } if let Payload::Media(Media::InvalidPlayerState) = response.payload { return Err(Error::MediaError( crate::error::MediaError::InvalidPlayerState, )); } if let Payload::Media(Media::LoadFailed) = response.payload { return Err(Error::MediaError(crate::error::MediaError::LoadFailed)); } if let Payload::Media(Media::LoadCancelled) = response.payload { return Err(Error::MediaError(crate::error::MediaError::LoadCancelled)); } Ok(()) } } cast-sender-0.2.0/src/namespace/connection.rs000064400000000000000000000004671046102023000172720ustar 00000000000000use crate::Payload; #[derive(Serialize, Deserialize, Clone, Debug)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(tag = "type")] pub enum Connection { Connect, Close, } impl From for Payload { fn from(val: Connection) -> Self { Payload::Connection(val.clone()) } } cast-sender-0.2.0/src/namespace/heartbeat.rs000064400000000000000000000004571046102023000170710ustar 00000000000000use crate::Payload; #[derive(Serialize, Deserialize, Clone, Debug)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(tag = "type")] pub enum Heartbeat { Ping, Pong, } impl From for Payload { fn from(val: Heartbeat) -> Self { Payload::Heartbeat(val.clone()) } } cast-sender-0.2.0/src/namespace/media.rs000064400000000000000000001034221046102023000162050ustar 00000000000000use crate::{Image, Payload, Volume}; // https://developers.google.com/cast/docs/reference/web_receiver/cast.framework.messages // https://developers.google.com/cast/docs/reference/web_receiver/index-all #[derive(Serialize, Deserialize, Clone, Debug)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(tag = "type")] pub enum Media { // Request GetStatus(GetStatusRequestData), Load(LoadRequestData), Pause(RequestData), Stop(RequestData), Play(RequestData), SkipAd(RequestData), PlayAgain(RequestData), Seek(SeekRequestData), SetPlaybackRate(SetPlaybackRateRequestData), SetVolume(VolumeRequestData), EditTracksInfo(EditTracksInfoRequestData), EditAudioTracks(EditAudioTracksRequestData), Precache(PrecacheRequestData), Preload(PreloadRequestData), QueueLoad(QueueLoadRequestData), QueueInsert(QueueInsertRequestData), QueueUpdate(QueueUpdateRequestData), QueueRemove(QueueRemoveRequestData), QueueReorder(QueueReorderRequestData), QueueNext(RequestData), QueuePrev(RequestData), QueueGetItemRange(FetchItemsRequestData), QueueGetItems(GetItemsInfoRequestData), QueueGetItemIds(RequestData), QueueShuffle(RequestData), SetCredentials(SetCredentialsRequestData), LoadByEntity(LoadByEntityRequestData), UserAction(UserActionRequestData), DisplayStatus(DisplayStatusRequestData), FocusState(FocusStateRequestData), StoreSession(StoreSessionRequestData), ResumeSession(ResumeSessionRequestData), // Response MediaStatus(ResponseData), CloudStatus(ResponseData), QueueChange(ResponseData), QueueItems(ResponseData), QueueItemIds(ResponseData), SessionState(StoreSessionResponseData), InvalidRequest(ErrorResponseData), InvalidPlayerState, LoadFailed, LoadCancelled, } impl From for Payload { fn from(val: Media) -> Self { Payload::Media(val.clone()) } } // REQUEST DATA ------------------------------------------------- #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct DisplayStatusRequestData {} #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct EditAudioTracksRequestData { pub is_suggested_language: Option, pub language: Option, pub media_session_id: Option, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct EditTracksInfoRequestData { pub active_track_ids: Option>, pub enable_text_tracks: Option, pub is_suggested_language: Option, pub language: Option, pub media_session_id: Option, pub text_track_style: Option, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct FetchItemsRequestData { pub item_id: i32, pub media_session_id: Option, pub next_count: i32, pub prev_count: i32, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct FocusStateRequestData { pub state: FocusState, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct GetItemsInfoRequestData { pub item_ids: Vec, pub media_session_id: Option, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct GetStatusRequestData { pub media_session_id: Option, pub options: Option, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct LoadByEntityRequestData { pub entity: String, pub shuffle: Option, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct LoadRequestData { pub active_track_ids: Option>, pub autoplay: Option, pub credentials: Option, pub credentials_type: Option, pub current_time: Option, pub media: MediaInformation, pub media_session_id: Option, pub playback_rate: Option, pub queue_data: Option, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct PrecacheRequestData { pub active_track_ids: Option>, pub autoplay: Option, pub credentials: Option, pub credentials_type: Option, pub current_time: Option, pub media: MediaInformation, pub media_session_id: Option, pub playback_rate: Option, pub precache_data: Option, pub queue_data: Option, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct PreloadRequestData { pub active_track_ids: Option>, pub autoplay: Option, pub credentials: Option, pub credentials_type: Option, pub current_time: Option, pub item_id: i32, pub media: MediaInformation, pub media_session_id: Option, pub playback_rate: Option, pub queue_data: Option, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct QueueInsertRequestData { pub current_item_id: Option, pub current_item_index: Option, pub current_time: Option, pub insert_before: Option, pub items: Vec, pub media_session_id: Option, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct QueueLoadRequestData { pub current_time: Option, pub items: Vec, pub media_session_id: Option, pub repeat_mode: Option, pub start_index: Option, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct QueueRemoveRequestData { pub current_item_id: Option, pub current_time: Option, pub item_ids: Vec, pub media_session_id: Option, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct QueueReorderRequestData { pub current_item_id: Option, pub current_time: Option, pub insert_before: Option, pub item_ids: Vec, pub media_session_id: Option, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct QueueUpdateRequestData { pub current_item_id: Option, pub current_time: Option, pub items: Option>, pub jump: Option, pub media_session_id: Option, pub repeat_mode: Option, pub shuffle: Option, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct RefreshCredentialsRequestData {} #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct RequestData { pub media_session_id: Option, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct ResumeSessionRequestData { pub session_state: SessionState, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct SeekRequestData { pub current_time: Option, pub media_session_id: Option, pub relative_time: Option, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct SetCredentialsRequestData { pub credentials: String, pub for_request: Option, pub is_likely_3p_account_linked_user: Option, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct SetPlaybackRateRequestData { pub media_session_id: Option, pub playback_rate: Option, pub relative_playback_rate: Option, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct StoreSessionRequestData {} #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct UserActionRequestData { pub clear: Option, pub user_action: UserAction, pub user_action_context: Option, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct VolumeRequestData { pub media_session_id: Option, pub volume: Volume, } // RESPONSE DATA ------------------------------------------------- #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default)] #[serde(rename_all = "camelCase")] pub struct ResponseData { pub status: Vec, } impl ResponseData { pub fn first(&self) -> T { self.status.first().cloned().unwrap() } } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default)] #[serde(rename_all = "camelCase")] pub struct ErrorResponseData { pub reason: ErrorReason, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default)] #[serde(rename_all = "camelCase")] pub struct StoreSessionResponseData { pub session_state: SessionState, } // STRUCTS ------------------------------------------------------ #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct AduioTrackInfo { pub audio_codec: Option, pub num_audio_channels: Option, pub spatial_audio: Option, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[serde(tag = "type")] #[serde(rename = "AUDIOBOOK_CHAPTER")] #[builder(setter(strip_option, into), default)] pub struct AudiobookChapterMediaMetadata { pub book_title: Option, pub chapter_number: Option, pub chapter_title: Option, pub images: Option>, pub subtitle: Option, pub title: Option, } impl From for MediaMetadata { fn from(val: AudiobookChapterMediaMetadata) -> Self { MediaMetadata { metadata_type: MetadataType::AudiobookChapter(val), ..Default::default() } } } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct AudiobookContainerMetadata { pub authors: Option>, pub narrators: Option>, pub publisher: Option, pub release_date: Option, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct Break { pub break_clip_ids: Vec, pub duration: Option, pub id: String, pub is_embedded: Option, pub is_watched: bool, pub position: f64, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct BreakClip { pub click_through_url: Option, pub content_id: Option, pub content_type: Option, pub content_url: Option, pub duration: Option, pub hls_segment_format: Option, pub id: String, pub poster_url: Option, pub title: Option, pub vast_ads_request: Option, pub when_skippable: Option, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct BreakStatus { pub break_clip_id: Option, pub break_id: Option, pub current_break_clip_time: Option, pub current_break_time: Option, pub when_skippable: Option, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct CloudMediaStatus {} #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct ContainerMetadata { pub container_duration: Option, pub container_images: Option>, pub container_type: ContainerType, pub sections: Option>, pub title: Option, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct ExtendedMediaStatus { pub player_state: ExtendedPlayerState, pub media: Option, pub media_session_id: Option, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[serde(tag = "type")] #[serde(rename = "GENERIC")] #[builder(setter(strip_option, into), default)] pub struct GenericMediaMetadata { pub images: Option>, pub release_date: Option, pub subtitle: Option, pub title: Option, } impl From for MediaMetadata { fn from(val: GenericMediaMetadata) -> Self { MediaMetadata { metadata_type: MetadataType::Generic(val), ..Default::default() } } } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct ItemsInfo { pub items: Option>, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct LiveSeekableRange { pub end: Option, pub is_live_done: Option, pub is_moving_window: Option, pub start: Option, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct MediaInformation { pub atv_entity: Option, pub break_clips: Option>, pub breaks: Option>, pub content_id: String, pub content_type: String, pub duration: Option, pub entity: Option, pub hls_segment_format: Option, pub hls_video_segment_format: Option, pub metadata: Option, pub start_absolute_time: Option, pub stream_type: StreamType, pub text_track_style: Option, pub tracks: Option>, pub user_action_states: Option>, pub vmap_ads_request: Option, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct MediaMetadata { #[serde(flatten)] pub metadata_type: MetadataType, pub poster_url: Option, pub queue_item_id: Option, pub section_duration: Option, pub section_start_absolute_time: Option, pub section_start_time_in_container: Option, pub section_start_time_in_media: Option, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct MediaStatus { pub active_track_ids: Option>, pub break_status: Option, pub current_item_id: Option, pub current_time: f64, pub extended_status: Option, pub idle_reason: Option, pub items: Option>, pub live_seekable_range: Option, pub loading_item_id: Option, pub media: Option, pub media_session_id: i32, pub playback_rate: i32, pub player_state: PlayerState, pub preloaded_item_id: Option, pub queue_data: Option, pub repeat_mode: Option, pub supported_media_commands: Command, pub video_info: Option, pub volume: Volume, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[serde(tag = "type")] #[serde(rename = "MOVIE")] #[builder(setter(strip_option, into), default)] pub struct MovieMediaMetadata { pub images: Option>, pub release_date: Option, pub studio: Option, pub subtitle: Option, pub title: Option, } impl From for MediaMetadata { fn from(val: MovieMediaMetadata) -> Self { MediaMetadata { metadata_type: MetadataType::Movie(val), ..Default::default() } } } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[serde(tag = "type")] #[serde(rename = "MUSIC_TRACK")] #[builder(setter(strip_option, into), default)] pub struct MusicTrackMediaMetadata { pub album_artist: Option, pub album_name: Option, pub artist: Option, pub composer: Option, pub disc_number: Option, pub images: Option>, pub release_date: Option, pub secondary_image: Option, pub title: Option, pub track_number: Option, } impl From for MediaMetadata { fn from(val: MusicTrackMediaMetadata) -> Self { MediaMetadata { metadata_type: MetadataType::MusicTrack(val), ..Default::default() } } } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[serde(tag = "type")] #[serde(rename = "PHOTO")] #[builder(setter(strip_option, into), default)] pub struct PhotoMediaMetadata { pub artist: Option, pub creation_date_time: Option, pub height: Option, pub images: Option>, pub latitude: Option, pub location: Option, pub longitude: Option, pub width: Option, } impl From for MediaMetadata { fn from(val: PhotoMediaMetadata) -> Self { MediaMetadata { metadata_type: MetadataType::Photo(val), ..Default::default() } } } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct QueueChange { pub change_type: Option, pub insert_before: Option, pub item_ids: Option>, pub reorder_item_ids: Option>, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct QueueData { pub container_metadata: Option, pub description: Option, pub entity: Option, pub id: Option, pub items: Option>, pub name: Option, pub queue_type: Option, pub repeat_mode: Option, pub shuffle: Option, pub start_index: Option, pub start_time: Option, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct QueueIds { pub item_ids: Option>, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct SeekableRange { pub end: Option, pub start: Option, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct SessionState { pub load_request_data: Option, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct QueueItem { pub active_track_ids: Option>, pub autoplay: Option, pub item_id: Option, pub media: Option, pub playback_duration: Option, pub preload_time: Option, pub start_time: Option, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct TextTrackStyle { pub background_color: Option, pub edge_color: Option, pub edge_type: Option, pub font_family: Option, pub font_generic_familiy: Option, pub font_scale: Option, pub font_style: Option, pub foreground_color: Option, pub window_color: Option, pub window_rounded_corner_radius: Option, pub window_type: Option, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct Track { pub audio_track_info: Option, pub is_inband: Option, pub language: Option, pub name: Option, pub roles: Option>, pub subtype: Option, pub track_content_id: Option, pub track_content_type: Option, pub track_id: i32, #[serde(rename = "type")] pub type_: TrackType, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct TracksInfo { pub active_track_ids: Option>, pub language: Option, pub text_track_style: Option, pub tracks: Option>, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[serde(tag = "type")] #[serde(rename = "TV_SHOW")] #[builder(setter(strip_option, into), default)] pub struct TvShowMediaMetadata { pub episode: Option, pub episode_title: Option, pub images: Option>, pub original_airdate: Option, pub season: Option, pub series_title: Option, pub title: Option, } impl From for MediaMetadata { fn from(val: TvShowMediaMetadata) -> Self { MediaMetadata { metadata_type: MetadataType::TvShow(val), ..Default::default() } } } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct UserActionState { pub user_action: UserAction, } #[derive(Serialize, Deserialize, Clone, Debug, Default)] #[serde(rename_all = "camelCase")] pub struct VastAdsRequest { pub ads_response: Option, pub ad_tag_url: Option, } #[skip_serializing_none] #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct VideoInformation { pub hdr_type: HdrType, pub height: i32, pub width: i32, } // ENUMS -------------------------------------------------------- #[derive(Serialize, Deserialize, Clone, Debug, Default)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum CaptionMimeType { #[default] Cea608, Ttml, Vtt, TtmlMp3, } #[derive(Serialize, Deserialize, Default)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum CommandOld { #[default] Pause, Seek, StreamVolume, StreamMute, AllBasicMedia, QueueNext, QueuePrev, QueueShuffle, QueueRepeatAll, QueueRepeatOne, QueueRepeat, SkipAd, EditTracks, PlaybackRate, Like, Dislike, Follow, Unfollow, StreamTransfer, Lyrics, } bitflags::bitflags! { #[derive(Debug, PartialEq, Eq, Clone, Default)] pub struct Command: u32 { const Pause = 1; const Seek = 2; const StreamVolume = 4; const StreamMute = 8; const AllBasicMedia = 12303; const QueueNext = 64; const QueuePrev = 128; const QueueShuffle = 256; const QueueRepeatAll = 1024; const QueueRepeatOne = 2048; const QueueRepeat = 3072; const SkipAd = 512; const EditTracks = 4096; const PlaybackRate = 8192; const Like = 16384; const Dislike = 32768; const Follow = 65536; const Unfollow = 131072; const StreamTransfer = 262144; const Lyrics = 524288; } } impl serde::Serialize for Command { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { serializer.serialize_u32(self.bits()) } } impl<'de> serde::Deserialize<'de> for Command { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let bits = u32::deserialize(deserializer)?; Command::from_bits(bits).ok_or_else(|| serde::de::Error::custom("invalid bitflags value")) } } #[derive(Serialize, Deserialize, Clone, Debug, Default)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum ContainerType { #[default] GenericContainer, AudiobookContainer, } #[derive(Serialize, Deserialize, Clone, Debug, Default)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum ContentFilteringMode { #[default] FilterExplicit, } #[derive(Serialize, Deserialize, Clone, Debug, Default)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum ErrorReason { #[default] None, InvalidCommand, InvalidParams, InvalidMediaSessionId, InvalidRequestId, SkipLimitReached, NotSupported, LanguageNotSupported, EndOfQueue, DuplicateRequestId, VideoDeviceRequired, PremiumAccountRequired, AppError, AuthenticationExpired, ConcurrentStreamLimit, ParentalControlRestricted, ContentFiltered, NotAvailableInRegion, ContentAlreadyPlaying, InvalidRequest, GenericLoadError, } #[derive(Serialize, Deserialize, Clone, Debug, Default)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum ExtendedPlayerState { #[default] Loading, } #[derive(Serialize, Deserialize, Clone, Debug, Default)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum FocusState { #[default] InFocus, NotInFocus, } #[derive(Serialize, Deserialize, Clone, Debug, Default)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum GetStatusOptions { #[default] NoMetadata, NoQueueItems, } #[derive(Serialize, Deserialize, Clone, Debug, Default)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum HdrType { #[default] Sdr, Hdr, Dv, } #[derive(Serialize, Deserialize, Clone, Debug, Default)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum HlsSegmentFormat { #[default] Aac, Ac3, Mp3, Ts, TsAac, EAc3, Fmp4, } #[derive(Serialize, Deserialize, Clone, Debug, Default)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum HlsVideoSegmentFormat { #[default] Mpeg2Ts, Fmp4, } #[derive(Serialize, Deserialize, Clone, Debug, Default)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum IdleReason { #[default] Cancelled, Interrupted, Finished, Error, } #[derive(Serialize, Deserialize, Clone, Debug, Default)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum MediaCategory { #[default] Audio, Video, Image, } #[derive(Serialize, Deserialize, Clone, Debug)] #[serde(untagged)] pub enum MetadataType { Generic(GenericMediaMetadata), Movie(MovieMediaMetadata), TvShow(TvShowMediaMetadata), MusicTrack(MusicTrackMediaMetadata), Photo(PhotoMediaMetadata), AudiobookChapter(AudiobookChapterMediaMetadata), } impl Default for MetadataType { fn default() -> Self { Self::Generic(GenericMediaMetadata::default()) } } #[derive(Serialize, Deserialize, Clone, Debug, Default)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum PlayerState { #[default] Idle, Playing, Paused, Buffering, } #[derive(Serialize, Deserialize, Clone, Debug, Default)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum QueueChangeType { #[default] Insert, Remove, ItemsChange, Update, NoChange, } #[derive(Serialize, Deserialize, Clone, Debug, Default)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum QueueType { #[default] Album, Playlist, Audiobook, RadioStation, PodcastSeries, TvSeries, VideoPlaylist, LiveTv, Movie, } #[derive(Serialize, Deserialize, Clone, Debug, Default)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum RepeatMode { #[default] RepeatOff, RepeatAll, RepeatSingle, RepeatAllAndShuffle, } #[derive(Serialize, Deserialize, Clone, Debug, Default)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum SeekResumeState { #[default] PlaybackStart, PlaybackPause, } #[derive(Serialize, Deserialize, Clone, Debug, Default)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum StreamingProtocolType { #[default] Unknown, MpegDash, Hls, SmoothStreaming, } #[derive(Serialize, Deserialize, Clone, Debug, Default)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum StreamType { #[default] None, Buffered, Live, } #[derive(Serialize, Deserialize, Clone, Debug, Default)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum TextTrackEdgeType { #[default] None, Outline, DropShadow, Raised, Depressed, } #[derive(Serialize, Deserialize, Clone, Debug, Default)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum TextTrackFontGenericFamily { #[default] SansSerif, MonospacedSansSerif, Serif, MonospacedSerif, Casual, Cursive, SmallCapitals, } #[derive(Serialize, Deserialize, Clone, Debug, Default)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum TextTrackFontStyle { #[default] Normal, Bold, BoldItalic, Italic, } #[derive(Serialize, Deserialize, Clone, Debug, Default)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum TextTrackType { #[default] Subtitles, Captions, Descriptions, Chapters, Metadata, } #[derive(Serialize, Deserialize, Clone, Debug, Default)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum TextTrackWindowType { #[default] None, Normal, RoundedCorners, } #[derive(Serialize, Deserialize, Clone, Debug, Default)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum TrackType { #[default] Text, Audio, Video, } #[derive(Serialize, Deserialize, Clone, Debug, Default)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum UserAction { #[default] Like, Dislike, Follow, Unfollow, Flag, SkipAd, Lyrics, } #[derive(Serialize, Deserialize, Clone, Debug, Default)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum UserActionContext { #[default] UnknownContext, Track, Album, Artist, Playlist, Episode, Series, Movie, Channel, Team, Player, Coach, } cast-sender-0.2.0/src/namespace/mod.rs000064400000000000000000000044571046102023000157150ustar 00000000000000use std::{collections::HashMap, str::FromStr}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde_json::Value; use strum_macros::{Display, EnumString}; use crate::Payload; pub mod connection; pub mod heartbeat; pub mod media; pub mod multizone; pub mod receiver; #[derive(EnumString, Display, Debug, Clone, Default, PartialEq, Eq)] pub enum NamespaceUrn { #[strum(serialize = "urn:x-cast:com.google.cast.cac")] Cac, #[default] #[strum(serialize = "urn:x-cast:com.google.cast.tp.connection")] Connection, #[strum(default)] Custom(String), #[strum(serialize = "urn:x-cast:com.google.cast.debugoverlay")] DebugOverlay, #[strum(serialize = "urn:x-cast:com.google.cast.tp.deviceauth")] DeviceAuth, #[strum(serialize = "urn:x-cast:com.google.cast.tp.heartbeat")] Heartbeat, #[strum(serialize = "urn:x-cast:com.google.cast.media")] Media, #[strum(serialize = "urn:x-cast:com.google.cast.multizone")] Multizone, #[strum(serialize = "urn:x-cast:com.google.cast.receiver")] Receiver, #[strum(serialize = "urn:x-cast:com.google.cast.remotecontrol")] RemoteControl, #[strum(serialize = "urn:x-cast:com.google.cast.sse")] Sse, } #[derive(Serialize, Deserialize, Clone, Debug, Default)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(tag = "type")] pub struct Custom { #[serde(skip)] pub namespace: NamespaceUrn, #[serde(flatten)] pub fields: HashMap, } impl From for Payload { fn from(val: Custom) -> Self { Payload::Custom(val.clone()) } } impl<'de> Deserialize<'de> for NamespaceUrn { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { #[derive(Deserialize)] pub struct Namespace { name: String, } let ns = Namespace::deserialize(deserializer)?; NamespaceUrn::from_str(&ns.name).map_err(serde::de::Error::custom) } } impl Serialize for NamespaceUrn { fn serialize(&self, serializer: S) -> Result where S: Serializer, { #[derive(Serialize)] pub struct Namespace { name: String, } Namespace { name: self.to_string(), } .serialize(serializer) } } cast-sender-0.2.0/src/namespace/multizone.rs000064400000000000000000000012741046102023000171560ustar 00000000000000use crate::Payload; use crate::Volume; #[derive(Serialize, Deserialize, Clone, Debug)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(tag = "type")] pub enum Multizone { DeviceUpdated(DeviceResponse), } impl From for Payload { fn from(val: Multizone) -> Self { Payload::Multizone(val.clone()) } } #[derive(Serialize, Deserialize, Clone, Debug, Default)] #[serde(rename_all = "camelCase")] pub struct DeviceResponse { pub device: Device, } #[derive(Serialize, Deserialize, Clone, Debug, Default)] #[serde(rename_all = "camelCase")] pub struct Device { pub capabilities: u32, pub device_id: String, pub name: String, pub volume: Volume, } cast-sender-0.2.0/src/namespace/receiver.rs000064400000000000000000000040261046102023000167320ustar 00000000000000use crate::{app::AppId, App, Payload, Volume}; #[derive(Serialize, Deserialize, Clone, Debug)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(tag = "type")] pub enum Receiver { // Request GetStatus, Launch(LaunchRequest), SetVolume(SetVolumeRequest), Stop(StopRequest), LaunchError(LaunchErrorResponse), // Response ReceiverStatus(ReceiverStatusResponse), } impl Receiver { pub fn set_volume_request(level: f64, muted: bool) -> Self { Self::SetVolume(SetVolumeRequest { volume: Volume { control_type: None, muted: Some(muted), level: Some(level), }, }) } pub fn launch_request(app_id: AppId) -> Self { Self::Launch(LaunchRequest { app_id: app_id.to_string(), }) } pub fn stop_request(session_id: String) -> Self { Self::Stop(StopRequest { session_id }) } } impl From for Payload { fn from(val: Receiver) -> Self { Payload::Receiver(val.clone()) } } #[derive(Serialize, Deserialize, Clone, Debug, Default)] #[serde(rename_all = "camelCase")] pub struct LaunchRequest { pub app_id: String, } #[derive(Serialize, Deserialize, Clone, Debug, Default)] #[serde(rename_all = "camelCase")] pub struct StopRequest { pub session_id: String, } #[derive(Serialize, Deserialize, Clone, Debug, Default)] #[serde(rename_all = "camelCase")] pub struct SetVolumeRequest { pub volume: Volume, } #[derive(Serialize, Deserialize, Clone, Debug, Default)] #[serde(rename_all = "camelCase")] pub struct ReceiverStatusResponse { pub status: Status, } #[derive(Serialize, Deserialize, Clone, Debug, Default)] #[serde(rename_all = "camelCase")] pub struct LaunchErrorResponse { pub reason: String, } #[derive(Serialize, Deserialize, Clone, Debug, Default)] #[serde(rename_all = "camelCase")] pub struct Status { pub applications: Option>, pub is_active_input: Option, pub is_standby: Option, pub volume: Volume, } cast-sender-0.2.0/src/payload.rs000064400000000000000000000015341046102023000146240ustar 00000000000000use crate::namespace::{ connection::*, heartbeat::*, media::*, multizone::*, receiver::*, Custom, NamespaceUrn, }; #[derive(Serialize, Deserialize, Clone, Debug)] #[serde(untagged)] pub enum Payload { Connection(Connection), Heartbeat(Heartbeat), Media(Media), Multizone(Multizone), Receiver(Receiver), // Fallback -> Needs to be last enum variant! Custom(Custom), } impl Payload { pub fn namespace(&self) -> NamespaceUrn { match self { Payload::Connection(_) => NamespaceUrn::Connection, Payload::Heartbeat(_) => NamespaceUrn::Heartbeat, Payload::Media(_) => NamespaceUrn::Media, Payload::Multizone(_) => NamespaceUrn::Multizone, Payload::Receiver(_) => NamespaceUrn::Receiver, Payload::Custom(pl) => pl.namespace.clone(), } } } cast-sender-0.2.0/src/proto/cast.rs000064400000000000000000000270421046102023000152720ustar 00000000000000// This file is @generated by prost-build. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct CastMessage { #[prost(enumeration = "cast_message::ProtocolVersion", required, tag = "1")] pub protocol_version: i32, /// source and destination ids identify the origin and destination of the /// message. They are used to route messages between endpoints that share a /// device-to-device channel. /// /// For messages between applications: /// - The sender application id is a unique identifier generated on behalf of /// the sender application. /// - The receiver id is always the the session id for the application. /// /// For messages to or from the sender or receiver platform, the special ids /// 'sender-0' and 'receiver-0' can be used. /// /// For messages intended for all endpoints using a given channel, the /// wildcard destination_id '*' can be used. #[prost(string, required, tag = "2")] pub source_id: ::prost::alloc::string::String, #[prost(string, required, tag = "3")] pub destination_id: ::prost::alloc::string::String, /// This is the core multiplexing key. All messages are sent on a namespace /// and endpoints sharing a channel listen on one or more namespaces. The /// namespace defines the protocol and semantics of the message. #[prost(string, required, tag = "4")] pub namespace: ::prost::alloc::string::String, #[prost(enumeration = "cast_message::PayloadType", required, tag = "5")] pub payload_type: i32, /// Depending on payload_type, exactly one of the following optional fields /// will always be set. #[prost(string, optional, tag = "6")] pub payload_utf8: ::core::option::Option<::prost::alloc::string::String>, #[prost(bytes = "vec", optional, tag = "7")] pub payload_binary: ::core::option::Option<::prost::alloc::vec::Vec>, /// Flag indicating whether there are more chunks to follow for this message. /// If the flag is false or is not present, then this is the last (or only) /// chunk of the message. #[prost(bool, optional, tag = "8")] pub continued: ::core::option::Option, /// If this is a chunk of a larger message, and the remaining length of the /// message payload (the sum of the lengths of the payloads of the remaining /// chunks) is known, this field will indicate that length. For a given /// chunked message, this field should either be present in all of the chunks, /// or in none of them. #[prost(uint32, optional, tag = "9")] pub remaining_length: ::core::option::Option, } /// Nested message and enum types in `CastMessage`. pub mod cast_message { /// Always pass a version of the protocol for future compatibility /// requirements. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] pub enum ProtocolVersion { Castv210 = 0, /// message chunking support (deprecated). Castv211 = 1, /// reworked message chunking. Castv212 = 2, /// binary payload over utf8. Castv213 = 3, } impl ProtocolVersion { /// String value of the enum field names used in the ProtoBuf definition. /// /// The values are not transformed in any way and thus are considered stable /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { ProtocolVersion::Castv210 => "CASTV2_1_0", ProtocolVersion::Castv211 => "CASTV2_1_1", ProtocolVersion::Castv212 => "CASTV2_1_2", ProtocolVersion::Castv213 => "CASTV2_1_3", } } /// Creates an enum from field names used in the ProtoBuf definition. pub fn from_str_name(value: &str) -> ::core::option::Option { match value { "CASTV2_1_0" => Some(Self::Castv210), "CASTV2_1_1" => Some(Self::Castv211), "CASTV2_1_2" => Some(Self::Castv212), "CASTV2_1_3" => Some(Self::Castv213), _ => None, } } } /// What type of data do we have in this message. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] pub enum PayloadType { String = 0, Binary = 1, } impl PayloadType { /// String value of the enum field names used in the ProtoBuf definition. /// /// The values are not transformed in any way and thus are considered stable /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { PayloadType::String => "STRING", PayloadType::Binary => "BINARY", } } /// Creates an enum from field names used in the ProtoBuf definition. pub fn from_str_name(value: &str) -> ::core::option::Option { match value { "STRING" => Some(Self::String), "BINARY" => Some(Self::Binary), _ => None, } } } } /// Messages for authentication protocol between a sender and a receiver. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct AuthChallenge { #[prost( enumeration = "SignatureAlgorithm", optional, tag = "1", default = "RsassaPkcs1v15" )] pub signature_algorithm: ::core::option::Option, #[prost(bytes = "vec", optional, tag = "2")] pub sender_nonce: ::core::option::Option<::prost::alloc::vec::Vec>, #[prost(enumeration = "HashAlgorithm", optional, tag = "3", default = "Sha1")] pub hash_algorithm: ::core::option::Option, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct AuthResponse { #[prost(bytes = "vec", required, tag = "1")] pub signature: ::prost::alloc::vec::Vec, #[prost(bytes = "vec", required, tag = "2")] pub client_auth_certificate: ::prost::alloc::vec::Vec, #[prost(bytes = "vec", repeated, tag = "3")] pub intermediate_certificate: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec>, #[prost( enumeration = "SignatureAlgorithm", optional, tag = "4", default = "RsassaPkcs1v15" )] pub signature_algorithm: ::core::option::Option, #[prost(bytes = "vec", optional, tag = "5")] pub sender_nonce: ::core::option::Option<::prost::alloc::vec::Vec>, #[prost(enumeration = "HashAlgorithm", optional, tag = "6", default = "Sha1")] pub hash_algorithm: ::core::option::Option, #[prost(bytes = "vec", optional, tag = "7")] pub crl: ::core::option::Option<::prost::alloc::vec::Vec>, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, Copy, PartialEq, ::prost::Message)] pub struct AuthError { #[prost(enumeration = "auth_error::ErrorType", required, tag = "1")] pub error_type: i32, } /// Nested message and enum types in `AuthError`. pub mod auth_error { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] pub enum ErrorType { InternalError = 0, /// The underlying connection is not TLS NoTls = 1, SignatureAlgorithmUnavailable = 2, } impl ErrorType { /// String value of the enum field names used in the ProtoBuf definition. /// /// The values are not transformed in any way and thus are considered stable /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { ErrorType::InternalError => "INTERNAL_ERROR", ErrorType::NoTls => "NO_TLS", ErrorType::SignatureAlgorithmUnavailable => "SIGNATURE_ALGORITHM_UNAVAILABLE", } } /// Creates an enum from field names used in the ProtoBuf definition. pub fn from_str_name(value: &str) -> ::core::option::Option { match value { "INTERNAL_ERROR" => Some(Self::InternalError), "NO_TLS" => Some(Self::NoTls), "SIGNATURE_ALGORITHM_UNAVAILABLE" => Some(Self::SignatureAlgorithmUnavailable), _ => None, } } } } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct DeviceAuthMessage { /// Request fields #[prost(message, optional, tag = "1")] pub challenge: ::core::option::Option, /// Response fields #[prost(message, optional, tag = "2")] pub response: ::core::option::Option, #[prost(message, optional, tag = "3")] pub error: ::core::option::Option, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] pub enum SignatureAlgorithm { Unspecified = 0, RsassaPkcs1v15 = 1, RsassaPss = 2, } impl SignatureAlgorithm { /// String value of the enum field names used in the ProtoBuf definition. /// /// The values are not transformed in any way and thus are considered stable /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { SignatureAlgorithm::Unspecified => "UNSPECIFIED", SignatureAlgorithm::RsassaPkcs1v15 => "RSASSA_PKCS1v15", SignatureAlgorithm::RsassaPss => "RSASSA_PSS", } } /// Creates an enum from field names used in the ProtoBuf definition. pub fn from_str_name(value: &str) -> ::core::option::Option { match value { "UNSPECIFIED" => Some(Self::Unspecified), "RSASSA_PKCS1v15" => Some(Self::RsassaPkcs1v15), "RSASSA_PSS" => Some(Self::RsassaPss), _ => None, } } } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] pub enum HashAlgorithm { Sha1 = 0, Sha256 = 1, } impl HashAlgorithm { /// String value of the enum field names used in the ProtoBuf definition. /// /// The values are not transformed in any way and thus are considered stable /// (if the ProtoBuf definition does not change) and safe for programmatic use. pub fn as_str_name(&self) -> &'static str { match self { HashAlgorithm::Sha1 => "SHA1", HashAlgorithm::Sha256 => "SHA256", } } /// Creates an enum from field names used in the ProtoBuf definition. pub fn from_str_name(value: &str) -> ::core::option::Option { match value { "SHA1" => Some(Self::Sha1), "SHA256" => Some(Self::Sha256), _ => None, } } } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct AuthorityKeys { #[prost(message, repeated, tag = "1")] pub keys: ::prost::alloc::vec::Vec, } /// Nested message and enum types in `AuthorityKeys`. pub mod authority_keys { #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct Key { #[prost(bytes = "vec", required, tag = "1")] pub fingerprint: ::prost::alloc::vec::Vec, #[prost(bytes = "vec", required, tag = "2")] pub public_key: ::prost::alloc::vec::Vec, } } cast-sender-0.2.0/src/proto/mod.rs000064400000000000000000000000331046102023000151060ustar 00000000000000mod cast; pub use cast::*; cast-sender-0.2.0/src/receiver.rs000064400000000000000000000204121046102023000147730ustar 00000000000000use std::collections::HashMap; use std::sync::Arc; use std::time::Duration; use async_channel::Sender; use smol::lock::Mutex; use smol_timeout::TimeoutExt; use crate::app::AppId; use crate::namespace::{ connection::*, heartbeat::*, receiver::{self, *}, NamespaceUrn, }; use crate::{App, Response, Volume}; use super::{Client, Error, Payload}; #[derive(Debug, Clone)] pub struct Receiver { client: Arc>>, platform: App, // Ids for request messages which get incremented request_id: Arc>, requests: Arc>>>, } impl Receiver { pub fn new() -> Self { Self { client: Arc::default(), platform: App::receiver(), request_id: Arc::default(), requests: Arc::default(), } } pub async fn connect(&self, addr: &str) -> Result<(), Error> { let client = Client::connect(addr).await?; self.client.lock().await.replace(client.clone()); // Establish virtual connection with cast receiver self.send(&self.platform, Connection::Connect).await?; // Ensure we're successfully connected by doing a ping <-> pong sequence self.send(&self.platform, Heartbeat::Ping).await?; client.receive().await?; // Spawn own task to receive messages from the receiver let d = self.clone(); smol::spawn(async move { loop { if let Some(client) = d.client().await { match client.receive().await { Ok(response) => { if let Err(err) = d.process_response(response).await { warn!("Unable to process received message: {}", err.to_string()) } } Err(err) => { error!("Unable to receive message: {}", err.to_string()); d.disconnect().await; break; } } } else { debug!("No client available, stop receiving."); break; } } }) .detach(); Ok(()) } /// Only closes the underlying connection, does not stop any running applications. pub async fn disconnect(&self) { // Try to close the virtual connection, but don't care about the result let _ = self.send(&self.platform, Connection::Close).await; let mut client = self.client.lock().await; *client = None; // Reset requestId counter *self.request_id.lock().await = 0; } pub async fn is_connected(&self) -> bool { self.client().await.is_some() } /// Currently running applications pub async fn applications(&self) -> Result, Error> { Ok(self.status().await?.applications.unwrap_or_default()) } pub async fn launch_app(&self, app_id: AppId) -> Result { let response = self .send_request( &self.platform, receiver::Receiver::launch_request(app_id.clone()), ) .await?; if let Payload::Receiver(payload) = response.payload { if let receiver::Receiver::LaunchError(LaunchErrorResponse { reason }) = payload { return Err(Error::LaunchError(reason)); } if let receiver::Receiver::ReceiverStatus(ReceiverStatusResponse { status }) = payload { if let Some(apps) = status.applications { for app in apps { if app.app_id == app_id { // Establish new virtual connection to be able to send/receive app specific payloads self.send(&app, Connection::Connect).await?; return Ok(app); } } } } } Err(Error::NoResponse) } pub async fn stop_app(&self, app: &App) -> Result<(), Error> { self.send_request( &self.platform, receiver::Receiver::stop_request(app.session_id.clone()), ) .await?; Ok(()) } pub async fn volume(&self) -> Result { Ok(self.status().await?.volume) } pub async fn set_volume(&self, level: f64, muted: bool) -> Result<(), Error> { self.send_request( &self.platform, receiver::Receiver::set_volume_request(level, muted), ) .await?; Ok(()) } pub async fn status(&self) -> Result { let response = self .send_request(&self.platform, receiver::Receiver::GetStatus) .await?; if let Payload::Receiver(receiver::Receiver::ReceiverStatus(ReceiverStatusResponse { status, })) = response.payload { return Ok(status); } Err(Error::NoResponse) } pub async fn send>(&self, app: &App, payload: P) -> Result<(), Error> { let payload: Payload = payload.into(); let namespace = payload.namespace(); if !app.namespaces.contains(&namespace) && namespace != NamespaceUrn::Connection { debug!( "Unsupported namespace {}, app supports: {:#?}", namespace, app.namespaces ); return Err(Error::UnsupportedNamespace); } let client = match self.client().await { Some(client) => client, None => { return Err(Error::NoConnection); } }; client.send(app.transport_id.clone(), payload, None).await?; Ok(()) } pub async fn send_request>( &self, app: &App, payload: P, ) -> Result { let payload: Payload = payload.into(); let namespace = payload.namespace(); if !app.namespaces.contains(&namespace) && namespace != NamespaceUrn::Connection { dbg!(&payload); debug!( "Unsupported namespace {}, app supports: {:#?}", namespace, app.namespaces ); return Err(Error::UnsupportedNamespace); } let client = match self.client().await { Some(client) => client, None => { return Err(Error::NoConnection); } }; let (response_tx, response_rx) = async_channel::bounded(1); // Each request message gets a unique requestId let request_id = { let mut id = self.request_id.lock().await; *id += 1; *id }; // Store request to be able to assign the response in `process_message()` let mut requests = self.requests.lock().await; requests.insert(request_id, response_tx); drop(requests); client .send(app.transport_id.clone(), payload, Some(request_id)) .await?; // Wait up to 10 seconds before giving up the request let res = response_rx.recv().timeout(Duration::from_secs(10)).await; match res { Some(response) => Ok(response?), None => { let mut requests = self.requests.lock().await; requests.remove(&request_id); Err(Error::ResponseTimeout) } } } async fn process_response(&self, response: Response) -> Result<(), Error> { // Check if this payload is a response to a sent request if let Some(request_id) = response.request_id { if request_id != 0 { match self.requests.lock().await.remove(&request_id) { Some(sender) => sender.send(response.clone()).await?, None => debug!("Ignore payload with unknown requestId"), } } } if let Payload::Heartbeat(Heartbeat::Ping) = &response.payload { self.send(&self.platform, Heartbeat::Pong).await?; } Ok(()) } async fn client(&self) -> Option { self.client.lock().await.clone() } } impl Default for Receiver { fn default() -> Self { Self::new() } } cast-sender-0.2.0/src/shared.rs000064400000000000000000000012451046102023000144400ustar 00000000000000#[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct Volume { #[serde(skip_serializing_if = "Option::is_none")] pub control_type: Option, pub muted: Option, pub level: Option, } #[derive(Serialize, Deserialize, Clone, Debug, Default, Builder)] #[serde(rename_all = "camelCase")] #[builder(setter(strip_option, into), default)] pub struct Image { pub url: String, #[serde(skip_serializing_if = "Option::is_none")] pub height: Option, #[serde(skip_serializing_if = "Option::is_none")] pub width: Option, }