zbus-4.3.1/.cargo_vcs_info.json0000644000000001420000000000100120210ustar { "git": { "sha1": "6f7efc9448cf02fb97dd5920da3de45164854a63" }, "path_in_vcs": "zbus" }zbus-4.3.1/Cargo.lock0000644000001240620000000000100100040ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "addr2line" version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" dependencies = [ "gimli", ] [[package]] name = "adler" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "async-broadcast" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20cd0e2e25ea8e5f7e9df04578dc6cf5c83577fd09b1a46aaf5c85e1c33f2a7e" dependencies = [ "event-listener", "event-listener-strategy", "futures-core", "pin-project-lite", ] [[package]] name = "async-channel" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" dependencies = [ "concurrent-queue", "event-listener-strategy", "futures-core", "pin-project-lite", ] [[package]] name = "async-executor" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8828ec6e544c02b0d6691d21ed9f9218d0384a82542855073c2a3f58304aaf0" dependencies = [ "async-task", "concurrent-queue", "fastrand", "futures-lite", "slab", ] [[package]] name = "async-fs" version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" dependencies = [ "async-lock", "blocking", "futures-lite", ] [[package]] name = "async-io" version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d6baa8f0178795da0e71bc42c9e5d13261aac7ee549853162e66a241ba17964" dependencies = [ "async-lock", "cfg-if", "concurrent-queue", "futures-io", "futures-lite", "parking", "polling", "rustix", "slab", "tracing", "windows-sys 0.52.0", ] [[package]] name = "async-lock" version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" dependencies = [ "event-listener", "event-listener-strategy", "pin-project-lite", ] [[package]] name = "async-process" version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7eda79bbd84e29c2b308d1dc099d7de8dcc7035e48f4bf5dc4a531a44ff5e2a" dependencies = [ "async-channel", "async-io", "async-lock", "async-signal", "async-task", "blocking", "cfg-if", "event-listener", "futures-lite", "rustix", "tracing", "windows-sys 0.52.0", ] [[package]] name = "async-recursion" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", "syn 2.0.68", ] [[package]] name = "async-signal" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "794f185324c2f00e771cd9f1ae8b5ac68be2ca7abb129a87afd6e86d228bc54d" dependencies = [ "async-io", "async-lock", "atomic-waker", "cfg-if", "futures-core", "futures-io", "rustix", "signal-hook-registry", "slab", "windows-sys 0.52.0", ] [[package]] name = "async-task" version = "4.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" version = "0.1.80" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", "syn 2.0.68", ] [[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 = "backtrace" version = "0.3.73" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" dependencies = [ "addr2line", "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", ] [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] name = "block-buffer" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "blocking" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" dependencies = [ "async-channel", "async-task", "futures-io", "futures-lite", "piper", ] [[package]] name = "bytes" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cc" version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cfg_aliases" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" [[package]] name = "cfg_aliases" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "num-traits", "serde", ] [[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 = "cpufeatures" version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] [[package]] name = "crossbeam-utils" version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crypto-common" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", ] [[package]] name = "deranged" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", "serde", ] [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", ] [[package]] name = "doc-comment" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "endi" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" [[package]] name = "enumflags2" version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d" dependencies = [ "enumflags2_derive", "serde", ] [[package]] name = "enumflags2_derive" version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" dependencies = [ "proc-macro2", "quote", "syn 2.0.68", ] [[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 = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" dependencies = [ "concurrent-queue", "parking", "pin-project-lite", ] [[package]] name = "event-listener-strategy" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" dependencies = [ "event-listener", "pin-project-lite", ] [[package]] name = "fastrand" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "form_urlencoded" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] [[package]] name = "futures" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", "futures-executor", "futures-io", "futures-sink", "futures-task", "futures-util", ] [[package]] name = "futures-channel" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", ] [[package]] name = "futures-core" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-executor" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" dependencies = [ "futures-core", "futures-task", "futures-util", ] [[package]] name = "futures-io" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-lite" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" dependencies = [ "fastrand", "futures-core", "futures-io", "parking", "pin-project-lite", ] [[package]] name = "futures-macro" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", "syn 2.0.68", ] [[package]] name = "futures-sink" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-channel", "futures-core", "futures-io", "futures-macro", "futures-sink", "futures-task", "memchr", "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "generic-array" version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", ] [[package]] name = "getrandom" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "gimli" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" [[package]] name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[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 = "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 = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "linux-raw-sys" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "matchers" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" dependencies = [ "regex-automata 0.1.10", ] [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memoffset" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" dependencies = [ "autocfg", ] [[package]] name = "memoffset" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] [[package]] name = "miniz_oxide" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" dependencies = [ "adler", ] [[package]] name = "mio" version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "wasi", "windows-sys 0.48.0", ] [[package]] name = "nix" version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" dependencies = [ "bitflags 1.3.2", "cfg-if", "libc", "memoffset 0.6.5", ] [[package]] name = "nix" version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ "bitflags 2.5.0", "cfg-if", "cfg_aliases 0.1.1", "libc", "memoffset 0.9.1", ] [[package]] name = "nix" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ "bitflags 2.5.0", "cfg-if", "cfg_aliases 0.2.1", "libc", "memoffset 0.9.1", ] [[package]] name = "ntest" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb183f0a1da7a937f672e5ee7b7edb727bf52b8a52d531374ba8ebb9345c0330" dependencies = [ "ntest_test_cases", "ntest_timeout", ] [[package]] name = "ntest_test_cases" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16d0d3f2a488592e5368ebbe996e7f1d44aa13156efad201f5b4d84e150eaa93" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "ntest_timeout" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcc7c92f190c97f79b4a332f5e81dcf68c8420af2045c936c9be0bc9de6f63b5" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "nu-ansi-term" version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" dependencies = [ "overload", "winapi", ] [[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 = "num_cpus" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ "hermit-abi 0.3.9", "libc", ] [[package]] name = "object" version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434" dependencies = [ "memchr", ] [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "ordered-stream" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" dependencies = [ "futures-core", "pin-project-lite", ] [[package]] name = "overload" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "parking" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" [[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.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.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae1d5c74c9876f070d3e8fd503d748c7d974c3e48da8f41350fa5222ef9b4391" dependencies = [ "atomic-waker", "fastrand", "futures-io", ] [[package]] name = "polling" version = "3.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3ed00ed3fbf728b5816498ecd316d1716eecaced9c0c8d2c5a6740ca214985b" dependencies = [ "cfg-if", "concurrent-queue", "hermit-abi 0.4.0", "pin-project-lite", "rustix", "tracing", "windows-sys 0.52.0", ] [[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro-crate" version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" dependencies = [ "toml_edit", ] [[package]] name = "proc-macro2" version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[package]] name = "quick-xml" version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" dependencies = [ "memchr", "serde", ] [[package]] name = "quote" version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] [[package]] name = "regex" version = "1.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" dependencies = [ "aho-corasick", "memchr", "regex-automata 0.4.7", "regex-syntax 0.8.4", ] [[package]] name = "regex-automata" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" dependencies = [ "regex-syntax 0.6.29", ] [[package]] name = "regex-automata" version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", "regex-syntax 0.8.4", ] [[package]] name = "regex-syntax" version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "rustc-demangle" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustix" version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ "bitflags 2.5.0", "errno", "libc", "linux-raw-sys", "windows-sys 0.52.0", ] [[package]] name = "serde" version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", "syn 2.0.68", ] [[package]] name = "serde_repr" version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", "syn 2.0.68", ] [[package]] name = "sha1" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", "digest", ] [[package]] name = "sharded-slab" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ "lazy_static", ] [[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 = "socket2" version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "syn" version = "2.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "tempfile" version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", "rustix", "windows-sys 0.52.0", ] [[package]] name = "test-log" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dffced63c2b5c7be278154d76b479f9f9920ed34e7574201407f0b14e2bbb93" dependencies = [ "test-log-macros", "tracing-subscriber", ] [[package]] name = "test-log-macros" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5999e24eaa32083191ba4e425deb75cdf25efefabe5aaccb7446dd0d4122a3f5" dependencies = [ "proc-macro2", "quote", "syn 2.0.68", ] [[package]] name = "thread_local" version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ "cfg-if", "once_cell", ] [[package]] name = "time" version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "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.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" version = "1.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" dependencies = [ "backtrace", "bytes", "libc", "mio", "num_cpus", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", "tracing", "windows-sys 0.48.0", ] [[package]] name = "tokio-macros" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", "syn 2.0.68", ] [[package]] name = "tokio-vsock" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52a15c15b1bc91f90902347eff163b5b682643aff0c8e972912cca79bd9208dd" dependencies = [ "bytes", "futures", "libc", "tokio", "vsock 0.3.0", ] [[package]] name = "toml_datetime" version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" [[package]] name = "toml_edit" version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ "indexmap", "toml_datetime", "winnow", ] [[package]] name = "tracing" version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "pin-project-lite", "tracing-attributes", "tracing-core", ] [[package]] name = "tracing-attributes" version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", "syn 2.0.68", ] [[package]] name = "tracing-core" version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", ] [[package]] name = "tracing-subscriber" version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ "matchers", "nu-ansi-term", "once_cell", "regex", "sharded-slab", "thread_local", "tracing", "tracing-core", ] [[package]] name = "typenum" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "uds_windows" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" dependencies = [ "memoffset 0.9.1", "tempfile", "winapi", ] [[package]] name = "unicode-bidi" version = "0.3.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", "serde", ] [[package]] name = "uuid" version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5de17fd2f7da591098415cff336e12965a28061ddace43b59cb3c430179c9439" dependencies = [ "serde", ] [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "vsock" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c8e1df0bf1e1b28095c24564d1b90acae64ca69b097ed73896e342fa6649c57" dependencies = [ "libc", "nix 0.24.3", ] [[package]] name = "vsock" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f82e291049535877d607cd861b6820eb622538c88853c1c01df72b1dbed4e32d" dependencies = [ "libc", "nix 0.28.0", ] [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[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-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.5", ] [[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.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ "windows_aarch64_gnullvm 0.52.5", "windows_aarch64_msvc 0.52.5", "windows_i686_gnu 0.52.5", "windows_i686_gnullvm", "windows_i686_msvc 0.52.5", "windows_x86_64_gnu 0.52.5", "windows_x86_64_gnullvm 0.52.5", "windows_x86_64_msvc 0.52.5", ] [[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.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[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.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[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.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" [[package]] name = "windows_i686_gnullvm" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[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.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[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.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[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.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[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.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winnow" version = "0.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" dependencies = [ "memchr", ] [[package]] name = "xdg-home" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca91dcf8f93db085f3a0a29358cd0b9d670915468f4290e8b85d118a34211ab8" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "zbus" version = "4.3.1" dependencies = [ "async-broadcast", "async-executor", "async-fs", "async-io", "async-lock", "async-process", "async-recursion", "async-task", "async-trait", "blocking", "doc-comment", "enumflags2", "event-listener", "futures-core", "futures-sink", "futures-util", "hex", "nix 0.29.0", "ntest", "ordered-stream", "rand", "serde", "serde_repr", "sha1", "static_assertions", "tempfile", "test-log", "tokio", "tokio-vsock", "tracing", "tracing-subscriber", "uds_windows", "vsock 0.5.0", "windows-sys 0.52.0", "xdg-home", "zbus_macros", "zbus_names", "zbus_xml", "zvariant", ] [[package]] name = "zbus_macros" version = "4.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d5a3f12c20bd473be3194af6b49d50d7bb804ef3192dc70eddedb26b85d9da7" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", "syn 2.0.68", "zvariant_utils", ] [[package]] name = "zbus_names" version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" dependencies = [ "serde", "static_assertions", "zvariant", ] [[package]] name = "zbus_xml" version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab3f374552b954f6abb4bd6ce979e6c9b38fb9d0cd7cc68a7d796e70c9f3a233" dependencies = [ "quick-xml", "serde", "static_assertions", "zbus_names", "zvariant", ] [[package]] name = "zvariant" version = "4.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1724a2b330760dc7d2a8402d841119dc869ef120b139d29862d6980e9c75bfc9" dependencies = [ "chrono", "endi", "enumflags2", "serde", "static_assertions", "time", "url", "uuid", "zvariant_derive", ] [[package]] name = "zvariant_derive" version = "4.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55025a7a518ad14518fb243559c058a2e5b848b015e31f1d90414f36e3317859" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", "syn 2.0.68", "zvariant_utils", ] [[package]] name = "zvariant_utils" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc242db087efc22bd9ade7aa7809e4ba828132edc312871584a6b4391bdf8786" dependencies = [ "proc-macro2", "quote", "syn 2.0.68", ] zbus-4.3.1/Cargo.toml0000644000000104570000000000100100310ustar # 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" rust-version = "1.75" name = "zbus" version = "4.3.1" authors = ["Zeeshan Ali Khan "] description = "API for D-Bus communication" readme = "README.md" keywords = [ "D-Bus", "DBus", "IPC", ] categories = ["os::unix-apis"] license = "MIT" repository = "https://github.com/dbus2/zbus/" [package.metadata.docs.rs] all-features = true targets = ["x86_64-unknown-linux-gnu"] [dependencies.async-broadcast] version = "0.7.0" [dependencies.async-executor] version = "1.11.0" optional = true [dependencies.async-fs] version = "2.1.2" optional = true [dependencies.async-io] version = "2.3.2" optional = true [dependencies.async-lock] version = "3.3.0" optional = true [dependencies.async-task] version = "4.7.1" optional = true [dependencies.async-trait] version = "0.1.80" [dependencies.blocking] version = "1.6.0" optional = true [dependencies.enumflags2] version = "0.7.9" features = ["serde"] [dependencies.event-listener] version = "5.3.0" [dependencies.futures-core] version = "0.3.30" [dependencies.futures-sink] version = "0.3.30" [dependencies.futures-util] version = "0.3.30" features = [ "sink", "std", ] default-features = false [dependencies.hex] version = "0.4.3" [dependencies.ordered-stream] version = "0.2" [dependencies.rand] version = "0.8.5" [dependencies.serde] version = "1.0.200" features = ["derive"] [dependencies.serde_repr] version = "0.1.19" [dependencies.sha1] version = "0.10.6" features = ["std"] [dependencies.static_assertions] version = "1.1.0" [dependencies.tokio] version = "1.37.0" features = [ "rt", "net", "time", "fs", "io-util", "process", "sync", "tracing", ] optional = true [dependencies.tokio-vsock] version = "0.4" optional = true [dependencies.tracing] version = "0.1.40" [dependencies.vsock] version = "0.5.0" optional = true [dependencies.xdg-home] version = "1.1.0" [dependencies.zbus_macros] version = "=4.3.1" [dependencies.zbus_names] version = "3.0" [dependencies.zvariant] version = "4.1.1" features = ["enumflags2"] default-features = false [dev-dependencies.doc-comment] version = "0.3.3" [dev-dependencies.futures-util] version = "0.3.30" [dev-dependencies.ntest] version = "0.9.2" [dev-dependencies.tempfile] version = "3.10.1" [dev-dependencies.test-log] version = "0.2.16" features = ["trace"] default-features = false [dev-dependencies.tokio] version = "1.37.0" features = [ "macros", "rt-multi-thread", "fs", "io-util", "net", "sync", ] [dev-dependencies.tracing-subscriber] version = "0.3.18" features = [ "env-filter", "fmt", "ansi", ] default-features = false [dev-dependencies.zbus_xml] version = "4.0.0" [features] async-io = [ "dep:async-io", "async-executor", "async-task", "async-lock", "async-fs", "blocking", "futures-util/io", ] bus-impl = ["p2p"] chrono = ["zvariant/chrono"] default = ["async-io"] option-as-array = ["zvariant/option-as-array"] p2p = [] time = ["zvariant/time"] tokio = ["dep:tokio"] tokio-vsock = [ "dep:tokio-vsock", "tokio", ] url = ["zvariant/url"] uuid = ["zvariant/uuid"] vsock = [ "dep:vsock", "dep:async-io", ] [target."cfg(any(target_os = \"macos\", windows))".dependencies.async-recursion] version = "1.1.1" [target."cfg(target_os = \"macos\")".dependencies.async-process] version = "2.2.2" [target."cfg(unix)".dependencies.nix] version = "0.29" features = [ "socket", "uio", "user", ] default-features = false [target."cfg(windows)".dependencies.uds_windows] version = "1.1.0" [target."cfg(windows)".dependencies.windows-sys] version = "0.52" features = [ "Win32_Foundation", "Win32_Security_Authorization", "Win32_System_Memory", "Win32_Networking", "Win32_Networking_WinSock", "Win32_NetworkManagement", "Win32_NetworkManagement_IpHelper", "Win32_System_Threading", ] zbus-4.3.1/Cargo.toml.orig000064400000000000000000000074311046102023000135100ustar 00000000000000[package] name = "zbus" version = "4.3.1" authors = ["Zeeshan Ali Khan "] edition = "2021" rust-version = "1.75" description = "API for D-Bus communication" repository = "https://github.com/dbus2/zbus/" keywords = ["D-Bus", "DBus", "IPC"] license = "MIT" categories = ["os::unix-apis"] readme = "README.md" [features] default = ["async-io"] uuid = ["zvariant/uuid"] url = ["zvariant/url"] time = ["zvariant/time"] chrono = ["zvariant/chrono"] # Enables ser/de of `Option` as an array of 0 or 1 elements. option-as-array = ["zvariant/option-as-array"] # Enables API that is only needed for bus implementations (enables `p2p`). bus-impl = ["p2p"] # Enables API that is only needed for peer-to-peer (p2p) connections. p2p = [] async-io = [ "dep:async-io", "async-executor", "async-task", "async-lock", "async-fs", "blocking", "futures-util/io", ] tokio = ["dep:tokio"] vsock = ["dep:vsock", "dep:async-io"] tokio-vsock = ["dep:tokio-vsock", "tokio"] [dependencies] serde = { version = "1.0.200", features = ["derive"] } serde_repr = "0.1.19" zvariant = { path = "../zvariant", version = "4.1.1", default-features = false, features = [ "enumflags2", ] } zbus_names = { path = "../zbus_names", version = "3.0" } zbus_macros = { path = "../zbus_macros", version = "=4.3.1" } enumflags2 = { version = "0.7.9", features = ["serde"] } async-io = { version = "2.3.2", optional = true } futures-core = "0.3.30" futures-sink = "0.3.30" futures-util = { version = "0.3.30", default-features = false, features = [ "sink", "std", ] } async-lock = { version = "3.3.0", optional = true } async-broadcast = "0.7.0" async-executor = { version = "1.11.0", optional = true } blocking = { version = "1.6.0", optional = true } async-task = { version = "4.7.1", optional = true } hex = "0.4.3" ordered-stream = "0.2" rand = "0.8.5" sha1 = { version = "0.10.6", features = ["std"] } event-listener = "5.3.0" static_assertions = "1.1.0" async-trait = "0.1.80" async-fs = { version = "2.1.2", optional = true } # FIXME: We should only enable process feature for Mac OS. See comment on async-process below for why we can't. tokio = { version = "1.37.0", optional = true, features = [ "rt", "net", "time", "fs", "io-util", "process", "sync", "tracing", ] } tracing = "0.1.40" vsock = { version = "0.5.0", optional = true } tokio-vsock = { version = "0.4", optional = true } xdg-home = "1.1.0" [target.'cfg(windows)'.dependencies] windows-sys = { version = "0.52", features = [ "Win32_Foundation", "Win32_Security_Authorization", "Win32_System_Memory", "Win32_Networking", "Win32_Networking_WinSock", "Win32_NetworkManagement", "Win32_NetworkManagement_IpHelper", "Win32_System_Threading", ] } uds_windows = "1.1.0" [target.'cfg(unix)'.dependencies] nix = { version = "0.29", default-features = false, features = [ "socket", "uio", "user", ] } [target.'cfg(target_os = "macos")'.dependencies] # FIXME: This should only be enabled if async-io feature is enabled but currently # Cargo doesn't provide a way to do that for only specific target OS: https://github.com/rust-lang/cargo/issues/1197. async-process = "2.2.2" [target.'cfg(any(target_os = "macos", windows))'.dependencies] async-recursion = "1.1.1" [dev-dependencies] zbus_xml = { path = "../zbus_xml", version = "4.0.0" } doc-comment = "0.3.3" futures-util = "0.3.30" # activate default features ntest = "0.9.2" test-log = { version = "0.2.16", features = [ "trace", ], default-features = false } tokio = { version = "1.37.0", features = [ "macros", "rt-multi-thread", "fs", "io-util", "net", "sync", ] } tracing-subscriber = { version = "0.3.18", features = [ "env-filter", "fmt", "ansi", ], default-features = false } tempfile = "3.10.1" [package.metadata.docs.rs] all-features = true targets = ["x86_64-unknown-linux-gnu"] zbus-4.3.1/LICENSE000064400000000000000000000020701046102023000116200ustar 00000000000000Copyright (c) 2024 Zeeshan Ali Khan & zbus contributors 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. zbus-4.3.1/README.md000064400000000000000000000111011046102023000120650ustar 00000000000000# zbus [![](https://docs.rs/zbus/badge.svg)](https://docs.rs/zbus/) [![](https://img.shields.io/crates/v/zbus)](https://crates.io/crates/zbus) This is the main subcrate of the [zbus] project, that provides the API to interact with D-Bus. It takes care of the establishment of a connection, the creation, sending and receiving of different kind of D-Bus messages (method calls, signals etc) for you. **Status:** Stable. ## Getting Started The best way to get started with zbus is the [book](https://dbus2.github.io/zbus/), where we start with basic D-Bus concepts and explain with code samples, how zbus makes D-Bus easy. ## Example code We'll create a simple D-Bus service and client to demonstrate the usage of zbus. Note that these examples assume that a D-Bus broker is setup on your machine and you've a session bus running (`DBUS_SESSION_BUS_ADDRESS` environment variable must be set). This is guaranteed to be the case on a typical Linux desktop session. ### Service A simple service that politely greets whoever calls its `SayHello` method: ```rust,no_run use std::{error::Error, future::pending}; use zbus::{connection, interface}; struct Greeter { count: u64 } #[interface(name = "org.zbus.MyGreeter1")] impl Greeter { // Can be `async` as well. fn say_hello(&mut self, name: &str) -> String { self.count += 1; format!("Hello {}! I have been called {} times.", name, self.count) } } // Although we use `tokio` here, you can use any async runtime of choice. #[tokio::main] async fn main() -> Result<(), Box> { let greeter = Greeter { count: 0 }; let _conn = connection::Builder::session()? .name("org.zbus.MyGreeter")? .serve_at("/org/zbus/MyGreeter", greeter)? .build() .await?; // Do other things or go to wait forever pending::<()>().await; Ok(()) } ``` You can use the following command to test it: ```bash $ busctl --user call org.zbus.MyGreeter /org/zbus/MyGreeter org.zbus.MyGreeter1 SayHello s "Maria" s "Hello Maria! I have been called 1 times." ``` ### Client Now let's write the client-side code for `MyGreeter` service: ```rust,no_run use zbus::{Connection, Result, proxy}; #[proxy( interface = "org.zbus.MyGreeter1", default_service = "org.zbus.MyGreeter", default_path = "/org/zbus/MyGreeter" )] trait MyGreeter { async fn say_hello(&self, name: &str) -> Result; } // Although we use `tokio` here, you can use any async runtime of choice. #[tokio::main] async fn main() -> Result<()> { let connection = Connection::session().await?; // `proxy` macro creates `MyGreaterProxy` based on `Notifications` trait. let proxy = MyGreeterProxy::new(&connection).await?; let reply = proxy.say_hello("Maria").await?; println!("{reply}"); Ok(()) } ``` ## Blocking API While zbus is primarily asynchronous (since 2.0), [blocking wrappers][bw] are provided for convenience. ## Compatibility with async runtimes zbus is runtime-agnostic and should work out of the box with different Rust async runtimes. However, in order to achieve that, zbus spawns a thread per connection to handle various internal tasks. If that is something you would like to avoid, you need to: * Use [`connection::Builder`] and disable the `internal_executor` flag. * Ensure the [internal executor keeps ticking continuously][iektc]. Moreover, by default zbus makes use of [`async-io`] for all I/O, which also launches its own thread to run its own internal executor. ### Special tokio support Since [`tokio`] is the most popular async runtime, zbus provides an easy way to enable tight integration with it without you having to worry about any of the above: Enabling the `tokio` feature: ```toml # Sample Cargo.toml snippet. [dependencies] # Also disable the default `async-io` feature to avoid unused dependencies. zbus = { version = "3", default-features = false, features = ["tokio"] } ``` That's it! No threads launched behind your back by zbus (directly or indirectly) now and no need to tick any executors etc. 😼 **Note**: On Windows, the `async-io` feature is currently required for UNIX domain socket support, see [the corresponding tokio issue on GitHub][tctiog]. [zbus]: https://github.com/dbus2/zbus\#readme [bw]: https://docs.rs/zbus/latest/zbus/blocking/index.html [iektc]: https://docs.rs/zbus/latest/zbus/connection/struct.Connection.html#examples-1 [tctiog]: https://github.com/tokio-rs/tokio/issues/2201 [`connection::Builder`]: https://docs.rs/zbus/latest/zbus/connection/struct.ConnectionBuilder.html [`tokio`]: https://crates.io/crates/tokio [`async-io`]: https://crates.io/crates/async-io zbus-4.3.1/examples/screen-brightness.rs000064400000000000000000000021441046102023000164260ustar 00000000000000// A simple cmdline app to change the screen brightness on laptops. // // NB: It only works on a GNOME (based) system. // // Usage is simple. Either pass a '+' as argument or no argument on commandline, and it increases // the brightness by 5%. Pass '-' for decreasing it by 5%. fn main() { tracing_subscriber::fmt::init(); let connection = zbus::blocking::Connection::session().unwrap(); let method = match std::env::args().nth(1) { Some(s) => { if s == "+" { "StepUp" } else if s == "-" { "StepDown" } else { panic!("Expected either '+' or '-' argument. Got: {}", s); } } None => "StepUp", }; let reply = connection .call_method( Some("org.gnome.SettingsDaemon.Power"), "/org/gnome/SettingsDaemon/Power", Some("org.gnome.SettingsDaemon.Power.Screen"), method, &(), ) .unwrap(); let (percent, _) = reply.body().deserialize::<(i32, &str)>().unwrap(); println!("New level: {percent}%"); } zbus-4.3.1/examples/watch-systemd-jobs.rs000064400000000000000000000032321046102023000165270ustar 00000000000000//! Example demonstrating how to monitor for D-Bus Signal events. //! //! Prints a message every time a systemd service or other job starts. //! //! Run with command: `cargo run --example watch-systemd-jobs` use futures_util::stream::StreamExt; use zbus::Connection; use zbus_macros::proxy; use zvariant::OwnedObjectPath; fn main() { async_io::block_on(watch_systemd_jobs()).expect("Error listening to signal"); } #[proxy( default_service = "org.freedesktop.systemd1", default_path = "/org/freedesktop/systemd1", interface = "org.freedesktop.systemd1.Manager" )] trait Systemd1Manager { // Defines signature for D-Bus signal named `JobNew` #[zbus(signal)] fn job_new(&self, id: u32, job: OwnedObjectPath, unit: String) -> zbus::Result<()>; } // NOTE: When changing this, please also keep `book/src/client.md` in sync. async fn watch_systemd_jobs() -> zbus::Result<()> { let connection = Connection::system().await?; // `Systemd1ManagerProxy` is generated from `Systemd1Manager` trait let systemd_proxy = Systemd1ManagerProxy::new(&connection).await?; // Method `receive_job_new` is generated from `job_new` signal let mut new_jobs_stream = systemd_proxy.receive_job_new().await?; println!("Monitoring started systemd services or jobs..."); while let Some(msg) = new_jobs_stream.next().await { // struct `JobNewArgs` is generated from `job_new` signal function arguments let args: JobNewArgs = msg.args().expect("Error parsing message"); println!( "JobNew received: unit={} id={} path={}", args.unit, args.id, args.job ); } panic!("Stream ended unexpectedly"); } zbus-4.3.1/src/abstractions/async_drop.rs000064400000000000000000000015451046102023000166130ustar 00000000000000/// Async equivalent of [`Drop`]. /// /// This trait is similar to [`Drop`], but it is async so types that need to perform async /// operations when they're dropped should implement this. Unfortunately, the `async_drop` method /// won't be implicitly called by the compiler for the user so types implementing this trait will /// also have to implement [`Drop`] to clean up (for example, by passing the async cleanup to /// another async task). This is also why the `async_drop` method consumes `self` instead of taking /// a `&mut self` like [`Drop`]. /// /// Hopefully this will be unnecessary [in the future][itf] when Rust gain an `AsyncDrop` itself. /// /// [itf]: https://rust-lang.github.io/async-fundamentals-initiative/roadmap/async_drop.html #[async_trait::async_trait] pub trait AsyncDrop { /// Perform the async cleanup. async fn async_drop(self); } zbus-4.3.1/src/abstractions/async_lock.rs000064400000000000000000000023711046102023000165750ustar 00000000000000#[cfg(not(feature = "tokio"))] pub(crate) use async_lock::{Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard}; #[cfg(feature = "tokio")] pub(crate) use tokio::sync::{Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard}; /// An abstraction over async semaphore API. #[cfg(not(feature = "tokio"))] pub(crate) struct Semaphore(async_lock::Semaphore); #[cfg(feature = "tokio")] pub(crate) struct Semaphore(tokio::sync::Semaphore); impl Semaphore { pub const fn new(permits: usize) -> Self { #[cfg(not(feature = "tokio"))] let semaphore = async_lock::Semaphore::new(permits); #[cfg(feature = "tokio")] let semaphore = tokio::sync::Semaphore::const_new(permits); Self(semaphore) } pub async fn acquire(&self) -> SemaphorePermit<'_> { #[cfg(not(feature = "tokio"))] { self.0.acquire().await } #[cfg(feature = "tokio")] { // SAFETY: Since we never explicitly close the sempaphore, `acquire` can't fail. self.0.acquire().await.unwrap() } } } #[cfg(not(feature = "tokio"))] pub(crate) type SemaphorePermit<'a> = async_lock::SemaphoreGuard<'a>; #[cfg(feature = "tokio")] pub(crate) type SemaphorePermit<'a> = tokio::sync::SemaphorePermit<'a>; zbus-4.3.1/src/abstractions/executor.rs000064400000000000000000000137511046102023000163120ustar 00000000000000#[cfg(not(feature = "tokio"))] use async_executor::Executor as AsyncExecutor; #[cfg(not(feature = "tokio"))] use async_task::Task as AsyncTask; #[cfg(not(feature = "tokio"))] use std::sync::Arc; #[cfg(feature = "tokio")] use std::{future::pending, marker::PhantomData}; use std::{ future::Future, pin::Pin, task::{Context, Poll}, }; #[cfg(feature = "tokio")] use tokio::task::JoinHandle; /// A wrapper around the underlying runtime/executor. /// /// This is used to run asynchronous tasks internally and allows integration with various runtimes. /// See [`crate::Connection::executor`] for an example of integration with external runtimes. /// /// **Note:** You can (and should) completely ignore this type when building with `tokio` feature /// enabled. #[cfg(not(feature = "tokio"))] #[derive(Debug, Clone)] pub struct Executor<'a> { executor: Arc>, } #[cfg(feature = "tokio")] #[derive(Debug, Clone)] pub struct Executor<'a> { phantom: PhantomData<&'a ()>, } impl<'a> Executor<'a> { /// Spawns a task onto the executor. #[doc(hidden)] pub fn spawn( &self, future: impl Future + Send + 'static, #[allow(unused)] name: &str, ) -> Task { #[cfg(not(feature = "tokio"))] { Task(Some(self.executor.spawn(future))) } #[cfg(feature = "tokio")] { #[cfg(tokio_unstable)] { Task(Some( tokio::task::Builder::new() .name(name) .spawn(future) // SAFETY: Looking at the code, this call always returns an `Ok`. .unwrap(), )) } #[cfg(not(tokio_unstable))] { Task(Some(tokio::task::spawn(future))) } } } /// Returns `true` if there are no unfinished tasks. /// /// With `tokio` feature enabled, this always returns `true`. pub fn is_empty(&self) -> bool { #[cfg(not(feature = "tokio"))] { self.executor.is_empty() } #[cfg(feature = "tokio")] true } /// Runs a single task. /// /// With `tokio` feature enabled, its a noop and never returns. pub async fn tick(&self) { #[cfg(not(feature = "tokio"))] { self.executor.tick().await } #[cfg(feature = "tokio")] { pending().await } } /// Create a new `Executor`. pub(crate) fn new() -> Self { #[cfg(not(feature = "tokio"))] { Self { executor: Arc::new(AsyncExecutor::new()), } } #[cfg(feature = "tokio")] { Self { phantom: PhantomData, } } } /// Runs the executor until the given future completes. /// /// With `tokio` feature enabled, it just awaits on the `future`. pub(crate) async fn run(&self, future: impl Future) -> T { #[cfg(not(feature = "tokio"))] { self.executor.run(future).await } #[cfg(feature = "tokio")] { future.await } } } /// A wrapper around the task API of the underlying runtime/executor. /// /// This follows the semantics of `async_task::Task` on drop: /// /// * it will be cancelled, rather than detached. For detaching, use the `detach` method. /// * errors from the task cancellation will will be ignored. If you need to know about task errors, /// convert the task to a `FallibleTask` using the `fallible` method. #[cfg(not(feature = "tokio"))] #[doc(hidden)] #[derive(Debug)] pub struct Task(Option>); #[cfg(feature = "tokio")] #[doc(hidden)] #[derive(Debug)] pub struct Task(Option>); impl Task { /// Detaches the task to let it keep running in the background. #[allow(unused_mut)] #[allow(unused)] pub fn detach(mut self) { #[cfg(not(feature = "tokio"))] { self.0.take().expect("async_task::Task is none").detach() } #[cfg(feature = "tokio")] { self.0.take().expect("tokio::task::JoinHandle is none"); } } } impl Task where T: Send + 'static, { /// Launch the given blocking function in a task. #[allow(unused)] pub(crate) fn spawn_blocking(f: F, #[allow(unused)] name: &str) -> Self where F: FnOnce() -> T + Send + 'static, { #[cfg(not(feature = "tokio"))] { Self(Some(blocking::unblock(f))) } #[cfg(feature = "tokio")] { #[cfg(tokio_unstable)] { Self(Some( tokio::task::Builder::new() .name(name) .spawn_blocking(f) // SAFETY: Looking at the code, this call always returns an `Ok`. .unwrap(), )) } #[cfg(not(tokio_unstable))] { Self(Some(tokio::task::spawn_blocking(f))) } } } } impl Drop for Task { fn drop(&mut self) { #[cfg(feature = "tokio")] { if let Some(join_handle) = self.0.take() { join_handle.abort(); } } } } impl Future for Task { type Output = T; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { #[cfg(not(feature = "tokio"))] { Pin::new(&mut self.get_mut().0.as_mut().expect("async_task::Task is none")).poll(cx) } #[cfg(feature = "tokio")] { Pin::new( &mut self .get_mut() .0 .as_mut() .expect("tokio::task::JoinHandle is none"), ) .poll(cx) .map(|r| r.expect("tokio::task::JoinHandle error")) } } } zbus-4.3.1/src/abstractions/file.rs000064400000000000000000000040171046102023000153660ustar 00000000000000//! Runtime-agnostic File I/O abstractions. //! //! Proving only specific API that we need internally. #[cfg(unix)] use std::fs::Metadata; use std::{ io::Result, path::Path, pin::Pin, task::{Context, Poll}, }; use futures_core::Stream; #[cfg(not(feature = "tokio"))] #[derive(Debug)] pub struct FileLines(futures_util::io::Lines>); #[cfg(feature = "tokio")] #[derive(Debug)] pub struct FileLines(tokio::io::Lines>); impl FileLines { pub async fn open(path: impl AsRef) -> Result { #[cfg(not(feature = "tokio"))] { async_fs::File::open(path) .await .map(futures_util::io::BufReader::new) .map(futures_util::AsyncBufReadExt::lines) .map(Self) } #[cfg(feature = "tokio")] { tokio::fs::File::open(path) .await .map(tokio::io::BufReader::new) .map(tokio::io::AsyncBufReadExt::lines) .map(Self) } } } impl Stream for FileLines { type Item = Result; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { #[cfg(not(feature = "tokio"))] { Stream::poll_next(Pin::new(&mut self.get_mut().0), cx) } #[cfg(feature = "tokio")] { let fut = self.get_mut().0.next_line(); futures_util::pin_mut!(fut); std::future::Future::poll(Pin::new(&mut fut), cx).map(Result::transpose) } } #[cfg(not(feature = "tokio"))] fn size_hint(&self) -> (usize, Option) { self.0.size_hint() } } // Not unix-specific itself but only used on unix. #[cfg(unix)] pub async fn metadata(path: impl AsRef) -> Result { #[cfg(not(feature = "tokio"))] { async_fs::metadata(path).await } #[cfg(feature = "tokio")] { tokio::fs::metadata(path).await } } zbus-4.3.1/src/abstractions/mod.rs000064400000000000000000000006071046102023000152270ustar 00000000000000/// This mod contains a bunch of abstractions. /// /// These abstractions allow us to make use of the appropriate API depending on which features are /// enabled. mod executor; pub use executor::*; mod async_drop; pub(crate) mod async_lock; pub use async_drop::*; pub(crate) mod file; // Not macOS-specific itself but only used on macOS. #[cfg(target_os = "macos")] pub(crate) mod process; zbus-4.3.1/src/abstractions/process.rs000064400000000000000000000010141046102023000161170ustar 00000000000000use std::{ffi::OsStr, io::Error, process::Output}; /// An asynchronous wrapper around running and getting command output pub async fn run(program: S, args: I) -> Result where I: IntoIterator, S: AsRef, { #[cfg(not(feature = "tokio"))] return async_process::Command::new(program) .args(args) .output() .await; #[cfg(feature = "tokio")] return tokio::process::Command::new(program) .args(args) .output() .await; } zbus-4.3.1/src/address/mod.rs000064400000000000000000000377311046102023000141700ustar 00000000000000//! D-Bus address handling. //! //! Server addresses consist of a transport name followed by a colon, and then an optional, //! comma-separated list of keys and values in the form key=value. //! //! See also: //! //! * [Server addresses] in the D-Bus specification. //! //! [Server addresses]: https://dbus.freedesktop.org/doc/dbus-specification.html#addresses pub mod transport; use crate::{Error, Guid, OwnedGuid, Result}; #[cfg(all(unix, not(target_os = "macos")))] use nix::unistd::Uid; use std::{collections::HashMap, env, str::FromStr}; use std::fmt::{Display, Formatter}; use self::transport::Stream; pub use self::transport::Transport; /// A bus address #[derive(Clone, Debug, PartialEq, Eq)] #[non_exhaustive] pub struct Address { guid: Option, transport: Transport, } impl Address { /// Create a new `Address` from a `Transport`. pub fn new(transport: Transport) -> Self { Self { transport, guid: None, } } /// Set the GUID for this address. pub fn set_guid(mut self, guid: G) -> Result where G: TryInto, G::Error: Into, { self.guid = Some(guid.try_into().map_err(Into::into)?); Ok(self) } /// The transport details for this address. pub fn transport(&self) -> &Transport { &self.transport } #[cfg_attr(any(target_os = "macos", windows), async_recursion::async_recursion)] pub(crate) async fn connect(self) -> Result { self.transport.connect().await } /// Get the address for session socket respecting the DBUS_SESSION_BUS_ADDRESS environment /// variable. If we don't recognize the value (or it's not set) we fall back to /// $XDG_RUNTIME_DIR/bus pub fn session() -> Result { match env::var("DBUS_SESSION_BUS_ADDRESS") { Ok(val) => Self::from_str(&val), _ => { #[cfg(windows)] return Self::from_str("autolaunch:"); #[cfg(all(unix, not(target_os = "macos")))] { let runtime_dir = env::var("XDG_RUNTIME_DIR") .unwrap_or_else(|_| format!("/run/user/{}", Uid::effective())); let path = format!("unix:path={runtime_dir}/bus"); Self::from_str(&path) } #[cfg(target_os = "macos")] return Self::from_str("launchd:env=DBUS_LAUNCHD_SESSION_BUS_SOCKET"); } } } /// Get the address for system bus respecting the DBUS_SYSTEM_BUS_ADDRESS environment /// variable. If we don't recognize the value (or it's not set) we fall back to /// /var/run/dbus/system_bus_socket pub fn system() -> Result { match env::var("DBUS_SYSTEM_BUS_ADDRESS") { Ok(val) => Self::from_str(&val), _ => { #[cfg(all(unix, not(target_os = "macos")))] return Self::from_str("unix:path=/var/run/dbus/system_bus_socket"); #[cfg(windows)] return Self::from_str("autolaunch:"); #[cfg(target_os = "macos")] return Self::from_str("launchd:env=DBUS_LAUNCHD_SESSION_BUS_SOCKET"); } } } /// The GUID for this address, if known. pub fn guid(&self) -> Option<&Guid<'_>> { self.guid.as_ref().map(|guid| guid.inner()) } } impl Display for Address { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { self.transport.fmt(f)?; if let Some(guid) = &self.guid { write!(f, ",guid={}", guid)?; } Ok(()) } } impl FromStr for Address { type Err = Error; /// Parse the transport part of a D-Bus address into a `Transport`. fn from_str(address: &str) -> Result { let col = address .find(':') .ok_or_else(|| Error::Address("address has no colon".to_owned()))?; let transport = &address[..col]; let mut options = HashMap::new(); if address.len() > col + 1 { for kv in address[col + 1..].split(',') { let (k, v) = match kv.find('=') { Some(eq) => (&kv[..eq], &kv[eq + 1..]), None => { return Err(Error::Address( "missing = when parsing key/value".to_owned(), )) } }; if options.insert(k, v).is_some() { return Err(Error::Address(format!( "Key `{k}` specified multiple times" ))); } } } Ok(Self { guid: options .remove("guid") .map(|s| Guid::from_str(s).map(|guid| OwnedGuid::from(guid).to_owned())) .transpose()?, transport: Transport::from_options(transport, options)?, }) } } impl TryFrom<&str> for Address { type Error = Error; fn try_from(value: &str) -> Result { Self::from_str(value) } } impl From for Address { fn from(transport: Transport) -> Self { Self::new(transport) } } #[cfg(test)] mod tests { use super::{ transport::{Tcp, TcpTransportFamily, Transport}, Address, }; #[cfg(target_os = "macos")] use crate::address::transport::Launchd; #[cfg(windows)] use crate::address::transport::{Autolaunch, AutolaunchScope}; use crate::{ address::transport::{Unix, UnixSocket}, Error, }; use std::str::FromStr; use test_log::test; #[test] fn parse_dbus_addresses() { match Address::from_str("").unwrap_err() { Error::Address(e) => assert_eq!(e, "address has no colon"), _ => panic!(), } match Address::from_str("foo").unwrap_err() { Error::Address(e) => assert_eq!(e, "address has no colon"), _ => panic!(), } match Address::from_str("foo:opt").unwrap_err() { Error::Address(e) => assert_eq!(e, "missing = when parsing key/value"), _ => panic!(), } match Address::from_str("foo:opt=1,opt=2").unwrap_err() { Error::Address(e) => assert_eq!(e, "Key `opt` specified multiple times"), _ => panic!(), } match Address::from_str("tcp:host=localhost").unwrap_err() { Error::Address(e) => assert_eq!(e, "tcp address is missing `port`"), _ => panic!(), } match Address::from_str("tcp:host=localhost,port=32f").unwrap_err() { Error::Address(e) => assert_eq!(e, "invalid tcp `port`"), _ => panic!(), } match Address::from_str("tcp:host=localhost,port=123,family=ipv7").unwrap_err() { Error::Address(e) => assert_eq!(e, "invalid tcp address `family`: ipv7"), _ => panic!(), } match Address::from_str("unix:foo=blah").unwrap_err() { Error::Address(e) => assert_eq!(e, "unix: address is invalid"), _ => panic!(), } #[cfg(target_os = "linux")] match Address::from_str("unix:path=/tmp,abstract=foo").unwrap_err() { Error::Address(e) => { assert_eq!(e, "unix: address is invalid") } _ => panic!(), } assert_eq!( Address::from_str("unix:path=/tmp/dbus-foo").unwrap(), Transport::Unix(Unix::new(UnixSocket::File("/tmp/dbus-foo".into()))).into(), ); #[cfg(target_os = "linux")] assert_eq!( Address::from_str("unix:abstract=/tmp/dbus-foo").unwrap(), Transport::Unix(Unix::new(UnixSocket::Abstract("/tmp/dbus-foo".into()))).into(), ); let guid = crate::Guid::generate(); assert_eq!( Address::from_str(&format!("unix:path=/tmp/dbus-foo,guid={guid}")).unwrap(), Address::from(Transport::Unix(Unix::new(UnixSocket::File( "/tmp/dbus-foo".into() )))) .set_guid(guid.clone()) .unwrap(), ); assert_eq!( Address::from_str("tcp:host=localhost,port=4142").unwrap(), Transport::Tcp(Tcp::new("localhost", 4142)).into(), ); assert_eq!( Address::from_str("tcp:host=localhost,port=4142,family=ipv4").unwrap(), Transport::Tcp(Tcp::new("localhost", 4142).set_family(Some(TcpTransportFamily::Ipv4))) .into(), ); assert_eq!( Address::from_str("tcp:host=localhost,port=4142,family=ipv6").unwrap(), Transport::Tcp(Tcp::new("localhost", 4142).set_family(Some(TcpTransportFamily::Ipv6))) .into(), ); assert_eq!( Address::from_str("tcp:host=localhost,port=4142,family=ipv6,noncefile=/a/file/path") .unwrap(), Transport::Tcp( Tcp::new("localhost", 4142) .set_family(Some(TcpTransportFamily::Ipv6)) .set_nonce_file(Some(b"/a/file/path".to_vec())) ) .into(), ); assert_eq!( Address::from_str( "nonce-tcp:host=localhost,port=4142,family=ipv6,noncefile=/a/file/path%20to%20file%201234" ) .unwrap(), Transport::Tcp( Tcp::new("localhost", 4142) .set_family(Some(TcpTransportFamily::Ipv6)) .set_nonce_file(Some(b"/a/file/path to file 1234".to_vec())) ).into() ); #[cfg(windows)] assert_eq!( Address::from_str("autolaunch:").unwrap(), Transport::Autolaunch(Autolaunch::new()).into(), ); #[cfg(windows)] assert_eq!( Address::from_str("autolaunch:scope=*my_cool_scope*").unwrap(), Transport::Autolaunch( Autolaunch::new() .set_scope(Some(AutolaunchScope::Other("*my_cool_scope*".to_string()))) ) .into(), ); #[cfg(target_os = "macos")] assert_eq!( Address::from_str("launchd:env=my_cool_env_key").unwrap(), Transport::Launchd(Launchd::new("my_cool_env_key")).into(), ); #[cfg(all(feature = "vsock", not(feature = "tokio")))] assert_eq!( Address::from_str(&format!("vsock:cid=98,port=2934,guid={guid}")).unwrap(), Address::from(Transport::Vsock(super::transport::Vsock::new(98, 2934))) .set_guid(guid) .unwrap(), ); assert_eq!( Address::from_str("unix:dir=/some/dir").unwrap(), Transport::Unix(Unix::new(UnixSocket::Dir("/some/dir".into()))).into(), ); assert_eq!( Address::from_str("unix:tmpdir=/some/dir").unwrap(), Transport::Unix(Unix::new(UnixSocket::TmpDir("/some/dir".into()))).into(), ); } #[test] fn stringify_dbus_addresses() { assert_eq!( Address::from(Transport::Unix(Unix::new(UnixSocket::File( "/tmp/dbus-foo".into() )))) .to_string(), "unix:path=/tmp/dbus-foo", ); assert_eq!( Address::from(Transport::Unix(Unix::new(UnixSocket::Dir( "/tmp/dbus-foo".into() )))) .to_string(), "unix:dir=/tmp/dbus-foo", ); assert_eq!( Address::from(Transport::Unix(Unix::new(UnixSocket::TmpDir( "/tmp/dbus-foo".into() )))) .to_string(), "unix:tmpdir=/tmp/dbus-foo" ); // FIXME: figure out how to handle abstract on Windows #[cfg(target_os = "linux")] assert_eq!( Address::from(Transport::Unix(Unix::new(UnixSocket::Abstract( "/tmp/dbus-foo".into() )))) .to_string(), "unix:abstract=/tmp/dbus-foo" ); assert_eq!( Address::from(Transport::Tcp(Tcp::new("localhost", 4142))).to_string(), "tcp:host=localhost,port=4142" ); assert_eq!( Address::from(Transport::Tcp( Tcp::new("localhost", 4142).set_family(Some(TcpTransportFamily::Ipv4)) )) .to_string(), "tcp:host=localhost,port=4142,family=ipv4" ); assert_eq!( Address::from(Transport::Tcp( Tcp::new("localhost", 4142).set_family(Some(TcpTransportFamily::Ipv6)) )) .to_string(), "tcp:host=localhost,port=4142,family=ipv6" ); assert_eq!( Address::from(Transport::Tcp(Tcp::new("localhost", 4142) .set_family(Some(TcpTransportFamily::Ipv6)) .set_nonce_file(Some(b"/a/file/path to file 1234".to_vec()) ))) .to_string(), "nonce-tcp:noncefile=/a/file/path%20to%20file%201234,host=localhost,port=4142,family=ipv6" ); #[cfg(windows)] assert_eq!( Address::from(Transport::Autolaunch(Autolaunch::new())).to_string(), "autolaunch:" ); #[cfg(windows)] assert_eq!( Address::from(Transport::Autolaunch(Autolaunch::new().set_scope(Some( AutolaunchScope::Other("*my_cool_scope*".to_string()) )))) .to_string(), "autolaunch:scope=*my_cool_scope*" ); #[cfg(target_os = "macos")] assert_eq!( Address::from(Transport::Launchd(Launchd::new("my_cool_key"))).to_string(), "launchd:env=my_cool_key" ); #[cfg(all(feature = "vsock", not(feature = "tokio")))] { let guid = crate::Guid::generate(); assert_eq!( Address::from(Transport::Vsock(super::transport::Vsock::new(98, 2934))) .set_guid(guid.clone()) .unwrap() .to_string(), format!("vsock:cid=98,port=2934,guid={guid}"), ); } } #[test] fn connect_tcp() { let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap(); let port = listener.local_addr().unwrap().port(); let addr = Address::from_str(&format!("tcp:host=localhost,port={port}")).unwrap(); crate::utils::block_on(async { addr.connect().await }).unwrap(); } #[test] fn connect_nonce_tcp() { struct PercentEncoded<'a>(&'a [u8]); impl std::fmt::Display for PercentEncoded<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { super::transport::encode_percents(f, self.0) } } use std::io::Write; const TEST_COOKIE: &[u8] = b"VERILY SECRETIVE"; let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap(); let port = listener.local_addr().unwrap().port(); let mut cookie = tempfile::NamedTempFile::new().unwrap(); cookie.as_file_mut().write_all(TEST_COOKIE).unwrap(); let encoded_path = format!( "{}", PercentEncoded(cookie.path().to_str().unwrap().as_ref()) ); let addr = Address::from_str(&format!( "nonce-tcp:host=localhost,port={port},noncefile={encoded_path}" )) .unwrap(); let (sender, receiver) = std::sync::mpsc::sync_channel(1); std::thread::spawn(move || { use std::io::Read; let mut client = listener.incoming().next().unwrap().unwrap(); let mut buf = [0u8; 16]; client.read_exact(&mut buf).unwrap(); sender.send(buf == TEST_COOKIE).unwrap(); }); crate::utils::block_on(addr.connect()).unwrap(); let saw_cookie = receiver .recv_timeout(std::time::Duration::from_millis(100)) .expect("nonce file content hasn't been received by server thread in time"); assert!( saw_cookie, "nonce file content has been received, but was invalid" ); } } zbus-4.3.1/src/address/transport/autolaunch.rs000064400000000000000000000045171046102023000176040ustar 00000000000000use crate::{Error, Result}; use std::collections::HashMap; /// Transport properties of an autolaunch D-Bus address. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Autolaunch { pub(super) scope: Option, } impl std::fmt::Display for Autolaunch { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "autolaunch:")?; if let Some(scope) = &self.scope { write!(f, "scope={}", scope)?; } Ok(()) } } impl Default for Autolaunch { fn default() -> Self { Self::new() } } impl Autolaunch { /// Create a new autolaunch transport. pub fn new() -> Self { Self { scope: None } } /// Set the `autolaunch:` address `scope` value. pub fn set_scope(mut self, scope: Option) -> Self { self.scope = scope; self } /// The optional scope. pub fn scope(&self) -> Option<&AutolaunchScope> { self.scope.as_ref() } pub(super) fn from_options(opts: HashMap<&str, &str>) -> Result { opts.get("scope") .map(|scope| -> Result<_> { let decoded = super::decode_percents(scope)?; match decoded.as_slice() { b"install-path" => Ok(AutolaunchScope::InstallPath), b"user" => Ok(AutolaunchScope::User), _ => String::from_utf8(decoded) .map(AutolaunchScope::Other) .map_err(|_| { Error::Address("autolaunch scope is not valid UTF-8".to_owned()) }), } }) .transpose() .map(|scope| Self { scope }) } } #[derive(Clone, Debug, PartialEq, Eq)] #[non_exhaustive] pub enum AutolaunchScope { /// Limit session bus to dbus installation path. InstallPath, /// Limit session bus to the recent user. User, /// other values - specify dedicated session bus like "release", "debug" or other. Other(String), } impl std::fmt::Display for AutolaunchScope { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::InstallPath => write!(f, "*install-path"), Self::User => write!(f, "*user"), Self::Other(o) => write!(f, "{o}"), } } } zbus-4.3.1/src/address/transport/launchd.rs000064400000000000000000000033521046102023000170530ustar 00000000000000use super::{Transport, Unix, UnixSocket}; use crate::{process::run, Result}; use std::collections::HashMap; #[derive(Clone, Debug, PartialEq, Eq)] #[non_exhaustive] /// The transport properties of a launchd D-Bus address. pub struct Launchd { pub(super) env: String, } impl Launchd { /// Create a new launchd D-Bus address. pub fn new(env: &str) -> Self { Self { env: env.to_string(), } } /// The path of the unix domain socket for the launchd created dbus-daemon. pub fn env(&self) -> &str { &self.env } /// Determine the actual transport details behin a launchd address. pub(super) async fn bus_address(&self) -> Result { let output = run("launchctl", ["getenv", self.env()]) .await .expect("failed to wait on launchctl output"); if !output.status.success() { return Err(crate::Error::Address(format!( "launchctl terminated with code: {}", output.status ))); } let addr = String::from_utf8(output.stdout).map_err(|e| { crate::Error::Address(format!("Unable to parse launchctl output as UTF-8: {}", e)) })?; Ok(Transport::Unix(Unix::new(UnixSocket::File( addr.trim().into(), )))) } pub(super) fn from_options(opts: HashMap<&str, &str>) -> Result { opts.get("env") .ok_or_else(|| crate::Error::Address("missing env key".into())) .map(|env| Self { env: env.to_string(), }) } } impl std::fmt::Display for Launchd { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "launchd:env={}", self.env) } } zbus-4.3.1/src/address/transport/mod.rs000064400000000000000000000276541046102023000162270ustar 00000000000000//! D-Bus transport Information module. //! //! This module provides the trasport information for D-Bus addresses. #[cfg(windows)] use crate::win32::autolaunch_bus_address; use crate::{Error, Result}; #[cfg(not(feature = "tokio"))] use async_io::Async; use std::collections::HashMap; #[cfg(not(feature = "tokio"))] use std::net::TcpStream; #[cfg(unix)] use std::os::unix::net::{SocketAddr, UnixStream}; #[cfg(feature = "tokio")] use tokio::net::TcpStream; #[cfg(feature = "tokio-vsock")] use tokio_vsock::VsockStream; #[cfg(windows)] use uds_windows::UnixStream; #[cfg(all(feature = "vsock", not(feature = "tokio")))] use vsock::VsockStream; use std::{ fmt::{Display, Formatter}, str::from_utf8_unchecked, }; mod unix; pub use unix::{Unix, UnixSocket}; mod tcp; pub use tcp::{Tcp, TcpTransportFamily}; #[cfg(windows)] mod autolaunch; #[cfg(windows)] pub use autolaunch::{Autolaunch, AutolaunchScope}; #[cfg(target_os = "macos")] mod launchd; #[cfg(target_os = "macos")] pub use launchd::Launchd; #[cfg(any( all(feature = "vsock", not(feature = "tokio")), feature = "tokio-vsock" ))] #[path = "vsock.rs"] // Gotta rename to avoid name conflict with the `vsock` crate. mod vsock_transport; #[cfg(target_os = "linux")] use std::os::linux::net::SocketAddrExt; #[cfg(any( all(feature = "vsock", not(feature = "tokio")), feature = "tokio-vsock" ))] pub use vsock_transport::Vsock; /// The transport properties of a D-Bus address. #[derive(Clone, Debug, PartialEq, Eq)] #[non_exhaustive] pub enum Transport { /// A Unix Domain Socket address. Unix(Unix), /// TCP address details Tcp(Tcp), /// autolaunch D-Bus address. #[cfg(windows)] Autolaunch(Autolaunch), /// launchd D-Bus address. #[cfg(target_os = "macos")] Launchd(Launchd), #[cfg(any( all(feature = "vsock", not(feature = "tokio")), feature = "tokio-vsock" ))] /// VSOCK address /// /// This variant is only available when either `vsock` or `tokio-vsock` feature is enabled. The /// type of `stream` is `vsock::VsockStream` with `vsock` feature and /// `tokio_vsock::VsockStream` with `tokio-vsock` feature. Vsock(Vsock), } impl Transport { #[cfg_attr(any(target_os = "macos", windows), async_recursion::async_recursion)] pub(super) async fn connect(self) -> Result { match self { Transport::Unix(unix) => { // This is a `path` in case of Windows until uds_windows provides the needed API: // https://github.com/haraldh/rust_uds_windows/issues/14 let addr = match unix.take_path() { #[cfg(unix)] UnixSocket::File(path) => SocketAddr::from_pathname(path)?, #[cfg(windows)] UnixSocket::File(path) => path, #[cfg(target_os = "linux")] UnixSocket::Abstract(name) => { SocketAddr::from_abstract_name(name.as_encoded_bytes())? } UnixSocket::Dir(_) | UnixSocket::TmpDir(_) => { // you can't connect to a unix:dir return Err(Error::Unsupported); } }; let stream = crate::Task::spawn_blocking( move || -> Result<_> { #[cfg(unix)] let stream = UnixStream::connect_addr(&addr)?; #[cfg(windows)] let stream = UnixStream::connect(addr)?; stream.set_nonblocking(true)?; Ok(stream) }, "unix stream connection", ) .await?; #[cfg(not(feature = "tokio"))] { Async::new(stream) .map(Stream::Unix) .map_err(|e| Error::InputOutput(e.into())) } #[cfg(feature = "tokio")] { #[cfg(unix)] { tokio::net::UnixStream::from_std(stream) .map(Stream::Unix) .map_err(|e| Error::InputOutput(e.into())) } #[cfg(not(unix))] { let _ = stream; Err(Error::Unsupported) } } } #[cfg(all(feature = "vsock", not(feature = "tokio")))] Transport::Vsock(addr) => { let stream = VsockStream::connect_with_cid_port(addr.cid(), addr.port())?; Async::new(stream).map(Stream::Vsock).map_err(Into::into) } #[cfg(feature = "tokio-vsock")] Transport::Vsock(addr) => VsockStream::connect(addr.cid(), addr.port()) .await .map(Stream::Vsock) .map_err(Into::into), Transport::Tcp(mut addr) => match addr.take_nonce_file() { Some(nonce_file) => { #[allow(unused_mut)] let mut stream = addr.connect().await?; #[cfg(unix)] let nonce_file = { use std::os::unix::ffi::OsStrExt; std::ffi::OsStr::from_bytes(&nonce_file) }; #[cfg(windows)] let nonce_file = std::str::from_utf8(&nonce_file).map_err(|_| { Error::Address("nonce file path is invalid UTF-8".to_owned()) })?; #[cfg(not(feature = "tokio"))] { let nonce = std::fs::read(nonce_file)?; let mut nonce = &nonce[..]; while !nonce.is_empty() { let len = stream .write_with(|mut s| std::io::Write::write(&mut s, nonce)) .await?; nonce = &nonce[len..]; } } #[cfg(feature = "tokio")] { let nonce = tokio::fs::read(nonce_file).await?; tokio::io::AsyncWriteExt::write_all(&mut stream, &nonce).await?; } Ok(Stream::Tcp(stream)) } None => addr.connect().await.map(Stream::Tcp), }, #[cfg(windows)] Transport::Autolaunch(Autolaunch { scope }) => match scope { Some(_) => Err(Error::Address( "Autolaunch scopes are currently unsupported".to_owned(), )), None => { let addr = autolaunch_bus_address()?; addr.connect().await } }, #[cfg(target_os = "macos")] Transport::Launchd(launchd) => { let addr = launchd.bus_address().await?; addr.connect().await } } } // Helper for `FromStr` impl of `Address`. pub(super) fn from_options(transport: &str, options: HashMap<&str, &str>) -> Result { match transport { "unix" => Unix::from_options(options).map(Self::Unix), "tcp" => Tcp::from_options(options, false).map(Self::Tcp), "nonce-tcp" => Tcp::from_options(options, true).map(Self::Tcp), #[cfg(any( all(feature = "vsock", not(feature = "tokio")), feature = "tokio-vsock" ))] "vsock" => Vsock::from_options(options).map(Self::Vsock), #[cfg(windows)] "autolaunch" => Autolaunch::from_options(options).map(Self::Autolaunch), #[cfg(target_os = "macos")] "launchd" => Launchd::from_options(options).map(Self::Launchd), _ => Err(Error::Address(format!( "unsupported transport '{transport}'" ))), } } } #[cfg(not(feature = "tokio"))] #[derive(Debug)] pub(crate) enum Stream { Unix(Async), Tcp(Async), #[cfg(feature = "vsock")] Vsock(Async), } #[cfg(feature = "tokio")] #[derive(Debug)] pub(crate) enum Stream { #[cfg(unix)] Unix(tokio::net::UnixStream), Tcp(TcpStream), #[cfg(feature = "tokio-vsock")] Vsock(VsockStream), } fn decode_hex(c: char) -> Result { match c { '0'..='9' => Ok(c as u8 - b'0'), 'a'..='f' => Ok(c as u8 - b'a' + 10), 'A'..='F' => Ok(c as u8 - b'A' + 10), _ => Err(Error::Address( "invalid hexadecimal character in percent-encoded sequence".to_owned(), )), } } pub(crate) fn decode_percents(value: &str) -> Result> { let mut iter = value.chars(); let mut decoded = Vec::new(); while let Some(c) = iter.next() { if matches!(c, '-' | '0'..='9' | 'A'..='Z' | 'a'..='z' | '_' | '/' | '.' | '\\' | '*') { decoded.push(c as u8) } else if c == '%' { decoded.push( decode_hex(iter.next().ok_or_else(|| { Error::Address("incomplete percent-encoded sequence".to_owned()) })?)? << 4 | decode_hex(iter.next().ok_or_else(|| { Error::Address("incomplete percent-encoded sequence".to_owned()) })?)?, ); } else { return Err(Error::Address("Invalid character in address".to_owned())); } } Ok(decoded) } pub(super) fn encode_percents(f: &mut Formatter<'_>, mut value: &[u8]) -> std::fmt::Result { const LOOKUP: &str = "\ %00%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e%0f\ %10%11%12%13%14%15%16%17%18%19%1a%1b%1c%1d%1e%1f\ %20%21%22%23%24%25%26%27%28%29%2a%2b%2c%2d%2e%2f\ %30%31%32%33%34%35%36%37%38%39%3a%3b%3c%3d%3e%3f\ %40%41%42%43%44%45%46%47%48%49%4a%4b%4c%4d%4e%4f\ %50%51%52%53%54%55%56%57%58%59%5a%5b%5c%5d%5e%5f\ %60%61%62%63%64%65%66%67%68%69%6a%6b%6c%6d%6e%6f\ %70%71%72%73%74%75%76%77%78%79%7a%7b%7c%7d%7e%7f\ %80%81%82%83%84%85%86%87%88%89%8a%8b%8c%8d%8e%8f\ %90%91%92%93%94%95%96%97%98%99%9a%9b%9c%9d%9e%9f\ %a0%a1%a2%a3%a4%a5%a6%a7%a8%a9%aa%ab%ac%ad%ae%af\ %b0%b1%b2%b3%b4%b5%b6%b7%b8%b9%ba%bb%bc%bd%be%bf\ %c0%c1%c2%c3%c4%c5%c6%c7%c8%c9%ca%cb%cc%cd%ce%cf\ %d0%d1%d2%d3%d4%d5%d6%d7%d8%d9%da%db%dc%dd%de%df\ %e0%e1%e2%e3%e4%e5%e6%e7%e8%e9%ea%eb%ec%ed%ee%ef\ %f0%f1%f2%f3%f4%f5%f6%f7%f8%f9%fa%fb%fc%fd%fe%ff"; loop { let pos = value.iter().position( |c| !matches!(c, b'-' | b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z' | b'_' | b'/' | b'.' | b'\\' | b'*'), ); if let Some(pos) = pos { // SAFETY: The above `position()` call made sure that only ASCII chars are in the string // up to `pos` f.write_str(unsafe { from_utf8_unchecked(&value[..pos]) })?; let c = value[pos]; value = &value[pos + 1..]; let pos = c as usize * 3; f.write_str(&LOOKUP[pos..pos + 3])?; } else { // SAFETY: The above `position()` call made sure that only ASCII chars are in the rest // of the string f.write_str(unsafe { from_utf8_unchecked(value) })?; return Ok(()); } } } impl Display for Transport { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { Self::Tcp(tcp) => write!(f, "{}", tcp)?, Self::Unix(unix) => write!(f, "{}", unix)?, #[cfg(any( all(feature = "vsock", not(feature = "tokio")), feature = "tokio-vsock" ))] Self::Vsock(vsock) => write!(f, "{}", vsock)?, #[cfg(windows)] Self::Autolaunch(autolaunch) => write!(f, "{}", autolaunch)?, #[cfg(target_os = "macos")] Self::Launchd(launchd) => write!(f, "{}", launchd)?, } Ok(()) } } zbus-4.3.1/src/address/transport/tcp.rs000064400000000000000000000143221046102023000162220ustar 00000000000000use super::encode_percents; use crate::{Error, Result}; #[cfg(not(feature = "tokio"))] use async_io::Async; #[cfg(not(feature = "tokio"))] use std::net::{SocketAddr, TcpStream, ToSocketAddrs}; use std::{ collections::HashMap, fmt::{Display, Formatter}, str::FromStr, }; #[cfg(feature = "tokio")] use tokio::net::TcpStream; /// A TCP transport in a D-Bus address. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Tcp { pub(super) host: String, pub(super) bind: Option, pub(super) port: u16, pub(super) family: Option, pub(super) nonce_file: Option>, } impl Tcp { /// Create a new TCP transport with the given host and port. pub fn new(host: &str, port: u16) -> Self { Self { host: host.to_owned(), port, bind: None, family: None, nonce_file: None, } } /// Set the `tcp:` address `bind` value. pub fn set_bind(mut self, bind: Option) -> Self { self.bind = bind; self } /// Set the `tcp:` address `family` value. pub fn set_family(mut self, family: Option) -> Self { self.family = family; self } /// Set the `tcp:` address `noncefile` value. pub fn set_nonce_file(mut self, nonce_file: Option>) -> Self { self.nonce_file = nonce_file; self } /// Returns the `tcp:` address `host` value. pub fn host(&self) -> &str { &self.host } /// Returns the `tcp:` address `bind` value. pub fn bind(&self) -> Option<&str> { self.bind.as_deref() } /// Returns the `tcp:` address `port` value. pub fn port(&self) -> u16 { self.port } /// Returns the `tcp:` address `family` value. pub fn family(&self) -> Option { self.family } /// The nonce file path, if any. pub fn nonce_file(&self) -> Option<&[u8]> { self.nonce_file.as_deref() } /// Take ownership of the nonce file path, if any. pub fn take_nonce_file(&mut self) -> Option> { self.nonce_file.take() } pub(super) fn from_options( opts: HashMap<&str, &str>, nonce_tcp_required: bool, ) -> Result { let bind = None; if opts.contains_key("bind") { return Err(Error::Address("`bind` isn't yet supported".into())); } let host = opts .get("host") .ok_or_else(|| Error::Address("tcp address is missing `host`".into()))? .to_string(); let port = opts .get("port") .ok_or_else(|| Error::Address("tcp address is missing `port`".into()))?; let port = port .parse::() .map_err(|_| Error::Address("invalid tcp `port`".into()))?; let family = opts .get("family") .map(|f| TcpTransportFamily::from_str(f)) .transpose()?; let nonce_file = opts .get("noncefile") .map(|f| super::decode_percents(f)) .transpose()?; if nonce_tcp_required && nonce_file.is_none() { return Err(Error::Address( "nonce-tcp address is missing `noncefile`".into(), )); } Ok(Self { host, bind, port, family, nonce_file, }) } #[cfg(not(feature = "tokio"))] pub(super) async fn connect(self) -> Result> { let addrs = crate::Task::spawn_blocking( move || -> Result> { let addrs = (self.host(), self.port()).to_socket_addrs()?.filter(|a| { if let Some(family) = self.family() { if family == TcpTransportFamily::Ipv4 { a.is_ipv4() } else { a.is_ipv6() } } else { true } }); Ok(addrs.collect()) }, "connect tcp", ) .await .map_err(|e| Error::Address(format!("Failed to receive TCP addresses: {e}")))?; // we could attempt connections in parallel? let mut last_err = Error::Address("Failed to connect".into()); for addr in addrs { match Async::::connect(addr).await { Ok(stream) => return Ok(stream), Err(e) => last_err = e.into(), } } Err(last_err) } #[cfg(feature = "tokio")] pub(super) async fn connect(self) -> Result { TcpStream::connect((self.host(), self.port())) .await .map_err(|e| Error::InputOutput(e.into())) } } impl Display for Tcp { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self.nonce_file() { Some(nonce_file) => { f.write_str("nonce-tcp:noncefile=")?; encode_percents(f, nonce_file)?; f.write_str(",")?; } None => f.write_str("tcp:")?, } f.write_str("host=")?; encode_percents(f, self.host().as_bytes())?; write!(f, ",port={}", self.port())?; if let Some(bind) = self.bind() { f.write_str(",bind=")?; encode_percents(f, bind.as_bytes())?; } if let Some(family) = self.family() { write!(f, ",family={family}")?; } Ok(()) } } /// A `tcp:` address family. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum TcpTransportFamily { Ipv4, Ipv6, } impl FromStr for TcpTransportFamily { type Err = Error; fn from_str(family: &str) -> Result { match family { "ipv4" => Ok(Self::Ipv4), "ipv6" => Ok(Self::Ipv6), _ => Err(Error::Address(format!( "invalid tcp address `family`: {family}" ))), } } } impl Display for TcpTransportFamily { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { Self::Ipv4 => write!(f, "ipv4"), Self::Ipv6 => write!(f, "ipv6"), } } } zbus-4.3.1/src/address/transport/unix.rs000064400000000000000000000101011046102023000164060ustar 00000000000000#[cfg(target_os = "linux")] use std::ffi::OsString; use std::{ ffi::OsStr, fmt::{Display, Formatter}, path::PathBuf, }; #[cfg(unix)] use super::encode_percents; /// A Unix domain socket transport in a D-Bus address. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Unix { path: UnixSocket, } impl Unix { /// Create a new Unix transport with the given path. pub fn new(path: UnixSocket) -> Self { Self { path } } /// The path. pub fn path(&self) -> &UnixSocket { &self.path } /// Take the path, consuming `self`. pub fn take_path(self) -> UnixSocket { self.path } pub(super) fn from_options(opts: std::collections::HashMap<&str, &str>) -> crate::Result { let path = opts.get("path"); let abs = opts.get("abstract"); let dir = opts.get("dir"); let tmpdir = opts.get("tmpdir"); let path = match (path, abs, dir, tmpdir) { (Some(p), None, None, None) => UnixSocket::File(PathBuf::from(p)), #[cfg(target_os = "linux")] (None, Some(p), None, None) => UnixSocket::Abstract(OsString::from(p)), #[cfg(not(target_os = "linux"))] (None, Some(_), None, None) => { return Err(crate::Error::Address( "abstract sockets currently Linux-only".to_owned(), )); } (None, None, Some(p), None) => UnixSocket::Dir(PathBuf::from(p)), (None, None, None, Some(p)) => UnixSocket::TmpDir(PathBuf::from(p)), _ => { return Err(crate::Error::Address("unix: address is invalid".to_owned())); } }; Ok(Self::new(path)) } } impl Display for Unix { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "unix:{}", self.path) } } /// A Unix domain socket path in a D-Bus address. #[derive(Clone, Debug, PartialEq, Eq)] #[non_exhaustive] pub enum UnixSocket { /// A path to a unix domain socket on the filesystem. File(PathBuf), /// A abstract unix domain socket name. #[cfg(target_os = "linux")] Abstract(OsString), /// A listenable address using the specified path, in which a socket file with a random file /// name starting with 'dbus-' will be created by the server. See [UNIX domain socket address] /// reference documentation. /// /// This address is mostly relevant to server (typically bus broker) implementations. /// /// [UNIX domain socket address]: https://dbus.freedesktop.org/doc/dbus-specification.html#transports-unix-domain-sockets-addresses Dir(PathBuf), /// The same as UnixDir, except that on platforms with abstract sockets, the server may attempt /// to create an abstract socket whose name starts with this directory instead of a path-based /// socket. /// /// This address is mostly relevant to server (typically bus broker) implementations. TmpDir(PathBuf), } impl Display for UnixSocket { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt_unix_path(f: &mut Formatter<'_>, path: &OsStr) -> std::fmt::Result { #[cfg(unix)] { use std::os::unix::ffi::OsStrExt; encode_percents(f, path.as_bytes())?; } #[cfg(windows)] write!(f, "{}", path.to_str().ok_or(std::fmt::Error)?)?; Ok(()) } match self { UnixSocket::File(path) => { f.write_str("path=")?; fmt_unix_path(f, path.as_os_str())?; } #[cfg(target_os = "linux")] UnixSocket::Abstract(name) => { f.write_str("abstract=")?; fmt_unix_path(f, name)?; } UnixSocket::Dir(path) => { f.write_str("dir=")?; fmt_unix_path(f, path.as_os_str())?; } UnixSocket::TmpDir(path) => { f.write_str("tmpdir=")?; fmt_unix_path(f, path.as_os_str())?; } } Ok(()) } } zbus-4.3.1/src/address/transport/vsock.rs000064400000000000000000000025011046102023000165550ustar 00000000000000use crate::{Error, Result}; use std::collections::HashMap; /// A `tcp:` D-Bus address. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Vsock { pub(super) cid: u32, pub(super) port: u32, } impl Vsock { /// Create a new VSOCK address. pub fn new(cid: u32, port: u32) -> Self { Self { cid, port } } /// The Client ID. pub fn cid(&self) -> u32 { self.cid } /// The port. pub fn port(&self) -> u32 { self.port } pub(super) fn from_options(opts: HashMap<&str, &str>) -> Result { let cid = opts .get("cid") .ok_or_else(|| Error::Address("VSOCK address is missing cid=".into()))?; let cid = cid .parse::() .map_err(|e| Error::Address(format!("Failed to parse VSOCK cid `{}`: {}", cid, e)))?; let port = opts .get("port") .ok_or_else(|| Error::Address("VSOCK address is missing port=".into()))?; let port = port .parse::() .map_err(|e| Error::Address(format!("Failed to parse VSOCK port `{}`: {}", port, e)))?; Ok(Self { cid, port }) } } impl std::fmt::Display for Vsock { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "vsock:cid={},port={}", self.cid, self.port) } } zbus-4.3.1/src/blocking/connection/builder.rs000064400000000000000000000213411046102023000173270ustar 00000000000000use static_assertions::assert_impl_all; #[cfg(not(feature = "tokio"))] use std::net::TcpStream; #[cfg(all(unix, not(feature = "tokio")))] use std::os::unix::net::UnixStream; #[cfg(feature = "tokio")] use tokio::net::TcpStream; #[cfg(all(unix, feature = "tokio"))] use tokio::net::UnixStream; #[cfg(all(windows, not(feature = "tokio")))] use uds_windows::UnixStream; use zvariant::{ObjectPath, Str}; #[cfg(feature = "p2p")] use crate::Guid; use crate::{ address::Address, blocking::Connection, connection::socket::BoxedSplit, names::WellKnownName, object_server::Interface, utils::block_on, AuthMechanism, Error, Result, }; /// A builder for [`zbus::blocking::Connection`]. #[derive(Debug)] #[must_use] pub struct Builder<'a>(crate::connection::Builder<'a>); assert_impl_all!(Builder<'_>: Send, Sync, Unpin); impl<'a> Builder<'a> { /// Create a builder for the session/user message bus connection. pub fn session() -> Result { crate::connection::Builder::session().map(Self) } /// Create a builder for the system-wide message bus connection. pub fn system() -> Result { crate::connection::Builder::system().map(Self) } /// Create a builder for connection that will use the given [D-Bus bus address]. /// /// [D-Bus bus address]: https://dbus.freedesktop.org/doc/dbus-specification.html#addresses pub fn address(address: A) -> Result where A: TryInto
, A::Error: Into, { crate::connection::Builder::address(address).map(Self) } /// Create a builder for connection that will use the given unix stream. /// /// If the default `async-io` feature is disabled, this method will expect /// [`tokio::net::UnixStream`](https://docs.rs/tokio/latest/tokio/net/struct.UnixStream.html) /// argument. /// /// Since tokio currently [does not support Unix domain sockets][tuds] on Windows, this method /// is not available when the `tokio` feature is enabled and building for Windows target. /// /// [tuds]: https://github.com/tokio-rs/tokio/issues/2201 #[cfg(any(unix, not(feature = "tokio")))] pub fn unix_stream(stream: UnixStream) -> Self { Self(crate::connection::Builder::unix_stream(stream)) } /// Create a builder for connection that will use the given TCP stream. /// /// If the default `async-io` feature is disabled, this method will expect /// [`tokio::net::TcpStream`](https://docs.rs/tokio/latest/tokio/net/struct.TcpStream.html) /// argument. pub fn tcp_stream(stream: TcpStream) -> Self { Self(crate::connection::Builder::tcp_stream(stream)) } /// Create a builder for a connection that will use the given pre-authenticated socket. /// /// This is similar to [`Builder::socket`], except that the socket is either already /// authenticated or does not require authentication. pub fn authenticated_socket(socket: S, guid: G) -> Result where S: Into, G: TryInto>, G::Error: Into, { crate::connection::Builder::authenticated_socket(socket, guid).map(Self) } /// Create a builder for connection that will use the given socket. pub fn socket>(socket: S) -> Self { Self(crate::connection::Builder::socket(socket)) } /// Specify the mechanism to use during authentication. pub fn auth_mechanism(self, auth_mechanism: AuthMechanism) -> Self { Self(self.0.auth_mechanism(auth_mechanism)) } /// Specify the mechanisms to use during authentication. #[deprecated(since = "4.1.3", note = "Use `auth_mechanism` instead.")] pub fn auth_mechanisms(self, auth_mechanisms: &[AuthMechanism]) -> Self { #[allow(deprecated)] Self(self.0.auth_mechanisms(auth_mechanisms)) } /// The cookie context to use during authentication. /// /// This is only used when the `cookie` authentication mechanism is enabled and only valid for /// server connection. /// /// If not specified, the default cookie context of `org_freedesktop_general` will be used. /// /// # Errors /// /// If the given string is not a valid cookie context. pub fn cookie_context(self, context: C) -> Result where C: Into>, { self.0.cookie_context(context).map(Self) } /// The ID of the cookie to use during authentication. /// /// This is only used when the `cookie` authentication mechanism is enabled and only valid for /// server connection. /// /// If not specified, the first cookie found in the cookie context file will be used. pub fn cookie_id(self, id: usize) -> Self { Self(self.0.cookie_id(id)) } /// The to-be-created connection will be a peer-to-peer connection. /// /// This method is only available when the `p2p` feature is enabled. #[cfg(feature = "p2p")] pub fn p2p(self) -> Self { Self(self.0.p2p()) } /// The to-be-created connection will be a server using the given GUID. /// /// The to-be-created connection will wait for incoming client authentication handshake and /// negotiation messages, for peer-to-peer communications after successful creation. /// /// This method is only available when the `p2p` feature is enabled. /// /// **NOTE:** This method is redundant when using [`Builder::authenticated_socket`] since the /// latter already sets the GUID for the connection and zbus doesn't differentiate between a /// server and a client connection, except for authentication. #[cfg(feature = "p2p")] pub fn server(self, guid: G) -> Result where G: TryInto>, G::Error: Into, { self.0.server(guid).map(Self) } /// Set the capacity of the main (unfiltered) queue. /// /// Since typically you'd want to set this at instantiation time, you can set it through the /// builder. /// /// # Example /// /// ``` /// # use std::error::Error; /// # use zbus::blocking::connection; /// # /// let conn = connection::Builder::session()? /// .max_queued(30) /// .build()?; /// assert_eq!(conn.max_queued(), 30); /// /// // Do something useful with `conn`.. /// # Ok::<_, Box>(()) /// ``` pub fn max_queued(self, max: usize) -> Self { Self(self.0.max_queued(max)) } /// Register a D-Bus [`Interface`] to be served at a given path. /// /// This is similar to [`zbus::blocking::ObjectServer::at`], except that it allows you to have /// your interfaces available immediately after the connection is established. Typically, this /// is exactly what you'd want. Also in contrast to [`zbus::blocking::ObjectServer::at`], this /// method will replace any previously added interface with the same name at the same path. pub fn serve_at(self, path: P, iface: I) -> Result where I: Interface, P: TryInto>, P::Error: Into, { self.0.serve_at(path, iface).map(Self) } /// Register a well-known name for this connection on the bus. /// /// This is similar to [`zbus::blocking::Connection::request_name`], except the name is /// requested as part of the connection setup ([`Builder::build`]), immediately after /// interfaces registered (through [`Builder::serve_at`]) are advertised. Typically /// this is exactly what you want. pub fn name(self, well_known_name: W) -> Result where W: TryInto>, W::Error: Into, { self.0.name(well_known_name).map(Self) } /// Sets the unique name of the connection. /// /// This method is only available when the `bus-impl` feature is enabled. /// /// # Panics /// /// This method panics if the to-be-created connection is not a peer-to-peer connection. /// It will always panic if the connection is to a message bus as it's the bus that assigns /// peers their unique names. This is mainly provided for bus implementations. All other users /// should not need to use this method. #[cfg(feature = "bus-impl")] pub fn unique_name(self, unique_name: U) -> Result where U: TryInto>, U::Error: Into, { self.0.unique_name(unique_name).map(Self) } /// Build the connection, consuming the builder. /// /// # Errors /// /// Until server-side bus connection is supported, attempting to build such a connection will /// result in [`Error::Unsupported`] error. pub fn build(self) -> Result { block_on(self.0.build()).map(Into::into) } } zbus-4.3.1/src/blocking/connection/mod.rs000064400000000000000000000246671046102023000164760ustar 00000000000000//! Blocking connection API. use enumflags2::BitFlags; use event_listener::EventListener; use static_assertions::assert_impl_all; use std::{io, ops::Deref}; use zbus_names::{BusName, ErrorName, InterfaceName, MemberName, OwnedUniqueName, WellKnownName}; use zvariant::ObjectPath; use crate::{ blocking::ObjectServer, fdo::{ConnectionCredentials, RequestNameFlags, RequestNameReply}, message::Message, utils::block_on, DBusError, Error, Result, }; mod builder; pub use builder::Builder; /// A blocking wrapper of [`zbus::Connection`]. /// /// Most of the API is very similar to [`zbus::Connection`], except it's blocking. #[derive(Debug, Clone)] #[must_use = "Dropping a `Connection` will close the underlying socket."] pub struct Connection { inner: crate::Connection, } assert_impl_all!(Connection: Send, Sync, Unpin); impl Connection { /// Create a `Connection` to the session/user message bus. pub fn session() -> Result { block_on(crate::Connection::session()).map(Self::from) } /// Create a `Connection` to the system-wide message bus. pub fn system() -> Result { block_on(crate::Connection::system()).map(Self::from) } /// The capacity of the main (unfiltered) queue. pub fn max_queued(&self) -> usize { self.inner.max_queued() } /// Set the capacity of the main (unfiltered) queue. pub fn set_max_queued(mut self, max: usize) { self.inner.set_max_queued(max) } /// The server's GUID. pub fn server_guid(&self) -> &str { self.inner.server_guid() } /// The unique name as assigned by the message bus or `None` if not a message bus connection. pub fn unique_name(&self) -> Option<&OwnedUniqueName> { self.inner.unique_name() } /// Send `msg` to the peer. pub fn send(&self, msg: &Message) -> Result<()> { block_on(self.inner.send(msg)) } /// Send a method call. /// /// Create a method-call message, send it over the connection, then wait for the reply. /// /// On successful reply, an `Ok(Message)` is returned. On error, an `Err` is returned. D-Bus /// error replies are returned as [`Error::MethodError`]. pub fn call_method<'d, 'p, 'i, 'm, D, P, I, M, B>( &self, destination: Option, path: P, iface: Option, method_name: M, body: &B, ) -> Result where D: TryInto>, P: TryInto>, I: TryInto>, M: TryInto>, D::Error: Into, P::Error: Into, I::Error: Into, M::Error: Into, B: serde::ser::Serialize + zvariant::DynamicType, { block_on( self.inner .call_method(destination, path, iface, method_name, body), ) } /// Emit a signal. /// /// Create a signal message, and send it over the connection. pub fn emit_signal<'d, 'p, 'i, 'm, D, P, I, M, B>( &self, destination: Option, path: P, iface: I, signal_name: M, body: &B, ) -> Result<()> where D: TryInto>, P: TryInto>, I: TryInto>, M: TryInto>, D::Error: Into, P::Error: Into, I::Error: Into, M::Error: Into, B: serde::ser::Serialize + zvariant::DynamicType, { block_on( self.inner .emit_signal(destination, path, iface, signal_name, body), ) } /// Reply to a message. /// /// Given an existing message (likely a method call), send a reply back to the caller with the /// given `body`. pub fn reply(&self, call: &Message, body: &B) -> Result<()> where B: serde::ser::Serialize + zvariant::DynamicType, { block_on(self.inner.reply(call, body)) } /// Reply an error to a message. /// /// Given an existing message (likely a method call), send an error reply back to the caller /// with the given `error_name` and `body`. /// /// Returns the message serial number. pub fn reply_error<'e, E, B>(&self, call: &Message, error_name: E, body: &B) -> Result<()> where B: serde::ser::Serialize + zvariant::DynamicType, E: TryInto>, E::Error: Into, { block_on(self.inner.reply_error(call, error_name, body)) } /// Reply to a method call with an error. /// /// Given an existing method call message header, send an error reply back to the caller /// using one of the standard interface reply types. /// /// Returns the message serial number. pub fn reply_dbus_error( &self, call: &zbus::message::Header<'_>, err: impl DBusError, ) -> Result<()> { block_on(self.inner.reply_dbus_error(call, err)) } /// Register a well-known name for this service on the bus. /// /// Blocking version of [`crate::Connection::request_name`]. See docs there for more details /// and caveats. pub fn request_name<'w, W>(&self, well_known_name: W) -> Result<()> where W: TryInto>, W::Error: Into, { block_on(self.inner.request_name(well_known_name)) } /// Register a well-known name for this service on the bus. /// /// Blocking version of [`crate::Connection::request_name_with_flags`]. See docs there for more /// details and caveats. pub fn request_name_with_flags<'w, W>( &self, well_known_name: W, flags: BitFlags, ) -> Result where W: TryInto>, W::Error: Into, { block_on(self.inner.request_name_with_flags(well_known_name, flags)) } /// Deregister a previously registered well-known name for this service on the bus. /// /// Use this method to deregister a well-known name, registered through /// [`Connection::request_name`]. /// /// Unless an error is encountered, returns `Ok(true)` if name was previously registered with /// the bus through `self` and it has now been successfully deregistered, `Ok(false)` if name /// was not previously registered or already deregistered. pub fn release_name<'w, W>(&self, well_known_name: W) -> Result where W: TryInto>, W::Error: Into, { block_on(self.inner.release_name(well_known_name)) } /// Checks if `self` is a connection to a message bus. /// /// This will return `false` for p2p connections. pub fn is_bus(&self) -> bool { self.inner.is_bus() } /// Get a reference to the associated [`ObjectServer`]. /// /// The `ObjectServer` is created on-demand. pub fn object_server(&self) -> impl Deref + '_ { self.inner.sync_object_server(true, None) } /// Get a reference to the underlying async Connection. pub fn inner(&self) -> &crate::Connection { &self.inner } /// Get the underlying async Connection, consuming `self`. pub fn into_inner(self) -> crate::Connection { self.inner } /// Returns a listener, notified on various connection activity. /// /// This function is meant for the caller to implement idle or timeout on inactivity. pub fn monitor_activity(&self) -> EventListener { self.inner.monitor_activity() } /// Returns the peer credentials. /// /// The fields are populated on the best effort basis. Some or all fields may not even make /// sense for certain sockets or on certain platforms and hence will be set to `None`. /// /// # Caveats /// /// Currently `unix_group_ids` and `linux_security_label` fields are not populated. pub fn peer_credentials(&self) -> io::Result { block_on(self.inner.peer_credentials()) } /// Close the connection. /// /// After this call, all reading and writing operations will fail. pub fn close(self) -> Result<()> { block_on(self.inner.close()) } } impl From for Connection { fn from(conn: crate::Connection) -> Self { Self { inner: conn } } } #[cfg(feature = "p2p")] #[cfg(all(test, unix))] mod tests { use event_listener::Listener; use ntest::timeout; #[cfg(all(unix, not(feature = "tokio")))] use std::os::unix::net::UnixStream; use std::thread; use test_log::test; #[cfg(all(unix, feature = "tokio"))] use tokio::net::UnixStream; #[cfg(all(windows, not(feature = "tokio")))] use uds_windows::UnixStream; use crate::{ blocking::{connection::Builder, MessageIterator}, Guid, }; #[test] #[timeout(15000)] fn unix_p2p() { let guid = Guid::generate(); // Tokio needs us to call the sync function from async context. :shrug: let (p0, p1) = crate::utils::block_on(async { UnixStream::pair().unwrap() }); let (tx, rx) = std::sync::mpsc::channel(); let server_thread = thread::spawn(move || { let c = Builder::unix_stream(p0) .server(guid) .unwrap() .p2p() .build() .unwrap(); rx.recv().unwrap(); let reply = c .call_method(None::<()>, "/", Some("org.zbus.p2p"), "Test", &()) .unwrap(); assert_eq!(reply.to_string(), "Method return"); let val: String = reply.body().deserialize().unwrap(); val }); let c = Builder::unix_stream(p1).p2p().build().unwrap(); let listener = c.monitor_activity(); let mut s = MessageIterator::from(&c); tx.send(()).unwrap(); let m = s.next().unwrap().unwrap(); assert_eq!(m.to_string(), "Method call Test"); c.reply(&m, &("yay")).unwrap(); for _ in s {} let val = server_thread.join().expect("failed to join server thread"); assert_eq!(val, "yay"); // there was some activity listener.wait(); // eventually, nothing happens and it will timeout loop { let listener = c.monitor_activity(); if listener .wait_timeout(std::time::Duration::from_millis(10)) .is_none() { break; } } } } zbus-4.3.1/src/blocking/fdo.rs000064400000000000000000000023001046102023000143040ustar 00000000000000//! D-Bus standard interfaces. //! //! Provides blocking versions of the proxy types in [`zbus::fdo`] module. use enumflags2::BitFlags; use static_assertions::assert_impl_all; use std::collections::HashMap; use zbus_names::{ BusName, InterfaceName, OwnedBusName, OwnedInterfaceName, OwnedUniqueName, UniqueName, WellKnownName, }; use zvariant::{ObjectPath, Optional, OwnedValue, Value}; use crate::{ fdo::{ ConnectionCredentials, ManagedObjects, ReleaseNameReply, RequestNameFlags, RequestNameReply, Result, }, proxy, OwnedGuid, }; gen_introspectable_proxy!(false, true); assert_impl_all!(IntrospectableProxy<'_>: Send, Sync, Unpin); gen_properties_proxy!(false, true); assert_impl_all!(PropertiesProxy<'_>: Send, Sync, Unpin); gen_object_manager_proxy!(false, true); assert_impl_all!(ObjectManagerProxy<'_>: Send, Sync, Unpin); gen_peer_proxy!(false, true); assert_impl_all!(PeerProxy<'_>: Send, Sync, Unpin); gen_monitoring_proxy!(false, true); assert_impl_all!(MonitoringProxy<'_>: Send, Sync, Unpin); gen_stats_proxy!(false, true); assert_impl_all!(StatsProxy<'_>: Send, Sync, Unpin); gen_dbus_proxy!(false, true); assert_impl_all!(DBusProxy<'_>: Send, Sync, Unpin); zbus-4.3.1/src/blocking/message_iterator.rs000064400000000000000000000125671046102023000171110ustar 00000000000000use futures_util::StreamExt; use static_assertions::assert_impl_all; use crate::{ blocking::Connection, message::Message, utils::block_on, MatchRule, OwnedMatchRule, Result, }; /// A blocking wrapper of [`crate::MessageStream`]. /// /// Just like [`crate::MessageStream`] must be continuously polled, you must continuously iterate /// over this type until it's consumed or dropped. #[derive(Debug, Clone)] pub struct MessageIterator { // Wrap it in an `Option` to ensure the stream is dropped in a `block_on` call. This is needed // for tokio because the proxy spawns a task in its `Drop` impl and that needs a runtime // context in case of tokio. Moreover, we want to use `AsyncDrop::async_drop` to drop the // stream to ensure any associated match rule is deregistered before the iterator is // dropped. pub(crate) azync: Option, } assert_impl_all!(MessageIterator: Send, Sync, Unpin); impl MessageIterator { /// Get a reference to the underlying async message stream. pub fn inner(&self) -> &crate::MessageStream { self.azync.as_ref().expect("Inner stream is `None`") } /// Get the underlying async message stream, consuming `self`. pub fn into_inner(mut self) -> crate::MessageStream { self.azync.take().expect("Inner stream is `None`") } /// Create a message iterator for the given match rule. /// /// This is a wrapper around [`crate::MessageStream::for_match_rule`]. Unlike the underlying /// `MessageStream`, the match rule is immediately deregistered when the iterator is dropped. /// /// # Example /// /// ``` /// use zbus::{blocking::{Connection, MessageIterator}, MatchRule, fdo::NameOwnerChanged}; /// /// # fn main() -> Result<(), Box> { /// let conn = Connection::session()?; /// let rule = MatchRule::builder() /// .msg_type(zbus::message::Type::Signal) /// .sender("org.freedesktop.DBus")? /// .interface("org.freedesktop.DBus")? /// .member("NameOwnerChanged")? /// .add_arg("org.freedesktop.zbus.MatchRuleIteratorTest42")? /// .build(); /// let mut iter = MessageIterator::for_match_rule( /// rule, /// &conn, /// // For such a specific match rule, we don't need a big queue. /// Some(1), /// )?; /// /// let rule_str = "type='signal',sender='org.freedesktop.DBus',\ /// interface='org.freedesktop.DBus',member='NameOwnerChanged',\ /// arg0='org.freedesktop.zbus.MatchRuleIteratorTest42'"; /// assert_eq!( /// iter.match_rule().map(|r| r.to_string()).as_deref(), /// Some(rule_str), /// ); /// /// // We register 2 names, starting with the uninteresting one. If `iter` wasn't filtering /// // messages based on the match rule, we'd receive method return call for each of these 2 /// // calls first. /// // /// // Note that the `NameOwnerChanged` signal will not be sent by the bus for the first name /// // we register since we setup an arg filter. /// conn.request_name("org.freedesktop.zbus.MatchRuleIteratorTest44")?; /// conn.request_name("org.freedesktop.zbus.MatchRuleIteratorTest42")?; /// /// let msg = iter.next().unwrap()?; /// let signal = NameOwnerChanged::from_message(msg).unwrap(); /// assert_eq!(signal.args()?.name(), "org.freedesktop.zbus.MatchRuleIteratorTest42"); /// /// # Ok(()) /// # } /// ``` /// /// # Caveats /// /// Since this method relies on [`MatchRule::matches`], it inherits its caveats. pub fn for_match_rule(rule: R, conn: &Connection, max_queued: Option) -> Result where R: TryInto, R::Error: Into, { block_on(crate::MessageStream::for_match_rule( rule, conn.inner(), max_queued, )) .map(Some) .map(|s| Self { azync: s }) } /// The associated match rule, if any. pub fn match_rule(&self) -> Option> { self.azync .as_ref() .expect("Inner stream is `None`") .match_rule() } } impl Iterator for MessageIterator { type Item = Result; fn next(&mut self) -> Option { block_on(self.azync.as_mut().expect("Inner stream is `None`").next()) } } impl From for MessageIterator { fn from(conn: Connection) -> Self { let azync = crate::MessageStream::from(conn.into_inner()); Self { azync: Some(azync) } } } impl From<&Connection> for MessageIterator { fn from(conn: &Connection) -> Self { Self::from(conn.clone()) } } impl From for Connection { fn from(mut iter: MessageIterator) -> Connection { Connection::from(crate::Connection::from( iter.azync.take().expect("Inner stream is `None`"), )) } } impl From<&MessageIterator> for Connection { fn from(iter: &MessageIterator) -> Connection { Connection::from(crate::Connection::from( iter.azync.as_ref().expect("Inner stream is `None`"), )) } } impl std::ops::Drop for MessageIterator { fn drop(&mut self) { block_on(async { if let Some(azync) = self.azync.take() { crate::AsyncDrop::async_drop(azync).await; } }); } } zbus-4.3.1/src/blocking/mod.rs000064400000000000000000000041161046102023000143220ustar 00000000000000//! The blocking API. //! //! This module hosts all our blocking API. All the types under this module are thin wrappers //! around the corresponding asynchronous types. Most of the method calls are simply calling their //! asynchronous counterparts on the underlying types and use [`async_io::block_on`] to turn them //! into blocking calls. //! //! # Caveats //! //! Since methods provided by these types run their own little runtime (`block_on`), you must not //! use them in async contexts because of the infamous [async sandwich footgun][asf]. This is //! an especially important fact to keep in mind for [`crate::interface`]. While //! `dbus_interface` allows non-async methods for convenience, these methods are called from an //! async context. The [`blocking` crate] provides an easy way around this problem though. //! //! [asf]: https://rust-lang.github.io/wg-async/vision/shiny_future/users_manual.html#caveat-beware-the-async-sandwich //! [`blocking` crate]: https://docs.rs/blocking/ pub mod connection; pub use connection::Connection; mod message_iterator; pub use message_iterator::*; pub mod object_server; pub use object_server::ObjectServer; pub mod proxy; pub use proxy::Proxy; #[deprecated(since = "4.0.0", note = "Use `proxy::Builder` instead")] #[doc(hidden)] pub use proxy::Builder as ProxyBuilder; #[deprecated(since = "4.0.0", note = "Use `proxy::OwnerChangedIterator` instead")] #[doc(hidden)] pub use proxy::OwnerChangedIterator; #[deprecated(since = "4.0.0", note = "Use `proxy::PropertyChanged` instead")] #[doc(hidden)] pub use proxy::PropertyChanged; #[deprecated(since = "4.0.0", note = "Use `proxy::PropertyIterator` instead")] #[doc(hidden)] pub use proxy::PropertyIterator; #[deprecated(since = "4.0.0", note = "Use `proxy::SignalIterator` instead")] #[doc(hidden)] pub use proxy::SignalIterator; #[deprecated(since = "4.0.0", note = "Use `object_server::InterfaceRef` instead")] #[doc(hidden)] pub use object_server::InterfaceRef; #[deprecated(since = "4.0.0", note = "Use `connection::Builder` instead")] #[doc(hidden)] pub use connection::Builder as ConnectionBuilder; pub mod fdo; zbus-4.3.1/src/blocking/object_server.rs000064400000000000000000000157061046102023000164060ustar 00000000000000//! The object server API. use static_assertions::assert_impl_all; use zvariant::ObjectPath; use crate::{ object_server::{Interface, InterfaceDeref, InterfaceDerefMut, SignalContext}, utils::block_on, Error, Result, }; /// Wrapper over an interface, along with its corresponding `SignalContext` /// instance. A reference to the underlying interface may be obtained via /// [`InterfaceRef::get`] and [`InterfaceRef::get_mut`]. pub struct InterfaceRef { azync: crate::object_server::InterfaceRef, } impl InterfaceRef where I: 'static, { /// Get a reference to the underlying interface. pub fn get(&self) -> InterfaceDeref<'_, I> { block_on(self.azync.get()) } /// Get a reference to the underlying interface. /// /// **WARNINGS:** Since the `ObjectServer` will not be able to access the interface in question /// until the return value of this method is dropped, it is highly recommended that the scope /// of the interface returned is restricted. /// /// # Errors /// /// If the interface at this instance's path is not valid, `Error::InterfaceNotFound` error is /// returned. /// /// # Examples /// /// ```no_run /// # use std::error::Error; /// # use async_io::block_on; /// # use zbus::{blocking::Connection, interface}; /// /// struct MyIface(u32); /// /// #[interface(name = "org.myiface.MyIface")] /// impl MyIface { /// #[zbus(property)] /// fn count(&self) -> u32 { /// self.0 /// } /// } /// // Setup connection and object_server etc here and then in another part of the code: /// # /// # let connection = Connection::session()?; /// # /// # let path = "/org/zbus/path"; /// # connection.object_server().at(path, MyIface(22))?; /// let object_server = connection.object_server(); /// let iface_ref = object_server.interface::<_, MyIface>(path)?; /// let mut iface = iface_ref.get_mut(); /// iface.0 = 42; /// block_on(iface.count_changed(iface_ref.signal_context()))?; /// # /// # Ok::<_, Box>(()) /// ``` pub fn get_mut(&self) -> InterfaceDerefMut<'_, I> { block_on(self.azync.get_mut()) } pub fn signal_context(&self) -> &SignalContext<'static> { self.azync.signal_context() } } /// A blocking wrapper of [`crate::ObjectServer`]. /// /// # Example /// /// This example exposes the `org.myiface.Example.Quit` method on the `/org/zbus/path` /// path. /// /// ```no_run /// # use std::error::Error; /// use zbus::{blocking::Connection, interface}; /// use event_listener::{Event, Listener}; /// /// struct Example { /// // Interfaces are owned by the ObjectServer. They can have /// // `&mut self` methods. /// quit_event: Event, /// } /// /// impl Example { /// fn new(quit_event: Event) -> Self { /// Self { quit_event } /// } /// } /// /// #[interface(name = "org.myiface.Example")] /// impl Example { /// // This will be the "Quit" D-Bus method. /// fn quit(&mut self) { /// self.quit_event.notify(1); /// } /// /// // See `interface` documentation to learn /// // how to expose properties & signals as well. /// } /// /// let connection = Connection::session()?; /// /// let quit_event = Event::new(); /// let quit_listener = quit_event.listen(); /// let interface = Example::new(quit_event); /// connection /// .object_server() /// .at("/org/zbus/path", interface)?; /// /// quit_listener.wait(); /// # Ok::<_, Box>(()) /// ``` #[derive(Debug)] pub struct ObjectServer { azync: crate::ObjectServer, } assert_impl_all!(ObjectServer: Send, Sync, Unpin); impl ObjectServer { /// Creates a new D-Bus `ObjectServer`. pub(crate) fn new(conn: &crate::Connection) -> Self { Self { azync: crate::ObjectServer::new(conn), } } /// Register a D-Bus [`Interface`] at a given path. (see the example above) /// /// Typically you'd want your interfaces to be registered immediately after the associated /// connection is established and therefore use /// [`zbus::blocking::connection::Builder::serve_at`] instead. However, there are /// situations where you'd need to register interfaces dynamically and that's where this /// method becomes useful. /// /// If the interface already exists at this path, returns false. /// /// [`Interface`]: trait.Interface.html pub fn at<'p, P, I>(&self, path: P, iface: I) -> Result where I: Interface, P: TryInto>, P::Error: Into, { block_on(self.azync.at(path, iface)) } /// Unregister a D-Bus [`Interface`] at a given path. /// /// If there are no more interfaces left at that path, destroys the object as well. /// Returns whether the object was destroyed. /// /// [`Interface`]: trait.Interface.html pub fn remove<'p, I, P>(&self, path: P) -> Result where I: Interface, P: TryInto>, P::Error: Into, { block_on(self.azync.remove::(path)) } /// Get the interface at the given path. /// /// # Errors /// /// If the interface is not registered at the given path, `Error::InterfaceNotFound` error is /// returned. /// /// # Examples /// /// The typical use of this is to emit signals outside of a dispatched handler: /// /// ```no_run /// # use std::error::Error; /// # use zbus::block_on; /// # use zbus::{ /// # SignalContext, /// # blocking::Connection, /// # interface, /// # }; /// # /// struct MyIface; /// #[interface(name = "org.myiface.MyIface")] /// impl MyIface { /// #[zbus(signal)] /// async fn emit_signal(ctxt: &SignalContext<'_>) -> zbus::Result<()>; /// } /// /// # let connection = Connection::session()?; /// # /// # let path = "/org/zbus/path"; /// # connection.object_server().at(path, MyIface)?; /// let iface_ref = connection /// .object_server() /// .interface::<_, MyIface>(path)?; /// block_on(MyIface::emit_signal(iface_ref.signal_context()))?; /// # /// # /// # Ok::<_, Box>(()) /// ``` pub fn interface<'p, P, I>(&self, path: P) -> Result> where I: Interface, P: TryInto>, P::Error: Into, { Ok(InterfaceRef { azync: block_on(self.azync.interface(path))?, }) } /// Get a reference to the underlying async ObjectServer. pub fn inner(&self) -> &crate::ObjectServer { &self.azync } /// Get the underlying async ObjectServer, consuming `self`. pub fn into_inner(self) -> crate::ObjectServer { self.azync } } impl From for ObjectServer { fn from(azync: crate::ObjectServer) -> Self { Self { azync } } } zbus-4.3.1/src/blocking/proxy/builder.rs000064400000000000000000000045531046102023000163570ustar 00000000000000use static_assertions::assert_impl_all; use zbus_names::{BusName, InterfaceName}; use zvariant::ObjectPath; use crate::{blocking::Connection, proxy::CacheProperties, utils::block_on, Error, Result}; pub use crate::proxy::ProxyDefault; /// Builder for proxies. #[derive(Debug, Clone)] pub struct Builder<'a, T = ()>(crate::proxy::Builder<'a, T>); assert_impl_all!(Builder<'_>: Send, Sync, Unpin); impl<'a, T> Builder<'a, T> { /// Set the proxy destination address. pub fn destination(self, destination: D) -> Result where D: TryInto>, D::Error: Into, { crate::proxy::Builder::destination(self.0, destination).map(Self) } /// Set the proxy path. pub fn path
, A::Error: Into, { Ok(Self::new(Target::Address( address.try_into().map_err(Into::into)?, ))) } /// Create a builder for connection that will use the given unix stream. /// /// If the default `async-io` feature is disabled, this method will expect /// [`tokio::net::UnixStream`](https://docs.rs/tokio/latest/tokio/net/struct.UnixStream.html) /// argument. /// /// Since tokio currently [does not support Unix domain sockets][tuds] on Windows, this method /// is not available when the `tokio` feature is enabled and building for Windows target. /// /// [tuds]: https://github.com/tokio-rs/tokio/issues/2201 #[cfg(any(unix, not(feature = "tokio")))] pub fn unix_stream(stream: UnixStream) -> Self { Self::new(Target::UnixStream(stream)) } /// Create a builder for connection that will use the given TCP stream. /// /// If the default `async-io` feature is disabled, this method will expect /// [`tokio::net::TcpStream`](https://docs.rs/tokio/latest/tokio/net/struct.TcpStream.html) /// argument. pub fn tcp_stream(stream: TcpStream) -> Self { Self::new(Target::TcpStream(stream)) } /// Create a builder for connection that will use the given VSOCK stream. /// /// This method is only available when either `vsock` or `tokio-vsock` feature is enabled. The /// type of `stream` is `vsock::VsockStream` with `vsock` feature and `tokio_vsock::VsockStream` /// with `tokio-vsock` feature. #[cfg(any( all(feature = "vsock", not(feature = "tokio")), feature = "tokio-vsock" ))] pub fn vsock_stream(stream: VsockStream) -> Self { Self::new(Target::VsockStream(stream)) } /// Create a builder for connection that will use the given socket. pub fn socket>(socket: S) -> Self { Self::new(Target::Socket(socket.into())) } /// Create a builder for a connection that will use the given pre-authenticated socket. /// /// This is similar to [`Builder::socket`], except that the socket is either already /// authenticated or does not require authentication. pub fn authenticated_socket(socket: S, guid: G) -> Result where S: Into, G: TryInto>, G::Error: Into, { let mut builder = Self::new(Target::AuthenticatedSocket(socket.into())); builder.guid = Some(guid.try_into().map_err(Into::into)?); Ok(builder) } /// Specify the mechanism to use during authentication. pub fn auth_mechanism(self, auth_mechanism: AuthMechanism) -> Self { #[allow(deprecated)] self.auth_mechanisms(&[auth_mechanism]) } /// Specify the mechanisms to use during authentication. #[deprecated(since = "4.1.3", note = "Use `auth_mechanism` instead.")] pub fn auth_mechanisms(mut self, auth_mechanisms: &[AuthMechanism]) -> Self { self.auth_mechanisms = Some(VecDeque::from(auth_mechanisms.to_vec())); self } /// The cookie context to use during authentication. /// /// This is only used when the `cookie` authentication mechanism is enabled and only valid for /// server connection. /// /// If not specified, the default cookie context of `org_freedesktop_general` will be used. /// /// # Errors /// /// If the given string is not a valid cookie context. pub fn cookie_context(mut self, context: C) -> Result where C: Into>, { self.cookie_context = Some(context.into().try_into()?); Ok(self) } /// The ID of the cookie to use during authentication. /// /// This is only used when the `cookie` authentication mechanism is enabled and only valid for /// server connection. /// /// If not specified, the first cookie found in the cookie context file will be used. pub fn cookie_id(mut self, id: usize) -> Self { self.cookie_id = Some(id); self } /// The to-be-created connection will be a peer-to-peer connection. /// /// This method is only available when the `p2p` feature is enabled. #[cfg(feature = "p2p")] pub fn p2p(mut self) -> Self { self.p2p = true; self } /// The to-be-created connection will be a server using the given GUID. /// /// The to-be-created connection will wait for incoming client authentication handshake and /// negotiation messages, for peer-to-peer communications after successful creation. /// /// This method is only available when the `p2p` feature is enabled. /// /// **NOTE:** This method is redundant when using [`Builder::authenticated_socket`] since the /// latter already sets the GUID for the connection and zbus doesn't differentiate between a /// server and a client connection, except for authentication. #[cfg(feature = "p2p")] pub fn server(mut self, guid: G) -> Result where G: TryInto>, G::Error: Into, { self.guid = Some(guid.try_into().map_err(Into::into)?); Ok(self) } /// Set the capacity of the main (unfiltered) queue. /// /// Since typically you'd want to set this at instantiation time, you can set it through the /// builder. /// /// # Example /// /// ``` /// # use std::error::Error; /// # use zbus::connection::Builder; /// # use zbus::block_on; /// # /// # block_on(async { /// let conn = Builder::session()? /// .max_queued(30) /// .build() /// .await?; /// assert_eq!(conn.max_queued(), 30); /// /// # Ok::<(), zbus::Error>(()) /// # }).unwrap(); /// # /// // Do something useful with `conn`.. /// # Ok::<_, Box>(()) /// ``` pub fn max_queued(mut self, max: usize) -> Self { self.max_queued = Some(max); self } /// Enable or disable the internal executor thread. /// /// The thread is enabled by default. /// /// See [Connection::executor] for more details. pub fn internal_executor(mut self, enabled: bool) -> Self { self.internal_executor = enabled; self } /// Register a D-Bus [`Interface`] to be served at a given path. /// /// This is similar to [`zbus::ObjectServer::at`], except that it allows you to have your /// interfaces available immediately after the connection is established. Typically, this is /// exactly what you'd want. Also in contrast to [`zbus::ObjectServer::at`], this method will /// replace any previously added interface with the same name at the same path. /// /// Standard interfaces (Peer, Introspectable, Properties) are added on your behalf. If you /// attempt to add yours, [`Builder::build()`] will fail. pub fn serve_at(mut self, path: P, iface: I) -> Result where I: Interface, P: TryInto>, P::Error: Into, { let path = path.try_into().map_err(Into::into)?; let entry = self.interfaces.entry(path).or_default(); entry.insert(I::name(), ArcInterface::new(iface)); Ok(self) } /// Register a well-known name for this connection on the bus. /// /// This is similar to [`zbus::Connection::request_name`], except the name is requested as part /// of the connection setup ([`Builder::build`]), immediately after interfaces /// registered (through [`Builder::serve_at`]) are advertised. Typically this is /// exactly what you want. pub fn name(mut self, well_known_name: W) -> Result where W: TryInto>, W::Error: Into, { let well_known_name = well_known_name.try_into().map_err(Into::into)?; self.names.insert(well_known_name); Ok(self) } /// Sets the unique name of the connection. /// /// This is mainly provided for bus implementations. All other users should not need to use this /// method. Hence why this method is only available when the `bus-impl` feature is enabled. /// /// # Panics /// /// It will panic if the connection is to a message bus as it's the bus that assigns /// peers their unique names. #[cfg(feature = "bus-impl")] pub fn unique_name(mut self, unique_name: U) -> Result where U: TryInto>, U::Error: Into, { if !self.p2p { panic!("unique name can only be set for peer-to-peer connections"); } let name = unique_name.try_into().map_err(Into::into)?; self.unique_name = Some(name); Ok(self) } /// Build the connection, consuming the builder. /// /// # Errors /// /// Until server-side bus connection is supported, attempting to build such a connection will /// result in [`Error::Unsupported`] error. pub async fn build(self) -> Result { let executor = Executor::new(); #[cfg(not(feature = "tokio"))] let internal_executor = self.internal_executor; // Box the future as it's large and can cause stack overflow. let conn = Box::pin(executor.run(self.build_(executor.clone()))).await?; #[cfg(not(feature = "tokio"))] start_internal_executor(&executor, internal_executor)?; Ok(conn) } async fn build_(mut self, executor: Executor<'static>) -> Result { #[cfg(feature = "p2p")] let is_bus_conn = !self.p2p; #[cfg(not(feature = "p2p"))] let is_bus_conn = true; #[cfg(not(feature = "bus-impl"))] let unique_name = None; #[cfg(feature = "bus-impl")] let unique_name = self.unique_name.take().map(Into::into); #[allow(unused_mut)] let (mut stream, server_guid, authenticated) = self.target_connect().await?; let mut auth = if authenticated { let (socket_read, socket_write) = stream.take(); Authenticated { #[cfg(unix)] cap_unix_fd: socket_read.can_pass_unix_fd(), socket_read: Some(socket_read), socket_write, // SAFETY: `server_guid` is provided as arg of `Builder::authenticated_socket`. server_guid: server_guid.unwrap(), already_received_bytes: vec![], unique_name, #[cfg(unix)] already_received_fds: vec![], } } else { #[cfg(feature = "p2p")] match self.guid { None => { // SASL Handshake Authenticated::client(stream, server_guid, self.auth_mechanisms, is_bus_conn) .await? } Some(guid) => { if !self.p2p { return Err(Error::Unsupported); } let creds = stream.read_mut().peer_credentials().await?; #[cfg(unix)] let client_uid = creds.unix_user_id(); #[cfg(windows)] let client_sid = creds.into_windows_sid(); Authenticated::server( stream, guid.to_owned().into(), #[cfg(unix)] client_uid, #[cfg(windows)] client_sid, self.auth_mechanisms, self.cookie_id, self.cookie_context.unwrap_or_default(), unique_name, ) .await? } } #[cfg(not(feature = "p2p"))] Authenticated::client(stream, server_guid, self.auth_mechanisms, is_bus_conn).await? }; // SAFETY: `Authenticated` is always built with these fields set to `Some`. let socket_read = auth.socket_read.take().unwrap(); let already_received_bytes = auth.already_received_bytes.drain(..).collect(); #[cfg(unix)] let already_received_fds = auth.already_received_fds.drain(..).collect(); let mut conn = Connection::new(auth, is_bus_conn, executor).await?; conn.set_max_queued(self.max_queued.unwrap_or(DEFAULT_MAX_QUEUED)); if !self.interfaces.is_empty() { let object_server = conn.sync_object_server(false, None); for (path, interfaces) in self.interfaces { for (name, iface) in interfaces { let added = object_server .inner() .add_arc_interface(path.clone(), name.clone(), iface.clone()) .await?; if !added { return Err(Error::InterfaceExists(name.clone(), path.to_owned())); } } } let started_event = Event::new(); let listener = started_event.listen(); conn.start_object_server(Some(started_event)); listener.await; } // Start the socket reader task. conn.init_socket_reader( socket_read, already_received_bytes, #[cfg(unix)] already_received_fds, ); for name in self.names { conn.request_name(name).await?; } Ok(conn) } fn new(target: Target) -> Self { Self { target: Some(target), #[cfg(feature = "p2p")] p2p: false, max_queued: None, guid: None, internal_executor: true, interfaces: HashMap::new(), names: HashSet::new(), auth_mechanisms: None, #[cfg(feature = "bus-impl")] unique_name: None, cookie_id: None, cookie_context: None, } } async fn target_connect(&mut self) -> Result<(BoxedSplit, Option, bool)> { let mut authenticated = false; let mut guid = None; // SAFETY: `self.target` is always `Some` from the beginning and this method is only called // once. let split = match self.target.take().unwrap() { #[cfg(not(feature = "tokio"))] Target::UnixStream(stream) => Async::new(stream)?.into(), #[cfg(all(unix, feature = "tokio"))] Target::UnixStream(stream) => stream.into(), #[cfg(not(feature = "tokio"))] Target::TcpStream(stream) => Async::new(stream)?.into(), #[cfg(feature = "tokio")] Target::TcpStream(stream) => stream.into(), #[cfg(all(feature = "vsock", not(feature = "tokio")))] Target::VsockStream(stream) => Async::new(stream)?.into(), #[cfg(feature = "tokio-vsock")] Target::VsockStream(stream) => stream.into(), Target::Address(address) => { guid = address.guid().map(|g| g.to_owned().into()); match address.connect().await? { #[cfg(any(unix, not(feature = "tokio")))] address::transport::Stream::Unix(stream) => stream.into(), address::transport::Stream::Tcp(stream) => stream.into(), #[cfg(any( all(feature = "vsock", not(feature = "tokio")), feature = "tokio-vsock" ))] address::transport::Stream::Vsock(stream) => stream.into(), } } Target::Socket(stream) => stream, Target::AuthenticatedSocket(stream) => { authenticated = true; guid = self.guid.take().map(Into::into); stream } }; Ok((split, guid, authenticated)) } } /// Start the internal executor thread. /// /// Returns a dummy task that keep the executor ticking thread from exiting due to absence of any /// tasks until socket reader task kicks in. #[cfg(not(feature = "tokio"))] fn start_internal_executor(executor: &Executor<'static>, internal_executor: bool) -> Result<()> { if internal_executor { let executor = executor.clone(); std::thread::Builder::new() .name("zbus::Connection executor".into()) .spawn(move || { crate::utils::block_on(async move { // Run as long as there is a task to run. while !executor.is_empty() { executor.tick().await; } }) })?; } Ok(()) } zbus-4.3.1/src/connection/handshake/auth_mechanism.rs000064400000000000000000000030461046102023000210260ustar 00000000000000use std::{fmt, str::FromStr}; use crate::{Error, Result}; /// Authentication mechanisms /// /// See #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum AuthMechanism { /// This is the recommended authentication mechanism on platforms where credentials can be /// transferred out-of-band, in particular Unix platforms that can perform credentials-passing /// over the `unix:` transport. External, /// This mechanism is designed to establish that a client has the ability to read a private /// file owned by the user being authenticated. Cookie, /// Does not perform any authentication at all, and should not be accepted by message buses. /// However, it might sometimes be useful for non-message-bus uses of D-Bus. Anonymous, } impl fmt::Display for AuthMechanism { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mech = match self { AuthMechanism::External => "EXTERNAL", AuthMechanism::Cookie => "DBUS_COOKIE_SHA1", AuthMechanism::Anonymous => "ANONYMOUS", }; write!(f, "{mech}") } } impl FromStr for AuthMechanism { type Err = Error; fn from_str(s: &str) -> Result { match s { "EXTERNAL" => Ok(AuthMechanism::External), "DBUS_COOKIE_SHA1" => Ok(AuthMechanism::Cookie), "ANONYMOUS" => Ok(AuthMechanism::Anonymous), _ => Err(Error::Handshake(format!("Unknown mechanism: {s}"))), } } } zbus-4.3.1/src/connection/handshake/client.rs000064400000000000000000000267121046102023000173240ustar 00000000000000use async_trait::async_trait; use std::collections::VecDeque; use tracing::{debug, instrument, trace, warn}; use sha1::{Digest, Sha1}; use crate::{conn::socket::ReadHalf, is_flatpak, names::OwnedUniqueName, Message}; use super::{ random_ascii, sasl_auth_id, AuthMechanism, Authenticated, BoxedSplit, Command, Common, Cookie, Error, Handshake, OwnedGuid, Result, Str, }; /// A representation of an in-progress handshake, client-side /// /// This struct is an async-compatible representation of the initial handshake that must be /// performed before a D-Bus connection can be used. #[derive(Debug)] pub struct Client { common: Common, server_guid: Option, bus: bool, } impl Client { /// Start a handshake on this client socket pub fn new( socket: BoxedSplit, mechanisms: Option>, server_guid: Option, bus: bool, ) -> Client { let mechanisms = mechanisms.unwrap_or_else(|| { let mut mechanisms = VecDeque::new(); mechanisms.push_back(AuthMechanism::External); mechanisms.push_back(AuthMechanism::Cookie); mechanisms.push_back(AuthMechanism::Anonymous); mechanisms }); Client { common: Common::new(socket, mechanisms), server_guid, bus, } } /// Respond to a cookie authentication challenge from the server. /// /// Returns the next command to send to the server. async fn handle_cookie_challenge(&mut self, data: Vec) -> Result { let context = std::str::from_utf8(&data) .map_err(|_| Error::Handshake("Cookie context was not valid UTF-8".into()))?; let mut split = context.split_ascii_whitespace(); let context = split .next() .ok_or_else(|| Error::Handshake("Missing cookie context name".into()))?; let context = Str::from(context).try_into()?; let id = split .next() .ok_or_else(|| Error::Handshake("Missing cookie ID".into()))?; let id = id .parse() .map_err(|e| Error::Handshake(format!("Invalid cookie ID `{id}`: {e}")))?; let server_challenge = split .next() .ok_or_else(|| Error::Handshake("Missing cookie challenge".into()))?; let cookie = Cookie::lookup(&context, id).await?; let cookie = cookie.cookie(); let client_challenge = random_ascii(16); let sec = format!("{server_challenge}:{client_challenge}:{cookie}"); let sha1 = hex::encode(Sha1::digest(sec)); let data = format!("{client_challenge} {sha1}").into_bytes(); Ok(Command::Data(Some(data))) } fn set_guid(&mut self, guid: OwnedGuid) -> Result<()> { match &self.server_guid { Some(server_guid) if *server_guid != guid => { return Err(Error::Handshake(format!( "Server GUID mismatch: expected {server_guid}, got {guid}", ))); } Some(_) => (), None => self.server_guid = Some(guid), } Ok(()) } // The dbus daemon on some platforms requires sending the zero byte as a // separate message with SCM_CREDS. #[instrument(skip(self))] #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] async fn send_zero_byte(&mut self) -> Result<()> { let write = self.common.socket_mut().write_mut(); let written = match write.send_zero_byte().await.map_err(|e| { Error::Handshake(format!("Could not send zero byte with credentials: {}", e)) })? { // This likely means that the socket type is unable to send SCM_CREDS. // Let's try to send the 0 byte as a regular message. None => write.sendmsg(&[0], &[]).await?, Some(n) => n, }; if written != 1 { return Err(Error::Handshake( "Could not send zero byte with credentials".to_string(), )); } Ok(()) } /// Perform the authentication handshake with the server. /// /// In case of cookie auth, it returns the challenge response to send to the server, so it can /// be batched with rest of the commands. #[instrument(skip(self))] async fn authenticate(&mut self) -> Result> { loop { let mechanism = self.common.next_mechanism()?; trace!("Trying {mechanism} mechanism"); let auth_cmd = match mechanism { AuthMechanism::Anonymous => Command::Auth(Some(mechanism), Some("zbus".into())), AuthMechanism::External => { Command::Auth(Some(mechanism), Some(sasl_auth_id()?.into_bytes())) } AuthMechanism::Cookie => Command::Auth( Some(AuthMechanism::Cookie), Some(sasl_auth_id()?.into_bytes()), ), }; self.common.write_command(auth_cmd).await?; match self.common.read_command().await? { Command::Ok(guid) => { trace!("Received OK from server"); self.set_guid(guid)?; return Ok(None); } Command::Data(data) if mechanism == AuthMechanism::Cookie => { let data = data.ok_or_else(|| { Error::Handshake("Received DATA with no data from server".into()) })?; trace!("Received cookie challenge from server"); let response = self.handle_cookie_challenge(data).await?; return Ok(Some(response)); } Command::Rejected(_) => debug!("{mechanism} rejected by the server"), Command::Error(e) => debug!("Received error from server: {e}"), cmd => { return Err(Error::Handshake(format!( "Unexpected command from server: {cmd}" ))) } } } } /// Sends out all commands after authentication. /// /// This includes the challenge response for cookie auth, if any and returns the number of /// responses expected from the server. #[instrument(skip(self))] async fn send_secondary_commands( &mut self, challenge_response: Option, ) -> Result { let mut commands = Vec::with_capacity(4); if let Some(response) = challenge_response { commands.push(response); } let can_pass_fd = self.common.socket_mut().read_mut().can_pass_unix_fd(); if can_pass_fd { // xdg-dbus-proxy can't handle pipelining, hence this special handling. // FIXME: Remove this as soon as flatpak is fixed and fix is available in major distros. // See https://github.com/flatpak/xdg-dbus-proxy/issues/21 if is_flatpak() { self.common.write_command(Command::NegotiateUnixFD).await?; match self.common.read_command().await? { Command::AgreeUnixFD => self.common.set_cap_unix_fd(true), Command::Error(e) => warn!("UNIX file descriptor passing rejected: {e}"), cmd => { return Err(Error::Handshake(format!( "Unexpected command from server: {cmd}" ))) } } } else { commands.push(Command::NegotiateUnixFD); } }; commands.push(Command::Begin); let hello_method = if self.bus { Some(create_hello_method_call()) } else { None }; self.common .write_commands(&commands, hello_method.as_ref().map(|m| &**m.data())) .await?; // Server replies to all commands except `BEGIN`. Ok(commands.len() - 1) } #[instrument(skip(self))] async fn receive_secondary_responses(&mut self, expected_n_responses: usize) -> Result<()> { for response in self.common.read_commands(expected_n_responses).await? { match response { Command::Ok(guid) => { trace!("Received OK from server"); self.set_guid(guid)?; } Command::AgreeUnixFD => self.common.set_cap_unix_fd(true), Command::Error(e) => warn!("UNIX file descriptor passing rejected: {e}"), // This also covers "REJECTED", which would mean that the server has rejected the // authentication challenge response (likely cookie) since it already agreed to the // mechanism. Theoretically we should be just trying the next auth mechanism but // this most likely means something is very wrong and we're already too deep into // the handshake to recover. cmd => { return Err(Error::Handshake(format!( "Unexpected command from server: {cmd}" ))) } } } Ok(()) } } #[async_trait] impl Handshake for Client { #[instrument(skip(self))] async fn perform(mut self) -> Result { trace!("Initializing"); #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] self.send_zero_byte().await?; let challenge_response = self.authenticate().await?; let expected_n_responses = self.send_secondary_commands(challenge_response).await?; if expected_n_responses > 0 { self.receive_secondary_responses(expected_n_responses) .await?; } trace!("Handshake done"); #[cfg(unix)] let (socket, mut recv_buffer, received_fds, cap_unix_fd, _) = self.common.into_components(); #[cfg(not(unix))] let (socket, mut recv_buffer, _, _) = self.common.into_components(); let (mut read, write) = socket.take(); // If we're a bus connection, we need to read the unique name from `Hello` response. let unique_name = if self.bus { let unique_name = receive_hello_response(&mut read, &mut recv_buffer).await?; Some(unique_name) } else { None }; Ok(Authenticated { socket_write: write, socket_read: Some(read), server_guid: self.server_guid.unwrap(), #[cfg(unix)] cap_unix_fd, already_received_bytes: recv_buffer, #[cfg(unix)] already_received_fds: received_fds, unique_name, }) } } fn create_hello_method_call() -> Message { Message::method("/org/freedesktop/DBus", "Hello") .unwrap() .destination("org.freedesktop.DBus") .unwrap() .interface("org.freedesktop.DBus") .unwrap() .build(&()) .unwrap() } async fn receive_hello_response( read: &mut Box, recv_buffer: &mut Vec, ) -> Result { use crate::message::Type; let reply = read .receive_message( 0, recv_buffer, #[cfg(unix)] &mut vec![], ) .await?; match reply.message_type() { Type::MethodReturn => reply.body().deserialize(), Type::Error => Err(Error::from(reply)), m => Err(Error::Handshake(format!("Unexpected messgage `{m:?}`"))), } } zbus-4.3.1/src/connection/handshake/command.rs000064400000000000000000000071101046102023000174530ustar 00000000000000use std::{fmt, str::FromStr}; use crate::{AuthMechanism, Error, Guid, OwnedGuid, Result}; // The plain-text SASL profile authentication protocol described here: // // // These are all the known commands, which can be parsed from or serialized to text. #[derive(Debug)] #[allow(clippy::upper_case_acronyms)] pub(super) enum Command { Auth(Option, Option>), Cancel, Begin, Data(Option>), Error(String), NegotiateUnixFD, Rejected(Vec), Ok(OwnedGuid), AgreeUnixFD, } impl From<&Command> for Vec { fn from(c: &Command) -> Self { c.to_string().into() } } impl fmt::Display for Command { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Command::Auth(mech, resp) => match (mech, resp) { (Some(mech), Some(resp)) => write!(f, "AUTH {mech} {}", hex::encode(resp)), (Some(mech), None) => write!(f, "AUTH {mech}"), _ => write!(f, "AUTH"), }, Command::Cancel => write!(f, "CANCEL"), Command::Begin => write!(f, "BEGIN"), Command::Data(data) => match data { None => write!(f, "DATA"), Some(data) => write!(f, "DATA {}", hex::encode(data)), }, Command::Error(expl) => write!(f, "ERROR {expl}"), Command::NegotiateUnixFD => write!(f, "NEGOTIATE_UNIX_FD"), Command::Rejected(mechs) => { write!( f, "REJECTED {}", mechs .iter() .map(|m| m.to_string()) .collect::>() .join(" ") ) } Command::Ok(guid) => write!(f, "OK {guid}"), Command::AgreeUnixFD => write!(f, "AGREE_UNIX_FD"), } } } impl FromStr for Command { type Err = Error; fn from_str(s: &str) -> Result { let mut words = s.split_ascii_whitespace(); let cmd = match words.next() { Some("AUTH") => { let mech = if let Some(m) = words.next() { Some(m.parse()?) } else { None }; let resp = match words.next() { Some(resp) => Some(hex::decode(resp)?), None => None, }; Command::Auth(mech, resp) } Some("CANCEL") => Command::Cancel, Some("BEGIN") => Command::Begin, Some("DATA") => { let data = match words.next() { Some(data) => Some(hex::decode(data)?), None => None, }; Command::Data(data) } Some("ERROR") => Command::Error(s.into()), Some("NEGOTIATE_UNIX_FD") => Command::NegotiateUnixFD, Some("REJECTED") => { let mechs = words.map(|m| m.parse()).collect::>()?; Command::Rejected(mechs) } Some("OK") => { let guid = words .next() .ok_or_else(|| Error::Handshake("Missing OK server GUID!".into()))?; Command::Ok(Guid::from_str(guid)?.into()) } Some("AGREE_UNIX_FD") => Command::AgreeUnixFD, _ => return Err(Error::Handshake(format!("Unknown command: {s}"))), }; Ok(cmd) } } zbus-4.3.1/src/connection/handshake/common.rs000064400000000000000000000140171046102023000173310ustar 00000000000000use std::collections::VecDeque; use tracing::{instrument, trace}; use super::{AuthMechanism, BoxedSplit, Command}; use crate::{Error, Result}; // Common code for the client and server side of the handshake. #[derive(Debug)] pub(super) struct Common { socket: BoxedSplit, recv_buffer: Vec, #[cfg(unix)] received_fds: Vec, cap_unix_fd: bool, // the current AUTH mechanism is front, ordered by priority mechanisms: VecDeque, first_command: bool, } impl Common { /// Start a handshake on this client socket pub fn new(socket: BoxedSplit, mechanisms: VecDeque) -> Self { Self { socket, recv_buffer: Vec::new(), #[cfg(unix)] received_fds: Vec::new(), cap_unix_fd: false, mechanisms, first_command: true, } } #[cfg(all(unix, feature = "p2p"))] pub fn socket(&self) -> &BoxedSplit { &self.socket } pub fn socket_mut(&mut self) -> &mut BoxedSplit { &mut self.socket } pub fn set_cap_unix_fd(&mut self, cap_unix_fd: bool) { self.cap_unix_fd = cap_unix_fd; } #[cfg(feature = "p2p")] pub fn mechanisms(&self) -> &VecDeque { &self.mechanisms } pub fn into_components(self) -> IntoComponentsReturn { ( self.socket, self.recv_buffer, #[cfg(unix)] self.received_fds, self.cap_unix_fd, self.mechanisms, ) } #[instrument(skip(self))] pub async fn write_command(&mut self, command: Command) -> Result<()> { self.write_commands(&[command], None).await } #[instrument(skip(self))] pub async fn write_commands( &mut self, commands: &[Command], extra_bytes: Option<&[u8]>, ) -> Result<()> { let mut send_buffer = commands .iter() .map(Vec::::from) .fold(vec![], |mut acc, mut c| { if self.first_command { // The first command is sent by the client so we can assume it's the client. self.first_command = false; // leading 0 is sent separately for `freebsd` and `dragonfly`. #[cfg(not(any(target_os = "freebsd", target_os = "dragonfly")))] acc.push(b'\0'); } acc.append(&mut c); acc.extend_from_slice(b"\r\n"); acc }); if let Some(extra_bytes) = extra_bytes { send_buffer.extend_from_slice(extra_bytes); } while !send_buffer.is_empty() { let written = self .socket .write_mut() .sendmsg( &send_buffer, #[cfg(unix)] &[], ) .await?; send_buffer.drain(..written); } trace!("Wrote all commands"); Ok(()) } #[instrument(skip(self))] pub async fn read_command(&mut self) -> Result { self.read_commands(1) .await .map(|cmds| cmds.into_iter().next().unwrap()) } #[instrument(skip(self))] pub async fn read_commands(&mut self, n_commands: usize) -> Result> { let mut commands = Vec::with_capacity(n_commands); let mut n_received_commands = 0; 'outer: loop { while let Some(lf_index) = self.recv_buffer.iter().position(|b| *b == b'\n') { if self.recv_buffer[lf_index - 1] != b'\r' { return Err(Error::Handshake("Invalid line ending in handshake".into())); } #[allow(unused_mut)] let mut start_index = 0; if self.first_command { // The first command is sent by the client so we can assume it's the server. self.first_command = false; if self.recv_buffer[0] != b'\0' { return Err(Error::Handshake( "First client byte is not NUL!".to_string(), )); } start_index = 1; }; let line_bytes = self.recv_buffer.drain(..=lf_index); let line = std::str::from_utf8(&line_bytes.as_slice()[start_index..]) .map_err(|e| Error::Handshake(e.to_string()))?; trace!("Reading {line}"); commands.push(line.parse()?); n_received_commands += 1; if n_received_commands == n_commands { break 'outer; } } let mut buf = vec![0; 1024]; let res = self.socket.read_mut().recvmsg(&mut buf).await?; let read = { #[cfg(unix)] { let (read, fds) = res; if !fds.is_empty() { // Most likely belonging to the messages already received. self.received_fds.extend(fds); } read } #[cfg(not(unix))] { res } }; if read == 0 { return Err(Error::Handshake("Unexpected EOF during handshake".into())); } self.recv_buffer.extend(&buf[..read]); } Ok(commands) } pub fn next_mechanism(&mut self) -> Result { self.mechanisms .pop_front() .ok_or_else(|| Error::Handshake("Exhausted available AUTH mechanisms".into())) } } #[cfg(unix)] type IntoComponentsReturn = ( BoxedSplit, Vec, Vec, bool, VecDeque, ); #[cfg(not(unix))] type IntoComponentsReturn = (BoxedSplit, Vec, bool, VecDeque); zbus-4.3.1/src/connection/handshake/cookies.rs000064400000000000000000000105031046102023000174710ustar 00000000000000use std::{fmt, path::PathBuf}; use futures_util::StreamExt; use tracing::trace; use xdg_home::home_dir; use zvariant::Str; use crate::{file::FileLines, Error, Result}; #[derive(Debug)] pub(super) struct Cookie { id: usize, cookie: String, } impl Cookie { #[cfg(feature = "p2p")] pub fn id(&self) -> usize { self.id } pub fn cookie(&self) -> &str { &self.cookie } fn keyring_path() -> Result { let mut path = home_dir() .ok_or_else(|| Error::Handshake("Failed to determine home directory".into()))?; path.push(".dbus-keyrings"); Ok(path) } async fn read_keyring(context: &CookieContext<'_>) -> Result> { let mut path = Cookie::keyring_path()?; #[cfg(unix)] { use std::os::unix::fs::PermissionsExt; let perms = crate::file::metadata(&path).await?.permissions().mode(); if perms & 0o066 != 0 { return Err(Error::Handshake( "DBus keyring has invalid permissions".into(), )); } } #[cfg(not(unix))] { // FIXME: add code to check directory permissions } path.push(&*context.0); trace!("Reading keyring {:?}", path); let mut lines = FileLines::open(&path).await?.enumerate(); let mut cookies = vec![]; while let Some((n, line)) = lines.next().await { let line = line?; let mut split = line.split_whitespace(); let id = split .next() .ok_or_else(|| { Error::Handshake(format!( "DBus cookie `{}` missing ID at line {n}", path.display(), )) })? .parse() .map_err(|e| { Error::Handshake(format!( "Failed to parse cookie ID in file `{}` at line {n}: {e}", path.display(), )) })?; let _ = split.next().ok_or_else(|| { Error::Handshake(format!( "DBus cookie `{}` missing creation time at line {n}", path.display(), )) })?; let cookie = split .next() .ok_or_else(|| { Error::Handshake(format!( "DBus cookie `{}` missing cookie data at line {}", path.to_str().unwrap(), n )) })? .to_string(); cookies.push(Cookie { id, cookie }) } trace!("Loaded keyring {:?}", cookies); Ok(cookies) } pub async fn lookup(context: &CookieContext<'_>, id: usize) -> Result { let keyring = Self::read_keyring(context).await?; keyring .into_iter() .find(|c| c.id == id) .ok_or_else(|| Error::Handshake(format!("DBus cookie ID {id} not found"))) } #[cfg(feature = "p2p")] pub async fn first(context: &CookieContext<'_>) -> Result { let keyring = Self::read_keyring(context).await?; keyring .into_iter() .next() .ok_or_else(|| Error::Handshake("No cookies available".into())) } } #[derive(Debug)] pub struct CookieContext<'c>(Str<'c>); impl<'c> TryFrom> for CookieContext<'c> { type Error = Error; fn try_from(value: Str<'c>) -> Result { if value.is_empty() { return Err(Error::Handshake("Empty cookie context".into())); } else if !value.is_ascii() || value.contains(['/', '\\', ' ', '\n', '\r', '\t', '.']) { return Err(Error::Handshake( "Invalid characters in cookie context".into(), )); } Ok(Self(value)) } } impl fmt::Display for CookieContext<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } impl Default for CookieContext<'_> { fn default() -> Self { Self(Str::from_static("org_freedesktop_general")) } } impl From for Error { fn from(e: hex::FromHexError) -> Self { Error::Handshake(format!("Invalid hexcode: {e}")) } } zbus-4.3.1/src/connection/handshake/mod.rs000064400000000000000000000207211046102023000166170ustar 00000000000000mod auth_mechanism; mod client; mod command; mod common; mod cookies; #[cfg(feature = "p2p")] mod server; use async_trait::async_trait; #[cfg(unix)] use nix::unistd::Uid; use std::{collections::VecDeque, fmt::Debug}; use zbus_names::OwnedUniqueName; use zvariant::Str; #[cfg(windows)] use crate::win32; use crate::{Error, OwnedGuid, Result}; use super::socket::{BoxedSplit, ReadHalf, WriteHalf}; pub use auth_mechanism::AuthMechanism; use client::Client; use command::Command; use common::Common; use cookies::Cookie; pub(crate) use cookies::CookieContext; #[cfg(feature = "p2p")] use server::Server; /// The result of a finalized handshake /// /// The result of a finalized [`ClientHandshake`] or [`ServerHandshake`]. /// /// [`ClientHandshake`]: struct.ClientHandshake.html /// [`ServerHandshake`]: struct.ServerHandshake.html #[derive(Debug)] pub struct Authenticated { pub(crate) socket_write: Box, /// The server Guid pub(crate) server_guid: OwnedGuid, /// Whether file descriptor passing has been accepted by both sides #[cfg(unix)] pub(crate) cap_unix_fd: bool, pub(crate) socket_read: Option>, pub(crate) already_received_bytes: Vec, #[cfg(unix)] pub(crate) already_received_fds: Vec, pub(crate) unique_name: Option, } impl Authenticated { /// Create a client-side `Authenticated` for the given `socket`. pub async fn client( socket: BoxedSplit, server_guid: Option, mechanisms: Option>, bus: bool, ) -> Result { Client::new(socket, mechanisms, server_guid, bus) .perform() .await } /// Create a server-side `Authenticated` for the given `socket`. /// /// The function takes `client_uid` on Unix only. On Windows, it takes `client_sid` instead. #[cfg(feature = "p2p")] pub async fn server( socket: BoxedSplit, guid: OwnedGuid, #[cfg(unix)] client_uid: Option, #[cfg(windows)] client_sid: Option, auth_mechanisms: Option>, cookie_id: Option, cookie_context: CookieContext<'_>, unique_name: Option, ) -> Result { Server::new( socket, guid, #[cfg(unix)] client_uid, #[cfg(windows)] client_sid, auth_mechanisms, cookie_id, cookie_context, unique_name, )? .perform() .await } } #[async_trait] pub trait Handshake { /// Perform the handshake. /// /// On a successful handshake, you get an `Authenticated`. If you need to send a Bus Hello, /// this remains to be done. async fn perform(mut self) -> Result; } fn random_ascii(len: usize) -> String { use rand::{distributions::Alphanumeric, thread_rng, Rng}; use std::iter; let mut rng = thread_rng(); iter::repeat(()) .map(|()| rng.sample(Alphanumeric)) .map(char::from) .take(len) .collect() } fn sasl_auth_id() -> Result { let id = { #[cfg(unix)] { Uid::effective().to_string() } #[cfg(windows)] { win32::ProcessToken::open(None)?.sid()? } }; Ok(id) } #[cfg(feature = "p2p")] #[cfg(unix)] #[cfg(test)] mod tests { use futures_util::future::join; #[cfg(not(feature = "tokio"))] use futures_util::io::{AsyncWrite, AsyncWriteExt}; use ntest::timeout; #[cfg(not(feature = "tokio"))] use std::os::unix::net::UnixStream; use test_log::test; #[cfg(feature = "tokio")] use tokio::{ io::{AsyncWrite, AsyncWriteExt}, net::UnixStream, }; use super::*; use crate::{Guid, Socket}; fn create_async_socket_pair() -> (impl AsyncWrite + Socket, impl AsyncWrite + Socket) { // Tokio needs us to call the sync function from async context. :shrug: let (p0, p1) = crate::utils::block_on(async { UnixStream::pair().unwrap() }); // initialize both handshakes #[cfg(not(feature = "tokio"))] let (p0, p1) = { p0.set_nonblocking(true).unwrap(); p1.set_nonblocking(true).unwrap(); ( async_io::Async::new(p0).unwrap(), async_io::Async::new(p1).unwrap(), ) }; (p0, p1) } #[test] #[timeout(15000)] fn handshake() { let (p0, p1) = create_async_socket_pair(); let guid = OwnedGuid::from(Guid::generate()); let client = Client::new(p0.into(), None, Some(guid.clone()), false); let server = Server::new( p1.into(), guid, Some(Uid::effective().into()), None, None, CookieContext::default(), None, ) .unwrap(); // proceed to the handshakes let (client, server) = crate::utils::block_on(join( async move { client.perform().await.unwrap() }, async move { server.perform().await.unwrap() }, )); assert_eq!(client.server_guid, server.server_guid); assert_eq!(client.cap_unix_fd, server.cap_unix_fd); } #[test] #[timeout(15000)] fn pipelined_handshake() { let (mut p0, p1) = create_async_socket_pair(); let server = Server::new( p1.into(), Guid::generate().into(), Some(Uid::effective().into()), None, None, CookieContext::default(), None, ) .unwrap(); crate::utils::block_on( p0.write_all( format!( "\0AUTH EXTERNAL {}\r\nNEGOTIATE_UNIX_FD\r\nBEGIN\r\n", hex::encode(sasl_auth_id().unwrap()) ) .as_bytes(), ), ) .unwrap(); let server = crate::utils::block_on(server.perform()).unwrap(); assert!(server.cap_unix_fd); } #[test] #[timeout(15000)] fn separate_external_data() { let (mut p0, p1) = create_async_socket_pair(); let server = Server::new( p1.into(), Guid::generate().into(), Some(Uid::effective().into()), None, None, CookieContext::default(), None, ) .unwrap(); crate::utils::block_on( p0.write_all( format!( "\0AUTH EXTERNAL\r\nDATA {}\r\nBEGIN\r\n", hex::encode(sasl_auth_id().unwrap()) ) .as_bytes(), ), ) .unwrap(); crate::utils::block_on(server.perform()).unwrap(); } #[test] #[timeout(15000)] fn missing_external_data() { let (mut p0, p1) = create_async_socket_pair(); let server = Server::new( p1.into(), Guid::generate().into(), Some(Uid::effective().into()), None, None, CookieContext::default(), None, ) .unwrap(); crate::utils::block_on(p0.write_all(b"\0AUTH EXTERNAL\r\nDATA\r\nBEGIN\r\n")).unwrap(); crate::utils::block_on(server.perform()).unwrap(); } #[test] #[timeout(15000)] fn anonymous_handshake() { let (mut p0, p1) = create_async_socket_pair(); let server = Server::new( p1.into(), Guid::generate().into(), Some(Uid::effective().into()), Some(vec![AuthMechanism::Anonymous].into()), None, CookieContext::default(), None, ) .unwrap(); crate::utils::block_on(p0.write_all(b"\0AUTH ANONYMOUS abcd\r\nBEGIN\r\n")).unwrap(); crate::utils::block_on(server.perform()).unwrap(); } #[test] #[timeout(15000)] fn separate_anonymous_data() { let (mut p0, p1) = create_async_socket_pair(); let server = Server::new( p1.into(), Guid::generate().into(), Some(Uid::effective().into()), Some(vec![AuthMechanism::Anonymous].into()), None, CookieContext::default(), None, ) .unwrap(); crate::utils::block_on(p0.write_all(b"\0AUTH ANONYMOUS\r\nDATA abcd\r\nBEGIN\r\n")) .unwrap(); crate::utils::block_on(server.perform()).unwrap(); } } zbus-4.3.1/src/connection/handshake/server.rs000064400000000000000000000261641046102023000173550ustar 00000000000000use async_trait::async_trait; use sha1::{Digest, Sha1}; use std::collections::VecDeque; use tracing::{instrument, trace}; use crate::names::OwnedUniqueName; use super::{ random_ascii, sasl_auth_id, AuthMechanism, Authenticated, BoxedSplit, Command, Common, Cookie, CookieContext, Error, Handshake, OwnedGuid, Result, }; /* * Server-side handshake logic */ #[derive(Debug, PartialEq)] #[allow(clippy::upper_case_acronyms)] enum ServerHandshakeStep { WaitingForAuth, WaitingForData(AuthMechanism), WaitingForBegin, Done, } /// A representation of an in-progress handshake, server-side /// /// This would typically be used to implement a D-Bus broker, or in the context of a P2P connection. #[derive(Debug)] pub struct Server<'s> { common: Common, step: ServerHandshakeStep, guid: OwnedGuid, #[cfg(unix)] client_uid: Option, #[cfg(windows)] client_sid: Option, cookie_id: Option, cookie_context: CookieContext<'s>, unique_name: Option, } impl<'s> Server<'s> { pub fn new( socket: BoxedSplit, guid: OwnedGuid, #[cfg(unix)] client_uid: Option, #[cfg(windows)] client_sid: Option, mechanisms: Option>, cookie_id: Option, cookie_context: CookieContext<'s>, unique_name: Option, ) -> Result> { let mechanisms = match mechanisms { Some(mechanisms) => mechanisms, None => { let mut mechanisms = VecDeque::new(); mechanisms.push_back(AuthMechanism::External); mechanisms } }; Ok(Server { common: Common::new(socket, mechanisms), step: ServerHandshakeStep::WaitingForAuth, #[cfg(unix)] client_uid, #[cfg(windows)] client_sid, cookie_id, cookie_context, guid, unique_name, }) } #[instrument(skip(self))] async fn auth_ok(&mut self) -> Result<()> { let guid = self.guid.clone(); let cmd = Command::Ok(guid); trace!("Sending authentication OK"); self.common.write_command(cmd).await?; self.step = ServerHandshakeStep::WaitingForBegin; Ok(()) } async fn check_external_auth(&mut self, sasl_id: &[u8]) -> Result<()> { let auth_ok = { let id = std::str::from_utf8(sasl_id) .map_err(|e| Error::Handshake(format!("Invalid ID: {e}")))?; #[cfg(unix)] { let uid = id .parse::() .map_err(|e| Error::Handshake(format!("Invalid UID: {e}")))?; self.client_uid.map(|u| u == uid).unwrap_or(false) } #[cfg(windows)] { self.client_sid.as_ref().map(|u| u == id).unwrap_or(false) } }; if auth_ok { self.auth_ok().await } else { self.rejected_error().await } } #[instrument(skip(self))] async fn check_cookie_auth(&mut self, sasl_id: &[u8]) -> Result<()> { let cookie = match self.cookie_id { Some(cookie_id) => Cookie::lookup(&self.cookie_context, cookie_id).await?, None => Cookie::first(&self.cookie_context).await?, }; let id = std::str::from_utf8(sasl_id) .map_err(|e| Error::Handshake(format!("Invalid ID: {e}")))?; if sasl_auth_id()? != id { // While the spec will make you believe that DBUS_COOKIE_SHA1 can be used to // authenticate any user, it is not even possible (or correct) for the server to manage // contents in random users' home directories. // // The dbus reference implementation also has the same limitation/behavior. self.rejected_error().await?; return Ok(()); } let server_challenge = random_ascii(16); let data = format!("{} {} {server_challenge}", self.cookie_context, cookie.id()); let cmd = Command::Data(Some(data.into_bytes())); trace!("Sending DBUS_COOKIE_SHA1 authentication challenge"); self.common.write_command(cmd).await?; let auth_data = match self.common.read_command().await? { Command::Data(data) => data, _ => None, }; let auth_data = auth_data.ok_or_else(|| { Error::Handshake("Expected DBUS_COOKIE_SHA1 authentication challenge response".into()) })?; let client_auth = std::str::from_utf8(&auth_data) .map_err(|e| Error::Handshake(format!("Invalid COOKIE authentication data: {e}")))?; let mut split = client_auth.split_ascii_whitespace(); let client_challenge = split .next() .ok_or_else(|| Error::Handshake("Missing cookie challenge".into()))?; let client_sha1 = split .next() .ok_or_else(|| Error::Handshake("Missing client cookie data".into()))?; let sec = format!("{server_challenge}:{client_challenge}:{}", cookie.cookie()); let sha1 = hex::encode(Sha1::digest(sec)); if sha1 == client_sha1 { self.auth_ok().await } else { self.rejected_error().await } } #[instrument(skip(self))] async fn unsupported_command_error(&mut self) -> Result<()> { let cmd = Command::Error("Unsupported or misplaced command".to_string()); self.common.write_command(cmd).await?; Ok(()) } #[instrument(skip(self))] async fn rejected_error(&mut self) -> Result<()> { let mechanisms = self.common.mechanisms().iter().cloned().collect(); let cmd = Command::Rejected(mechanisms); trace!("Sending authentication error"); self.common.write_command(cmd).await?; self.step = ServerHandshakeStep::WaitingForAuth; Ok(()) } /// Perform the next step in the handshake. #[instrument(skip(self))] async fn next_step(&mut self) -> Result { match self.step { ServerHandshakeStep::WaitingForAuth => self.handle_auth().await?, ServerHandshakeStep::WaitingForData(mech) => self.handle_auth_data(mech).await?, ServerHandshakeStep::WaitingForBegin => self.finalize().await?, ServerHandshakeStep::Done => return Ok(true), } Ok(false) } /// Handle the authentication step of the handshake. #[instrument(skip(self))] async fn handle_auth(&mut self) -> Result<()> { assert_eq!(self.step, ServerHandshakeStep::WaitingForAuth); trace!("Waiting for authentication"); let reply = self.common.read_command().await?; match reply { Command::Auth(mech, resp) => { let mech = mech.filter(|m| self.common.mechanisms().contains(m)); match (mech, &resp) { (Some(mech), None) => { trace!("Sending data request"); self.common.write_command(Command::Data(None)).await?; self.step = ServerHandshakeStep::WaitingForData(mech); } (Some(AuthMechanism::Anonymous), Some(_)) => { self.auth_ok().await?; } (Some(AuthMechanism::External), Some(sasl_id)) => { self.check_external_auth(sasl_id).await?; } (Some(AuthMechanism::Cookie), Some(sasl_id)) => { self.check_cookie_auth(sasl_id).await?; } _ => self.rejected_error().await?, } } Command::Cancel | Command::Error(_) => { trace!("Received CANCEL or ERROR command from the client"); self.rejected_error().await?; } _ => self.unsupported_command_error().await?, } Ok(()) } /// Handle the authentication data receiving step of the handshake. #[instrument(skip(self))] async fn handle_auth_data(&mut self, mech: AuthMechanism) -> Result<()> { assert!(matches!(self.step, ServerHandshakeStep::WaitingForData(_))); trace!("Waiting for authentication data"); let reply = self.common.read_command().await?; match (mech, reply) { (AuthMechanism::External, Command::Data(None)) => self.auth_ok().await?, (AuthMechanism::External, Command::Data(Some(data))) => { self.check_external_auth(&data).await?; } (AuthMechanism::Anonymous, Command::Data(_)) => self.auth_ok().await?, (_, Command::Data(_)) => self.rejected_error().await?, (_, _) => self.unsupported_command_error().await?, } Ok(()) } /// Finalize the handshake. #[instrument(skip(self))] async fn finalize(&mut self) -> Result<()> { assert_eq!(self.step, ServerHandshakeStep::WaitingForBegin); trace!("Waiting for Begin command from the client"); let reply = self.common.read_command().await?; match reply { Command::Begin => { trace!("Received Begin command from the client"); self.step = ServerHandshakeStep::Done; } Command::Cancel | Command::Error(_) => { trace!("Received CANCEL or ERROR command from the client"); self.rejected_error().await?; } #[cfg(unix)] Command::NegotiateUnixFD => { trace!("Received NEGOTIATE_UNIX_FD command from the client"); if self.common.socket().read().can_pass_unix_fd() { self.common.set_cap_unix_fd(true); trace!("Sending AGREE_UNIX_FD to the client"); self.common.write_command(Command::AgreeUnixFD).await?; } else { trace!("FD transmission not possible on this socket type. Rejecting.."); let cmd = Command::Error("FD-passing not possible on this socket type".to_string()); self.common.write_command(cmd).await?; } } _ => self.unsupported_command_error().await?, } Ok(()) } } #[async_trait] impl Handshake for Server<'_> { #[instrument(skip(self))] async fn perform(mut self) -> Result { while !self.next_step().await? {} trace!("Handshake done"); #[cfg(unix)] let (socket, recv_buffer, received_fds, cap_unix_fd, _) = self.common.into_components(); #[cfg(not(unix))] let (socket, recv_buffer, _, _) = self.common.into_components(); let (read, write) = socket.take(); Ok(Authenticated { socket_write: write, socket_read: Some(read), server_guid: self.guid, #[cfg(unix)] cap_unix_fd, already_received_bytes: recv_buffer, #[cfg(unix)] already_received_fds: received_fds, unique_name: self.unique_name, }) } } zbus-4.3.1/src/connection/mod.rs000064400000000000000000001740211046102023000146740ustar 00000000000000//! Connection API. use async_broadcast::{broadcast, InactiveReceiver, Receiver, Sender as Broadcaster}; use enumflags2::BitFlags; use event_listener::{Event, EventListener}; use ordered_stream::{OrderedFuture, OrderedStream, PollResult}; use static_assertions::assert_impl_all; use std::{ collections::HashMap, io::{self, ErrorKind}, num::NonZeroU32, ops::Deref, pin::Pin, sync::{Arc, OnceLock, Weak}, task::{Context, Poll}, }; use tracing::{debug, info_span, instrument, trace, trace_span, warn, Instrument}; use zbus_names::{BusName, ErrorName, InterfaceName, MemberName, OwnedUniqueName, WellKnownName}; use zvariant::ObjectPath; use futures_core::Future; use futures_util::StreamExt; use crate::{ async_lock::{Mutex, Semaphore, SemaphorePermit}, blocking, fdo::{self, ConnectionCredentials, RequestNameFlags, RequestNameReply}, is_flatpak, message::{Flags, Message, Type}, proxy::CacheProperties, DBusError, Error, Executor, MatchRule, MessageStream, ObjectServer, OwnedGuid, OwnedMatchRule, Result, Task, }; mod builder; pub use builder::Builder; pub mod socket; pub use socket::Socket; mod socket_reader; use socket_reader::SocketReader; pub(crate) mod handshake; use handshake::Authenticated; const DEFAULT_MAX_QUEUED: usize = 64; const DEFAULT_MAX_METHOD_RETURN_QUEUED: usize = 8; /// Inner state shared by Connection and WeakConnection #[derive(Debug)] pub(crate) struct ConnectionInner { server_guid: OwnedGuid, #[cfg(unix)] cap_unix_fd: bool, #[cfg(feature = "p2p")] bus_conn: bool, unique_name: OnceLock, registered_names: Mutex, NameStatus>>, activity_event: Arc, socket_write: Mutex>, // Our executor executor: Executor<'static>, // Socket reader task #[allow(unused)] socket_reader_task: OnceLock>, pub(crate) msg_receiver: InactiveReceiver>, pub(crate) method_return_receiver: InactiveReceiver>, msg_senders: Arc, MsgBroadcaster>>>, subscriptions: Mutex, object_server: OnceLock, object_server_dispatch_task: OnceLock>, } type Subscriptions = HashMap>)>; pub(crate) type MsgBroadcaster = Broadcaster>; /// A D-Bus connection. /// /// A connection to a D-Bus bus, or a direct peer. /// /// Once created, the connection is authenticated and negotiated and messages can be sent or /// received, such as [method calls] or [signals]. /// /// For higher-level message handling (typed functions, introspection, documentation reasons etc), /// it is recommended to wrap the low-level D-Bus messages into Rust functions with the /// [`proxy`] and [`interface`] macros instead of doing it directly on a `Connection`. /// /// Typically, a connection is made to the session bus with [`Connection::session`], or to the /// system bus with [`Connection::system`]. Then the connection is used with [`crate::Proxy`] /// instances or the on-demand [`ObjectServer`] instance that can be accessed through /// [`Connection::object_server`]. /// /// `Connection` implements [`Clone`] and cloning it is a very cheap operation, as the underlying /// data is not cloned. This makes it very convenient to share the connection between different /// parts of your code. `Connection` also implements [`std::marker::Sync`] and [`std::marker::Send`] /// so you can send and share a connection instance across threads as well. /// /// `Connection` keeps internal queues of incoming message. The default capacity of each of these is /// 64. The capacity of the main (unfiltered) queue is configurable through the [`set_max_queued`] /// method. When the queue is full, no more messages can be received until room is created for more. /// This is why it's important to ensure that all [`crate::MessageStream`] and /// [`crate::blocking::MessageIterator`] instances are continuously polled and iterated on, /// respectively. /// /// For sending messages you can either use [`Connection::send`] method. /// /// [method calls]: struct.Connection.html#method.call_method /// [signals]: struct.Connection.html#method.emit_signal /// [`proxy`]: attr.proxy.html /// [`interface`]: attr.interface.html /// [`Clone`]: https://doc.rust-lang.org/std/clone/trait.Clone.html /// [`set_max_queued`]: struct.Connection.html#method.set_max_queued /// /// ### Examples /// /// #### Get the session bus ID /// /// ``` /// # zbus::block_on(async { /// use zbus::Connection; /// /// let connection = Connection::session().await?; /// /// let reply_body = connection /// .call_method( /// Some("org.freedesktop.DBus"), /// "/org/freedesktop/DBus", /// Some("org.freedesktop.DBus"), /// "GetId", /// &(), /// ) /// .await? /// .body(); /// /// let id: &str = reply_body.deserialize()?; /// println!("Unique ID of the bus: {}", id); /// # Ok::<(), zbus::Error>(()) /// # }).unwrap(); /// ``` /// /// #### Monitoring all messages /// /// Let's eavesdrop on the session bus 😈 using the [Monitor] interface: /// /// ```rust,no_run /// # zbus::block_on(async { /// use futures_util::stream::TryStreamExt; /// use zbus::{Connection, MessageStream}; /// /// let connection = Connection::session().await?; /// /// connection /// .call_method( /// Some("org.freedesktop.DBus"), /// "/org/freedesktop/DBus", /// Some("org.freedesktop.DBus.Monitoring"), /// "BecomeMonitor", /// &(&[] as &[&str], 0u32), /// ) /// .await?; /// /// let mut stream = MessageStream::from(connection); /// while let Some(msg) = stream.try_next().await? { /// println!("Got message: {}", msg); /// } /// /// # Ok::<(), zbus::Error>(()) /// # }).unwrap(); /// ``` /// /// This should print something like: /// /// ```console /// Got message: Signal NameAcquired from org.freedesktop.DBus /// Got message: Signal NameLost from org.freedesktop.DBus /// Got message: Method call GetConnectionUnixProcessID from :1.1324 /// Got message: Error org.freedesktop.DBus.Error.NameHasNoOwner: /// Could not get PID of name ':1.1332': no such name from org.freedesktop.DBus /// Got message: Method call AddMatch from :1.918 /// Got message: Method return from org.freedesktop.DBus /// ``` /// /// [Monitor]: https://dbus.freedesktop.org/doc/dbus-specification.html#bus-messages-become-monitor #[derive(Clone, Debug)] #[must_use = "Dropping a `Connection` will close the underlying socket."] pub struct Connection { pub(crate) inner: Arc, } assert_impl_all!(Connection: Send, Sync, Unpin); /// A method call whose completion can be awaited or joined with other streams. /// /// This is useful for cache population method calls, where joining the [`JoinableStream`] with /// an update signal stream can be used to ensure that cache updates are not overwritten by a cache /// population whose task is scheduled later. #[derive(Debug)] pub(crate) struct PendingMethodCall { stream: Option, serial: NonZeroU32, } impl Future for PendingMethodCall { type Output = Result; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { self.poll_before(cx, None).map(|ret| { ret.map(|(_, r)| r).unwrap_or_else(|| { Err(crate::Error::InputOutput( io::Error::new(ErrorKind::BrokenPipe, "socket closed").into(), )) }) }) } } impl OrderedFuture for PendingMethodCall { type Output = Result; type Ordering = zbus::message::Sequence; fn poll_before( self: Pin<&mut Self>, cx: &mut Context<'_>, before: Option<&Self::Ordering>, ) -> Poll> { let this = self.get_mut(); if let Some(stream) = &mut this.stream { loop { match Pin::new(&mut *stream).poll_next_before(cx, before) { Poll::Ready(PollResult::Item { data: Ok(msg), ordering, }) => { if msg.header().reply_serial() != Some(this.serial) { continue; } let res = match msg.message_type() { Type::Error => Err(msg.into()), Type::MethodReturn => Ok(msg), _ => continue, }; this.stream = None; return Poll::Ready(Some((ordering, res))); } Poll::Ready(PollResult::Item { data: Err(e), ordering, }) => { return Poll::Ready(Some((ordering, Err(e)))); } Poll::Ready(PollResult::NoneBefore) => { return Poll::Ready(None); } Poll::Ready(PollResult::Terminated) => { return Poll::Ready(None); } Poll::Pending => return Poll::Pending, } } } Poll::Ready(None) } } impl Connection { /// Send `msg` to the peer. pub async fn send(&self, msg: &Message) -> Result<()> { #[cfg(unix)] if !msg.data().fds().is_empty() && !self.inner.cap_unix_fd { return Err(Error::Unsupported); } self.inner.activity_event.notify(usize::MAX); let mut write = self.inner.socket_write.lock().await; write.send_message(msg).await } /// Send a method call. /// /// Create a method-call message, send it over the connection, then wait for the reply. /// /// On successful reply, an `Ok(Message)` is returned. On error, an `Err` is returned. D-Bus /// error replies are returned as [`Error::MethodError`]. pub async fn call_method<'d, 'p, 'i, 'm, D, P, I, M, B>( &self, destination: Option, path: P, interface: Option, method_name: M, body: &B, ) -> Result where D: TryInto>, P: TryInto>, I: TryInto>, M: TryInto>, D::Error: Into, P::Error: Into, I::Error: Into, M::Error: Into, B: serde::ser::Serialize + zvariant::DynamicType, { self.call_method_raw( destination, path, interface, method_name, BitFlags::empty(), body, ) .await? .expect("no reply") .await } /// Send a method call. /// /// Send the given message, which must be a method call, over the connection and return an /// object that allows the reply to be retrieved. Typically you'd want to use /// [`Connection::call_method`] instead. /// /// If the `flags` do not contain `MethodFlags::NoReplyExpected`, the return value is /// guaranteed to be `Ok(Some(_))`, if there was no error encountered. /// /// INTERNAL NOTE: If this method is ever made pub, flags should become `BitFlags`. pub(crate) async fn call_method_raw<'d, 'p, 'i, 'm, D, P, I, M, B>( &self, destination: Option, path: P, interface: Option, method_name: M, flags: BitFlags, body: &B, ) -> Result> where D: TryInto>, P: TryInto>, I: TryInto>, M: TryInto>, D::Error: Into, P::Error: Into, I::Error: Into, M::Error: Into, B: serde::ser::Serialize + zvariant::DynamicType, { let _permit = acquire_serial_num_semaphore().await; let mut builder = Message::method(path, method_name)?; if let Some(sender) = self.unique_name() { builder = builder.sender(sender)? } if let Some(destination) = destination { builder = builder.destination(destination)? } if let Some(interface) = interface { builder = builder.interface(interface)? } for flag in flags { builder = builder.with_flags(flag)?; } let msg = builder.build(body)?; let msg_receiver = self.inner.method_return_receiver.activate_cloned(); let stream = Some(MessageStream::for_subscription_channel( msg_receiver, // This is a lie but we only use the stream internally so it's fine. None, self, )); let serial = msg.primary_header().serial_num(); self.send(&msg).await?; if flags.contains(Flags::NoReplyExpected) { Ok(None) } else { Ok(Some(PendingMethodCall { stream, serial })) } } /// Emit a signal. /// /// Create a signal message, and send it over the connection. pub async fn emit_signal<'d, 'p, 'i, 'm, D, P, I, M, B>( &self, destination: Option, path: P, interface: I, signal_name: M, body: &B, ) -> Result<()> where D: TryInto>, P: TryInto>, I: TryInto>, M: TryInto>, D::Error: Into, P::Error: Into, I::Error: Into, M::Error: Into, B: serde::ser::Serialize + zvariant::DynamicType, { let _permit = acquire_serial_num_semaphore().await; let mut b = Message::signal(path, interface, signal_name)?; if let Some(sender) = self.unique_name() { b = b.sender(sender)?; } if let Some(destination) = destination { b = b.destination(destination)?; } let m = b.build(body)?; self.send(&m).await } /// Reply to a message. /// /// Given an existing message (likely a method call), send a reply back to the caller with the /// given `body`. pub async fn reply(&self, call: &Message, body: &B) -> Result<()> where B: serde::ser::Serialize + zvariant::DynamicType, { let _permit = acquire_serial_num_semaphore().await; let mut b = Message::method_reply(call)?; if let Some(sender) = self.unique_name() { b = b.sender(sender)?; } let m = b.build(body)?; self.send(&m).await } /// Reply an error to a message. /// /// Given an existing message (likely a method call), send an error reply back to the caller /// with the given `error_name` and `body`. pub async fn reply_error<'e, E, B>(&self, call: &Message, error_name: E, body: &B) -> Result<()> where B: serde::ser::Serialize + zvariant::DynamicType, E: TryInto>, E::Error: Into, { let _permit = acquire_serial_num_semaphore().await; let mut b = Message::method_error(call, error_name)?; if let Some(sender) = self.unique_name() { b = b.sender(sender)?; } let m = b.build(body)?; self.send(&m).await } /// Reply an error to a message. /// /// Given an existing message (likely a method call), send an error reply back to the caller /// using one of the standard interface reply types. pub async fn reply_dbus_error( &self, call: &zbus::message::Header<'_>, err: impl DBusError, ) -> Result<()> { let _permit = acquire_serial_num_semaphore().await; let m = err.create_reply(call)?; self.send(&m).await } /// Register a well-known name for this connection. /// /// When connecting to a bus, the name is requested from the bus. In case of p2p connection, the /// name (if requested) is used of self-identification. /// /// You can request multiple names for the same connection. Use [`Connection::release_name`] for /// deregistering names registered through this method. /// /// Note that exclusive ownership without queueing is requested (using /// [`RequestNameFlags::ReplaceExisting`] and [`RequestNameFlags::DoNotQueue`] flags) since that /// is the most typical case. If that is not what you want, you should use /// [`Connection::request_name_with_flags`] instead (but make sure then that name is requested /// **after** you've setup your service implementation with the `ObjectServer`). /// /// # Caveats /// /// The associated `ObjectServer` will only handle method calls destined for the unique name of /// this connection or any of the registered well-known names. If no well-known name is /// registered, the method calls destined to all well-known names will be handled. /// /// Since names registered through any other means than `Connection` or [`Builder`] /// API are not known to the connection, method calls destined to those names will only be /// handled by the associated `ObjectServer` if none of the names are registered through /// `Connection*` API. Simply put, either register all the names through `Connection*` API or /// none of them. /// /// # Errors /// /// Fails with `zbus::Error::NameTaken` if the name is already owned by another peer. pub async fn request_name<'w, W>(&self, well_known_name: W) -> Result<()> where W: TryInto>, W::Error: Into, { self.request_name_with_flags( well_known_name, RequestNameFlags::ReplaceExisting | RequestNameFlags::DoNotQueue, ) .await .map(|_| ()) } /// Register a well-known name for this connection. /// /// This is the same as [`Connection::request_name`] but allows to specify the flags to use when /// requesting the name. /// /// If the [`RequestNameFlags::DoNotQueue`] flag is not specified and request ends up in the /// queue, you can use [`fdo::NameAcquiredStream`] to be notified when the name is acquired. A /// queued name request can be cancelled using [`Connection::release_name`]. /// /// If the [`RequestNameFlags::AllowReplacement`] flag is specified, the requested name can be /// lost if another peer requests the same name. You can use [`fdo::NameLostStream`] to be /// notified when the name is lost /// /// # Example /// /// ``` /// # /// # zbus::block_on(async { /// use zbus::{Connection, fdo::{DBusProxy, RequestNameFlags, RequestNameReply}}; /// use enumflags2::BitFlags; /// use futures_util::stream::StreamExt; /// /// let name = "org.freedesktop.zbus.QueuedNameTest"; /// let conn1 = Connection::session().await?; /// // This should just work right away. /// conn1.request_name(name).await?; /// /// let conn2 = Connection::session().await?; /// // A second request from the another connection will fail with `DoNotQueue` flag, which is /// // implicit with `request_name` method. /// assert!(conn2.request_name(name).await.is_err()); /// /// // Now let's try w/o `DoNotQueue` and we should be queued. /// let reply = conn2 /// .request_name_with_flags(name, RequestNameFlags::AllowReplacement.into()) /// .await?; /// assert_eq!(reply, RequestNameReply::InQueue); /// // Another request should just give us the same response. /// let reply = conn2 /// // The flags on subsequent requests will however be ignored. /// .request_name_with_flags(name, BitFlags::empty()) /// .await?; /// assert_eq!(reply, RequestNameReply::InQueue); /// let mut acquired_stream = DBusProxy::new(&conn2) /// .await? /// .receive_name_acquired() /// .await?; /// assert!(conn1.release_name(name).await?); /// // This would have waited forever if `conn1` hadn't just release the name. /// let acquired = acquired_stream.next().await.unwrap(); /// assert_eq!(acquired.args().unwrap().name, name); /// /// // conn2 made the mistake of being too nice and allowed name replacemnt, so conn1 should be /// // able to take it back. /// let mut lost_stream = DBusProxy::new(&conn2) /// .await? /// .receive_name_lost() /// .await?; /// conn1.request_name(name).await?; /// let lost = lost_stream.next().await.unwrap(); /// assert_eq!(lost.args().unwrap().name, name); /// /// # Ok::<(), zbus::Error>(()) /// # }).unwrap(); /// ``` /// /// # Caveats /// /// * Same as that of [`Connection::request_name`]. /// * If you wish to track changes to name ownership after this call, make sure that the /// [`fdo::NameAcquired`] and/or [`fdo::NameLostStream`] instance(s) are created **before** /// calling this method. Otherwise, you may loose the signal if it's emitted after this call but /// just before the stream instance get created. pub async fn request_name_with_flags<'w, W>( &self, well_known_name: W, flags: BitFlags, ) -> Result where W: TryInto>, W::Error: Into, { let well_known_name = well_known_name.try_into().map_err(Into::into)?; // We keep the lock until the end of this function so that the (possibly) spawned task // doesn't end up accessing the name entry before it's inserted. let mut names = self.inner.registered_names.lock().await; match names.get(&well_known_name) { Some(NameStatus::Owner(_)) => return Ok(RequestNameReply::AlreadyOwner), Some(NameStatus::Queued(_)) => return Ok(RequestNameReply::InQueue), None => (), } if !self.is_bus() { names.insert(well_known_name.to_owned(), NameStatus::Owner(None)); return Ok(RequestNameReply::PrimaryOwner); } let dbus_proxy = fdo::DBusProxy::builder(self) .cache_properties(CacheProperties::No) .build() .await?; let mut acquired_stream = dbus_proxy.receive_name_acquired().await?; let mut lost_stream = dbus_proxy.receive_name_lost().await?; let reply = dbus_proxy .request_name(well_known_name.clone(), flags) .await?; let lost_task_name = format!("monitor name {well_known_name} lost"); let name_lost_fut = if flags.contains(RequestNameFlags::AllowReplacement) { let weak_conn = WeakConnection::from(self); let well_known_name = well_known_name.to_owned(); Some( async move { loop { let signal = lost_stream.next().await; let inner = match weak_conn.upgrade() { Some(conn) => conn.inner.clone(), None => break, }; match signal { Some(signal) => match signal.args() { Ok(args) if args.name == well_known_name => { tracing::info!( "Connection `{}` lost name `{}`", // SAFETY: This is bus connection so unique name can't be // None. inner.unique_name.get().unwrap(), well_known_name ); inner.registered_names.lock().await.remove(&well_known_name); break; } Ok(_) => (), Err(e) => warn!("Failed to parse `NameLost` signal: {}", e), }, None => { trace!("`NameLost` signal stream closed"); // This is a very strange state we end up in. Now the name is // question remains in the queue // forever. Maybe we can do better here but I // think it's a very unlikely scenario anyway. // // Can happen if the connection is lost/dropped but then the whole // `Connection` instance will go away soon anyway and hence this // strange state along with it. break; } } } } .instrument(info_span!("{}", lost_task_name)), ) } else { None }; let status = match reply { RequestNameReply::InQueue => { let weak_conn = WeakConnection::from(self); let well_known_name = well_known_name.to_owned(); let task_name = format!("monitor name {well_known_name} acquired"); let task = self.executor().spawn( async move { loop { let signal = acquired_stream.next().await; let inner = match weak_conn.upgrade() { Some(conn) => conn.inner.clone(), None => break, }; match signal { Some(signal) => match signal.args() { Ok(args) if args.name == well_known_name => { let mut names = inner.registered_names.lock().await; if let Some(status) = names.get_mut(&well_known_name) { let task = name_lost_fut.map(|fut| { inner.executor.spawn(fut, &lost_task_name) }); *status = NameStatus::Owner(task); break; } // else the name was released in the meantime. :shrug: } Ok(_) => (), Err(e) => warn!("Failed to parse `NameAcquired` signal: {}", e), }, None => { trace!("`NameAcquired` signal stream closed"); // See comment above for similar state in case of `NameLost` // stream. break; } } } } .instrument(info_span!("{}", task_name)), &task_name, ); NameStatus::Queued(task) } RequestNameReply::PrimaryOwner | RequestNameReply::AlreadyOwner => { let task = name_lost_fut.map(|fut| self.executor().spawn(fut, &lost_task_name)); NameStatus::Owner(task) } RequestNameReply::Exists => return Err(Error::NameTaken), }; names.insert(well_known_name.to_owned(), status); Ok(reply) } /// Deregister a previously registered well-known name for this service on the bus. /// /// Use this method to deregister a well-known name, registered through /// [`Connection::request_name`]. /// /// Unless an error is encountered, returns `Ok(true)` if name was previously registered with /// the bus through `self` and it has now been successfully deregistered, `Ok(false)` if name /// was not previously registered or already deregistered. pub async fn release_name<'w, W>(&self, well_known_name: W) -> Result where W: TryInto>, W::Error: Into, { let well_known_name: WellKnownName<'w> = well_known_name.try_into().map_err(Into::into)?; let mut names = self.inner.registered_names.lock().await; // FIXME: Should be possible to avoid cloning/allocation here if names.remove(&well_known_name.to_owned()).is_none() { return Ok(false); }; if !self.is_bus() { return Ok(true); } fdo::DBusProxy::builder(self) .cache_properties(CacheProperties::No) .build() .await? .release_name(well_known_name) .await .map(|_| true) .map_err(Into::into) } /// Checks if `self` is a connection to a message bus. /// /// This will return `false` for p2p connections. When the `p2p` feature is enabled, this will /// always return `true`. pub fn is_bus(&self) -> bool { #[cfg(feature = "p2p")] { self.inner.bus_conn } #[cfg(not(feature = "p2p"))] { true } } /// The unique name of the connection, if set/applicable. /// /// The unique name is assigned by the message bus or set manually using /// [`Connection::set_unique_name`]. pub fn unique_name(&self) -> Option<&OwnedUniqueName> { self.inner.unique_name.get() } /// Sets the unique name of the connection (if not already set). /// /// This is mainly provided for bus implementations. All other users should not need to use this /// method. Hence why this method is only available when the `bus-impl` feature is enabled. /// /// # Panics /// /// This method panics if the unique name is already set. It will always panic if the connection /// is to a message bus as it's the bus that assigns peers their unique names. #[cfg(feature = "bus-impl")] pub fn set_unique_name(&self, unique_name: U) -> Result<()> where U: TryInto, U::Error: Into, { let name = unique_name.try_into().map_err(Into::into)?; self.set_unique_name_(name); Ok(()) } /// The capacity of the main (unfiltered) queue. pub fn max_queued(&self) -> usize { self.inner.msg_receiver.capacity() } /// Set the capacity of the main (unfiltered) queue. pub fn set_max_queued(&mut self, max: usize) { self.inner.msg_receiver.clone().set_capacity(max); } /// The server's GUID. pub fn server_guid(&self) -> &OwnedGuid { &self.inner.server_guid } /// The underlying executor. /// /// When a connection is built with internal_executor set to false, zbus will not spawn a /// thread to run the executor. You're responsible to continuously [tick the executor][tte]. /// Failure to do so will result in hangs. /// /// # Examples /// /// Here is how one would typically run the zbus executor through tokio's scheduler: /// /// ``` /// # // Disable on windows because somehow it triggers a stack overflow there: /// # // https://gitlab.freedesktop.org/zeenix/zbus/-/jobs/34023494 /// # #[cfg(not(target_os = "unix"))] /// # { /// use zbus::connection::Builder; /// use tokio::task::spawn; /// /// # struct SomeIface; /// # /// # #[zbus::interface] /// # impl SomeIface { /// # } /// # /// #[tokio::main] /// async fn main() { /// let conn = Builder::session() /// .unwrap() /// .internal_executor(false) /// # // This is only for testing a deadlock that used to happen with this combo. /// # .serve_at("/some/iface", SomeIface) /// # .unwrap() /// .build() /// .await /// .unwrap(); /// { /// let conn = conn.clone(); /// spawn(async move { /// loop { /// conn.executor().tick().await; /// } /// }); /// } /// /// // All your other async code goes here. /// } /// # } /// ``` /// /// **Note**: zbus 2.1 added support for tight integration with tokio. This means, if you use /// zbus with tokio, you do not need to worry about this at all. All you need to do is enable /// `tokio` feature. You should also disable the (default) `async-io` feature in your /// `Cargo.toml` to avoid unused dependencies. Also note that **prior** to zbus 3.0, disabling /// `async-io` was required to enable tight `tokio` integration. /// /// [tte]: https://docs.rs/async-executor/1.4.1/async_executor/struct.Executor.html#method.tick pub fn executor(&self) -> &Executor<'static> { &self.inner.executor } /// Get a reference to the associated [`ObjectServer`]. /// /// The `ObjectServer` is created on-demand. /// /// **Note**: Once the `ObjectServer` is created, it will be replying to all method calls /// received on `self`. If you want to manually reply to method calls, do not use this /// method (or any of the `ObjectServer` related API). pub fn object_server(&self) -> impl Deref + '_ { // FIXME: Maybe it makes sense after all to implement Deref for // crate::ObjectServer instead of this wrapper? struct Wrapper<'a>(&'a blocking::ObjectServer); impl<'a> Deref for Wrapper<'a> { type Target = ObjectServer; fn deref(&self) -> &Self::Target { self.0.inner() } } Wrapper(self.sync_object_server(true, None)) } pub(crate) fn sync_object_server( &self, start: bool, started_event: Option, ) -> &blocking::ObjectServer { self.inner .object_server .get_or_init(move || self.setup_object_server(start, started_event)) } fn setup_object_server( &self, start: bool, started_event: Option, ) -> blocking::ObjectServer { if start { self.start_object_server(started_event); } blocking::ObjectServer::new(self) } #[instrument(skip(self))] pub(crate) fn start_object_server(&self, started_event: Option) { self.inner.object_server_dispatch_task.get_or_init(|| { trace!("starting ObjectServer task"); let weak_conn = WeakConnection::from(self); let obj_server_task_name = "ObjectServer task"; self.inner.executor.spawn( async move { let mut stream = match weak_conn.upgrade() { Some(conn) => { let mut builder = MatchRule::builder().msg_type(Type::MethodCall); if let Some(unique_name) = conn.unique_name() { builder = builder.destination(&**unique_name).expect("unique name"); } let rule = builder.build(); match conn.add_match(rule.into(), None).await { Ok(stream) => stream, Err(e) => { // Very unlikely but can happen I guess if connection is closed. debug!("Failed to create message stream: {}", e); return; } } } None => { trace!("Connection is gone, stopping associated object server task"); return; } }; if let Some(started_event) = started_event { started_event.notify(1); } trace!("waiting for incoming method call messages.."); while let Some(msg) = stream.next().await.and_then(|m| { if let Err(e) = &m { debug!("Error while reading from object server stream: {:?}", e); } m.ok() }) { if let Some(conn) = weak_conn.upgrade() { let hdr = msg.header(); match hdr.destination() { // Unique name is already checked by the match rule. Some(BusName::Unique(_)) | None => (), Some(BusName::WellKnown(dest)) => { let names = conn.inner.registered_names.lock().await; // destination doesn't matter if no name has been registered // (probably means name it's registered through external means). if !names.is_empty() && !names.contains_key(dest) { trace!( "Got a method call for a different destination: {}", dest ); continue; } } } let server = conn.object_server(); if let Err(e) = server.dispatch_call(&msg, &hdr).await { debug!( "Error dispatching message. Message: {:?}, error: {:?}", msg, e ); } } else { // If connection is completely gone, no reason to keep running the task // anymore. trace!("Connection is gone, stopping associated object server task"); break; } } } .instrument(info_span!("{}", obj_server_task_name)), obj_server_task_name, ) }); } pub(crate) async fn add_match( &self, rule: OwnedMatchRule, max_queued: Option, ) -> Result>> { use std::collections::hash_map::Entry; if self.inner.msg_senders.lock().await.is_empty() { // This only happens if socket reader task has errored out. return Err(Error::InputOutput(Arc::new(io::Error::new( io::ErrorKind::BrokenPipe, "Socket reader task has errored out", )))); } let mut subscriptions = self.inner.subscriptions.lock().await; let msg_type = rule.msg_type().unwrap_or(Type::Signal); match subscriptions.entry(rule.clone()) { Entry::Vacant(e) => { let max_queued = max_queued.unwrap_or(DEFAULT_MAX_QUEUED); let (sender, mut receiver) = broadcast(max_queued); receiver.set_await_active(false); if self.is_bus() && msg_type == Type::Signal { fdo::DBusProxy::builder(self) .cache_properties(CacheProperties::No) .build() .await? .add_match_rule(e.key().inner().clone()) .await?; } e.insert((1, receiver.clone().deactivate())); self.inner .msg_senders .lock() .await .insert(Some(rule), sender); Ok(receiver) } Entry::Occupied(mut e) => { let (num_subscriptions, receiver) = e.get_mut(); *num_subscriptions += 1; if let Some(max_queued) = max_queued { if max_queued > receiver.capacity() { receiver.set_capacity(max_queued); } } Ok(receiver.activate_cloned()) } } } pub(crate) async fn remove_match(&self, rule: OwnedMatchRule) -> Result { use std::collections::hash_map::Entry; let mut subscriptions = self.inner.subscriptions.lock().await; // TODO when it becomes stable, use HashMap::raw_entry and only require expr: &str // (both here and in add_match) let msg_type = rule.msg_type().unwrap_or(Type::Signal); match subscriptions.entry(rule) { Entry::Vacant(_) => Ok(false), Entry::Occupied(mut e) => { let rule = e.key().inner().clone(); e.get_mut().0 -= 1; if e.get().0 == 0 { if self.is_bus() && msg_type == Type::Signal { fdo::DBusProxy::builder(self) .cache_properties(CacheProperties::No) .build() .await? .remove_match_rule(rule.clone()) .await?; } e.remove(); self.inner .msg_senders .lock() .await .remove(&Some(rule.into())); } Ok(true) } } } pub(crate) fn queue_remove_match(&self, rule: OwnedMatchRule) { let conn = self.clone(); let task_name = format!("Remove match `{}`", *rule); let remove_match = async move { conn.remove_match(rule).await }.instrument(trace_span!("{}", task_name)); self.inner.executor.spawn(remove_match, &task_name).detach() } pub(crate) async fn new( auth: Authenticated, #[allow(unused)] bus_connection: bool, executor: Executor<'static>, ) -> Result { #[cfg(unix)] let cap_unix_fd = auth.cap_unix_fd; macro_rules! create_msg_broadcast_channel { ($size:expr) => {{ let (msg_sender, msg_receiver) = broadcast($size); let mut msg_receiver = msg_receiver.deactivate(); msg_receiver.set_await_active(false); (msg_sender, msg_receiver) }}; } // The unfiltered message channel. let (msg_sender, msg_receiver) = create_msg_broadcast_channel!(DEFAULT_MAX_QUEUED); let mut msg_senders = HashMap::new(); msg_senders.insert(None, msg_sender); // The special method return & error channel. let (method_return_sender, method_return_receiver) = create_msg_broadcast_channel!(DEFAULT_MAX_METHOD_RETURN_QUEUED); let rule = MatchRule::builder() .msg_type(Type::MethodReturn) .build() .into(); msg_senders.insert(Some(rule), method_return_sender.clone()); let rule = MatchRule::builder().msg_type(Type::Error).build().into(); msg_senders.insert(Some(rule), method_return_sender); let msg_senders = Arc::new(Mutex::new(msg_senders)); let subscriptions = Mutex::new(HashMap::new()); let connection = Self { inner: Arc::new(ConnectionInner { activity_event: Arc::new(Event::new()), socket_write: Mutex::new(auth.socket_write), server_guid: auth.server_guid, #[cfg(unix)] cap_unix_fd, #[cfg(feature = "p2p")] bus_conn: bus_connection, unique_name: OnceLock::new(), subscriptions, object_server: OnceLock::new(), object_server_dispatch_task: OnceLock::new(), executor, socket_reader_task: OnceLock::new(), msg_senders, msg_receiver, method_return_receiver, registered_names: Mutex::new(HashMap::new()), }), }; if let Some(unique_name) = auth.unique_name { connection.set_unique_name_(unique_name); } Ok(connection) } /// Create a `Connection` to the session/user message bus. pub async fn session() -> Result { Builder::session()?.build().await } /// Create a `Connection` to the system-wide message bus. pub async fn system() -> Result { Builder::system()?.build().await } /// Returns a listener, notified on various connection activity. /// /// This function is meant for the caller to implement idle or timeout on inactivity. pub fn monitor_activity(&self) -> EventListener { self.inner.activity_event.listen() } /// Returns the peer credentials. /// /// The fields are populated on the best effort basis. Some or all fields may not even make /// sense for certain sockets or on certain platforms and hence will be set to `None`. /// /// # Caveats /// /// Currently `unix_group_ids` and `linux_security_label` fields are not populated. pub async fn peer_credentials(&self) -> io::Result { self.inner .socket_write .lock() .await .peer_credentials() .await } /// Close the connection. /// /// After this call, all reading and writing operations will fail. pub async fn close(self) -> Result<()> { self.inner.activity_event.notify(usize::MAX); self.inner .socket_write .lock() .await .close() .await .map_err(Into::into) } pub(crate) fn init_socket_reader( &self, socket_read: Box, already_read: Vec, #[cfg(unix)] already_received_fds: Vec, ) { let inner = &self.inner; inner .socket_reader_task .set( SocketReader::new( socket_read, inner.msg_senders.clone(), already_read, #[cfg(unix)] already_received_fds, inner.activity_event.clone(), ) .spawn(&inner.executor), ) .expect("Attempted to set `socket_reader_task` twice"); } fn set_unique_name_(&self, name: OwnedUniqueName) { self.inner .unique_name .set(name) // programmer (probably our) error if this fails. .expect("unique name already set"); } } impl From for Connection { fn from(conn: crate::blocking::Connection) -> Self { conn.into_inner() } } // Internal API that allows keeping a weak connection ref around. #[derive(Debug)] pub(crate) struct WeakConnection { inner: Weak, } impl WeakConnection { /// Upgrade to a Connection. pub fn upgrade(&self) -> Option { self.inner.upgrade().map(|inner| Connection { inner }) } } impl From<&Connection> for WeakConnection { fn from(conn: &Connection) -> Self { Self { inner: Arc::downgrade(&conn.inner), } } } #[derive(Debug)] enum NameStatus { // The task waits for name lost signal if owner allows replacement. Owner(#[allow(unused)] Option>), // The task waits for name acquisition signal. Queued(#[allow(unused)] Task<()>), } static SERIAL_NUM_SEMAPHORE: Semaphore = Semaphore::new(1); // Make message creation and sending an atomic operation, using an async // semaphore if flatpak portal is detected to workaround an xdg-dbus-proxy issue: // // https://github.com/flatpak/xdg-dbus-proxy/issues/46 async fn acquire_serial_num_semaphore() -> Option> { if is_flatpak() { Some(SERIAL_NUM_SEMAPHORE.acquire().await) } else { None } } #[cfg(test)] mod tests { use super::*; use crate::fdo::DBusProxy; use ntest::timeout; use test_log::test; #[cfg(windows)] #[test] fn connect_autolaunch_session_bus() { let addr = crate::win32::autolaunch_bus_address().expect("Unable to get session bus address"); crate::block_on(async { addr.connect().await }).expect("Unable to connect to session bus"); } #[cfg(target_os = "macos")] #[test] fn connect_launchd_session_bus() { use crate::address::{transport::Launchd, Address, Transport}; crate::block_on(async { let addr = Address::from(Transport::Launchd(Launchd::new( "DBUS_LAUNCHD_SESSION_BUS_SOCKET", ))); addr.connect().await }) .expect("Unable to connect to session bus"); } #[test] #[timeout(15000)] fn disconnect_on_drop() { // Reproducer for https://github.com/dbus2/zbus/issues/308 where setting up the // objectserver would cause the connection to not disconnect on drop. crate::utils::block_on(test_disconnect_on_drop()); } async fn test_disconnect_on_drop() { #[derive(Default)] struct MyInterface {} #[crate::interface(name = "dev.peelz.FooBar.Baz")] impl MyInterface { fn do_thing(&self) {} } let name = "dev.peelz.foobar"; let connection = Builder::session() .unwrap() .name(name) .unwrap() .serve_at("/dev/peelz/FooBar", MyInterface::default()) .unwrap() .build() .await .unwrap(); let connection2 = Connection::session().await.unwrap(); let dbus = DBusProxy::new(&connection2).await.unwrap(); let mut stream = dbus .receive_name_owner_changed_with_args(&[(0, name), (2, "")]) .await .unwrap(); drop(connection); // If the connection is not dropped, this will hang forever. stream.next().await.unwrap(); // Let's still make sure the name is gone. let name_has_owner = dbus.name_has_owner(name.try_into().unwrap()).await.unwrap(); assert!(!name_has_owner); } } #[cfg(feature = "p2p")] #[cfg(test)] mod p2p_tests { use futures_util::stream::TryStreamExt; use ntest::timeout; use test_log::test; use zvariant::{Endian, NATIVE_ENDIAN}; use crate::{AuthMechanism, Guid}; use super::*; // Same numbered client and server are already paired up. async fn test_p2p( server1: Connection, client1: Connection, server2: Connection, client2: Connection, ) -> Result<()> { let forward1 = { let stream = MessageStream::from(server1.clone()); let sink = client2.clone(); stream.try_for_each(move |msg| { let sink = sink.clone(); async move { sink.send(&msg).await } }) }; let forward2 = { let stream = MessageStream::from(client2.clone()); let sink = server1.clone(); stream.try_for_each(move |msg| { let sink = sink.clone(); async move { sink.send(&msg).await } }) }; let _forward_task = client1.executor().spawn( async move { futures_util::try_join!(forward1, forward2) }, "forward_task", ); let server_ready = Event::new(); let server_ready_listener = server_ready.listen(); let client_done = Event::new(); let client_done_listener = client_done.listen(); let server_future = async move { let mut stream = MessageStream::from(&server2); server_ready.notify(1); let method = loop { let m = stream.try_next().await?.unwrap(); if m.to_string() == "Method call Test" { assert_eq!(m.body().deserialize::().unwrap(), 64); break m; } }; // Send another message first to check the queueing function on client side. server2 .emit_signal(None::<()>, "/", "org.zbus.p2p", "ASignalForYou", &()) .await?; server2.reply(&method, &("yay")).await?; client_done_listener.await; Ok(()) }; let client_future = async move { let mut stream = MessageStream::from(&client1); server_ready_listener.await; // We want to set non-native endian to ensure that: // 1. the message is actually encoded with the specified endian. // 2. the server side is able to decode it and replies in the same encoding. let endian = match NATIVE_ENDIAN { Endian::Little => Endian::Big, Endian::Big => Endian::Little, }; let method = Message::method("/", "Test")? .interface("org.zbus.p2p")? .endian(endian) .build(&64u64)?; client1.send(&method).await?; // Check we didn't miss the signal that was sent during the call. let m = stream.try_next().await?.unwrap(); client_done.notify(1); assert_eq!(m.to_string(), "Signal ASignalForYou"); let reply = stream.try_next().await?.unwrap(); assert_eq!(reply.to_string(), "Method return"); // Check if the reply was in the non-native endian. assert_eq!(Endian::from(reply.primary_header().endian_sig()), endian); reply.body().deserialize::() }; let (val, _) = futures_util::try_join!(client_future, server_future,)?; assert_eq!(val, "yay"); Ok(()) } #[test] #[timeout(15000)] fn tcp_p2p() { crate::utils::block_on(test_tcp_p2p()).unwrap(); } async fn test_tcp_p2p() -> Result<()> { let (server1, client1) = tcp_p2p_pipe().await?; let (server2, client2) = tcp_p2p_pipe().await?; test_p2p(server1, client1, server2, client2).await } async fn tcp_p2p_pipe() -> Result<(Connection, Connection)> { let guid = Guid::generate(); #[cfg(not(feature = "tokio"))] let (server_conn_builder, client_conn_builder) = { let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap(); let addr = listener.local_addr().unwrap(); let p1 = std::net::TcpStream::connect(addr).unwrap(); let p0 = listener.incoming().next().unwrap().unwrap(); ( Builder::tcp_stream(p0) .server(guid) .unwrap() .p2p() .auth_mechanism(AuthMechanism::Anonymous), Builder::tcp_stream(p1).p2p(), ) }; #[cfg(feature = "tokio")] let (server_conn_builder, client_conn_builder) = { let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); let addr = listener.local_addr().unwrap(); let p1 = tokio::net::TcpStream::connect(addr).await.unwrap(); let p0 = listener.accept().await.unwrap().0; ( Builder::tcp_stream(p0) .server(guid) .unwrap() .p2p() .auth_mechanism(AuthMechanism::Anonymous), Builder::tcp_stream(p1).p2p(), ) }; futures_util::try_join!(server_conn_builder.build(), client_conn_builder.build()) } #[cfg(unix)] #[test] #[timeout(15000)] fn unix_p2p() { crate::utils::block_on(test_unix_p2p()).unwrap(); } #[cfg(unix)] async fn test_unix_p2p() -> Result<()> { let (server1, client1) = unix_p2p_pipe().await?; let (server2, client2) = unix_p2p_pipe().await?; test_p2p(server1, client1, server2, client2).await } #[cfg(unix)] async fn unix_p2p_pipe() -> Result<(Connection, Connection)> { #[cfg(not(feature = "tokio"))] use std::os::unix::net::UnixStream; #[cfg(feature = "tokio")] use tokio::net::UnixStream; #[cfg(all(windows, not(feature = "tokio")))] use uds_windows::UnixStream; let guid = Guid::generate(); let (p0, p1) = UnixStream::pair().unwrap(); futures_util::try_join!( Builder::unix_stream(p1).p2p().build(), Builder::unix_stream(p0).server(guid).unwrap().p2p().build(), ) } // Compile-test only since we don't have a VM setup to run this with/in. #[cfg(any( all(feature = "vsock", not(feature = "tokio")), feature = "tokio-vsock" ))] #[test] #[timeout(15000)] #[ignore] fn vsock_p2p() { crate::utils::block_on(test_vsock_p2p()).unwrap(); } #[cfg(any( all(feature = "vsock", not(feature = "tokio")), feature = "tokio-vsock" ))] async fn test_vsock_p2p() -> Result<()> { let (server1, client1) = vsock_p2p_pipe().await?; let (server2, client2) = vsock_p2p_pipe().await?; test_p2p(server1, client1, server2, client2).await } #[cfg(all(feature = "vsock", not(feature = "tokio")))] async fn vsock_p2p_pipe() -> Result<(Connection, Connection)> { let guid = Guid::generate(); let listener = vsock::VsockListener::bind_with_cid_port(vsock::VMADDR_CID_ANY, 42).unwrap(); let addr = listener.local_addr().unwrap(); let client = vsock::VsockStream::connect(&addr).unwrap(); let server = listener.incoming().next().unwrap().unwrap(); futures_util::try_join!( Builder::vsock_stream(server) .server(guid) .unwrap() .p2p() .auth_mechanism(AuthMechanism::Anonymous) .build(), Builder::vsock_stream(client).p2p().build(), ) } #[cfg(feature = "tokio-vsock")] async fn vsock_p2p_pipe() -> Result<(Connection, Connection)> { let guid = Guid::generate(); let listener = tokio_vsock::VsockListener::bind(2, 42).unwrap(); let client = tokio_vsock::VsockStream::connect(3, 42).await.unwrap(); let server = listener.incoming().next().await.unwrap().unwrap(); futures_util::try_join!( Builder::vsock_stream(server) .server(guid) .unwrap() .p2p() .auth_mechanism(AuthMechanism::Anonymous) .build(), Builder::vsock_stream(client).p2p().build(), ) } #[cfg(any(unix, not(feature = "tokio")))] #[test] #[timeout(15000)] fn unix_p2p_cookie_auth() { use crate::utils::block_on; use std::{ fs::{create_dir_all, remove_file, write}, time::{SystemTime as Time, UNIX_EPOCH}, }; #[cfg(unix)] use std::{ fs::{set_permissions, Permissions}, os::unix::fs::PermissionsExt, }; use xdg_home::home_dir; let cookie_context = "zbus-test-cookie-context"; let cookie_id = 123456789; let cookie = hex::encode(b"our cookie"); // Ensure cookie directory exists. let cookie_dir = home_dir().unwrap().join(".dbus-keyrings"); create_dir_all(&cookie_dir).unwrap(); #[cfg(unix)] set_permissions(&cookie_dir, Permissions::from_mode(0o700)).unwrap(); // Create a cookie file. let cookie_file = cookie_dir.join(cookie_context); let ts = Time::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); let cookie_entry = format!("{cookie_id} {ts} {cookie}"); write(&cookie_file, cookie_entry).unwrap(); // Explicit cookie ID. let res1 = block_on(test_unix_p2p_cookie_auth(cookie_context, Some(cookie_id))); // Implicit cookie ID (first one should be picked). let res2 = block_on(test_unix_p2p_cookie_auth(cookie_context, None)); // Remove the cookie file. remove_file(&cookie_file).unwrap(); res1.unwrap(); res2.unwrap(); } #[cfg(any(unix, not(feature = "tokio")))] async fn test_unix_p2p_cookie_auth( cookie_context: &'static str, cookie_id: Option, ) -> Result<()> { #[cfg(all(unix, not(feature = "tokio")))] use std::os::unix::net::UnixStream; #[cfg(all(unix, feature = "tokio"))] use tokio::net::UnixStream; #[cfg(all(windows, not(feature = "tokio")))] use uds_windows::UnixStream; let guid = Guid::generate(); let (p0, p1) = UnixStream::pair().unwrap(); let mut server_builder = Builder::unix_stream(p0) .server(guid) .unwrap() .p2p() .auth_mechanism(AuthMechanism::Cookie) .cookie_context(cookie_context) .unwrap(); if let Some(cookie_id) = cookie_id { server_builder = server_builder.cookie_id(cookie_id); } futures_util::try_join!( Builder::unix_stream(p1).p2p().build(), server_builder.build(), ) .map(|_| ()) } #[test] #[timeout(15000)] fn channel_pair() { crate::utils::block_on(test_channel_pair()).unwrap(); } async fn test_channel_pair() -> Result<()> { let (server1, client1) = create_channel_pair().await; let (server2, client2) = create_channel_pair().await; test_p2p(server1, client1, server2, client2).await } async fn create_channel_pair() -> (Connection, Connection) { let (a, b) = socket::Channel::pair(); let guid = crate::Guid::generate(); let conn1 = Builder::authenticated_socket(a, guid.clone()) .unwrap() .p2p() .build() .await .unwrap(); let conn2 = Builder::authenticated_socket(b, guid) .unwrap() .p2p() .build() .await .unwrap(); (conn1, conn2) } } zbus-4.3.1/src/connection/socket/channel.rs000064400000000000000000000067161046102023000170220ustar 00000000000000use std::io; use async_broadcast::{broadcast, Receiver, Sender}; use crate::{fdo::ConnectionCredentials, Message}; /// An in-process channel-based socket. /// /// This is a pair of two cross-wired channels. Since all communication happens in-process, there is /// no need for any authentication. /// /// This type is only available when `p2p` feature is enabled. #[derive(Debug)] pub struct Channel { writer: Writer, reader: Reader, } impl Channel { /// Create a pair of cross-wired channels. /// /// Use [`crate::connection::Builder::authenticated_socket`] to create `Connection` instances /// from each channel. pub fn pair() -> (Self, Self) { let (tx1, rx1) = broadcast(CHANNEL_CAPACITY); let (tx2, rx2) = broadcast(CHANNEL_CAPACITY); ( Self { writer: Writer(tx1), reader: Reader(rx2), }, Self { writer: Writer(tx2), reader: Reader(rx1), }, ) } } impl super::Socket for Channel { type ReadHalf = Reader; type WriteHalf = Writer; fn split(self) -> super::Split { super::Split { read: self.reader, write: self.writer, } } } /// The reader half of a [`Channel`]. /// /// This type is only available when `p2p` feature is enabled. #[derive(Debug)] pub struct Reader(Receiver); #[async_trait::async_trait] impl super::ReadHalf for Reader { async fn receive_message( &mut self, _seq: u64, _already_received_bytes: &mut Vec, #[cfg(unix)] _already_received_fds: &mut Vec, ) -> crate::Result { self.0.recv().await.map_err(|e| { crate::Error::InputOutput(io::Error::new(io::ErrorKind::BrokenPipe, e).into()) }) } async fn peer_credentials(&mut self) -> io::Result { self_credentials().await } } /// The writer half of a [`Channel`]. /// /// This type is only available when `p2p` feature is enabled. #[derive(Debug)] pub struct Writer(Sender); #[async_trait::async_trait] impl super::WriteHalf for Writer { async fn send_message(&mut self, msg: &Message) -> crate::Result<()> { self.0 .broadcast_direct(msg.clone()) .await .map_err(|e| { crate::Error::InputOutput(io::Error::new(io::ErrorKind::BrokenPipe, e).into()) }) .map(|removed| { // We don't enable `overflow` mode so items should never be removed. assert!(removed.is_none()); }) } async fn close(&mut self) -> io::Result<()> { self.0.close(); Ok(()) } async fn peer_credentials(&mut self) -> io::Result { self_credentials().await } } /// Return the credentials of the current process. async fn self_credentials() -> io::Result { let mut creds = ConnectionCredentials::default().set_process_id(std::process::id()); #[cfg(unix)] { use nix::unistd::{Gid, Uid}; creds = creds .set_unix_user_id(Uid::effective().into()) .add_unix_group_id(Gid::effective().into()); } #[cfg(windows)] { let sid = crate::win32::ProcessToken::open(None)?.sid()?; creds = creds.set_windows_sid(sid); } Ok(creds) } const CHANNEL_CAPACITY: usize = 32; zbus-4.3.1/src/connection/socket/mod.rs000064400000000000000000000342151046102023000161640ustar 00000000000000#[cfg(feature = "p2p")] pub mod channel; #[cfg(feature = "p2p")] pub use channel::Channel; mod split; pub use split::{BoxedSplit, Split}; mod tcp; mod unix; mod vsock; #[cfg(not(feature = "tokio"))] use async_io::Async; #[cfg(not(feature = "tokio"))] use std::sync::Arc; use std::{io, mem}; use tracing::trace; use crate::{ fdo::ConnectionCredentials, message::{ header::{MAX_MESSAGE_SIZE, MIN_MESSAGE_SIZE}, PrimaryHeader, }, padding_for_8_bytes, Message, }; #[cfg(unix)] use std::os::fd::{AsFd, BorrowedFd, OwnedFd}; use zvariant::{ serialized::{self, Context}, Endian, }; #[cfg(unix)] type RecvmsgResult = io::Result<(usize, Vec)>; #[cfg(not(unix))] type RecvmsgResult = io::Result; /// Trait representing some transport layer over which the DBus protocol can be used /// /// In order to allow simultaneous reading and writing, this trait requires you to split the socket /// into a read half and a write half. The reader and writer halves can be any types that implement /// [`ReadHalf`] and [`WriteHalf`] respectively. /// /// The crate provides implementations for `async_io` and `tokio`'s `UnixStream` wrappers if you /// enable the corresponding crate features (`async_io` is enabled by default). /// /// You can implement it manually to integrate with other runtimes or other dbus transports. Feel /// free to submit pull requests to add support for more runtimes to zbus itself so rust's orphan /// rules don't force the use of a wrapper struct (and to avoid duplicating the work across many /// projects). pub trait Socket { type ReadHalf: ReadHalf; type WriteHalf: WriteHalf; /// Split the socket into a read half and a write half. fn split(self) -> Split where Self: Sized; } /// The read half of a socket. /// /// See [`Socket`] for more details. #[async_trait::async_trait] pub trait ReadHalf: std::fmt::Debug + Send + Sync + 'static { /// Receive a message on the socket. /// /// This is the higher-level method to receive a full D-Bus message. /// /// The default implementation uses `recvmsg` to receive the message. Implementers should /// override either this or `recvmsg`. Note that if you override this method, zbus will not be /// able perform an authentication handshake and hence will skip the handshake. Therefore your /// implementation will only be useful for pre-authenticated connections or connections that do /// not require authentication. /// /// # Parameters /// /// - `seq`: The sequence number of the message. The returned message should have this sequence. /// - `already_received_bytes`: Sometimes, zbus already received some bytes from the socket /// belonging to the first message(s) (as part of the connection handshake process). This is /// the buffer containing those bytes (if any). If you're implementing this method, most /// likely you can safely ignore this parameter. /// - `already_received_fds`: Same goes for file descriptors belonging to first messages. async fn receive_message( &mut self, seq: u64, already_received_bytes: &mut Vec, #[cfg(unix)] already_received_fds: &mut Vec, ) -> crate::Result { #[cfg(unix)] let mut fds = vec![]; let mut bytes = if already_received_bytes.len() < MIN_MESSAGE_SIZE { let mut bytes = vec![]; if !already_received_bytes.is_empty() { mem::swap(already_received_bytes, &mut bytes); } let mut pos = bytes.len(); bytes.resize(MIN_MESSAGE_SIZE, 0); // We don't have enough data to make a proper message header yet. // Some partial read may be in raw_in_buffer, so we try to complete it // until we have MIN_MESSAGE_SIZE bytes // // Given that MIN_MESSAGE_SIZE is 16, this codepath is actually extremely unlikely // to be taken more than once while pos < MIN_MESSAGE_SIZE { let res = self.recvmsg(&mut bytes[pos..]).await?; let len = { #[cfg(unix)] { fds.extend(res.1); res.0 } #[cfg(not(unix))] { res } }; pos += len; if len == 0 { return Err(std::io::Error::new( std::io::ErrorKind::UnexpectedEof, "failed to receive message", ) .into()); } } bytes } else { already_received_bytes.drain(..MIN_MESSAGE_SIZE).collect() }; let (primary_header, fields_len) = PrimaryHeader::read(&bytes)?; let header_len = MIN_MESSAGE_SIZE + fields_len as usize; let body_padding = padding_for_8_bytes(header_len); let body_len = primary_header.body_len() as usize; let total_len = header_len + body_padding + body_len; if total_len > MAX_MESSAGE_SIZE { return Err(crate::Error::ExcessData); } // By this point we have a full primary header, so we know the exact length of the complete // message. if !already_received_bytes.is_empty() { // still have some bytes buffered. let pending = total_len - bytes.len(); let to_take = std::cmp::min(pending, already_received_bytes.len()); bytes.extend(already_received_bytes.drain(..to_take)); } let mut pos = bytes.len(); bytes.resize(total_len, 0); // Read the rest, if any while pos < total_len { let res = self.recvmsg(&mut bytes[pos..]).await?; let read = { #[cfg(unix)] { fds.extend(res.1); res.0 } #[cfg(not(unix))] { res } }; pos += read; if read == 0 { return Err(crate::Error::InputOutput( std::io::Error::new( std::io::ErrorKind::UnexpectedEof, "failed to receive message", ) .into(), )); } } // If we reach here, the message is complete; return it let endian = Endian::from(primary_header.endian_sig()); #[cfg(unix)] if !already_received_fds.is_empty() { use crate::message::{header::PRIMARY_HEADER_SIZE, Field}; let ctxt = Context::new_dbus(endian, PRIMARY_HEADER_SIZE); let encoded_fields = serialized::Data::new(&bytes[PRIMARY_HEADER_SIZE..header_len], ctxt); let fields: crate::message::Fields<'_> = encoded_fields.deserialize()?.0; let num_required_fds = match fields.get_field(crate::message::FieldCode::UnixFDs) { Some(Field::UnixFDs(num_fds)) => *num_fds as usize, _ => 0, }; let num_pending = num_required_fds .checked_sub(fds.len()) .ok_or_else(|| crate::Error::ExcessData)?; // If we had previously received FDs, `num_pending` has to be > 0 if num_pending == 0 { return Err(crate::Error::MissingParameter("Missing file descriptors")); } // All previously received FDs must go first in the list. let mut already_received: Vec<_> = already_received_fds.drain(..num_pending).collect(); mem::swap(&mut already_received, &mut fds); fds.extend(already_received); } let ctxt = Context::new_dbus(endian, 0); #[cfg(unix)] let bytes = serialized::Data::new_fds(bytes, ctxt, fds); #[cfg(not(unix))] let bytes = serialized::Data::new(bytes, ctxt); Message::from_raw_parts(bytes, seq) } /// Attempt to receive bytes from the socket. /// /// On success, returns the number of bytes read as well as a `Vec` containing /// any associated file descriptors. /// /// The default implementation simply panics. Implementers must override either `read_message` /// or this method. async fn recvmsg(&mut self, _buf: &mut [u8]) -> RecvmsgResult { unimplemented!("`ReadHalf` implementers must either override `read_message` or `recvmsg`"); } /// Supports passing file descriptors. /// /// Default implementation returns `false`. fn can_pass_unix_fd(&self) -> bool { false } /// Return the peer credentials. async fn peer_credentials(&mut self) -> io::Result { Ok(ConnectionCredentials::default()) } } /// The write half of a socket. /// /// See [`Socket`] for more details. #[async_trait::async_trait] pub trait WriteHalf: std::fmt::Debug + Send + Sync + 'static { /// Send a message on the socket. /// /// This is the higher-level method to send a full D-Bus message. /// /// The default implementation uses `sendmsg` to send the message. Implementers should override /// either this or `sendmsg`. async fn send_message(&mut self, msg: &Message) -> crate::Result<()> { let data = msg.data(); let serial = msg.primary_header().serial_num(); trace!("Sending message: {:?}", msg); let mut pos = 0; while pos < data.len() { #[cfg(unix)] let fds = if pos == 0 { data.fds().iter().map(|f| f.as_fd()).collect() } else { vec![] }; pos += self .sendmsg( &data[pos..], #[cfg(unix)] &fds, ) .await?; } trace!("Sent message with serial: {}", serial); Ok(()) } /// Attempt to send a message on the socket /// /// On success, return the number of bytes written. There may be a partial write, in /// which case the caller is responsible of sending the remaining data by calling this /// method again until everything is written or it returns an error of kind `WouldBlock`. /// /// If at least one byte has been written, then all the provided file descriptors will /// have been sent as well, and should not be provided again in subsequent calls. /// /// If the underlying transport does not support transmitting file descriptors, this /// will return `Err(ErrorKind::InvalidInput)`. /// /// The default implementation simply panics. Implementers must override either `send_message` /// or this method. async fn sendmsg( &mut self, _buffer: &[u8], #[cfg(unix)] _fds: &[BorrowedFd<'_>], ) -> io::Result { unimplemented!("`WriteHalf` implementers must either override `send_message` or `sendmsg`"); } /// The dbus daemon on `freebsd` and `dragonfly` currently requires sending the zero byte /// as a separate message with SCM_CREDS, as part of the `EXTERNAL` authentication on unix /// sockets. This method is used by the authentication machinery in zbus to send this /// zero byte. Socket implementations based on unix sockets should implement this method. #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] async fn send_zero_byte(&mut self) -> io::Result> { Ok(None) } /// Close the socket. /// /// After this call, it is valid for all reading and writing operations to fail. async fn close(&mut self) -> io::Result<()>; /// Supports passing file descriptors. /// /// Default implementation returns `false`. fn can_pass_unix_fd(&self) -> bool { false } /// Return the peer credentials. async fn peer_credentials(&mut self) -> io::Result { Ok(ConnectionCredentials::default()) } } #[async_trait::async_trait] impl ReadHalf for Box { fn can_pass_unix_fd(&self) -> bool { (**self).can_pass_unix_fd() } async fn receive_message( &mut self, seq: u64, already_received_bytes: &mut Vec, #[cfg(unix)] already_received_fds: &mut Vec, ) -> crate::Result { (**self) .receive_message( seq, already_received_bytes, #[cfg(unix)] already_received_fds, ) .await } async fn recvmsg(&mut self, buf: &mut [u8]) -> RecvmsgResult { (**self).recvmsg(buf).await } async fn peer_credentials(&mut self) -> io::Result { (**self).peer_credentials().await } } #[async_trait::async_trait] impl WriteHalf for Box { async fn send_message(&mut self, msg: &Message) -> crate::Result<()> { (**self).send_message(msg).await } async fn sendmsg( &mut self, buffer: &[u8], #[cfg(unix)] fds: &[BorrowedFd<'_>], ) -> io::Result { (**self) .sendmsg( buffer, #[cfg(unix)] fds, ) .await } #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] async fn send_zero_byte(&mut self) -> io::Result> { (**self).send_zero_byte().await } async fn close(&mut self) -> io::Result<()> { (**self).close().await } fn can_pass_unix_fd(&self) -> bool { (**self).can_pass_unix_fd() } async fn peer_credentials(&mut self) -> io::Result { (**self).peer_credentials().await } } #[cfg(not(feature = "tokio"))] impl Socket for Async where T: std::fmt::Debug + Send + Sync, Arc>: ReadHalf + WriteHalf, { type ReadHalf = Arc>; type WriteHalf = Arc>; fn split(self) -> Split { let arc = Arc::new(self); Split { read: arc.clone(), write: arc, } } } zbus-4.3.1/src/connection/socket/split.rs000064400000000000000000000021221046102023000165300ustar 00000000000000use super::{ReadHalf, Socket, WriteHalf}; /// A pair of socket read and write halves. #[derive(Debug)] pub struct Split { pub(super) read: R, pub(super) write: W, } impl Split { /// Reference to the read half. pub fn read(&self) -> &R { &self.read } /// Mutable reference to the read half. pub fn read_mut(&mut self) -> &mut R { &mut self.read } /// Reference to the write half. pub fn write(&self) -> &W { &self.write } /// Mutable reference to the write half. pub fn write_mut(&mut self) -> &mut W { &mut self.write } /// Take the read and write halves. pub fn take(self) -> (R, W) { (self.read, self.write) } } /// A boxed `Split`. pub type BoxedSplit = Split, Box>; impl From for BoxedSplit { fn from(socket: S) -> Self { let split = socket.split(); Split { read: Box::new(split.read), write: Box::new(split.write), } } } zbus-4.3.1/src/connection/socket/tcp.rs000064400000000000000000000120021046102023000161610ustar 00000000000000#[cfg(not(feature = "tokio"))] use async_io::Async; use std::io; #[cfg(unix)] use std::os::fd::BorrowedFd; #[cfg(not(feature = "tokio"))] use std::{net::TcpStream, sync::Arc}; use super::{ReadHalf, RecvmsgResult, WriteHalf}; #[cfg(feature = "tokio")] use super::{Socket, Split}; #[cfg(not(feature = "tokio"))] #[async_trait::async_trait] impl ReadHalf for Arc> { async fn recvmsg(&mut self, buf: &mut [u8]) -> RecvmsgResult { match futures_util::AsyncReadExt::read(&mut self.as_ref(), buf).await { Err(e) => Err(e), Ok(len) => { #[cfg(unix)] let ret = (len, vec![]); #[cfg(not(unix))] let ret = len; Ok(ret) } } } #[cfg(windows)] async fn peer_credentials(&mut self) -> io::Result { let stream = self.clone(); crate::Task::spawn_blocking( move || { use crate::win32::{tcp_stream_get_peer_pid, ProcessToken}; let pid = tcp_stream_get_peer_pid(stream.get_ref())? as _; let sid = ProcessToken::open(if pid != 0 { Some(pid as _) } else { None }) .and_then(|process_token| process_token.sid())?; io::Result::Ok( crate::fdo::ConnectionCredentials::default() .set_process_id(pid) .set_windows_sid(sid), ) }, "peer credentials", ) .await } } #[cfg(not(feature = "tokio"))] #[async_trait::async_trait] impl WriteHalf for Arc> { async fn sendmsg( &mut self, buf: &[u8], #[cfg(unix)] fds: &[BorrowedFd<'_>], ) -> io::Result { #[cfg(unix)] if !fds.is_empty() { return Err(io::Error::new( io::ErrorKind::InvalidInput, "fds cannot be sent with a tcp stream", )); } futures_util::AsyncWriteExt::write(&mut self.as_ref(), buf).await } async fn close(&mut self) -> io::Result<()> { let stream = self.clone(); crate::Task::spawn_blocking( move || stream.get_ref().shutdown(std::net::Shutdown::Both), "close socket", ) .await } async fn peer_credentials(&mut self) -> io::Result { ReadHalf::peer_credentials(self).await } } #[cfg(feature = "tokio")] impl Socket for tokio::net::TcpStream { type ReadHalf = tokio::net::tcp::OwnedReadHalf; type WriteHalf = tokio::net::tcp::OwnedWriteHalf; fn split(self) -> Split { let (read, write) = self.into_split(); Split { read, write } } } #[cfg(feature = "tokio")] #[async_trait::async_trait] impl ReadHalf for tokio::net::tcp::OwnedReadHalf { async fn recvmsg(&mut self, buf: &mut [u8]) -> RecvmsgResult { use tokio::io::{AsyncReadExt, ReadBuf}; let mut read_buf = ReadBuf::new(buf); self.read_buf(&mut read_buf).await.map(|_| { let ret = read_buf.filled().len(); #[cfg(unix)] let ret = (ret, vec![]); ret }) } #[cfg(windows)] async fn peer_credentials(&mut self) -> io::Result { let peer_addr = self.peer_addr()?.clone(); crate::Task::spawn_blocking( move || win32_credentials_from_addr(&peer_addr), "peer credentials", ) .await } } #[cfg(feature = "tokio")] #[async_trait::async_trait] impl WriteHalf for tokio::net::tcp::OwnedWriteHalf { async fn sendmsg( &mut self, buf: &[u8], #[cfg(unix)] fds: &[BorrowedFd<'_>], ) -> io::Result { use tokio::io::AsyncWriteExt; #[cfg(unix)] if !fds.is_empty() { return Err(io::Error::new( io::ErrorKind::InvalidInput, "fds cannot be sent with a tcp stream", )); } self.write(buf).await } async fn close(&mut self) -> io::Result<()> { tokio::io::AsyncWriteExt::shutdown(self).await } #[cfg(windows)] async fn peer_credentials(&mut self) -> io::Result { let peer_addr = self.peer_addr()?.clone(); crate::Task::spawn_blocking( move || win32_credentials_from_addr(&peer_addr), "peer credentials", ) .await } } #[cfg(feature = "tokio")] #[cfg(windows)] fn win32_credentials_from_addr( addr: &std::net::SocketAddr, ) -> io::Result { use crate::win32::{socket_addr_get_pid, ProcessToken}; let pid = socket_addr_get_pid(addr)? as _; let sid = ProcessToken::open(if pid != 0 { Some(pid as _) } else { None }) .and_then(|process_token| process_token.sid())?; Ok(crate::fdo::ConnectionCredentials::default() .set_process_id(pid) .set_windows_sid(sid)) } zbus-4.3.1/src/connection/socket/unix.rs000064400000000000000000000300661046102023000163700ustar 00000000000000#[cfg(not(feature = "tokio"))] use async_io::Async; #[cfg(unix)] use std::os::unix::io::{AsRawFd, BorrowedFd, FromRawFd, RawFd}; #[cfg(all(unix, not(feature = "tokio")))] use std::os::unix::net::UnixStream; #[cfg(not(feature = "tokio"))] use std::sync::Arc; #[cfg(unix)] use std::{ future::poll_fn, io::{self, IoSlice, IoSliceMut}, os::fd::OwnedFd, task::Poll, }; #[cfg(all(windows, not(feature = "tokio")))] use uds_windows::UnixStream; #[cfg(unix)] use nix::{ cmsg_space, sys::socket::{recvmsg, sendmsg, ControlMessage, ControlMessageOwned, MsgFlags, UnixAddr}, }; #[cfg(unix)] use crate::utils::FDS_MAX; #[cfg(all(unix, not(feature = "tokio")))] #[async_trait::async_trait] impl super::ReadHalf for Arc> { async fn recvmsg(&mut self, buf: &mut [u8]) -> super::RecvmsgResult { poll_fn(|cx| { let (len, fds) = loop { match fd_recvmsg(self.as_raw_fd(), buf) { Err(e) if e.kind() == io::ErrorKind::Interrupted => {} Err(e) if e.kind() == io::ErrorKind::WouldBlock => match self.poll_readable(cx) { Poll::Pending => return Poll::Pending, Poll::Ready(res) => res?, }, v => break v?, } }; Poll::Ready(Ok((len, fds))) }) .await } /// Supports passing file descriptors. fn can_pass_unix_fd(&self) -> bool { true } async fn peer_credentials(&mut self) -> io::Result { get_unix_peer_creds(self).await } } #[cfg(all(unix, not(feature = "tokio")))] #[async_trait::async_trait] impl super::WriteHalf for Arc> { async fn sendmsg( &mut self, buffer: &[u8], #[cfg(unix)] fds: &[BorrowedFd<'_>], ) -> io::Result { poll_fn(|cx| loop { match fd_sendmsg( self.as_raw_fd(), buffer, #[cfg(unix)] fds, ) { Err(e) if e.kind() == io::ErrorKind::Interrupted => {} Err(e) if e.kind() == io::ErrorKind::WouldBlock => match self.poll_writable(cx) { Poll::Pending => return Poll::Pending, Poll::Ready(res) => res?, }, v => return Poll::Ready(v), } }) .await } async fn close(&mut self) -> io::Result<()> { let stream = self.clone(); crate::Task::spawn_blocking( move || stream.get_ref().shutdown(std::net::Shutdown::Both), "close socket", ) .await } #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] async fn send_zero_byte(&mut self) -> io::Result> { send_zero_byte(self).await.map(Some) } /// Supports passing file descriptors. fn can_pass_unix_fd(&self) -> bool { true } async fn peer_credentials(&mut self) -> io::Result { get_unix_peer_creds(self).await } } #[cfg(all(unix, feature = "tokio"))] impl super::Socket for tokio::net::UnixStream { type ReadHalf = tokio::net::unix::OwnedReadHalf; type WriteHalf = tokio::net::unix::OwnedWriteHalf; fn split(self) -> super::Split { let (read, write) = self.into_split(); super::Split { read, write } } } #[cfg(all(unix, feature = "tokio"))] #[async_trait::async_trait] impl super::ReadHalf for tokio::net::unix::OwnedReadHalf { async fn recvmsg(&mut self, buf: &mut [u8]) -> super::RecvmsgResult { let stream = self.as_ref(); poll_fn(|cx| { loop { match stream.try_io(tokio::io::Interest::READABLE, || { // We use own custom function for reading because we need to receive file // descriptors too. fd_recvmsg(stream.as_raw_fd(), buf) }) { Err(e) if e.kind() == io::ErrorKind::Interrupted => {} Err(e) if e.kind() == io::ErrorKind::WouldBlock => { match stream.poll_read_ready(cx) { Poll::Pending => return Poll::Pending, Poll::Ready(res) => res?, } } v => return Poll::Ready(v), } } }) .await } /// Supports passing file descriptors. fn can_pass_unix_fd(&self) -> bool { true } async fn peer_credentials(&mut self) -> io::Result { get_unix_peer_creds(self.as_ref()).await } } #[cfg(all(unix, feature = "tokio"))] #[async_trait::async_trait] impl super::WriteHalf for tokio::net::unix::OwnedWriteHalf { async fn sendmsg( &mut self, buffer: &[u8], #[cfg(unix)] fds: &[BorrowedFd<'_>], ) -> io::Result { let stream = self.as_ref(); poll_fn(|cx| loop { match stream.try_io(tokio::io::Interest::WRITABLE, || { fd_sendmsg( stream.as_raw_fd(), buffer, #[cfg(unix)] fds, ) }) { Err(e) if e.kind() == io::ErrorKind::Interrupted => {} Err(e) if e.kind() == io::ErrorKind::WouldBlock => { match stream.poll_write_ready(cx) { Poll::Pending => return Poll::Pending, Poll::Ready(res) => res?, } } v => return Poll::Ready(v), } }) .await } async fn close(&mut self) -> io::Result<()> { tokio::io::AsyncWriteExt::shutdown(self).await } #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] async fn send_zero_byte(&mut self) -> io::Result> { send_zero_byte(self.as_ref()).await.map(Some) } /// Supports passing file descriptors. fn can_pass_unix_fd(&self) -> bool { true } async fn peer_credentials(&mut self) -> io::Result { get_unix_peer_creds(self.as_ref()).await } } #[cfg(all(windows, not(feature = "tokio")))] #[async_trait::async_trait] impl super::ReadHalf for Arc> { async fn recvmsg(&mut self, buf: &mut [u8]) -> super::RecvmsgResult { match futures_util::AsyncReadExt::read(&mut self.as_ref(), buf).await { Err(e) => Err(e), Ok(len) => { #[cfg(unix)] let ret = (len, vec![]); #[cfg(not(unix))] let ret = len; Ok(ret) } } } async fn peer_credentials(&mut self) -> std::io::Result { let stream = self.clone(); crate::Task::spawn_blocking( move || { use crate::win32::{unix_stream_get_peer_pid, ProcessToken}; let pid = unix_stream_get_peer_pid(stream.get_ref())? as _; let sid = ProcessToken::open(if pid != 0 { Some(pid as _) } else { None }) .and_then(|process_token| process_token.sid())?; Ok(crate::fdo::ConnectionCredentials::default() .set_process_id(pid) .set_windows_sid(sid)) }, "peer credentials", ) .await } } #[cfg(all(windows, not(feature = "tokio")))] #[async_trait::async_trait] impl super::WriteHalf for Arc> { async fn sendmsg( &mut self, buf: &[u8], #[cfg(unix)] _fds: &[BorrowedFd<'_>], ) -> std::io::Result { futures_util::AsyncWriteExt::write(&mut self.as_ref(), buf).await } async fn close(&mut self) -> std::io::Result<()> { let stream = self.clone(); crate::Task::spawn_blocking( move || stream.get_ref().shutdown(std::net::Shutdown::Both), "close socket", ) .await } async fn peer_credentials(&mut self) -> std::io::Result { super::ReadHalf::peer_credentials(self).await } } #[cfg(unix)] fn fd_recvmsg(fd: RawFd, buffer: &mut [u8]) -> io::Result<(usize, Vec)> { let mut iov = [IoSliceMut::new(buffer)]; let mut cmsgspace = cmsg_space!([RawFd; FDS_MAX]); let msg = recvmsg::(fd, &mut iov, Some(&mut cmsgspace), MsgFlags::empty())?; if msg.bytes == 0 { return Err(io::Error::new( io::ErrorKind::BrokenPipe, "failed to read from socket", )); } let mut fds = vec![]; for cmsg in msg.cmsgs()? { #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] if let ControlMessageOwned::ScmCreds(_) = cmsg { continue; } if let ControlMessageOwned::ScmRights(fd) = cmsg { fds.extend(fd.iter().map(|&f| unsafe { OwnedFd::from_raw_fd(f) })); } else { return Err(io::Error::new( io::ErrorKind::InvalidData, "unexpected CMSG kind", )); } } Ok((msg.bytes, fds)) } #[cfg(unix)] fn fd_sendmsg(fd: RawFd, buffer: &[u8], fds: &[BorrowedFd<'_>]) -> io::Result { // FIXME: Remove this conversion once nix supports BorrowedFd here. // // Tracking issue: https://github.com/nix-rust/nix/issues/1750 let fds: Vec<_> = fds.iter().map(|f| f.as_raw_fd()).collect(); let cmsg = if !fds.is_empty() { vec![ControlMessage::ScmRights(&fds)] } else { vec![] }; let iov = [IoSlice::new(buffer)]; match sendmsg::(fd, &iov, &cmsg, MsgFlags::empty(), None) { // can it really happen? Ok(0) => Err(io::Error::new( io::ErrorKind::WriteZero, "failed to write to buffer", )), Ok(n) => Ok(n), Err(e) => Err(e.into()), } } #[cfg(unix)] async fn get_unix_peer_creds(fd: &impl AsRawFd) -> io::Result { let fd = fd.as_raw_fd(); // FIXME: Is it likely enough for sending of 1 byte to block, to justify a task (possibly // launching a thread in turn)? crate::Task::spawn_blocking(move || get_unix_peer_creds_blocking(fd), "peer credentials").await } #[cfg(unix)] fn get_unix_peer_creds_blocking(fd: RawFd) -> io::Result { // TODO: get this BorrowedFd directly from get_unix_peer_creds(), but this requires a // 'static lifetime due to the Task. let fd = unsafe { BorrowedFd::borrow_raw(fd) }; #[cfg(any(target_os = "android", target_os = "linux"))] { use nix::sys::socket::{getsockopt, sockopt::PeerCredentials}; getsockopt(&fd, PeerCredentials) .map(|creds| { crate::fdo::ConnectionCredentials::default() .set_process_id(creds.pid() as _) .set_unix_user_id(creds.uid()) }) .map_err(|e| e.into()) } #[cfg(any( target_os = "macos", target_os = "ios", target_os = "freebsd", target_os = "dragonfly", target_os = "openbsd", target_os = "netbsd" ))] { let uid = nix::unistd::getpeereid(fd).map(|(uid, _)| uid.into())?; // FIXME: Handle pid fetching too. Ok(crate::fdo::ConnectionCredentials::default().set_unix_user_id(uid)) } } // Send 0 byte as a separate SCM_CREDS message. #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] async fn send_zero_byte(fd: &impl AsRawFd) -> io::Result { let fd = fd.as_raw_fd(); crate::Task::spawn_blocking(move || send_zero_byte_blocking(fd), "send zero byte").await } #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] fn send_zero_byte_blocking(fd: RawFd) -> io::Result { let iov = [std::io::IoSlice::new(b"\0")]; sendmsg::<()>( fd, &iov, &[ControlMessage::ScmCreds], MsgFlags::empty(), None, ) .map_err(|e| e.into()) } zbus-4.3.1/src/connection/socket/vsock.rs000064400000000000000000000057001046102023000165270ustar 00000000000000#[cfg(feature = "tokio-vsock")] use super::{Socket, Split}; #[cfg(all(feature = "vsock", not(feature = "tokio")))] #[async_trait::async_trait] impl super::ReadHalf for std::sync::Arc> { async fn recvmsg(&mut self, buf: &mut [u8]) -> super::RecvmsgResult { match futures_util::AsyncReadExt::read(&mut self.as_ref(), buf).await { Err(e) => Err(e), Ok(len) => { #[cfg(unix)] let ret = (len, vec![]); #[cfg(not(unix))] let ret = len; Ok(ret) } } } } #[cfg(all(feature = "vsock", not(feature = "tokio")))] #[async_trait::async_trait] impl super::WriteHalf for std::sync::Arc> { async fn sendmsg( &mut self, buf: &[u8], #[cfg(unix)] fds: &[std::os::fd::BorrowedFd<'_>], ) -> std::io::Result { use std::io; #[cfg(unix)] if !fds.is_empty() { return Err(io::Error::new( io::ErrorKind::InvalidInput, "fds cannot be sent with a vsock stream", )); } futures_util::AsyncWriteExt::write(&mut self.as_ref(), buf).await } async fn close(&mut self) -> std::io::Result<()> { let stream = self.clone(); crate::Task::spawn_blocking( move || stream.get_ref().shutdown(std::net::Shutdown::Both), "close socket", ) .await } } #[cfg(feature = "tokio-vsock")] impl Socket for tokio_vsock::VsockStream { type ReadHalf = tokio_vsock::ReadHalf; type WriteHalf = tokio_vsock::WriteHalf; fn split(self) -> Split { let (read, write) = self.split(); Split { read, write } } } #[cfg(feature = "tokio-vsock")] #[async_trait::async_trait] impl super::ReadHalf for tokio_vsock::ReadHalf { async fn recvmsg(&mut self, buf: &mut [u8]) -> super::RecvmsgResult { use tokio::io::{AsyncReadExt, ReadBuf}; let mut read_buf = ReadBuf::new(buf); self.read_buf(&mut read_buf).await.map(|_| { let ret = read_buf.filled().len(); #[cfg(unix)] let ret = (ret, vec![]); ret }) } } #[cfg(feature = "tokio-vsock")] #[async_trait::async_trait] impl super::WriteHalf for tokio_vsock::WriteHalf { async fn sendmsg( &mut self, buf: &[u8], #[cfg(unix)] fds: &[std::os::fd::BorrowedFd<'_>], ) -> std::io::Result { use std::io; use tokio::io::AsyncWriteExt; #[cfg(unix)] if !fds.is_empty() { return Err(io::Error::new( io::ErrorKind::InvalidInput, "fds cannot be sent with a vsock stream", )); } self.write(buf).await } async fn close(&mut self) -> std::io::Result<()> { tokio::io::AsyncWriteExt::shutdown(self).await } } zbus-4.3.1/src/connection/socket_reader.rs000064400000000000000000000067741046102023000167400ustar 00000000000000use std::{collections::HashMap, sync::Arc}; use event_listener::Event; use tracing::{debug, instrument, trace}; use crate::{ async_lock::Mutex, connection::MsgBroadcaster, Executor, Message, OwnedMatchRule, Task, }; use super::socket::ReadHalf; #[derive(Debug)] pub(crate) struct SocketReader { socket: Box, senders: Arc, MsgBroadcaster>>>, already_received_bytes: Vec, #[cfg(unix)] already_received_fds: Vec, prev_seq: u64, activity_event: Arc, } impl SocketReader { pub fn new( socket: Box, senders: Arc, MsgBroadcaster>>>, already_received_bytes: Vec, #[cfg(unix)] already_received_fds: Vec, activity_event: Arc, ) -> Self { Self { socket, senders, already_received_bytes, #[cfg(unix)] already_received_fds, prev_seq: 0, activity_event, } } pub fn spawn(self, executor: &Executor<'_>) -> Task<()> { executor.spawn(self.receive_msg(), "socket reader") } // Keep receiving messages and put them on the queue. #[instrument(name = "socket reader", skip(self))] async fn receive_msg(mut self) { loop { trace!("Waiting for message on the socket.."); let msg = self.read_socket().await; match &msg { Ok(msg) => trace!("Message received on the socket: {:?}", msg), Err(e) => trace!("Error reading from the socket: {:?}", e), }; let mut senders = self.senders.lock().await; for (rule, sender) in &*senders { if let Ok(msg) = &msg { if let Some(rule) = rule.as_ref() { match rule.matches(msg) { Ok(true) => (), Ok(false) => continue, Err(e) => { debug!("Error matching message against rule: {:?}", e); continue; } } } } if let Err(e) = sender.broadcast_direct(msg.clone()).await { // An error would be due to either of these: // // 1. the channel is closed. // 2. No active receivers. // // In either case, just log it. trace!( "Error broadcasting message to stream for `{:?}`: {:?}", rule, e ); } } trace!("Broadcasted to all streams: {:?}", msg); if msg.is_err() { senders.clear(); trace!("Socket reading task stopped"); return; } } } #[instrument] async fn read_socket(&mut self) -> crate::Result { self.activity_event.notify(usize::MAX); let seq = self.prev_seq + 1; let msg = self .socket .receive_message( seq, &mut self.already_received_bytes, #[cfg(unix)] &mut self.already_received_fds, ) .await?; self.prev_seq = seq; Ok(msg) } } zbus-4.3.1/src/dbus_error.rs000064400000000000000000000016511046102023000141220ustar 00000000000000use crate::{ message::{Header, Message}, names::ErrorName, Result, }; /// A trait that needs to be implemented by error types to be returned from D-Bus methods. /// /// Typically, you'd use the [`crate::fdo::Error`] since that covers quite a lot of failures but /// occasionally you might find yourself needing to use a custom error type. You'll need to /// implement this trait for your error type. The easiest way to achieve that is to make use of the /// [`DBusError` macro][dm]. /// /// [dm]: derive.DBusError.html pub trait DBusError { /// Generate an error reply message for the given method call. fn create_reply(&self, msg: &Header<'_>) -> Result; // The name of the error. // // Every D-Bus error must have a name. See [`ErrorName`] for more information. fn name(&self) -> ErrorName<'_>; // The optional description for the error. fn description(&self) -> Option<&str>; } zbus-4.3.1/src/error.rs000064400000000000000000000230361046102023000131060ustar 00000000000000use static_assertions::assert_impl_all; use std::{convert::Infallible, error, fmt, io, sync::Arc}; use zbus_names::{Error as NamesError, InterfaceName, OwnedErrorName}; use zvariant::{Error as VariantError, ObjectPath}; use crate::{ fdo, message::{Message, Type}, }; /// The error type for `zbus`. /// /// The various errors that can be reported by this crate. #[derive(Debug)] #[non_exhaustive] #[allow(clippy::upper_case_acronyms)] pub enum Error { /// Interface not found InterfaceNotFound, /// Invalid D-Bus address. Address(String), /// An I/O error. InputOutput(Arc), /// Invalid message field. InvalidField, /// Data too large. ExcessData, /// A [zvariant](../zvariant/index.html) error. Variant(VariantError), /// A [zbus_names](../zbus_names/index.html) error. Names(NamesError), /// Endian signature invalid or doesn't match expectation. IncorrectEndian, /// Initial handshake error. Handshake(String), /// Unexpected or incorrect reply. InvalidReply, /// A D-Bus method error reply. // According to the spec, there can be all kinds of details in D-Bus errors but nobody adds // anything more than a string description. MethodError(OwnedErrorName, Option, Message), /// A required field is missing in the message headers. MissingField, /// Invalid D-Bus GUID. InvalidGUID, /// Unsupported function, or support currently lacking. Unsupported, /// A [`fdo::Error`] transformed into [`Error`]. FDO(Box), /// The requested name was already claimed by another peer. NameTaken, /// Invalid [match rule][MR] string. /// /// [MR]: https://dbus.freedesktop.org/doc/dbus-specification.html#message-bus-routing-match-rules InvalidMatchRule, /// Generic error. Failure(String), /// A required parameter was missing. MissingParameter(&'static str), /// Serial number in the message header is 0 (which is invalid). InvalidSerial, /// The given interface already exists at the given path. InterfaceExists(InterfaceName<'static>, ObjectPath<'static>), } assert_impl_all!(Error: Send, Sync, Unpin); impl PartialEq for Error { fn eq(&self, other: &Self) -> bool { match (self, other) { (Self::Address(_), Self::Address(_)) => true, (Self::InterfaceNotFound, Self::InterfaceNotFound) => true, (Self::Handshake(_), Self::Handshake(_)) => true, (Self::InvalidReply, Self::InvalidReply) => true, (Self::ExcessData, Self::ExcessData) => true, (Self::IncorrectEndian, Self::IncorrectEndian) => true, (Self::MethodError(_, _, _), Self::MethodError(_, _, _)) => true, (Self::MissingField, Self::MissingField) => true, (Self::InvalidGUID, Self::InvalidGUID) => true, (Self::InvalidSerial, Self::InvalidSerial) => true, (Self::Unsupported, Self::Unsupported) => true, (Self::FDO(s), Self::FDO(o)) => s == o, (Self::InvalidField, Self::InvalidField) => true, (Self::InvalidMatchRule, Self::InvalidMatchRule) => true, (Self::Variant(s), Self::Variant(o)) => s == o, (Self::Names(s), Self::Names(o)) => s == o, (Self::NameTaken, Self::NameTaken) => true, (Error::InputOutput(_), Self::InputOutput(_)) => false, (Self::Failure(s1), Self::Failure(s2)) => s1 == s2, (Self::InterfaceExists(s1, s2), Self::InterfaceExists(o1, o2)) => s1 == o1 && s2 == o2, (_, _) => false, } } } impl error::Error for Error { fn source(&self) -> Option<&(dyn error::Error + 'static)> { match self { Error::InterfaceNotFound => None, Error::Address(_) => None, Error::InputOutput(e) => Some(e), Error::ExcessData => None, Error::Handshake(_) => None, Error::IncorrectEndian => None, Error::Variant(e) => Some(e), Error::Names(e) => Some(e), Error::InvalidReply => None, Error::MethodError(_, _, _) => None, Error::InvalidGUID => None, Error::Unsupported => None, Error::FDO(e) => Some(e), Error::InvalidField => None, Error::MissingField => None, Error::NameTaken => None, Error::InvalidMatchRule => None, Error::Failure(_) => None, Error::MissingParameter(_) => None, Error::InvalidSerial => None, Error::InterfaceExists(_, _) => None, } } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Error::InterfaceNotFound => write!(f, "Interface not found"), Error::Address(e) => write!(f, "address error: {e}"), Error::ExcessData => write!(f, "excess data"), Error::InputOutput(e) => write!(f, "I/O error: {e}"), Error::Handshake(e) => write!(f, "D-Bus handshake failed: {e}"), Error::IncorrectEndian => write!(f, "incorrect endian"), Error::InvalidField => write!(f, "invalid message field"), Error::Variant(e) => write!(f, "{e}"), Error::Names(e) => write!(f, "{e}"), Error::InvalidReply => write!(f, "Invalid D-Bus method reply"), Error::MissingField => write!(f, "A required field is missing from message headers"), Error::MethodError(name, detail, _reply) => write!( f, "{}: {}", **name, detail.as_ref().map(|s| s.as_str()).unwrap_or("no details") ), Error::InvalidGUID => write!(f, "Invalid GUID"), Error::Unsupported => write!(f, "Connection support is lacking"), Error::FDO(e) => write!(f, "{e}"), Error::NameTaken => write!(f, "name already taken on the bus"), Error::InvalidMatchRule => write!(f, "Invalid match rule string"), Error::Failure(e) => write!(f, "{e}"), Error::MissingParameter(p) => { write!(f, "Parameter `{}` was not specified but it is required", p) } Error::InvalidSerial => write!(f, "Serial number in the message header is 0"), Error::InterfaceExists(i, p) => write!(f, "Interface `{i}` already exists at `{p}`"), } } } impl Clone for Error { fn clone(&self) -> Self { match self { Error::InterfaceNotFound => Error::InterfaceNotFound, Error::Address(e) => Error::Address(e.clone()), Error::ExcessData => Error::ExcessData, Error::InputOutput(e) => Error::InputOutput(e.clone()), Error::Handshake(e) => Error::Handshake(e.clone()), Error::IncorrectEndian => Error::IncorrectEndian, Error::InvalidField => Error::InvalidField, Error::Variant(e) => Error::Variant(e.clone()), Error::Names(e) => Error::Names(e.clone()), Error::InvalidReply => Error::InvalidReply, Error::MissingField => Error::MissingField, Error::MethodError(name, detail, reply) => { Error::MethodError(name.clone(), detail.clone(), reply.clone()) } Error::InvalidGUID => Error::InvalidGUID, Error::Unsupported => Error::Unsupported, Error::FDO(e) => Error::FDO(e.clone()), Error::NameTaken => Error::NameTaken, Error::InvalidMatchRule => Error::InvalidMatchRule, Error::Failure(e) => Error::Failure(e.clone()), Error::MissingParameter(p) => Error::MissingParameter(p), Error::InvalidSerial => Error::InvalidSerial, Error::InterfaceExists(i, p) => Error::InterfaceExists(i.clone(), p.clone()), } } } impl From for Error { fn from(val: io::Error) -> Self { Error::InputOutput(Arc::new(val)) } } #[cfg(unix)] impl From for Error { fn from(val: nix::Error) -> Self { io::Error::from_raw_os_error(val as i32).into() } } impl From for Error { fn from(val: VariantError) -> Self { Error::Variant(val) } } impl From for Error { fn from(val: NamesError) -> Self { match val { NamesError::Variant(e) => Error::Variant(e), e => Error::Names(e), } } } impl From for Error { fn from(val: fdo::Error) -> Self { match val { fdo::Error::ZBus(e) => e, e => Error::FDO(Box::new(e)), } } } impl From for Error { fn from(i: Infallible) -> Self { match i {} } } // For messages that are D-Bus error returns impl From for Error { fn from(message: Message) -> Error { // FIXME: Instead of checking this, we should have Method as trait and specific types for // each message type. let header = message.header(); if header.primary().msg_type() != Type::Error { return Error::InvalidReply; } if let Some(name) = header.error_name() { let name = name.to_owned().into(); match message.body().deserialize_unchecked::<&str>() { Ok(detail) => Error::MethodError(name, Some(String::from(detail)), message), Err(_) => Error::MethodError(name, None, message), } } else { Error::InvalidReply } } } /// Alias for a `Result` with the error type `zbus::Error`. pub type Result = std::result::Result; zbus-4.3.1/src/fdo.rs000064400000000000000000001320141046102023000125220ustar 00000000000000//! D-Bus standard interfaces. //! //! The D-Bus specification defines the message bus messages and some standard interfaces that may //! be useful across various D-Bus applications. This module provides their proxy. use enumflags2::{bitflags, BitFlags}; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use static_assertions::assert_impl_all; use std::collections::HashMap; use zbus_names::{ BusName, InterfaceName, OwnedBusName, OwnedInterfaceName, OwnedUniqueName, UniqueName, WellKnownName, }; use zvariant::{ DeserializeDict, ObjectPath, Optional, OwnedObjectPath, OwnedValue, SerializeDict, Type, Value, }; use crate::{ interface, message::Header, object_server::SignalContext, proxy, DBusError, ObjectServer, OwnedGuid, }; #[rustfmt::skip] macro_rules! gen_introspectable_proxy { ($gen_async:literal, $gen_blocking:literal) => { /// Proxy for the `org.freedesktop.DBus.Introspectable` interface. #[proxy( interface = "org.freedesktop.DBus.Introspectable", default_path = "/", gen_async = $gen_async, gen_blocking = $gen_blocking, )] trait Introspectable { /// Returns an XML description of the object, including its interfaces (with signals and /// methods), objects below it in the object path tree, and its properties. fn introspect(&self) -> Result; } }; } gen_introspectable_proxy!(true, false); assert_impl_all!(IntrospectableProxy<'_>: Send, Sync, Unpin); /// Server-side implementation for the `org.freedesktop.DBus.Introspectable` interface. /// This interface is implemented automatically for any object registered to the /// [ObjectServer](crate::ObjectServer). pub(crate) struct Introspectable; #[interface(name = "org.freedesktop.DBus.Introspectable")] impl Introspectable { async fn introspect( &self, #[zbus(object_server)] server: &ObjectServer, #[zbus(header)] header: Header<'_>, ) -> Result { let path = header.path().ok_or(crate::Error::MissingField)?; let root = server.root().read().await; let node = root .get_child(path) .ok_or_else(|| Error::UnknownObject(format!("Unknown object '{path}'")))?; Ok(node.introspect().await) } } #[rustfmt::skip] macro_rules! gen_properties_proxy { ($gen_async:literal, $gen_blocking:literal) => { /// Proxy for the `org.freedesktop.DBus.Properties` interface. #[proxy( interface = "org.freedesktop.DBus.Properties", gen_async = $gen_async, gen_blocking = $gen_blocking, )] trait Properties { /// Get a property value. async fn get( &self, interface_name: InterfaceName<'_>, property_name: &str, ) -> Result; /// Set a property value. async fn set( &self, interface_name: InterfaceName<'_>, property_name: &str, value: &Value<'_>, ) -> Result<()>; /// Get all properties. async fn get_all( &self, interface_name: Optional>, ) -> Result>; #[zbus(signal)] async fn properties_changed( &self, interface_name: InterfaceName<'_>, changed_properties: HashMap<&str, Value<'_>>, invalidated_properties: Vec<&str>, ) -> Result<()>; } }; } gen_properties_proxy!(true, false); assert_impl_all!(PropertiesProxy<'_>: Send, Sync, Unpin); /// Server-side implementation for the `org.freedesktop.DBus.Properties` interface. /// This interface is implemented automatically for any object registered to the /// [ObjectServer]. pub struct Properties; assert_impl_all!(Properties: Send, Sync, Unpin); #[interface(name = "org.freedesktop.DBus.Properties")] impl Properties { async fn get( &self, interface_name: InterfaceName<'_>, property_name: &str, #[zbus(object_server)] server: &ObjectServer, #[zbus(header)] header: Header<'_>, ) -> Result { let path = header.path().ok_or(crate::Error::MissingField)?; let root = server.root().read().await; let iface = root .get_child(path) .and_then(|node| node.interface_lock(interface_name.as_ref())) .ok_or_else(|| { Error::UnknownInterface(format!("Unknown interface '{interface_name}'")) })?; let res = iface.instance.read().await.get(property_name).await; res.unwrap_or_else(|| { Err(Error::UnknownProperty(format!( "Unknown property '{property_name}'" ))) }) } async fn set( &self, interface_name: InterfaceName<'_>, property_name: &str, value: Value<'_>, #[zbus(object_server)] server: &ObjectServer, #[zbus(header)] header: Header<'_>, #[zbus(signal_context)] ctxt: SignalContext<'_>, ) -> Result<()> { let path = header.path().ok_or(crate::Error::MissingField)?; let root = server.root().read().await; let iface = root .get_child(path) .and_then(|node| node.interface_lock(interface_name.as_ref())) .ok_or_else(|| { Error::UnknownInterface(format!("Unknown interface '{interface_name}'")) })?; match iface .instance .read() .await .set(property_name, &value, &ctxt) { zbus::object_server::DispatchResult::RequiresMut => {} zbus::object_server::DispatchResult::NotFound => { return Err(Error::UnknownProperty(format!( "Unknown property '{property_name}'" ))); } zbus::object_server::DispatchResult::Async(f) => { return f.await.map_err(Into::into); } } let res = iface .instance .write() .await .set_mut(property_name, &value, &ctxt) .await; res.unwrap_or_else(|| { Err(Error::UnknownProperty(format!( "Unknown property '{property_name}'" ))) }) } async fn get_all( &self, interface_name: InterfaceName<'_>, #[zbus(object_server)] server: &ObjectServer, #[zbus(header)] header: Header<'_>, ) -> Result> { let path = header.path().ok_or(crate::Error::MissingField)?; let root = server.root().read().await; let iface = root .get_child(path) .and_then(|node| node.interface_lock(interface_name.as_ref())) .ok_or_else(|| { Error::UnknownInterface(format!("Unknown interface '{interface_name}'")) })?; let res = iface.instance.read().await.get_all().await?; Ok(res) } /// Emits the `org.freedesktop.DBus.Properties.PropertiesChanged` signal. #[zbus(signal)] #[rustfmt::skip] pub async fn properties_changed( ctxt: &SignalContext<'_>, interface_name: InterfaceName<'_>, changed_properties: &HashMap<&str, &Value<'_>>, invalidated_properties: &[&str], ) -> zbus::Result<()>; } /// The type returned by the [`ObjectManagerProxy::get_managed_objects`] method. pub type ManagedObjects = HashMap>>; #[rustfmt::skip] macro_rules! gen_object_manager_proxy { ($gen_async:literal, $gen_blocking:literal) => { /// Proxy for the `org.freedesktop.DBus.ObjectManager` interface. /// /// **NB:** Changes to properties on existing interfaces are not reported using this interface. /// Please use [`PropertiesProxy::receive_properties_changed`] to monitor changes to properties on /// objects. #[proxy( interface = "org.freedesktop.DBus.ObjectManager", gen_async = $gen_async, gen_blocking = $gen_blocking, )] trait ObjectManager { /// The return value of this method is a dict whose keys are object paths. All returned object /// paths are children of the object path implementing this interface, i.e. their object paths /// start with the ObjectManager's object path plus '/'. /// /// Each value is a dict whose keys are interfaces names. Each value in this inner dict is the /// same dict that would be returned by the org.freedesktop.DBus.Properties.GetAll() method for /// that combination of object path and interface. If an interface has no properties, the empty /// dict is returned. fn get_managed_objects(&self) -> Result; /// This signal is emitted when either a new object is added or when an existing object gains /// one or more interfaces. The `interfaces_and_properties` argument contains a map with the /// interfaces and properties (if any) that have been added to the given object path. #[zbus(signal)] fn interfaces_added( &self, object_path: ObjectPath<'_>, interfaces_and_properties: HashMap<&str, HashMap<&str, Value<'_>>>, ) -> Result<()>; /// This signal is emitted whenever an object is removed or it loses one or more interfaces. /// The `interfaces` parameters contains a list of the interfaces that were removed. #[zbus(signal)] fn interfaces_removed( &self, object_path: ObjectPath<'_>, interfaces: Vec<&str>, ) -> Result<()>; } }; } gen_object_manager_proxy!(true, false); assert_impl_all!(ObjectManagerProxy<'_>: Send, Sync, Unpin); /// Service-side [Object Manager][om] interface implementation. /// /// The recommended path to add this interface at is the path form of the well-known name of a D-Bus /// service, or below. For example, if a D-Bus service is available at the well-known name /// `net.example.ExampleService1`, this interface should typically be registered at /// `/net/example/ExampleService1`, or below (to allow for multiple object managers in a service). /// /// It is supported, but not recommended, to add this interface at the root path, `/`. /// /// When added to an `ObjectServer`, `InterfacesAdded` signal is emitted for all the objects under /// the `path` its added at. You can use this fact to minimize the signal emissions by populating /// the entire (sub)tree under `path` before registering an object manager. /// /// [om]: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-objectmanager #[derive(Debug, Clone)] pub struct ObjectManager; #[interface(name = "org.freedesktop.DBus.ObjectManager")] impl ObjectManager { async fn get_managed_objects( &self, #[zbus(object_server)] server: &ObjectServer, #[zbus(header)] header: Header<'_>, ) -> Result { let path = header.path().ok_or(crate::Error::MissingField)?; let root = server.root().read().await; let node = root .get_child(path) .ok_or_else(|| Error::UnknownObject(format!("Unknown object '{path}'")))?; node.get_managed_objects().await } /// This signal is emitted when either a new object is added or when an existing object gains /// one or more interfaces. The `interfaces_and_properties` argument contains a map with the /// interfaces and properties (if any) that have been added to the given object path. #[zbus(signal)] pub async fn interfaces_added( ctxt: &SignalContext<'_>, object_path: &ObjectPath<'_>, interfaces_and_properties: &HashMap, HashMap<&str, Value<'_>>>, ) -> zbus::Result<()>; /// This signal is emitted whenever an object is removed or it loses one or more interfaces. /// The `interfaces` parameters contains a list of the interfaces that were removed. #[zbus(signal)] pub async fn interfaces_removed( ctxt: &SignalContext<'_>, object_path: &ObjectPath<'_>, interfaces: &[InterfaceName<'_>], ) -> zbus::Result<()>; } #[rustfmt::skip] macro_rules! gen_peer_proxy { ($gen_async:literal, $gen_blocking:literal) => { /// Proxy for the `org.freedesktop.DBus.Peer` interface. #[proxy( interface = "org.freedesktop.DBus.Peer", gen_async = $gen_async, gen_blocking = $gen_blocking, )] trait Peer { /// On receipt, an application should do nothing other than reply as usual. It does not matter /// which object path a ping is sent to. fn ping(&self) -> Result<()>; /// An application should reply the containing a hex-encoded UUID representing the identity of /// the machine the process is running on. This UUID must be the same for all processes on a /// single system at least until that system next reboots. It should be the same across reboots /// if possible, but this is not always possible to implement and is not guaranteed. It does not /// matter which object path a GetMachineId is sent to. fn get_machine_id(&self) -> Result; } }; } gen_peer_proxy!(true, false); assert_impl_all!(PeerProxy<'_>: Send, Sync, Unpin); pub(crate) struct Peer; /// Server-side implementation for the `org.freedesktop.DBus.Peer` interface. /// This interface is implemented automatically for any object registered to the /// [ObjectServer](crate::ObjectServer). #[interface(name = "org.freedesktop.DBus.Peer")] impl Peer { fn ping(&self) {} fn get_machine_id(&self) -> Result { let mut id = match std::fs::read_to_string("/var/lib/dbus/machine-id") { Ok(id) => id, Err(e) => { if let Ok(id) = std::fs::read_to_string("/etc/machine-id") { id } else { return Err(Error::IOError(format!( "Failed to read from /var/lib/dbus/machine-id or /etc/machine-id: {e}" ))); } } }; let len = id.trim_end().len(); id.truncate(len); Ok(id) } } #[rustfmt::skip] macro_rules! gen_monitoring_proxy { ($gen_async:literal, $gen_blocking:literal) => { /// Proxy for the `org.freedesktop.DBus.Monitoring` interface. #[proxy( interface = "org.freedesktop.DBus.Monitoring", default_service = "org.freedesktop.DBus", default_path = "/org/freedesktop/DBus", gen_async = $gen_async, gen_blocking = $gen_blocking, )] trait Monitoring { /// Converts the connection into a monitor connection which can be used as a /// debugging/monitoring tool. /// /// After this call successfully returns, sending any messages on the bus will result /// in an error. This is why this method takes ownership of `self`, since there is not /// much use for the proxy anymore. It is highly recommended to convert the underlying /// [`Connection`] to a [`MessageStream`] and iterate over messages from the stream, /// after this call. /// /// See [the spec] for details on all the implications and caveats. /// /// # Arguments /// /// * `match_rules` - A list of match rules describing the messages you want to receive. /// An empty list means you are want to receive all messages going through the bus. /// * `flags` - This argument is currently unused by the bus. Just pass a `0`. /// /// [the spec]: https://dbus.freedesktop.org/doc/dbus-specification.html#bus-messages-become-monitor /// [`Connection`]: https://docs.rs/zbus/latest/zbus/connection/struct.Connection.html /// [`MessageStream`]: https://docs.rs/zbus/latest/zbus/struct.MessageStream.html fn become_monitor( self, match_rules: &[crate::MatchRule<'_>], flags: u32, ) -> Result<()>; } }; } gen_monitoring_proxy!(true, false); assert_impl_all!(MonitoringProxy<'_>: Send, Sync, Unpin); #[rustfmt::skip] macro_rules! gen_stats_proxy { ($gen_async:literal, $gen_blocking:literal) => { /// Proxy for the `org.freedesktop.DBus.Debug.Stats` interface. #[proxy( interface = "org.freedesktop.DBus.Debug.Stats", default_service = "org.freedesktop.DBus", default_path = "/org/freedesktop/DBus", gen_async = $gen_async, gen_blocking = $gen_blocking, )] trait Stats { /// GetStats (undocumented) fn get_stats(&self) -> Result>>; /// GetConnectionStats (undocumented) fn get_connection_stats(&self, name: BusName<'_>) -> Result>>; /// GetAllMatchRules (undocumented) fn get_all_match_rules(&self) -> Result< Vec< HashMap< crate::names::OwnedUniqueName, Vec, > > >; } }; } gen_stats_proxy!(true, false); assert_impl_all!(StatsProxy<'_>: Send, Sync, Unpin); /// The flags used by the bus [`request_name`] method. /// /// [`request_name`]: struct.DBusProxy.html#method.request_name #[bitflags] #[repr(u32)] #[derive(Type, Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] pub enum RequestNameFlags { /// If an application A specifies this flag and succeeds in becoming the owner of the name, and /// another application B later calls [`request_name`] with the [`ReplaceExisting`] flag, then /// application A will lose ownership and receive a `org.freedesktop.DBus.NameLost` signal, and /// application B will become the new owner. If [`AllowReplacement`] is not specified by /// application A, or [`ReplaceExisting`] is not specified by application B, then application B /// will not replace application A as the owner. /// /// [`ReplaceExisting`]: enum.RequestNameFlags.html#variant.ReplaceExisting /// [`AllowReplacement`]: enum.RequestNameFlags.html#variant.AllowReplacement /// [`request_name`]: struct.DBusProxy.html#method.request_name AllowReplacement = 0x01, /// Try to replace the current owner if there is one. If this flag is not set the application /// will only become the owner of the name if there is no current owner. If this flag is set, /// the application will replace the current owner if the current owner specified /// [`AllowReplacement`]. /// /// [`AllowReplacement`]: enum.RequestNameFlags.html#variant.AllowReplacement ReplaceExisting = 0x02, /// Without this flag, if an application requests a name that is already owned, the /// application will be placed in a queue to own the name when the current owner gives it /// up. If this flag is given, the application will not be placed in the queue, the /// request for the name will simply fail. This flag also affects behavior when an /// application is replaced as name owner; by default the application moves back into the /// waiting queue, unless this flag was provided when the application became the name /// owner. DoNotQueue = 0x04, } assert_impl_all!(RequestNameFlags: Send, Sync, Unpin); /// The return code of the [`request_name`] method. /// /// [`request_name`]: struct.DBusProxy.html#method.request_name #[repr(u32)] #[derive(Deserialize_repr, Serialize_repr, Type, Debug, PartialEq, Eq)] pub enum RequestNameReply { /// The caller is now the primary owner of the name, replacing any previous owner. Either the /// name had no owner before, or the caller specified [`ReplaceExisting`] and the current owner /// specified [`AllowReplacement`]. /// /// [`ReplaceExisting`]: enum.RequestNameFlags.html#variant.ReplaceExisting /// [`AllowReplacement`]: enum.RequestNameFlags.html#variant.AllowReplacement PrimaryOwner = 0x01, /// The name already had an owner, [`DoNotQueue`] was not specified, and either the current /// owner did not specify [`AllowReplacement`] or the requesting application did not specify /// [`ReplaceExisting`]. /// /// [`DoNotQueue`]: enum.RequestNameFlags.html#variant.DoNotQueue /// [`ReplaceExisting`]: enum.RequestNameFlags.html#variant.ReplaceExisting /// [`AllowReplacement`]: enum.RequestNameFlags.html#variant.AllowReplacement InQueue = 0x02, /// The name already has an owner, [`DoNotQueue`] was specified, and either /// [`AllowReplacement`] was not specified by the current owner, or [`ReplaceExisting`] was /// not specified by the requesting application. /// /// [`DoNotQueue`]: enum.RequestNameFlags.html#variant.DoNotQueue /// [`ReplaceExisting`]: enum.RequestNameFlags.html#variant.ReplaceExisting /// [`AllowReplacement`]: enum.RequestNameFlags.html#variant.AllowReplacement Exists = 0x03, /// The application trying to request ownership of a name is already the owner of it. AlreadyOwner = 0x04, } assert_impl_all!(RequestNameReply: Send, Sync, Unpin); /// The return code of the [`release_name`] method. /// /// [`release_name`]: struct.DBusProxy.html#method.release_name #[repr(u32)] #[derive(Deserialize_repr, Serialize_repr, Type, Debug, PartialEq, Eq)] pub enum ReleaseNameReply { /// The caller has released their claim on the given name. Either the caller was the primary /// owner of the name, and the name is now unused or taken by somebody waiting in the queue for /// the name, or the caller was waiting in the queue for the name and has now been removed from /// the queue. Released = 0x01, /// The given name does not exist on this bus. NonExistent = 0x02, /// The caller was not the primary owner of this name, and was also not waiting in the queue to /// own this name. NotOwner = 0x03, } assert_impl_all!(ReleaseNameReply: Send, Sync, Unpin); /// Credentials of a process connected to a bus server. /// /// If unable to determine certain credentials (for instance, because the process is not on the same /// machine as the bus daemon, or because this version of the bus daemon does not support a /// particular security framework), or if the values of those credentials cannot be represented as /// documented here, then those credentials are omitted. /// /// **Note**: unknown keys, in particular those with "." that are not from the specification, will /// be ignored. Use your own implementation or contribute your keys here, or in the specification. #[derive(Debug, Default, DeserializeDict, PartialEq, Eq, SerializeDict, Type)] #[zvariant(signature = "a{sv}")] pub struct ConnectionCredentials { #[zvariant(rename = "UnixUserID")] pub(crate) unix_user_id: Option, #[zvariant(rename = "UnixGroupIDs")] pub(crate) unix_group_ids: Option>, #[zvariant(rename = "ProcessID")] pub(crate) process_id: Option, #[zvariant(rename = "WindowsSID")] pub(crate) windows_sid: Option, #[zvariant(rename = "LinuxSecurityLabel")] pub(crate) linux_security_label: Option>, } impl ConnectionCredentials { /// The numeric Unix user ID, as defined by POSIX. pub fn unix_user_id(&self) -> Option { self.unix_user_id } /// The numeric Unix group IDs (including both the primary group and the supplementary groups), /// as defined by POSIX, in numerically sorted order. This array is either complete or absent: /// if the message bus is able to determine some but not all of the caller's groups, or if one /// of the groups is not representable in a UINT32, it must not add this credential to the /// dictionary. pub fn unix_group_ids(&self) -> Option<&Vec> { self.unix_group_ids.as_ref() } /// Same as [`ConnectionCredentials::unix_group_ids`], but consumes `self` and returns the group /// IDs Vec. pub fn into_unix_group_ids(self) -> Option> { self.unix_group_ids } /// The numeric process ID, on platforms that have this concept. On Unix, this is the process ID /// defined by POSIX. pub fn process_id(&self) -> Option { self.process_id } /// The Windows security identifier in its string form, e.g. /// `S-1-5-21-3623811015-3361044348-30300820-1013` for a domain or local computer user or /// "S-1-5-18` for the LOCAL_SYSTEM user. pub fn windows_sid(&self) -> Option<&String> { self.windows_sid.as_ref() } /// Same as [`ConnectionCredentials::windows_sid`], but consumes `self` and returns the SID /// string. pub fn into_windows_sid(self) -> Option { self.windows_sid } /// On Linux systems, the security label that would result from the SO_PEERSEC getsockopt call. /// The array contains the non-zero bytes of the security label in an unspecified /// ASCII-compatible encoding, followed by a single zero byte. /// /// For example, the SELinux context `system_u:system_r:init_t:s0` (a string of length 27) would /// be encoded as 28 bytes ending with `':', 's', '0', '\x00'` /// /// On SELinux systems this is the SELinux context, as output by `ps -Z` or `ls -Z`. Typical /// values might include `system_u:system_r:init_t:s0`, /// `unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023`, or /// `unconfined_u:unconfined_r:chrome_sandbox_t:s0-s0:c0.c1023`. /// /// On Smack systems, this is the Smack label. Typical values might include `_`, `*`, `User`, /// `System` or `System::Shared`. /// /// On AppArmor systems, this is the AppArmor context, a composite string encoding the AppArmor /// label (one or more profiles) and the enforcement mode. Typical values might include /// `unconfined`, `/usr/bin/firefox (enforce)` or `user1 (complain)`. pub fn linux_security_label(&self) -> Option<&Vec> { self.linux_security_label.as_ref() } /// Same as [`ConnectionCredentials::linux_security_label`], but consumes `self` and returns /// the security label bytes. pub fn into_linux_security_label(self) -> Option> { self.linux_security_label } /// Set the numeric Unix user ID, as defined by POSIX. pub fn set_unix_user_id(mut self, unix_user_id: u32) -> Self { self.unix_user_id = Some(unix_user_id); self } /// Add a numeric Unix group ID. /// /// See [`ConnectionCredentials::unix_group_ids`] for more information. pub fn add_unix_group_id(mut self, unix_group_id: u32) -> Self { self.unix_group_ids .get_or_insert_with(Vec::new) .push(unix_group_id); self } /// Set the numeric process ID, on platforms that have this concept. /// /// See [`ConnectionCredentials::process_id`] for more information. pub fn set_process_id(mut self, process_id: u32) -> Self { self.process_id = Some(process_id); self } /// Set the Windows security identifier in its string form. pub fn set_windows_sid(mut self, windows_sid: String) -> Self { self.windows_sid = Some(windows_sid); self } /// Set the Linux security label. /// /// See [`ConnectionCredentials::linux_security_label`] for more information. pub fn set_linux_security_label(mut self, linux_security_label: Vec) -> Self { self.linux_security_label = Some(linux_security_label); self } } #[rustfmt::skip] macro_rules! gen_dbus_proxy { ($gen_async:literal, $gen_blocking:literal) => { /// Proxy for the `org.freedesktop.DBus` interface. #[proxy( default_service = "org.freedesktop.DBus", default_path = "/org/freedesktop/DBus", interface = "org.freedesktop.DBus", gen_async = $gen_async, gen_blocking = $gen_blocking, )] trait DBus { /// Adds a match rule to match messages going through the message bus #[zbus(name = "AddMatch")] fn add_match_rule(&self, rule: crate::MatchRule<'_>) -> Result<()>; /// Returns auditing data used by Solaris ADT, in an unspecified binary format. fn get_adt_audit_session_data(&self, bus_name: BusName<'_>) -> Result>; /// Returns as many credentials as possible for the process connected to the server. fn get_connection_credentials( &self, bus_name: BusName<'_>, ) -> Result; /// Returns the security context used by SELinux, in an unspecified format. #[zbus(name = "GetConnectionSELinuxSecurityContext")] fn get_connection_selinux_security_context( &self, bus_name: BusName<'_>, ) -> Result>; /// Returns the Unix process ID of the process connected to the server. #[zbus(name = "GetConnectionUnixProcessID")] fn get_connection_unix_process_id(&self, bus_name: BusName<'_>) -> Result; /// Returns the Unix user ID of the process connected to the server. fn get_connection_unix_user(&self, bus_name: BusName<'_>) -> Result; /// Gets the unique ID of the bus. fn get_id(&self) -> Result; /// Returns the unique connection name of the primary owner of the name given. fn get_name_owner(&self, name: BusName<'_>) -> Result; /// Returns the unique name assigned to the connection. fn hello(&self) -> Result; /// Returns a list of all names that can be activated on the bus. fn list_activatable_names(&self) -> Result>; /// Returns a list of all currently-owned names on the bus. fn list_names(&self) -> Result>; /// List the connections currently queued for a bus name. fn list_queued_owners(&self, name: WellKnownName<'_>) -> Result>; /// Checks if the specified name exists (currently has an owner). fn name_has_owner(&self, name: BusName<'_>) -> Result; /// Ask the message bus to release the method caller's claim to the given name. fn release_name(&self, name: WellKnownName<'_>) -> Result; /// Reload server configuration. fn reload_config(&self) -> Result<()>; /// Removes the first rule that matches. #[zbus(name = "RemoveMatch")] fn remove_match_rule(&self, rule: crate::MatchRule<'_>) -> Result<()>; /// Ask the message bus to assign the given name to the method caller. fn request_name( &self, name: WellKnownName<'_>, flags: BitFlags, ) -> Result; /// Tries to launch the executable associated with a name (service /// activation), as an explicit request. fn start_service_by_name(&self, name: WellKnownName<'_>, flags: u32) -> Result; /// This method adds to or modifies that environment when activating services. fn update_activation_environment(&self, environment: HashMap<&str, &str>) -> Result<()>; /// This signal indicates that the owner of a name has /// changed. It's also the signal to use to detect the appearance /// of new names on the bus. #[zbus(signal)] fn name_owner_changed( &self, name: BusName<'_>, old_owner: Optional>, new_owner: Optional>, ); /// This signal is sent to a specific application when it loses ownership of a name. #[zbus(signal)] fn name_lost(&self, name: BusName<'_>); /// This signal is sent to a specific application when it gains ownership of a name. #[zbus(signal)] fn name_acquired(&self, name: BusName<'_>); /// This property lists abstract “features” provided by the message bus, and can be used by /// clients to detect the capabilities of the message bus with which they are communicating. #[zbus(property)] fn features(&self) -> Result>; /// This property lists interfaces provided by the `/org/freedesktop/DBus` object, and can be /// used by clients to detect the capabilities of the message bus with which they are /// communicating. Unlike the standard Introspectable interface, querying this property does not /// require parsing XML. This property was added in version 1.11.x of the reference /// implementation of the message bus. /// /// The standard `org.freedesktop.DBus` and `org.freedesktop.DBus.Properties` interfaces are not /// included in the value of this property, because their presence can be inferred from the fact /// that a method call on `org.freedesktop.DBus.Properties` asking for properties of /// `org.freedesktop.DBus` was successful. The standard `org.freedesktop.DBus.Peer` and /// `org.freedesktop.DBus.Introspectable` interfaces are not included in the value of this /// property either, because they do not indicate features of the message bus implementation. #[zbus(property)] fn interfaces(&self) -> Result>; } }; } gen_dbus_proxy!(true, false); assert_impl_all!(DBusProxy<'_>: Send, Sync, Unpin); /// Errors from #[derive(Clone, Debug, DBusError, PartialEq)] #[zbus(prefix = "org.freedesktop.DBus.Error", impl_display = true)] #[allow(clippy::upper_case_acronyms)] pub enum Error { /// Unknown or fall-through zbus error. #[zbus(error)] ZBus(zbus::Error), /// A generic error; "something went wrong" - see the error message for more. Failed(String), /// There was not enough memory to complete an operation. NoMemory(String), /// The bus doesn't know how to launch a service to supply the bus name you wanted. ServiceUnknown(String), /// The bus name you referenced doesn't exist (i.e. no application owns it). NameHasNoOwner(String), /// No reply to a message expecting one, usually means a timeout occurred. NoReply(String), /// Something went wrong reading or writing to a socket, for example. IOError(String), /// A D-Bus bus address was malformed. BadAddress(String), /// Requested operation isn't supported (like ENOSYS on UNIX). NotSupported(String), /// Some limited resource is exhausted. LimitsExceeded(String), /// Security restrictions don't allow doing what you're trying to do. AccessDenied(String), /// Authentication didn't work. AuthFailed(String), /// Unable to connect to server (probably caused by ECONNREFUSED on a socket). NoServer(String), /// Certain timeout errors, possibly ETIMEDOUT on a socket. /// Note that `TimedOut` is used for message reply timeouts. Timeout(String), /// No network access (probably ENETUNREACH on a socket). NoNetwork(String), /// Can't bind a socket since its address is in use (i.e. EADDRINUSE). AddressInUse(String), /// The connection is disconnected and you're trying to use it. Disconnected(String), /// Invalid arguments passed to a method call. InvalidArgs(String), /// Missing file. FileNotFound(String), /// Existing file and the operation you're using does not silently overwrite. FileExists(String), /// Method name you invoked isn't known by the object you invoked it on. UnknownMethod(String), /// Object you invoked a method on isn't known. UnknownObject(String), /// Interface you invoked a method on isn't known by the object. UnknownInterface(String), /// Property you tried to access isn't known by the object. UnknownProperty(String), /// Property you tried to set is read-only. PropertyReadOnly(String), /// Certain timeout errors, e.g. while starting a service. TimedOut(String), /// Tried to remove or modify a match rule that didn't exist. MatchRuleNotFound(String), /// The match rule isn't syntactically valid. MatchRuleInvalid(String), /// While starting a new process, the exec() call failed. #[zbus(name = "Spawn.ExecFailed")] SpawnExecFailed(String), /// While starting a new process, the fork() call failed. #[zbus(name = "Spawn.ForkFailed")] SpawnForkFailed(String), /// While starting a new process, the child exited with a status code. #[zbus(name = "Spawn.ChildExited")] SpawnChildExited(String), /// While starting a new process, the child exited on a signal. #[zbus(name = "Spawn.ChildSignaled")] SpawnChildSignaled(String), /// While starting a new process, something went wrong. #[zbus(name = "Spawn.Failed")] SpawnFailed(String), /// We failed to setup the environment correctly. #[zbus(name = "Spawn.FailedToSetup")] SpawnFailedToSetup(String), /// We failed to setup the config parser correctly. #[zbus(name = "Spawn.ConfigInvalid")] SpawnConfigInvalid(String), /// Bus name was not valid. #[zbus(name = "Spawn.ServiceNotValid")] SpawnServiceNotValid(String), /// Service file not found in system-services directory. #[zbus(name = "Spawn.ServiceNotFound")] SpawnServiceNotFound(String), /// Permissions are incorrect on the setuid helper. #[zbus(name = "Spawn.PermissionsInvalid")] SpawnPermissionsInvalid(String), /// Service file invalid (Name, User or Exec missing). #[zbus(name = "Spawn.FileInvalid")] SpawnFileInvalid(String), /// There was not enough memory to complete the operation. #[zbus(name = "Spawn.NoMemory")] SpawnNoMemory(String), /// Tried to get a UNIX process ID and it wasn't available. UnixProcessIdUnknown(String), /// A type signature is not valid. InvalidSignature(String), /// A file contains invalid syntax or is otherwise broken. InvalidFileContent(String), /// Asked for SELinux security context and it wasn't available. SELinuxSecurityContextUnknown(String), /// Asked for ADT audit data and it wasn't available. AdtAuditDataUnknown(String), /// There's already an object with the requested object path. ObjectPathInUse(String), /// The message meta data does not match the payload. e.g. expected number of file descriptors /// were not sent over the socket this message was received on. InconsistentMessage(String), /// The message is not allowed without performing interactive authorization, but could have /// succeeded if an interactive authorization step was allowed. InteractiveAuthorizationRequired(String), /// The connection is not from a container, or the specified container instance does not exist. NotContainer(String), } assert_impl_all!(Error: Send, Sync, Unpin); /// Alias for a `Result` with the error type [`zbus::fdo::Error`]. /// /// [`zbus::fdo::Error`]: enum.Error.html pub type Result = std::result::Result; #[cfg(test)] mod tests { use crate::{fdo, message::Message, DBusError, Error}; use futures_util::StreamExt; use ntest::timeout; use test_log::test; use tokio::runtime; use zbus_names::WellKnownName; #[test] fn error_from_zerror() { let m = Message::method("/", "foo") .unwrap() .destination(":1.2") .unwrap() .build(&()) .unwrap(); let m = Message::method_error(&m, "org.freedesktop.DBus.Error.TimedOut") .unwrap() .build(&("so long")) .unwrap(); let e: Error = m.into(); let e: fdo::Error = e.into(); assert_eq!(e, fdo::Error::TimedOut("so long".to_string()),); assert_eq!(e.name(), "org.freedesktop.DBus.Error.TimedOut"); assert_eq!(e.description(), Some("so long")); } #[test] #[timeout(15000)] fn signal() { // Multi-threaded scheduler. runtime::Runtime::new().unwrap().block_on(test_signal()); // single-threaded scheduler. runtime::Builder::new_current_thread() .enable_io() .build() .unwrap() .block_on(test_signal()); } async fn test_signal() { let conn = crate::Connection::session().await.unwrap(); let proxy = fdo::DBusProxy::new(&conn).await.unwrap(); // Register a well-known name with the session bus and ensure we get the appropriate // signals called for that. let well_known = "org.freedesktop.zbus.FdoSignalStreamTest"; let unique_name = conn.unique_name().unwrap(); let owner_change_stream = proxy .receive_name_owner_changed_with_args(&[(0, well_known), (2, unique_name.as_str())]) .await .unwrap(); let name_acquired_stream = proxy .receive_name_acquired_with_args(&[(0, well_known)]) .await .unwrap(); let mut stream = owner_change_stream.zip(name_acquired_stream); let well_known: WellKnownName<'static> = well_known.try_into().unwrap(); proxy .request_name( well_known.as_ref(), fdo::RequestNameFlags::ReplaceExisting.into(), ) .await .unwrap(); let (name_owner_changed, name_acquired) = stream.next().await.unwrap(); assert_eq!(name_owner_changed.args().unwrap().name(), &well_known); assert_eq!( *name_owner_changed .args() .unwrap() .new_owner() .as_ref() .unwrap(), *unique_name ); assert_eq!(name_acquired.args().unwrap().name(), &well_known); let result = proxy.release_name(well_known.as_ref()).await.unwrap(); assert_eq!(result, fdo::ReleaseNameReply::Released); let result = proxy.release_name(well_known).await.unwrap(); assert_eq!(result, fdo::ReleaseNameReply::NonExistent); let _stream = proxy .receive_features_changed() .await .filter_map(|changed| async move { let v = changed.get().await.ok(); dbg!(v) }); } #[test] #[timeout(15000)] fn no_object_manager_signals_before_hello() { use zbus::blocking; // We were emitting `InterfacesAdded` signals before `Hello` was called, which is wrong and // results in us getting disconnected by the bus. This test case ensures we don't do that // and also that the signals are eventually emitted. // Let's first create an interator to get the signals (it has to be another connection). let conn = blocking::Connection::session().unwrap(); let mut iterator = blocking::MessageIterator::for_match_rule( zbus::MatchRule::builder() .msg_type(zbus::MessageType::Signal) .interface("org.freedesktop.DBus.ObjectManager") .unwrap() .path("/org/zbus/NoObjectManagerSignalsBeforeHello") .unwrap() .build(), &conn, None, ) .unwrap(); // Now create the service side. struct TestObj; #[super::interface(name = "org.zbus.TestObj")] impl TestObj { #[zbus(property)] fn test(&self) -> String { "test".into() } } let _conn = blocking::connection::Builder::session() .unwrap() .name("org.zbus.NoObjectManagerSignalsBeforeHello") .unwrap() .serve_at("/org/zbus/NoObjectManagerSignalsBeforeHello/Obj", TestObj) .unwrap() .serve_at( "/org/zbus/NoObjectManagerSignalsBeforeHello", super::ObjectManager, ) .unwrap() .build() .unwrap(); // Let's see if the `InterfacesAdded` signal was emitted. let msg = iterator.next().unwrap().unwrap(); let signal = super::InterfacesAdded::from_message(msg).unwrap(); assert_eq!( signal.args().unwrap().interfaces_and_properties, vec![( "org.zbus.TestObj", vec![("Test", zvariant::Value::new("test"))] .into_iter() .collect() )] .into_iter() .collect() ); } } zbus-4.3.1/src/guid.rs000064400000000000000000000144641046102023000127120ustar 00000000000000use std::{ borrow::{Borrow, Cow}, fmt::{self, Debug, Display, Formatter}, iter::repeat_with, ops::Deref, str::FromStr, time::{SystemTime, UNIX_EPOCH}, }; use serde::{de, Deserialize, Serialize}; use static_assertions::assert_impl_all; use zvariant::{Str, Type}; /// A D-Bus server GUID. /// /// See the D-Bus specification [UUIDs chapter] for details. /// /// You can create a `Guid` from an existing string with [`Guid::try_from::<&str>`][TryFrom]. /// /// [UUIDs chapter]: https://dbus.freedesktop.org/doc/dbus-specification.html#uuids /// [TryFrom]: #impl-TryFrom%3C%26%27_%20str%3E #[derive(Clone, Debug, PartialEq, Eq, Hash, Type, Serialize)] pub struct Guid<'g>(Str<'g>); assert_impl_all!(Guid<'_>: Send, Sync, Unpin); impl Guid<'_> { /// Generate a D-Bus GUID that can be used with e.g. /// [`connection::Builder::server`](crate::connection::Builder::server). pub fn generate() -> Guid<'static> { let r: Vec = repeat_with(rand::random::).take(3).collect(); let r3 = match SystemTime::now().duration_since(UNIX_EPOCH) { Ok(n) => n.as_secs() as u32, Err(_) => rand::random::(), }; let s = format!("{:08x}{:08x}{:08x}{:08x}", r[0], r[1], r[2], r3); Guid(s.into()) } /// Returns a string slice for the GUID. pub fn as_str(&self) -> &str { self.0.as_str() } /// Same as `try_from`, except it takes a `&'static str`. pub fn from_static_str(guid: &'static str) -> crate::Result { validate_guid(guid)?; Ok(Self(Str::from_static(guid))) } /// Create an owned copy of the GUID. pub fn to_owned(&self) -> Guid<'static> { Guid(self.0.to_owned()) } } impl fmt::Display for Guid<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.as_str()) } } impl<'g> TryFrom<&'g str> for Guid<'g> { type Error = crate::Error; /// Creates a GUID from a string with 32 hex digits. /// /// Returns `Err(`[`Error::InvalidGUID`]`)` if the provided string is not a well-formed GUID. /// /// [`Error::InvalidGUID`]: enum.Error.html#variant.InvalidGUID fn try_from(value: &'g str) -> std::result::Result { validate_guid(value)?; Ok(Self(Str::from(value))) } } impl<'g> TryFrom> for Guid<'g> { type Error = crate::Error; /// Creates a GUID from a string with 32 hex digits. /// /// Returns `Err(`[`Error::InvalidGUID`]`)` if the provided string is not a well-formed GUID. /// /// [`Error::InvalidGUID`]: enum.Error.html#variant.InvalidGUID fn try_from(value: Str<'g>) -> std::result::Result { validate_guid(&value)?; Ok(Guid(value)) } } impl TryFrom for Guid<'_> { type Error = crate::Error; fn try_from(value: String) -> std::result::Result { validate_guid(&value)?; Ok(Guid(value.into())) } } impl<'g> TryFrom> for Guid<'g> { type Error = crate::Error; fn try_from(value: Cow<'g, str>) -> std::result::Result { validate_guid(&value)?; Ok(Guid(value.into())) } } impl FromStr for Guid<'static> { type Err = crate::Error; fn from_str(s: &str) -> Result { s.try_into().map(|guid: Guid<'_>| guid.to_owned()) } } impl<'de> Deserialize<'de> for Guid<'de> { fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { >::deserialize(deserializer) .and_then(|s| s.try_into().map_err(serde::de::Error::custom)) } } fn validate_guid(value: &str) -> crate::Result<()> { if value.as_bytes().len() != 32 || value.chars().any(|c| !char::is_ascii_hexdigit(&c)) { return Err(crate::Error::InvalidGUID); } Ok(()) } impl From> for String { fn from(guid: Guid<'_>) -> Self { guid.0.into() } } impl Deref for Guid<'_> { type Target = str; fn deref(&self) -> &Self::Target { self.as_str() } } impl AsRef for Guid<'_> { fn as_ref(&self) -> &str { self.as_str() } } impl Borrow for Guid<'_> { fn borrow(&self) -> &str { self.as_str() } } /// Owned version of [`Guid`]. #[derive(Clone, Debug, PartialEq, Eq, Hash, Type, Serialize)] pub struct OwnedGuid(#[serde(borrow)] Guid<'static>); assert_impl_all!(OwnedGuid: Send, Sync, Unpin); impl OwnedGuid { /// Get a reference to the inner [`Guid`]. pub fn inner(&self) -> &Guid<'static> { &self.0 } } impl Deref for OwnedGuid { type Target = Guid<'static>; fn deref(&self) -> &Self::Target { &self.0 } } impl Borrow for OwnedGuid { fn borrow(&self) -> &str { self.0.as_str() } } impl From for Guid<'_> { fn from(o: OwnedGuid) -> Self { o.0 } } impl<'unowned, 'owned: 'unowned> From<&'owned OwnedGuid> for Guid<'unowned> { fn from(guid: &'owned OwnedGuid) -> Self { guid.0.clone() } } impl From> for OwnedGuid { fn from(guid: Guid<'_>) -> Self { OwnedGuid(guid.to_owned()) } } impl From for Str<'_> { fn from(value: OwnedGuid) -> Self { value.0 .0 } } impl<'de> Deserialize<'de> for OwnedGuid { fn deserialize(deserializer: D) -> std::result::Result where D: de::Deserializer<'de>, { String::deserialize(deserializer) .and_then(|n| Guid::try_from(n).map_err(|e| de::Error::custom(e.to_string()))) .map(Self) } } impl PartialEq<&str> for OwnedGuid { fn eq(&self, other: &&str) -> bool { self.as_str() == *other } } impl PartialEq> for OwnedGuid { fn eq(&self, other: &Guid<'_>) -> bool { self.0 == *other } } impl Display for OwnedGuid { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { Display::fmt(&Guid::from(self), f) } } #[cfg(test)] mod tests { use crate::Guid; use test_log::test; #[test] fn generate() { let u1 = Guid::generate(); let u2 = Guid::generate(); assert_eq!(u1.as_str().len(), 32); assert_eq!(u2.as_str().len(), 32); assert_ne!(u1, u2); assert_ne!(u1.as_str(), u2.as_str()); } } zbus-4.3.1/src/lib.rs000064400000000000000000001164371046102023000125330ustar 00000000000000#![deny(rust_2018_idioms)] #![doc( html_logo_url = "https://raw.githubusercontent.com/dbus2/zbus/9f7a90d2b594ddc48b7a5f39fda5e00cd56a7dfb/logo.png" )] #![doc = include_str!("../README.md")] #![doc(test(attr( warn(unused), deny(warnings), allow(dead_code), // W/o this, we seem to get some bogus warning about `extern crate zbus`. allow(unused_extern_crates), )))] #[cfg(doctest)] mod doctests { // Repo README. doc_comment::doctest!("../../README.md"); // Book markdown checks doc_comment::doctest!("../../book/src/client.md"); doc_comment::doctest!("../../book/src/concepts.md"); // The connection chapter contains a p2p example. #[cfg(feature = "p2p")] doc_comment::doctest!("../../book/src/connection.md"); doc_comment::doctest!("../../book/src/contributors.md"); doc_comment::doctest!("../../book/src/introduction.md"); doc_comment::doctest!("../../book/src/service.md"); doc_comment::doctest!("../../book/src/blocking.md"); doc_comment::doctest!("../../book/src/faq.md"); } #[cfg(all(not(feature = "async-io"), not(feature = "tokio")))] mod error_message { #[cfg(windows)] compile_error!("Either \"async-io\" (default) or \"tokio\" must be enabled. On Windows \"async-io\" is (currently) required for UNIX socket support"); #[cfg(not(windows))] compile_error!("Either \"async-io\" (default) or \"tokio\" must be enabled."); } #[cfg(windows)] mod win32; mod dbus_error; pub use dbus_error::*; mod error; pub use error::*; pub mod address; pub use address::Address; mod guid; pub use guid::*; pub mod message; pub use message::Message; #[deprecated(since = "4.0.0", note = "Use `message::Builder` instead")] #[doc(hidden)] pub use message::Builder as MessageBuilder; #[deprecated(since = "4.0.0", note = "Use `message::EndianSig` instead")] #[doc(hidden)] pub use message::EndianSig; #[doc(hidden)] pub use message::Flags as MessageFlags; #[deprecated(since = "4.0.0", note = "Use `message::Header` instead")] #[doc(hidden)] pub use message::Header as MessageHeader; #[deprecated(since = "4.0.0", note = "Use `message::PrimaryHeader` instead")] #[doc(hidden)] pub use message::PrimaryHeader as MessagePrimaryHeader; #[deprecated(since = "4.0.0", note = "Use `message::Sequence` instead")] #[doc(hidden)] pub use message::Sequence as MessageSequence; #[deprecated(since = "4.0.0", note = "Use `message::Type` instead")] #[doc(hidden)] pub use message::Type as MessageType; #[deprecated(since = "4.0.0", note = "Use `message::NATIVE_ENDIAN_SIG` instead")] #[doc(hidden)] pub use message::NATIVE_ENDIAN_SIG; pub mod connection; /// Alias for `connection` module, for convenience. pub use connection as conn; pub use connection::{handshake::AuthMechanism, Connection}; #[deprecated(since = "4.0.0", note = "Use `connection::Builder` instead")] #[doc(hidden)] pub use connection::Builder as ConnectionBuilder; mod message_stream; pub use message_stream::*; mod abstractions; pub use abstractions::*; pub mod match_rule; pub use match_rule::{MatchRule, OwnedMatchRule}; #[deprecated(since = "4.0.0", note = "Use `match_rule::Builder` instead")] #[doc(hidden)] pub use match_rule::Builder as MatchRuleBuilder; #[deprecated(since = "4.0.0", note = "Use `match_rule::PathSpec` instead")] #[doc(hidden)] pub use match_rule::PathSpec as MatchRulePathSpec; pub mod proxy; pub use proxy::Proxy; #[deprecated(since = "4.0.0", note = "Use `proxy::Builder` instead")] #[doc(hidden)] pub use proxy::Builder as ProxyBuilder; #[deprecated(since = "4.0.0", note = "Use `proxy::CacheProperties` instead")] #[doc(hidden)] pub use proxy::CacheProperties; #[deprecated(since = "4.0.0", note = "Use `proxy::MethodFlags` instead")] #[doc(hidden)] pub use proxy::MethodFlags; #[deprecated(since = "4.0.0", note = "Use `proxy::OwnerChangedStream` instead")] #[doc(hidden)] pub use proxy::OwnerChangedStream; #[deprecated(since = "4.0.0", note = "Use `proxy::PropertyChanged` instead")] #[doc(hidden)] pub use proxy::PropertyChanged; #[deprecated(since = "4.0.0", note = "Use `proxy::PropertyStream` instead")] #[doc(hidden)] pub use proxy::PropertyStream; #[deprecated(since = "4.0.0", note = "Use `proxy::ProxyDefault` instead")] #[doc(hidden)] pub use proxy::ProxyDefault; pub mod object_server; pub use object_server::ObjectServer; #[deprecated(since = "4.0.0", note = "Use `object_server::DispatchResult` instead")] #[doc(hidden)] pub use object_server::DispatchResult; #[deprecated(since = "4.0.0", note = "Use `object_server::Interface` instead")] #[doc(hidden)] pub use object_server::Interface; #[deprecated(since = "4.0.0", note = "Use `object_server::InterfaceDeref` instead")] #[doc(hidden)] pub use object_server::InterfaceDeref; #[deprecated( since = "4.0.0", note = "Use `object_server::InterfaceDerefMut` instead" )] #[doc(hidden)] pub use object_server::InterfaceDerefMut; #[deprecated(since = "4.0.0", note = "Use `object_server::InterfaceRef` instead")] #[doc(hidden)] pub use object_server::InterfaceRef; #[deprecated( since = "4.0.0", note = "Use `object_server::ResponseDispatchNotifier` instead" )] #[doc(hidden)] pub use object_server::ResponseDispatchNotifier; #[deprecated(since = "4.0.0", note = "Use `object_server::SignalContext` instead")] #[doc(hidden)] pub use object_server::SignalContext; mod utils; pub use utils::*; #[macro_use] pub mod fdo; #[deprecated(since = "4.0.0", note = "Use `connection::Socket` instead")] #[doc(hidden)] pub use connection::Socket; pub mod blocking; pub use zbus_macros::{interface, proxy, DBusError}; // Old names used for backwards compatibility pub use zbus_macros::{dbus_interface, dbus_proxy}; // Required for the macros to function within this crate. extern crate self as zbus; // Macro support module, not part of the public API. #[doc(hidden)] pub mod export { pub use async_trait; pub use futures_core; pub use futures_util; pub use ordered_stream; pub use serde; pub use static_assertions; } pub use zbus_names as names; pub use zvariant; #[cfg(test)] mod tests { use std::{ collections::HashMap, sync::{mpsc::channel, Arc, Condvar, Mutex}, }; use crate::utils::block_on; use enumflags2::BitFlags; use event_listener::Event; use ntest::timeout; use test_log::test; use tracing::{debug, instrument, trace}; use zbus_names::UniqueName; use zvariant::{OwnedObjectPath, OwnedValue, Type}; use crate::{ blocking::{self, MessageIterator}, fdo::{RequestNameFlags, RequestNameReply}, message::Message, object_server::SignalContext, Connection, Result, }; #[test] fn msg() { let m = Message::method("/org/freedesktop/DBus", "GetMachineId") .unwrap() .destination("org.freedesktop.DBus") .unwrap() .interface("org.freedesktop.DBus.Peer") .unwrap() .build(&()) .unwrap(); let hdr = m.header(); assert_eq!(hdr.path().unwrap(), "/org/freedesktop/DBus"); assert_eq!(hdr.interface().unwrap(), "org.freedesktop.DBus.Peer"); assert_eq!(hdr.member().unwrap(), "GetMachineId"); } #[test] #[timeout(15000)] #[instrument] fn basic_connection() { let connection = blocking::Connection::session() .map_err(|e| { debug!("error: {}", e); e }) .unwrap(); // Hello method is already called during connection creation so subsequent calls are // expected to fail but only with a D-Bus error. match connection.call_method( Some("org.freedesktop.DBus"), "/org/freedesktop/DBus", Some("org.freedesktop.DBus"), "Hello", &(), ) { Err(crate::Error::MethodError(_, _, _)) => (), Err(e) => panic!("{}", e), _ => panic!(), }; } #[test] #[timeout(15000)] fn basic_connection_async() { block_on(test_basic_connection()).unwrap(); } async fn test_basic_connection() -> Result<()> { let connection = Connection::session().await?; match connection .call_method( Some("org.freedesktop.DBus"), "/org/freedesktop/DBus", Some("org.freedesktop.DBus"), "Hello", &(), ) .await { Err(crate::Error::MethodError(_, _, _)) => (), Err(e) => panic!("{}", e), _ => panic!(), }; Ok(()) } #[cfg(all(unix, not(target_os = "macos")))] #[test] #[timeout(15000)] fn fdpass_systemd() { use std::{fs::File, os::unix::io::AsRawFd}; use zvariant::OwnedFd; let connection = blocking::Connection::system().unwrap(); let reply = connection .call_method( Some("org.freedesktop.systemd1"), "/org/freedesktop/systemd1", Some("org.freedesktop.systemd1.Manager"), "DumpByFileDescriptor", &(), ) .unwrap(); let fd: OwnedFd = reply.body().deserialize().unwrap(); assert!(fd.as_raw_fd() >= 0); let f = File::from(std::os::fd::OwnedFd::from(fd)); f.metadata().unwrap(); } #[test] #[instrument] #[timeout(15000)] fn freedesktop_api() { let connection = blocking::Connection::session() .map_err(|e| { debug!("error: {}", e); e }) .unwrap(); let reply = connection .call_method( Some("org.freedesktop.DBus"), "/org/freedesktop/DBus", Some("org.freedesktop.DBus"), "RequestName", &( "org.freedesktop.zbus.sync", BitFlags::from(RequestNameFlags::ReplaceExisting), ), ) .unwrap(); let body = reply.body(); assert!(body.signature().map(|s| s == "u").unwrap()); let reply: RequestNameReply = body.deserialize().unwrap(); assert_eq!(reply, RequestNameReply::PrimaryOwner); let reply = connection .call_method( Some("org.freedesktop.DBus"), "/org/freedesktop/DBus", Some("org.freedesktop.DBus"), "GetId", &(), ) .unwrap(); let body = reply.body(); assert!(body.signature().map(|s| s == <&str>::signature()).unwrap()); let id: &str = body.deserialize().unwrap(); debug!("Unique ID of the bus: {}", id); let reply = connection .call_method( Some("org.freedesktop.DBus"), "/org/freedesktop/DBus", Some("org.freedesktop.DBus"), "NameHasOwner", &"org.freedesktop.zbus.sync", ) .unwrap(); let body = reply.body(); assert!(body.signature().map(|s| s == bool::signature()).unwrap()); assert!(body.deserialize::().unwrap()); let reply = connection .call_method( Some("org.freedesktop.DBus"), "/org/freedesktop/DBus", Some("org.freedesktop.DBus"), "GetNameOwner", &"org.freedesktop.zbus.sync", ) .unwrap(); let body = reply.body(); assert!(body.signature().map(|s| s == <&str>::signature()).unwrap()); assert_eq!( body.deserialize::>().unwrap(), *connection.unique_name().unwrap(), ); let reply = connection .call_method( Some("org.freedesktop.DBus"), "/org/freedesktop/DBus", Some("org.freedesktop.DBus"), "GetConnectionCredentials", &"org.freedesktop.DBus", ) .unwrap(); let body = reply.body(); assert!(body.signature().map(|s| s == "a{sv}").unwrap()); let hashmap: HashMap<&str, OwnedValue> = body.deserialize().unwrap(); let pid: u32 = (&hashmap["ProcessID"]).try_into().unwrap(); debug!("DBus bus PID: {}", pid); #[cfg(unix)] { let uid: u32 = (&hashmap["UnixUserID"]).try_into().unwrap(); debug!("DBus bus UID: {}", uid); } } #[test] #[timeout(15000)] fn freedesktop_api_async() { block_on(test_freedesktop_api()).unwrap(); } #[instrument] async fn test_freedesktop_api() -> Result<()> { let connection = Connection::session().await?; let reply = connection .call_method( Some("org.freedesktop.DBus"), "/org/freedesktop/DBus", Some("org.freedesktop.DBus"), "RequestName", &( "org.freedesktop.zbus.async", BitFlags::from(RequestNameFlags::ReplaceExisting), ), ) .await .unwrap(); let body = reply.body(); assert!(body.signature().map(|s| s == "u").unwrap()); let reply: RequestNameReply = body.deserialize().unwrap(); assert_eq!(reply, RequestNameReply::PrimaryOwner); let reply = connection .call_method( Some("org.freedesktop.DBus"), "/org/freedesktop/DBus", Some("org.freedesktop.DBus"), "GetId", &(), ) .await .unwrap(); let body = reply.body(); assert!(body.signature().map(|s| s == <&str>::signature()).unwrap()); let id: &str = body.deserialize().unwrap(); debug!("Unique ID of the bus: {}", id); let reply = connection .call_method( Some("org.freedesktop.DBus"), "/org/freedesktop/DBus", Some("org.freedesktop.DBus"), "NameHasOwner", &"org.freedesktop.zbus.async", ) .await .unwrap(); let body = reply.body(); assert!(body.signature().map(|s| s == bool::signature()).unwrap()); assert!(body.deserialize::().unwrap()); let reply = connection .call_method( Some("org.freedesktop.DBus"), "/org/freedesktop/DBus", Some("org.freedesktop.DBus"), "GetNameOwner", &"org.freedesktop.zbus.async", ) .await .unwrap(); let body = reply.body(); assert!(body.signature().map(|s| s == <&str>::signature()).unwrap()); assert_eq!( body.deserialize::>().unwrap(), *connection.unique_name().unwrap(), ); let reply = connection .call_method( Some("org.freedesktop.DBus"), "/org/freedesktop/DBus", Some("org.freedesktop.DBus"), "GetConnectionCredentials", &"org.freedesktop.DBus", ) .await .unwrap(); let body = reply.body(); assert!(body.signature().map(|s| s == "a{sv}").unwrap()); let hashmap: HashMap<&str, OwnedValue> = body.deserialize().unwrap(); let pid: u32 = (&hashmap["ProcessID"]).try_into().unwrap(); debug!("DBus bus PID: {}", pid); #[cfg(unix)] { let uid: u32 = (&hashmap["UnixUserID"]).try_into().unwrap(); debug!("DBus bus UID: {}", uid); } Ok(()) } #[test] #[timeout(15000)] fn issue_68() { // Tests the fix for https://github.com/dbus2/zbus/issues/68 // // While this is not an exact reproduction of the issue 68, the underlying problem it // produces is exactly the same: `Connection::call_method` dropping all incoming messages // while waiting for the reply to the method call. let conn = blocking::Connection::session().unwrap(); let stream = MessageIterator::from(&conn); // Send a message as client before service starts to process messages let client_conn = blocking::Connection::session().unwrap(); let destination = conn.unique_name().map(UniqueName::<'_>::from).unwrap(); let msg = Message::method("/org/freedesktop/Issue68", "Ping") .unwrap() .destination(destination) .unwrap() .interface("org.freedesktop.Issue68") .unwrap() .build(&()) .unwrap(); let serial = msg.primary_header().serial_num(); client_conn.send(&msg).unwrap(); crate::blocking::fdo::DBusProxy::new(&conn) .unwrap() .get_id() .unwrap(); for m in stream { let msg = m.unwrap(); if msg.primary_header().serial_num() == serial { break; } } } #[test] #[timeout(15000)] fn issue104() { // Tests the fix for https://github.com/dbus2/zbus/issues/104 // // The issue is caused by `proxy` macro adding `()` around the return value of methods // with multiple out arguments, ending up with double parenthesis around the signature of // the return type and zbus only removing the outer `()` only and then it not matching the // signature we receive on the reply message. use zvariant::{ObjectPath, Value}; struct Secret; #[super::interface(name = "org.freedesktop.Secret.Service")] impl Secret { fn open_session( &self, _algorithm: &str, input: Value<'_>, ) -> zbus::fdo::Result<(OwnedValue, OwnedObjectPath)> { Ok(( OwnedValue::try_from(input).unwrap(), ObjectPath::try_from("/org/freedesktop/secrets/Blah") .unwrap() .into(), )) } } let secret = Secret; let conn = blocking::connection::Builder::session() .unwrap() .serve_at("/org/freedesktop/secrets", secret) .unwrap() .build() .unwrap(); let service_name = conn.unique_name().unwrap().clone(); { let conn = blocking::Connection::session().unwrap(); #[super::proxy( interface = "org.freedesktop.Secret.Service", assume_defaults = true, gen_async = false )] trait Secret { fn open_session( &self, algorithm: &str, input: &zvariant::Value<'_>, ) -> zbus::Result<(OwnedValue, OwnedObjectPath)>; } let proxy = SecretProxy::builder(&conn) .destination(UniqueName::from(service_name)) .unwrap() .path("/org/freedesktop/secrets") .unwrap() .build() .unwrap(); trace!("Calling open_session"); proxy.open_session("plain", &Value::from("")).unwrap(); trace!("Called open_session"); }; } // This one we just want to see if it builds, no need to run it. For details see: // // https://github.com/dbus2/zbus/issues/121 #[test] #[ignore] fn issue_121() { use crate::proxy; #[proxy(interface = "org.freedesktop.IBus", assume_defaults = true)] trait IBus { /// CurrentInputContext property #[zbus(property)] fn current_input_context(&self) -> zbus::Result; /// Engines property #[zbus(property)] fn engines(&self) -> zbus::Result>; } } #[test] #[timeout(15000)] fn issue_122() { let conn = blocking::Connection::session().unwrap(); let stream = MessageIterator::from(&conn); #[allow(clippy::mutex_atomic)] let pair = Arc::new((Mutex::new(false), Condvar::new())); let pair2 = Arc::clone(&pair); let child = std::thread::spawn(move || { { let (lock, cvar) = &*pair2; let mut started = lock.lock().unwrap(); *started = true; cvar.notify_one(); } for m in stream { let msg = m.unwrap(); let hdr = msg.header(); if hdr.member().map(|m| m.as_str()) == Some("ZBusIssue122") { break; } } }); // Wait for the receiving thread to start up. let (lock, cvar) = &*pair; let mut started = lock.lock().unwrap(); while !*started { started = cvar.wait(started).unwrap(); } // Still give it some milliseconds to ensure it's already blocking on receive_message call // when we send a message. std::thread::sleep(std::time::Duration::from_millis(100)); let destination = conn.unique_name().map(UniqueName::<'_>::from).unwrap(); let msg = Message::method("/does/not/matter", "ZBusIssue122") .unwrap() .destination(destination) .unwrap() .build(&()) .unwrap(); conn.send(&msg).unwrap(); child.join().unwrap(); } #[test] #[ignore] fn issue_81() { use zbus::proxy; use zvariant::{OwnedValue, Type}; #[derive( Debug, PartialEq, Eq, Clone, Type, OwnedValue, serde::Serialize, serde::Deserialize, )] pub struct DbusPath { id: String, path: OwnedObjectPath, } #[proxy(assume_defaults = true)] trait Session { #[zbus(property)] fn sessions_tuple(&self) -> zbus::Result<(String, String)>; #[zbus(property)] fn sessions_struct(&self) -> zbus::Result; } } #[test] #[timeout(15000)] fn issue173() { // Tests the fix for https://github.com/dbus2/zbus/issues/173 // // The issue is caused by proxy not keeping track of its destination's owner changes // (service restart) and failing to receive signals as a result. let (tx, rx) = channel(); let child = std::thread::spawn(move || { let conn = blocking::Connection::session().unwrap(); #[super::proxy( interface = "org.freedesktop.zbus.ComeAndGo", default_service = "org.freedesktop.zbus.ComeAndGo", default_path = "/org/freedesktop/zbus/ComeAndGo" )] trait ComeAndGo { #[zbus(signal)] fn the_signal(&self) -> zbus::Result<()>; } let proxy = ComeAndGoProxyBlocking::new(&conn).unwrap(); let signals = proxy.receive_the_signal().unwrap(); tx.send(()).unwrap(); // We receive two signals, each time from different unique names. W/o the fix for // issue#173, the second iteration hangs. for _ in signals.take(2) { tx.send(()).unwrap(); } }); struct ComeAndGo; #[super::interface(name = "org.freedesktop.zbus.ComeAndGo")] impl ComeAndGo { #[zbus(signal)] async fn the_signal(signal_ctxt: &SignalContext<'_>) -> zbus::Result<()>; } rx.recv().unwrap(); for _ in 0..2 { let conn = blocking::connection::Builder::session() .unwrap() .serve_at("/org/freedesktop/zbus/ComeAndGo", ComeAndGo) .unwrap() .name("org.freedesktop.zbus.ComeAndGo") .unwrap() .build() .unwrap(); let iface_ref = conn .object_server() .interface::<_, ComeAndGo>("/org/freedesktop/zbus/ComeAndGo") .unwrap(); block_on(ComeAndGo::the_signal(iface_ref.signal_context())).unwrap(); rx.recv().unwrap(); // Now we release the name ownership to use a different connection (i-e new unique // name). conn.release_name("org.freedesktop.zbus.ComeAndGo").unwrap(); } child.join().unwrap(); } #[test] #[timeout(15000)] fn uncached_property() { block_on(test_uncached_property()).unwrap(); } async fn test_uncached_property() -> Result<()> { // A dummy boolean test service. It starts as `false` and can be // flipped to `true`. Two properties can access the inner value, with // and without caching. #[derive(Default)] struct ServiceUncachedPropertyTest(bool); #[crate::interface(name = "org.freedesktop.zbus.UncachedPropertyTest")] impl ServiceUncachedPropertyTest { #[zbus(property)] fn cached_prop(&self) -> bool { self.0 } #[zbus(property)] fn uncached_prop(&self) -> bool { self.0 } async fn set_inner_to_true(&mut self) -> zbus::fdo::Result<()> { self.0 = true; Ok(()) } } #[crate::proxy( interface = "org.freedesktop.zbus.UncachedPropertyTest", default_service = "org.freedesktop.zbus.UncachedPropertyTest", default_path = "/org/freedesktop/zbus/UncachedPropertyTest" )] trait UncachedPropertyTest { #[zbus(property)] fn cached_prop(&self) -> zbus::Result; #[zbus(property(emits_changed_signal = "false"))] fn uncached_prop(&self) -> zbus::Result; fn set_inner_to_true(&self) -> zbus::Result<()>; } let service = crate::connection::Builder::session() .unwrap() .serve_at( "/org/freedesktop/zbus/UncachedPropertyTest", ServiceUncachedPropertyTest(false), ) .unwrap() .build() .await .unwrap(); let dest = service.unique_name().unwrap(); let client_conn = crate::Connection::session().await.unwrap(); let client = UncachedPropertyTestProxy::builder(&client_conn) .destination(dest) .unwrap() .build() .await .unwrap(); // Query properties; this populates the cache too. assert!(!client.cached_prop().await.unwrap()); assert!(!client.uncached_prop().await.unwrap()); // Flip the inner value so we can observe the different semantics of // the two properties. client.set_inner_to_true().await.unwrap(); // Query properties again; the first one should incur a stale read from // cache, while the second one should be able to read the live/updated // value. assert!(!client.cached_prop().await.unwrap()); assert!(client.uncached_prop().await.unwrap()); Ok(()) } #[test] #[timeout(15000)] fn issue_260() { // Low-level server example in the book doesn't work. The reason was that // `Connection::request_name` implicitly created the associated `ObjectServer` to avoid // #68. This meant that the `ObjectServer` ended up replying to the incoming method call // with an error, before the service code could do so. block_on(async { let connection = Connection::session().await?; connection.request_name("org.zbus.Issue260").await?; futures_util::try_join!( issue_260_service(&connection), issue_260_client(&connection), )?; Ok::<(), zbus::Error>(()) }) .unwrap(); } async fn issue_260_service(connection: &Connection) -> Result<()> { use futures_util::stream::TryStreamExt; let mut stream = zbus::MessageStream::from(connection); while let Some(msg) = stream.try_next().await? { let msg_header = msg.header(); match msg_header.message_type() { zbus::message::Type::MethodCall => { connection.reply(&msg, &()).await?; break; } _ => continue, } } Ok(()) } async fn issue_260_client(connection: &Connection) -> Result<()> { zbus::Proxy::new( connection, "org.zbus.Issue260", "/org/zbus/Issue260", "org.zbus.Issue260", ) .await? .call("Whatever", &()) .await?; Ok(()) } #[test(tokio::test(flavor = "multi_thread", worker_threads = 2))] // Issue specific to tokio runtime. #[cfg(all(unix, feature = "tokio", feature = "p2p"))] #[instrument] async fn issue_279() { // On failure to read from the socket, we were closing the error channel from the sender // side and since the underlying tokio API doesn't provide a `close` method on the sender, // the async-channel abstraction was achieving this through calling `close` on receiver, // which is behind an async mutex and we end up with a deadlock. use crate::{connection::Builder, MessageStream}; use futures_util::{stream::TryStreamExt, try_join}; use tokio::net::UnixStream; let guid = crate::Guid::generate(); let (p0, p1) = UnixStream::pair().unwrap(); let server = Builder::unix_stream(p0).server(guid).unwrap().p2p().build(); let client = Builder::unix_stream(p1).p2p().build(); let (client, server) = try_join!(client, server).unwrap(); let mut stream = MessageStream::from(client); let next_msg_fut = stream.try_next(); drop(server); assert!(matches!(next_msg_fut.await, Err(_))); } #[test(tokio::test(flavor = "multi_thread"))] // Issue specific to tokio runtime. #[cfg(all(unix, feature = "tokio"))] #[instrument] async fn issue_310() { // The issue was we were deadlocking on fetching the new property value after invalidation. // This turned out to be caused by us trying to grab a read lock on resource while holding // a write lock. Thanks to connman for being weird and invalidating the property just before // updating it, so this issue could be exposed. use futures_util::StreamExt; use zbus::connection::Builder; struct Station(u64); #[zbus::interface(name = "net.connman.iwd.Station")] impl Station { #[zbus(property)] fn connected_network(&self) -> OwnedObjectPath { format!("/net/connman/iwd/0/33/Network/{}", self.0) .try_into() .unwrap() } } #[zbus::proxy( interface = "net.connman.iwd.Station", default_service = "net.connman.iwd" )] trait Station { #[zbus(property)] fn connected_network(&self) -> zbus::Result; } let connection = Builder::session() .unwrap() .serve_at("/net/connman/iwd/0/33", Station(0)) .unwrap() .name("net.connman.iwd") .unwrap() .build() .await .unwrap(); let event = Arc::new(event_listener::Event::new()); let conn_clone = connection.clone(); let event_clone = event.clone(); tokio::spawn(async move { for _ in 0..10 { let listener = event_clone.listen(); let iface_ref = conn_clone .object_server() .interface::<_, Station>("/net/connman/iwd/0/33") .await .unwrap(); { let iface = iface_ref.get().await; iface .connected_network_invalidate(iface_ref.signal_context()) .await .unwrap(); iface .connected_network_changed(iface_ref.signal_context()) .await .unwrap(); } listener.await; iface_ref.get_mut().await.0 += 1; } }); let station = StationProxy::builder(&connection) .path("/net/connman/iwd/0/33") .unwrap() .build() .await .unwrap(); let mut changes = station.receive_connected_network_changed().await; let mut last_received = 0; while last_received < 9 { let change = changes.next().await.unwrap(); let path = change.get().await.unwrap(); let received: u64 = path .split('/') .last() .unwrap() .parse() .expect("invalid path"); assert!(received >= last_received); last_received = received; event.notify(1); } } #[test] #[ignore] fn issue_466() { #[crate::proxy(interface = "org.Some.Thing1", assume_defaults = true)] trait MyGreeter { fn foo( &self, arg: &(u32, zbus::zvariant::Value<'_>), ) -> zbus::Result<(u32, zbus::zvariant::OwnedValue)>; #[zbus(property)] fn bar(&self) -> zbus::Result<(u32, zbus::zvariant::OwnedValue)>; } } #[instrument] #[test] fn concurrent_interface_methods() { // This is test case for ensuring the regression of #799 doesn't come back. block_on(async { struct Iface(Event); #[zbus::interface(name = "org.zbus.test.issue799")] impl Iface { async fn method1(&self) { self.0.notify(1); // Never return std::future::pending::<()>().await; } async fn method2(&self) {} } let event = Event::new(); let listener = event.listen(); let iface = Iface(event); let conn = zbus::connection::Builder::session() .unwrap() .name("org.zbus.test.issue799") .unwrap() .serve_at("/org/zbus/test/issue799", iface) .unwrap() .build() .await .unwrap(); #[zbus::proxy( default_service = "org.zbus.test.issue799", default_path = "/org/zbus/test/issue799", interface = "org.zbus.test.issue799" )] trait Iface { async fn method1(&self) -> Result<()>; async fn method2(&self) -> Result<()>; } let proxy = IfaceProxy::new(&conn).await.unwrap(); let proxy_clone = proxy.clone(); conn.executor() .spawn( async move { proxy_clone.method1().await.unwrap(); }, "method1", ) .detach(); // Wait till the `method1`` is called. listener.await; // Now while the `method1` is in progress, a call to `method2` should just work. proxy.method2().await.unwrap(); }) } #[cfg(all(unix, feature = "p2p"))] #[instrument] #[test] #[timeout(15000)] fn issue_813() { // Our server-side handshake code was unable to handle FDs being sent in the first messages // if the client sent them too quickly after sending `BEGIN` command. // // We test this by manually sending out the auth commands together with 2 method calls with // 1 FD each. Before a fix for this issue, the server handshake would fail with an // `Unexpected FDs during handshake` error. use crate::{conn::socket::WriteHalf, connection::Builder}; use futures_util::try_join; use nix::unistd::Uid; #[cfg(not(feature = "tokio"))] use std::os::unix::net::UnixStream; use std::{os::fd::AsFd, vec}; #[cfg(feature = "tokio")] use tokio::net::UnixStream; use zvariant::Fd; #[derive(Debug)] struct Issue813Iface { event: event_listener::Event, call_count: u8, } #[crate::interface(interface = "org.zbus.Issue813")] impl Issue813Iface { #[instrument] fn pass_fd(&mut self, fd: Fd<'_>) { self.call_count += 1; debug!("`PassFd` called with {} {} times", fd, self.call_count); if self.call_count == 2 { self.event.notify(1); } } } #[crate::proxy( gen_blocking = false, default_path = "/org/zbus/Issue813", interface = "org.zbus.Issue813" )] trait Issue813 { fn pass_fd(&self, fd: Fd<'_>) -> zbus::Result<()>; } block_on(async move { let guid = crate::Guid::generate(); let (p0, p1) = UnixStream::pair().unwrap(); let client_event = event_listener::Event::new(); let client_listener = client_event.listen(); let server_event = event_listener::Event::new(); let server_listener = server_event.listen(); let server = async move { let _conn = Builder::unix_stream(p0) .server(guid)? .p2p() .serve_at( "/org/zbus/Issue813", Issue813Iface { event: server_event, call_count: 0, }, )? .name("org.zbus.Issue813")? .build() .await?; client_listener.await; Result::<()>::Ok(()) }; let client = async move { let commands = format!( "\0AUTH EXTERNAL {}\r\nNEGOTIATE_UNIX_FD\r\nBEGIN\r\n", hex::encode(Uid::effective().to_string()) ); let mut bytes: Vec = commands.bytes().collect(); let fd = std::io::stdin(); let msg = crate::message::Message::method("/org/zbus/Issue813", "PassFd")? .destination("org.zbus.Issue813")? .interface("org.zbus.Issue813")? .build(&(Fd::from(fd.as_fd())))?; let msg_data = msg.data(); let mut fds = vec![]; for _ in 0..2 { bytes.extend_from_slice(&*msg_data); fds.push(fd.as_fd()); } #[cfg(feature = "tokio")] let mut split = crate::conn::Socket::split(p1); #[cfg(not(feature = "tokio"))] let mut split = crate::conn::Socket::split(async_io::Async::new(p1)?); split.write_mut().sendmsg(&bytes, &fds).await?; server_listener.await; client_event.notify(1); Ok(()) }; let (_, _) = try_join!(client, server)?; Result::<()>::Ok(()) }) .unwrap(); } } zbus-4.3.1/src/match_rule/builder.rs000064400000000000000000000172471046102023000155350ustar 00000000000000use static_assertions::assert_impl_all; use crate::{ match_rule::PathSpec, message::Type, names::{BusName, InterfaceName, MemberName, UniqueName}, zvariant::{ObjectPath, Str}, Error, MatchRule, Result, }; const MAX_ARGS: u8 = 64; /// Builder for [`MatchRule`]. /// /// This is created by [`MatchRule::builder`]. #[derive(Debug)] pub struct Builder<'m>(MatchRule<'m>); assert_impl_all!(Builder<'_>: Send, Sync, Unpin); impl<'m> Builder<'m> { /// Build the `MatchRule`. pub fn build(self) -> MatchRule<'m> { self.0 } /// Set the sender. pub fn sender(mut self, sender: B) -> Result where B: TryInto>, B::Error: Into, { self.0.sender = Some(sender.try_into().map_err(Into::into)?); Ok(self) } /// Set the message type. pub fn msg_type(mut self, msg_type: Type) -> Self { self.0.msg_type = Some(msg_type); self } /// Set the interface. pub fn interface(mut self, interface: I) -> Result where I: TryInto>, I::Error: Into, { self.0.interface = Some(interface.try_into().map_err(Into::into)?); Ok(self) } /// Set the member. pub fn member(mut self, member: M) -> Result where M: TryInto>, M::Error: Into, { self.0.member = Some(member.try_into().map_err(Into::into)?); Ok(self) } /// Set the path. /// /// Note: Since both a path and a path namespace are not allowed to appear in a match rule at /// the same time, this overrides any path namespace previously set. pub fn path

(mut self, path: P) -> Result where P: TryInto>, P::Error: Into, { self.0.path_spec = path .try_into() .map(PathSpec::Path) .map(Some) .map_err(Into::into)?; Ok(self) } /// Set the path namespace. /// /// Note: Since both a path and a path namespace are not allowed to appear in a match rule at /// the same time, this overrides any path previously set. pub fn path_namespace

(mut self, path_namespace: P) -> Result where P: TryInto>, P::Error: Into, { self.0.path_spec = path_namespace .try_into() .map(PathSpec::PathNamespace) .map(Some) .map_err(Into::into)?; Ok(self) } /// Set the destination. pub fn destination(mut self, destination: B) -> Result where B: TryInto>, B::Error: Into, { self.0.destination = Some(destination.try_into().map_err(Into::into)?); Ok(self) } /// Append an arguments. /// /// Use this in instead of [`Builder::arg`] if you want to sequentially add args. /// /// # Errors /// /// [`Error::InvalidMatchRule`] on attempt to add the 65th argument. pub fn add_arg(self, arg: S) -> Result where S: Into>, { let idx = self.0.args.len() as u8; self.arg(idx, arg) } /// Add an argument of a specified index. /// /// # Errors /// /// [`Error::InvalidMatchRule`] if `idx` is greater than 64. pub fn arg(mut self, idx: u8, arg: S) -> Result where S: Into>, { if idx >= MAX_ARGS { return Err(Error::InvalidMatchRule); } let value = (idx, arg.into()); let vec_idx = match self.0.args().binary_search_by(|(i, _)| i.cmp(&idx)) { Ok(i) => { // If the argument is already present, replace it. self.0.args.remove(i); i } Err(i) => i, }; self.0.args.insert(vec_idx, value); Ok(self) } /// Append a path argument. /// /// Use this in instead of [`Builder::arg_path`] if you want to sequentially add args. /// /// # Errors /// /// [`Error::InvalidMatchRule`] on attempt to add the 65th path argument. pub fn add_arg_path

(self, arg_path: P) -> Result where P: TryInto>, P::Error: Into, { let idx = self.0.arg_paths.len() as u8; self.arg_path(idx, arg_path) } /// Add a path argument of a specified index. /// /// # Errors /// /// [`Error::InvalidMatchRule`] if `idx` is greater than 64. pub fn arg_path

(mut self, idx: u8, arg_path: P) -> Result where P: TryInto>, P::Error: Into, { if idx >= MAX_ARGS { return Err(Error::InvalidMatchRule); } let value = (idx, arg_path.try_into().map_err(Into::into)?); let vec_idx = match self.0.arg_paths().binary_search_by(|(i, _)| i.cmp(&idx)) { Ok(i) => { // If the argument is already present, replace it. self.0.arg_paths.remove(i); i } Err(i) => i, }; self.0.arg_paths.insert(vec_idx, value); Ok(self) } /// Set 0th argument's namespace. /// /// The namespace be a valid bus name or a valid element of a bus name. For more information, /// see [the spec](https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-names-bus). /// /// # Examples /// /// ``` /// # use zbus::MatchRule; /// // Valid namespaces /// MatchRule::builder().arg0ns("org.mpris.MediaPlayer2").unwrap(); /// MatchRule::builder().arg0ns("org").unwrap(); /// MatchRule::builder().arg0ns(":org").unwrap(); /// MatchRule::builder().arg0ns(":1org").unwrap(); /// /// // Invalid namespaces /// MatchRule::builder().arg0ns("org.").unwrap_err(); /// MatchRule::builder().arg0ns(".org").unwrap_err(); /// MatchRule::builder().arg0ns("1org").unwrap_err(); /// MatchRule::builder().arg0ns(".").unwrap_err(); /// MatchRule::builder().arg0ns("org..freedesktop").unwrap_err(); /// ```` pub fn arg0ns(mut self, namespace: S) -> Result where S: Into>, { let namespace: Str<'m> = namespace.into(); // Rules: https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-names-bus // minus the requirement to have more than one element. if namespace.is_empty() || namespace.len() > 255 { return Err(Error::InvalidMatchRule); } let (is_unique, namespace_str) = match namespace.strip_prefix(':') { Some(s) => (true, s), None => (false, namespace.as_str()), }; let valid_first_char = |s: &str| match s.chars().next() { None | Some('.') => false, Some('0'..='9') if !is_unique => false, _ => true, }; if !valid_first_char(namespace_str) || !namespace_str.split('.').all(valid_first_char) || !namespace_str .chars() .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_' || c == '.') { return Err(Error::InvalidMatchRule); } self.0.arg0ns = Some(namespace); Ok(self) } /// Create a builder for `MatchRule`. pub(crate) fn new() -> Self { Self(MatchRule { msg_type: None, sender: None, interface: None, member: None, path_spec: None, destination: None, args: Vec::with_capacity(MAX_ARGS as usize), arg_paths: Vec::with_capacity(MAX_ARGS as usize), arg0ns: None, }) } } zbus-4.3.1/src/match_rule/mod.rs000064400000000000000000000445701046102023000146650ustar 00000000000000//! Bus match rule API. use std::{ fmt::{Display, Write}, ops::Deref, }; use serde::{de, Deserialize, Serialize}; use static_assertions::assert_impl_all; use zvariant::Structure; use crate::{ message::Type, names::{BusName, InterfaceName, MemberName, UniqueName}, zvariant::{ObjectPath, Str, Type as VariantType}, Error, Result, }; mod builder; pub use builder::Builder; /// A bus match rule for subscribing to specific messages. /// /// This is mainly used by peer to subscribe to specific signals as by default the bus will not /// send out most broadcasted signals. This API is intended to make it easy to create and parse /// match rules. See the [match rules section of the D-Bus specification][mrs] for a description of /// each possible element of a match rule. /// /// # Examples /// /// ``` /// # fn main() -> Result<(), Box> { /// # use zbus::MatchRule; /// /// // Let's take the most typical example of match rule to subscribe to properties' changes: /// let rule = MatchRule::builder() /// .msg_type(zbus::message::Type::Signal) /// .sender("org.freedesktop.DBus")? /// .interface("org.freedesktop.DBus.Properties")? /// .member("PropertiesChanged")? /// .add_arg("org.zbus")? /// // Sometimes it's useful to match empty strings (null check). /// .add_arg("")? /// .build(); /// let rule_str = rule.to_string(); /// assert_eq!( /// rule_str, /// "type='signal',\ /// sender='org.freedesktop.DBus',\ /// interface='org.freedesktop.DBus.Properties',\ /// member='PropertiesChanged',\ /// arg0='org.zbus',\ /// arg1=''", /// ); /// /// // Let's parse it back. /// let parsed_rule = MatchRule::try_from(rule_str.as_str())?; /// assert_eq!(rule, parsed_rule); /// /// // Now for `ObjectManager::InterfacesAdded` signal. /// let rule = MatchRule::builder() /// .msg_type(zbus::message::Type::Signal) /// .sender("org.zbus")? /// .interface("org.freedesktop.DBus.ObjectManager")? /// .member("InterfacesAdded")? /// .arg_path(0, "/org/zbus/NewPath")? /// .build(); /// let rule_str = rule.to_string(); /// assert_eq!( /// rule_str, /// "type='signal',\ /// sender='org.zbus',\ /// interface='org.freedesktop.DBus.ObjectManager',\ /// member='InterfacesAdded',\ /// arg0path='/org/zbus/NewPath'", /// ); /// /// // Let's parse it back. /// let parsed_rule = MatchRule::try_from(rule_str.as_str())?; /// assert_eq!(rule, parsed_rule); /// /// # Ok(()) /// # } /// ``` /// /// # Caveats /// /// The `PartialEq` implementation assumes arguments in both rules are in the same order. /// /// [mrs]: https://dbus.freedesktop.org/doc/dbus-specification.html#message-bus-routing-match-rules #[derive(Clone, Debug, PartialEq, Eq, Hash, VariantType)] #[zvariant(signature = "s")] pub struct MatchRule<'m> { pub(crate) msg_type: Option, pub(crate) sender: Option>, pub(crate) interface: Option>, pub(crate) member: Option>, pub(crate) path_spec: Option>, pub(crate) destination: Option>, pub(crate) args: Vec<(u8, Str<'m>)>, pub(crate) arg_paths: Vec<(u8, ObjectPath<'m>)>, pub(crate) arg0ns: Option>, } assert_impl_all!(MatchRule<'_>: Send, Sync, Unpin); impl<'m> MatchRule<'m> { /// Create a builder for `MatchRule`. pub fn builder() -> Builder<'m> { Builder::new() } /// The sender, if set. pub fn sender(&self) -> Option<&BusName<'_>> { self.sender.as_ref() } /// The message type, if set. pub fn msg_type(&self) -> Option { self.msg_type } /// The interfac, if set. pub fn interface(&self) -> Option<&InterfaceName<'_>> { self.interface.as_ref() } /// The member name if set. pub fn member(&self) -> Option<&MemberName<'_>> { self.member.as_ref() } /// The path or path namespace, if set. pub fn path_spec(&self) -> Option<&PathSpec<'_>> { self.path_spec.as_ref() } /// The destination, if set. pub fn destination(&self) -> Option<&UniqueName<'_>> { self.destination.as_ref() } /// The arguments. pub fn args(&self) -> &[(u8, Str<'_>)] { self.args.as_ref() } /// The argument paths. pub fn arg_paths(&self) -> &[(u8, ObjectPath<'_>)] { self.arg_paths.as_ref() } /// Match messages whose first argument is within the specified namespace. pub fn arg0ns(&self) -> Option<&Str<'m>> { self.arg0ns.as_ref() } /// Creates an owned clone of `self`. pub fn to_owned(&self) -> MatchRule<'static> { MatchRule { msg_type: self.msg_type, sender: self.sender.as_ref().map(|s| s.to_owned()), interface: self.interface.as_ref().map(|i| i.to_owned()), member: self.member.as_ref().map(|m| m.to_owned()), path_spec: self.path_spec.as_ref().map(|p| p.to_owned()), destination: self.destination.as_ref().map(|d| d.to_owned()), args: self.args.iter().map(|(i, s)| (*i, s.to_owned())).collect(), arg_paths: self .arg_paths .iter() .map(|(i, p)| (*i, p.to_owned())) .collect(), arg0ns: self.arg0ns.as_ref().map(|a| a.to_owned()), } } /// Creates an owned clone of `self`. pub fn into_owned(self) -> MatchRule<'static> { MatchRule { msg_type: self.msg_type, sender: self.sender.map(|s| s.into_owned()), interface: self.interface.map(|i| i.into_owned()), member: self.member.map(|m| m.into_owned()), path_spec: self.path_spec.map(|p| p.into_owned()), destination: self.destination.map(|d| d.into_owned()), args: self .args .into_iter() .map(|(i, s)| (i, s.into_owned())) .collect(), arg_paths: self .arg_paths .into_iter() .map(|(i, p)| (i, p.into_owned())) .collect(), arg0ns: self.arg0ns.map(|a| a.into_owned()), } } /// Match the given message against this rule. /// /// # Caveats /// /// Since this method doesn't have any knowledge of names on the bus (or even connection to a /// bus) matching always succeeds for: /// /// * `sender` in the rule (if set) that is a well-known name. The `sender` on a message is /// always a unique name. /// * `destination` in the rule when `destination` on the `msg` is a well-known name. The /// `destination` on match rule is always a unique name. pub fn matches(&self, msg: &zbus::message::Message) -> Result { let hdr = msg.header(); // Start with message type. if let Some(msg_type) = self.msg_type() { if msg_type != msg.message_type() { return Ok(false); } } // Then check sender. if let Some(sender) = self.sender() { match sender { BusName::Unique(name) if Some(name) != hdr.sender() => { return Ok(false); } BusName::Unique(_) => (), // We can't match against a well-known name. BusName::WellKnown(_) => (), } } // The interface. if let Some(interface) = self.interface() { match hdr.interface() { Some(msg_interface) if interface != msg_interface => return Ok(false), Some(_) => (), None => return Ok(false), } } // The member. if let Some(member) = self.member() { match hdr.member() { Some(msg_member) if member != msg_member => return Ok(false), Some(_) => (), None => return Ok(false), } } // The destination. if let Some(destination) = self.destination() { match hdr.destination() { Some(BusName::Unique(name)) if destination != name => { return Ok(false); } Some(BusName::Unique(_)) | None => (), // We can't match against a well-known name. Some(BusName::WellKnown(_)) => (), }; } // The path. if let Some(path_spec) = self.path_spec() { let msg_path = match hdr.path() { Some(p) => p, None => return Ok(false), }; match path_spec { PathSpec::Path(path) if path != msg_path => return Ok(false), PathSpec::PathNamespace(path_ns) if !msg_path.starts_with(path_ns.as_str()) => { return Ok(false); } PathSpec::Path(_) | PathSpec::PathNamespace(_) => (), } } // The arg0 namespace. if let Some(arg0_ns) = self.arg0ns() { if let Ok(arg0) = msg.body().deserialize_unchecked::>() { match arg0.strip_prefix(arg0_ns.as_str()) { None => return Ok(false), Some(s) if !s.is_empty() && !s.starts_with('.') => return Ok(false), _ => (), } } else { return Ok(false); } } // Args if self.args().is_empty() && self.arg_paths().is_empty() { return Ok(true); } let body = msg.body(); let structure = match body.deserialize::>() { Ok(s) => s, Err(_) => return Ok(false), }; let args = structure.fields(); for (i, arg) in self.args() { match args.get(*i as usize) { Some(msg_arg) => match <&str>::try_from(msg_arg) { Ok(msg_arg) if arg != msg_arg => return Ok(false), Ok(_) => (), Err(_) => return Ok(false), }, None => return Ok(false), } } // Path args for (i, path) in self.arg_paths() { match args.get(*i as usize) { Some(msg_arg) => match >::try_from(msg_arg) { Ok(msg_arg) if *path != msg_arg => return Ok(false), Ok(_) => (), Err(_) => return Ok(false), }, None => return Ok(false), } } Ok(true) } } impl Display for MatchRule<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let mut first_component = true; if let Some(msg_type) = self.msg_type() { let type_str = match msg_type { Type::Error => "error", Type::MethodCall => "method_call", Type::MethodReturn => "method_return", Type::Signal => "signal", }; write_match_rule_string_component(f, "type", type_str, &mut first_component)?; } if let Some(sender) = self.sender() { write_match_rule_string_component(f, "sender", sender, &mut first_component)?; } if let Some(interface) = self.interface() { write_match_rule_string_component(f, "interface", interface, &mut first_component)?; } if let Some(member) = self.member() { write_match_rule_string_component(f, "member", member, &mut first_component)?; } if let Some(destination) = self.destination() { write_match_rule_string_component(f, "destination", destination, &mut first_component)?; } if let Some(path_spec) = self.path_spec() { let (key, value) = match path_spec { PathSpec::Path(path) => ("path", path), PathSpec::PathNamespace(ns) => ("path_namespace", ns), }; write_match_rule_string_component(f, key, value, &mut first_component)?; } for (i, arg) in self.args() { write_comma(f, &mut first_component)?; write!(f, "arg{i}='{arg}'")?; } for (i, arg_path) in self.arg_paths() { write_comma(f, &mut first_component)?; write!(f, "arg{i}path='{arg_path}'")?; } if let Some(arg0namespace) = self.arg0ns() { write_comma(f, &mut first_component)?; write!(f, "arg0namespace='{arg0namespace}'")?; } Ok(()) } } fn write_match_rule_string_component( f: &mut std::fmt::Formatter<'_>, key: &str, value: &str, first_component: &mut bool, ) -> std::fmt::Result { write_comma(f, first_component)?; f.write_str(key)?; f.write_str("='")?; f.write_str(value)?; f.write_char('\'')?; Ok(()) } fn write_comma(f: &mut std::fmt::Formatter<'_>, first_component: &mut bool) -> std::fmt::Result { if *first_component { *first_component = false; } else { f.write_char(',')?; } Ok(()) } impl<'m> TryFrom<&'m str> for MatchRule<'m> { type Error = Error; fn try_from(s: &'m str) -> Result { let components = s.split(','); if components.clone().peekable().peek().is_none() { return Err(Error::InvalidMatchRule); } let mut builder = MatchRule::builder(); for component in components { let (key, value) = component.split_once('=').ok_or(Error::InvalidMatchRule)?; if key.is_empty() || value.len() < 2 || !value.starts_with('\'') || !value.ends_with('\'') { return Err(Error::InvalidMatchRule); } let value = &value[1..value.len() - 1]; builder = match key { "type" => { let msg_type = match value { "error" => Type::Error, "method_call" => Type::MethodCall, "method_return" => Type::MethodReturn, "signal" => Type::Signal, _ => return Err(Error::InvalidMatchRule), }; builder.msg_type(msg_type) } "sender" => builder.sender(value)?, "interface" => builder.interface(value)?, "member" => builder.member(value)?, "path" => builder.path(value)?, "path_namespace" => builder.path_namespace(value)?, "destination" => builder.destination(value)?, "arg0namespace" => builder.arg0ns(value)?, key if key.starts_with("arg") => { if let Some(trailing_idx) = key.find("path") { let idx = key[3..trailing_idx] .parse::() .map_err(|_| Error::InvalidMatchRule)?; builder.arg_path(idx, value)? } else { let idx = key[3..] .parse::() .map_err(|_| Error::InvalidMatchRule)?; builder.arg(idx, value)? } } _ => return Err(Error::InvalidMatchRule), }; } Ok(builder.build()) } } impl<'de: 'm, 'm> Deserialize<'de> for MatchRule<'m> { fn deserialize(deserializer: D) -> core::result::Result where D: serde::Deserializer<'de>, { let name = <&str>::deserialize(deserializer)?; Self::try_from(name).map_err(|e| de::Error::custom(e.to_string())) } } impl Serialize for MatchRule<'_> { fn serialize(&self, serializer: S) -> core::result::Result where S: serde::Serializer, { serializer.serialize_str(&self.to_string()) } } /// The path or path namespace. #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum PathSpec<'m> { Path(ObjectPath<'m>), PathNamespace(ObjectPath<'m>), } assert_impl_all!(PathSpec<'_>: Send, Sync, Unpin); impl<'m> PathSpec<'m> { /// Creates an owned clone of `self`. fn to_owned(&self) -> PathSpec<'static> { match self { PathSpec::Path(path) => PathSpec::Path(path.to_owned()), PathSpec::PathNamespace(ns) => PathSpec::PathNamespace(ns.to_owned()), } } /// Creates an owned clone of `self`. pub fn into_owned(self) -> PathSpec<'static> { match self { PathSpec::Path(path) => PathSpec::Path(path.into_owned()), PathSpec::PathNamespace(ns) => PathSpec::PathNamespace(ns.into_owned()), } } } /// Owned sibling of [`MatchRule`]. #[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, VariantType)] pub struct OwnedMatchRule(#[serde(borrow)] MatchRule<'static>); assert_impl_all!(OwnedMatchRule: Send, Sync, Unpin); impl OwnedMatchRule { /// Convert to the inner `MatchRule`, consuming `self`. pub fn into_inner(self) -> MatchRule<'static> { self.0 } /// Get a reference to the inner `MatchRule`. pub fn inner(&self) -> &MatchRule<'static> { &self.0 } } impl Deref for OwnedMatchRule { type Target = MatchRule<'static>; fn deref(&self) -> &Self::Target { &self.0 } } impl From for MatchRule<'_> { fn from(o: OwnedMatchRule) -> Self { o.into_inner() } } impl<'unowned, 'owned: 'unowned> From<&'owned OwnedMatchRule> for MatchRule<'unowned> { fn from(rule: &'owned OwnedMatchRule) -> Self { rule.inner().clone() } } impl From> for OwnedMatchRule { fn from(rule: MatchRule<'_>) -> Self { OwnedMatchRule(rule.into_owned()) } } impl TryFrom<&'_ str> for OwnedMatchRule { type Error = Error; fn try_from(value: &str) -> Result { Ok(Self::from(MatchRule::try_from(value)?)) } } impl<'de> Deserialize<'de> for OwnedMatchRule { fn deserialize(deserializer: D) -> std::result::Result where D: de::Deserializer<'de>, { String::deserialize(deserializer) .and_then(|r| { MatchRule::try_from(r.as_str()) .map(|r| r.to_owned()) .map_err(|e| de::Error::custom(e.to_string())) }) .map(Self) } } impl PartialEq> for OwnedMatchRule { fn eq(&self, other: &MatchRule<'_>) -> bool { self.0 == *other } } zbus-4.3.1/src/message/body.rs000064400000000000000000000041071046102023000143340ustar 00000000000000use zvariant::{ serialized::{self, Data}, Signature, Type, }; use crate::{Error, Message, Result}; /// The body of a message. /// /// This contains the bytes and the signature of the body. #[derive(Clone, Debug)] pub struct Body { data: Data<'static, 'static>, msg: Message, } impl Body { pub(super) fn new(data: Data<'static, 'static>, msg: Message) -> Self { Self { data, msg } } /// Deserialize the body using the contained signature. pub fn deserialize<'s, B>(&'s self) -> Result where B: zvariant::DynamicDeserialize<'s>, { let body_sig = self .signature() .unwrap_or_else(|| Signature::from_static_str_unchecked("")); self.data .deserialize_for_dynamic_signature(body_sig) .map_err(Error::from) .map(|b| b.0) } /// Deserialize the body (without checking signature matching). pub fn deserialize_unchecked<'d, 'm: 'd, B>(&'m self) -> Result where B: serde::de::Deserialize<'d> + Type, { self.data.deserialize().map_err(Error::from).map(|b| b.0) } /// The signature of the body. /// /// **Note:** While zbus treats multiple arguments as a struct (to allow you to use the tuple /// syntax), D-Bus does not. Since this method gives you the signature expected on the wire by /// D-Bus, the trailing and leading STRUCT signature parenthesis will not be present in case of /// multiple arguments. pub fn signature(&self) -> Option> { self.msg.inner.quick_fields.signature(&self.msg) } /// The length of the body in bytes. pub fn len(&self) -> usize { self.data.len() } /// Whether the body is empty. pub fn is_empty(&self) -> bool { self.data.is_empty() } /// Get a reference to the underlying byte encoding of the message. pub fn data(&self) -> &serialized::Data<'static, 'static> { &self.data } /// Reference to the message this body belongs to. pub fn message(&self) -> &Message { &self.msg } } zbus-4.3.1/src/message/builder.rs000064400000000000000000000303451046102023000150300ustar 00000000000000use std::{ io::{Cursor, Write}, sync::Arc, }; #[cfg(unix)] use zvariant::OwnedFd; use enumflags2::BitFlags; use zbus_names::{BusName, ErrorName, InterfaceName, MemberName, UniqueName}; use zvariant::{serialized, Endian}; use crate::{ message::{Field, FieldCode, Fields, Flags, Header, Message, PrimaryHeader, Sequence, Type}, utils::padding_for_8_bytes, zvariant::{serialized::Context, DynamicType, ObjectPath, Signature}, EndianSig, Error, Result, }; use crate::message::{fields::QuickFields, header::MAX_MESSAGE_SIZE}; #[cfg(unix)] type BuildGenericResult = Vec; #[cfg(not(unix))] type BuildGenericResult = (); macro_rules! dbus_context { ($self:ident, $n_bytes_before: expr) => { Context::new_dbus($self.header.primary().endian_sig().into(), $n_bytes_before) }; } /// A builder for [`Message`] #[derive(Debug, Clone)] pub struct Builder<'a> { header: Header<'a>, } impl<'a> Builder<'a> { fn new(msg_type: Type) -> Self { let primary = PrimaryHeader::new(msg_type, 0); let fields = Fields::new(); let header = Header::new(primary, fields); Self { header } } /// Create a message of type [`Type::MethodCall`]. #[deprecated(since = "4.0.0", note = "Please use `Message::method` instead")] pub fn method_call<'p: 'a, 'm: 'a, P, M>(path: P, method_name: M) -> Result where P: TryInto>, M: TryInto>, P::Error: Into, M::Error: Into, { Self::new(Type::MethodCall).path(path)?.member(method_name) } /// Create a message of type [`Type::Signal`]. #[deprecated(since = "4.0.0", note = "Please use `Message::signal` instead")] pub fn signal<'p: 'a, 'i: 'a, 'm: 'a, P, I, M>(path: P, interface: I, name: M) -> Result where P: TryInto>, I: TryInto>, M: TryInto>, P::Error: Into, I::Error: Into, M::Error: Into, { Self::new(Type::Signal) .path(path)? .interface(interface)? .member(name) } /// Create a message of type [`Type::MethodReturn`]. #[deprecated(since = "4.0.0", note = "Please use `Message::method_reply` instead")] pub fn method_return(reply_to: &Header<'_>) -> Result { Self::new(Type::MethodReturn).reply_to(reply_to) } /// Create a message of type [`Type::Error`]. #[deprecated(since = "4.0.0", note = "Please use `Message::method_error` instead")] pub fn error<'e: 'a, E>(reply_to: &Header<'_>, name: E) -> Result where E: TryInto>, E::Error: Into, { Self::new(Type::Error).error_name(name)?.reply_to(reply_to) } /// Add flags to the message. /// /// See [`Flags`] documentation for the meaning of the flags. /// /// The function will return an error if invalid flags are given for the message type. pub fn with_flags(mut self, flag: Flags) -> Result { if self.header.message_type() != Type::MethodCall && BitFlags::from_flag(flag).contains(Flags::NoReplyExpected) { return Err(Error::InvalidField); } let flags = self.header.primary().flags() | flag; self.header.primary_mut().set_flags(flags); Ok(self) } /// Set the unique name of the sending connection. pub fn sender<'s: 'a, S>(mut self, sender: S) -> Result where S: TryInto>, S::Error: Into, { self.header .fields_mut() .replace(Field::Sender(sender.try_into().map_err(Into::into)?)); Ok(self) } /// Set the object to send a call to, or the object a signal is emitted from. pub fn path<'p: 'a, P>(mut self, path: P) -> Result where P: TryInto>, P::Error: Into, { self.header .fields_mut() .replace(Field::Path(path.try_into().map_err(Into::into)?)); Ok(self) } /// Set the interface to invoke a method call on, or that a signal is emitted from. pub fn interface<'i: 'a, I>(mut self, interface: I) -> Result where I: TryInto>, I::Error: Into, { self.header .fields_mut() .replace(Field::Interface(interface.try_into().map_err(Into::into)?)); Ok(self) } /// Set the member, either the method name or signal name. pub fn member<'m: 'a, M>(mut self, member: M) -> Result where M: TryInto>, M::Error: Into, { self.header .fields_mut() .replace(Field::Member(member.try_into().map_err(Into::into)?)); Ok(self) } fn error_name<'e: 'a, E>(mut self, error: E) -> Result where E: TryInto>, E::Error: Into, { self.header .fields_mut() .replace(Field::ErrorName(error.try_into().map_err(Into::into)?)); Ok(self) } /// Set the name of the connection this message is intended for. pub fn destination<'d: 'a, D>(mut self, destination: D) -> Result where D: TryInto>, D::Error: Into, { self.header.fields_mut().replace(Field::Destination( destination.try_into().map_err(Into::into)?, )); Ok(self) } fn reply_to(mut self, reply_to: &Header<'_>) -> Result { let serial = reply_to.primary().serial_num(); self.header.fields_mut().replace(Field::ReplySerial(serial)); self = self.endian(reply_to.primary().endian_sig().into()); if let Some(sender) = reply_to.sender() { self.destination(sender.to_owned()) } else { Ok(self) } } /// Set the endianness of the message. /// /// The default endianness is native. pub fn endian(mut self, endian: Endian) -> Self { let sig = EndianSig::from(endian); self.header.primary_mut().set_endian_sig(sig); self } /// Build the [`Message`] with the given body. /// /// You may pass `()` as the body if the message has no body. /// /// The caller is currently required to ensure that the resulting message contains the headers /// as compliant with the [specification]. Additional checks may be added to this builder over /// time as needed. /// /// [specification]: /// https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-header-fields pub fn build(self, body: &B) -> Result where B: serde::ser::Serialize + DynamicType, { let ctxt = dbus_context!(self, 0); // Note: this iterates the body twice, but we prefer efficient handling of large messages // to efficient handling of ones that are complex to serialize. let body_size = zvariant::serialized_size(ctxt, body)?; let signature = body.dynamic_signature(); self.build_generic(signature, body_size, move |cursor| { // SAFETY: build_generic puts FDs and the body in the same Message. unsafe { zvariant::to_writer(cursor, ctxt, body) } .map(|s| { #[cfg(unix)] { s.into_fds() } #[cfg(not(unix))] { let _ = s; } }) .map_err(Into::into) }) } /// Create a new message from a raw slice of bytes to populate the body with, rather than by /// serializing a value. The message body will be the exact bytes. /// /// # Safety /// /// This method is unsafe because it can be used to build an invalid message. pub unsafe fn build_raw_body<'b, S>( self, body_bytes: &[u8], signature: S, #[cfg(unix)] fds: Vec, ) -> Result where S: TryInto>, S::Error: Into, { let signature: Signature<'b> = signature.try_into().map_err(Into::into)?; let body_size = serialized::Size::new(body_bytes.len(), dbus_context!(self, 0)); #[cfg(unix)] let body_size = { let num_fds = fds.len().try_into().map_err(|_| Error::ExcessData)?; body_size.set_num_fds(num_fds) }; self.build_generic( signature, body_size, move |cursor: &mut Cursor<&mut Vec>| { cursor.write_all(body_bytes)?; #[cfg(unix)] return Ok::, Error>(fds); #[cfg(not(unix))] return Ok::<(), Error>(()); }, ) } fn build_generic( self, mut signature: Signature<'_>, body_size: serialized::Size, write_body: WriteFunc, ) -> Result where WriteFunc: FnOnce(&mut Cursor<&mut Vec>) -> Result, { let ctxt = dbus_context!(self, 0); let mut header = self.header; if !signature.is_empty() { if signature.starts_with(zvariant::STRUCT_SIG_START_STR) { // Remove leading and trailing STRUCT delimiters signature = signature.slice(1..signature.len() - 1); } header.fields_mut().add(Field::Signature(signature)); } let body_len_u32 = body_size.size().try_into().map_err(|_| Error::ExcessData)?; header.primary_mut().set_body_len(body_len_u32); #[cfg(unix)] { let fds_len = body_size.num_fds(); if fds_len != 0 { header.fields_mut().add(Field::UnixFDs(fds_len)); } } let hdr_len = *zvariant::serialized_size(ctxt, &header)?; // We need to align the body to 8-byte boundary. let body_padding = padding_for_8_bytes(hdr_len); let body_offset = hdr_len + body_padding; let total_len = body_offset + body_size.size(); if total_len > MAX_MESSAGE_SIZE { return Err(Error::ExcessData); } let mut bytes: Vec = Vec::with_capacity(total_len); let mut cursor = Cursor::new(&mut bytes); // SAFETY: There are no FDs involved. unsafe { zvariant::to_writer(&mut cursor, ctxt, &header) }?; for _ in 0..body_padding { cursor.write_all(&[0u8])?; } #[cfg(unix)] let fds: Vec<_> = write_body(&mut cursor)?.into_iter().collect(); #[cfg(not(unix))] write_body(&mut cursor)?; let primary_header = header.into_primary(); #[cfg(unix)] let bytes = serialized::Data::new_fds(bytes, ctxt, fds); #[cfg(not(unix))] let bytes = serialized::Data::new(bytes, ctxt); let (header, actual_hdr_len): (Header<'_>, _) = bytes.deserialize()?; assert_eq!(hdr_len, actual_hdr_len); let quick_fields = QuickFields::new(&bytes, &header)?; Ok(Message { inner: Arc::new(super::Inner { primary_header, quick_fields, bytes, body_offset, recv_seq: Sequence::default(), }), }) } } impl<'m> From> for Builder<'m> { fn from(mut header: Header<'m>) -> Self { // Signature and Fds are added by body* methods. let fields = header.fields_mut(); fields.remove(FieldCode::Signature); fields.remove(FieldCode::UnixFDs); Self { header } } } #[cfg(test)] mod tests { use super::Message; use crate::Error; use test_log::test; #[test] fn test_raw() -> Result<(), Error> { let raw_body: &[u8] = &[16, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0]; let message_builder = Message::signal("/", "test.test", "test")?; let message = unsafe { message_builder.build_raw_body( raw_body, "ai", #[cfg(unix)] vec![], )? }; let output: Vec = message.body().deserialize()?; assert_eq!(output, vec![1, 2, 3, 4]); Ok(()) } } zbus-4.3.1/src/message/field.rs000064400000000000000000000152061046102023000144640ustar 00000000000000use std::num::NonZeroU32; use serde::{ de::{Deserialize, Deserializer, Error}, ser::{Serialize, Serializer}, }; use serde_repr::{Deserialize_repr, Serialize_repr}; use static_assertions::assert_impl_all; use zbus_names::{BusName, ErrorName, InterfaceName, MemberName, UniqueName}; use zvariant::{ObjectPath, Signature, Type, Value}; /// The message field code. /// /// Every [`Field`] has an associated code. This is mostly an internal D-Bus protocol detail /// that you would not need to ever care about when using the high-level API. When using the /// low-level API, this is how you can [retrieve a specific field] from [`Fields`]. /// /// [`Field`]: enum.Field.html /// [retrieve a specific field]: struct.Fields.html#method.get_field /// [`Fields`]: struct.Fields.html #[repr(u8)] #[derive(Copy, Clone, Debug, Deserialize_repr, PartialEq, Eq, Serialize_repr, Type)] pub(crate) enum FieldCode { /// Code for [`Field::Path`](enum.Field.html#variant.Path) Path = 1, /// Code for [`Field::Interface`](enum.Field.html#variant.Interface) Interface = 2, /// Code for [`Field::Member`](enum.Field.html#variant.Member) Member = 3, /// Code for [`Field::ErrorName`](enum.Field.html#variant.ErrorName) ErrorName = 4, /// Code for [`Field::ReplySerial`](enum.Field.html#variant.ReplySerial) ReplySerial = 5, /// Code for [`Field::Destination`](enum.Field.html#variant.Destination) Destination = 6, /// Code for [`Field::Sender`](enum.Field.html#variant.Sender) Sender = 7, /// Code for [`Field::Signature`](enum.Field.html#variant.Signature) Signature = 8, /// Code for [`Field::UnixFDs`](enum.Field.html#variant.UnixFDs) UnixFDs = 9, } assert_impl_all!(FieldCode: Send, Sync, Unpin); impl<'f> Field<'f> { /// Get the associated code for this field. pub fn code(&self) -> FieldCode { match self { Field::Path(_) => FieldCode::Path, Field::Interface(_) => FieldCode::Interface, Field::Member(_) => FieldCode::Member, Field::ErrorName(_) => FieldCode::ErrorName, Field::ReplySerial(_) => FieldCode::ReplySerial, Field::Destination(_) => FieldCode::Destination, Field::Sender(_) => FieldCode::Sender, Field::Signature(_) => FieldCode::Signature, Field::UnixFDs(_) => FieldCode::UnixFDs, } } } /// The dynamic message header. /// /// All D-Bus messages contain a set of metadata [headers]. Some of these headers [are fixed] for /// all types of messages, while others depend on the type of the message in question. The latter /// are called message fields. /// /// Please consult the [Message Format] section of the D-Bus spec for more details. /// /// [headers]: struct.Header.html /// [are fixed]: struct.PrimaryHeader.html /// [Message Format]: https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-messages #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) enum Field<'f> { /// The object to send a call to, or the object a signal is emitted from. Path(ObjectPath<'f>), /// The interface to invoke a method call on, or that a signal is emitted from. Interface(InterfaceName<'f>), /// The member, either the method name or signal name. Member(MemberName<'f>), /// The name of the error that occurred, for errors ErrorName(ErrorName<'f>), /// The serial number of the message this message is a reply to. ReplySerial(NonZeroU32), /// The name of the connection this message is intended for. Destination(BusName<'f>), /// Unique name of the sending connection. Sender(UniqueName<'f>), /// The signature of the message body. Signature(Signature<'f>), /// The number of Unix file descriptors that accompany the message. UnixFDs(u32), } assert_impl_all!(Field<'_>: Send, Sync, Unpin); impl<'f> Type for Field<'f> { fn signature() -> Signature<'static> { Signature::from_static_str_unchecked("(yv)") } } impl<'f> Serialize for Field<'f> { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let tuple: (FieldCode, Value<'_>) = match self { Field::Path(value) => (FieldCode::Path, value.as_ref().into()), Field::Interface(value) => (FieldCode::Interface, value.as_str().into()), Field::Member(value) => (FieldCode::Member, value.as_str().into()), Field::ErrorName(value) => (FieldCode::ErrorName, value.as_str().into()), Field::ReplySerial(value) => (FieldCode::ReplySerial, value.get().into()), Field::Destination(value) => (FieldCode::Destination, value.as_str().into()), Field::Sender(value) => (FieldCode::Sender, value.as_str().into()), Field::Signature(value) => (FieldCode::Signature, value.as_ref().into()), Field::UnixFDs(value) => (FieldCode::UnixFDs, (*value).into()), }; tuple.serialize(serializer) } } impl<'de: 'f, 'f> Deserialize<'de> for Field<'f> { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { let (code, value) = <(FieldCode, Value<'_>)>::deserialize(deserializer)?; Ok(match code { FieldCode::Path => Field::Path(ObjectPath::try_from(value).map_err(D::Error::custom)?), FieldCode::Interface => { Field::Interface(InterfaceName::try_from(value).map_err(D::Error::custom)?) } FieldCode::Member => { Field::Member(MemberName::try_from(value).map_err(D::Error::custom)?) } FieldCode::ErrorName => Field::ErrorName( ErrorName::try_from(value) .map(Into::into) .map_err(D::Error::custom)?, ), FieldCode::ReplySerial => { let value = u32::try_from(value) .map_err(D::Error::custom) .and_then(|v| v.try_into().map_err(D::Error::custom))?; Field::ReplySerial(value) } FieldCode::Destination => Field::Destination( BusName::try_from(value) .map(Into::into) .map_err(D::Error::custom)?, ), FieldCode::Sender => Field::Sender( UniqueName::try_from(value) .map(Into::into) .map_err(D::Error::custom)?, ), FieldCode::Signature => { Field::Signature(Signature::try_from(value).map_err(D::Error::custom)?) } FieldCode::UnixFDs => Field::UnixFDs(u32::try_from(value).map_err(D::Error::custom)?), }) } } zbus-4.3.1/src/message/fields.rs000064400000000000000000000161321046102023000146460ustar 00000000000000use serde::{Deserialize, Serialize}; use static_assertions::assert_impl_all; use std::num::NonZeroU32; use zbus_names::{BusName, ErrorName, InterfaceName, MemberName, UniqueName}; use zvariant::{ObjectPath, Signature, Type}; use crate::{ message::{Field, FieldCode, Header, Message}, Result, }; // It's actually 10 (and even not that) but let's round it to next 8-byte alignment const MAX_FIELDS_IN_MESSAGE: usize = 16; /// A collection of [`Field`] instances. /// /// [`Field`]: enum.Field.html #[derive(Debug, Clone, Serialize, Deserialize, Type)] pub(crate) struct Fields<'m>(#[serde(borrow)] Vec>); assert_impl_all!(Fields<'_>: Send, Sync, Unpin); impl<'m> Fields<'m> { /// Creates an empty collection of fields. pub fn new() -> Self { Self::default() } /// Appends a [`Field`] to the collection of fields in the message. /// /// [`Field`]: enum.Field.html pub fn add<'f: 'm>(&mut self, field: Field<'f>) { self.0.push(field); } /// Replaces a [`Field`] from the collection of fields with one with the same code, /// returning the old value if present. /// /// [`Field`]: enum.Field.html pub fn replace<'f: 'm>(&mut self, field: Field<'f>) -> Option> { let code = field.code(); if let Some(found) = self.0.iter_mut().find(|f| f.code() == code) { return Some(std::mem::replace(found, field)); } self.add(field); None } /// Returns a slice with all the [`Field`] in the message. /// /// [`Field`]: enum.Field.html pub fn get(&self) -> &[Field<'m>] { &self.0 } /// Gets a reference to a specific [`Field`] by its code. /// /// Returns `None` if the message has no such field. /// /// [`Field`]: enum.Field.html pub fn get_field(&self, code: FieldCode) -> Option<&Field<'m>> { self.0.iter().find(|f| f.code() == code) } /// Remove the field matching the `code`. /// /// Returns `true` if a field was found and removed, `false` otherwise. pub(crate) fn remove(&mut self, code: FieldCode) -> bool { match self.0.iter().enumerate().find(|(_, f)| f.code() == code) { Some((i, _)) => { self.0.remove(i); true } None => false, } } } /// A byte range of a field in a Message, used in [`QuickFields`]. /// /// Some invalid encodings (end = 0) are used to indicate "not cached" and "not present". #[derive(Debug, Default, Clone, Copy)] pub(crate) struct FieldPos { start: u32, end: u32, } impl FieldPos { pub fn new_not_present() -> Self { Self { start: 1, end: 0 } } pub fn build(msg_buf: &[u8], field_buf: &str) -> Option { let buf_start = msg_buf.as_ptr() as usize; let field_start = field_buf.as_ptr() as usize; let offset = field_start.checked_sub(buf_start)?; if offset <= msg_buf.len() && offset + field_buf.len() <= msg_buf.len() { Some(Self { start: offset.try_into().ok()?, end: (offset + field_buf.len()).try_into().ok()?, }) } else { None } } pub fn new(msg_buf: &[u8], field: Option<&T>) -> Self where T: std::ops::Deref, { field .and_then(|f| Self::build(msg_buf, f.deref())) .unwrap_or_else(Self::new_not_present) } /// Reassemble a previously cached field. /// /// **NOTE**: The caller must ensure that the `msg_buff` is the same one `build` was called for. /// Otherwise, you'll get a panic. pub fn read<'m, T>(&self, msg_buf: &'m [u8]) -> Option where T: TryFrom<&'m str>, T::Error: std::fmt::Debug, { match self { Self { start: 0..=1, end: 0, } => None, Self { start, end } => { let s = std::str::from_utf8(&msg_buf[(*start as usize)..(*end as usize)]) .expect("Invalid utf8 when reconstructing string"); // We already check the fields during the construction of `Self`. T::try_from(s) .map(Some) .expect("Invalid field reconstruction") } } } } /// A cache of the Message header fields. #[derive(Debug, Default, Copy, Clone)] pub(crate) struct QuickFields { path: FieldPos, interface: FieldPos, member: FieldPos, error_name: FieldPos, reply_serial: Option, destination: FieldPos, sender: FieldPos, signature: FieldPos, unix_fds: Option, } impl QuickFields { pub fn new(buf: &[u8], header: &Header<'_>) -> Result { Ok(Self { path: FieldPos::new(buf, header.path()), interface: FieldPos::new(buf, header.interface()), member: FieldPos::new(buf, header.member()), error_name: FieldPos::new(buf, header.error_name()), reply_serial: header.reply_serial(), destination: FieldPos::new(buf, header.destination()), sender: FieldPos::new(buf, header.sender()), signature: FieldPos::new(buf, header.signature()), unix_fds: header.unix_fds(), }) } pub fn path<'m>(&self, msg: &'m Message) -> Option> { self.path.read(msg.data()) } pub fn interface<'m>(&self, msg: &'m Message) -> Option> { self.interface.read(msg.data()) } pub fn member<'m>(&self, msg: &'m Message) -> Option> { self.member.read(msg.data()) } pub fn error_name<'m>(&self, msg: &'m Message) -> Option> { self.error_name.read(msg.data()) } pub fn reply_serial(&self) -> Option { self.reply_serial } pub fn destination<'m>(&self, msg: &'m Message) -> Option> { self.destination.read(msg.data()) } pub fn sender<'m>(&self, msg: &'m Message) -> Option> { self.sender.read(msg.data()) } pub fn signature<'m>(&self, msg: &'m Message) -> Option> { self.signature.read(msg.data()) } pub fn unix_fds(&self) -> Option { self.unix_fds } } impl<'m> Default for Fields<'m> { fn default() -> Self { Self(Vec::with_capacity(MAX_FIELDS_IN_MESSAGE)) } } impl<'m> std::ops::Deref for Fields<'m> { type Target = [Field<'m>]; fn deref(&self) -> &Self::Target { self.get() } } #[cfg(test)] mod tests { use super::{Field, Fields}; #[test] fn test() { let mut mf = Fields::new(); assert_eq!(mf.len(), 0); mf.add(Field::ReplySerial(42.try_into().unwrap())); assert_eq!(mf.len(), 1); mf.add(Field::ReplySerial(43.try_into().unwrap())); assert_eq!(mf.len(), 2); let mut mf = Fields::new(); assert_eq!(mf.len(), 0); mf.replace(Field::ReplySerial(42.try_into().unwrap())); assert_eq!(mf.len(), 1); mf.replace(Field::ReplySerial(43.try_into().unwrap())); assert_eq!(mf.len(), 1); } } zbus-4.3.1/src/message/header.rs000064400000000000000000000314731046102023000146350ustar 00000000000000use std::{ num::NonZeroU32, sync::atomic::{AtomicU32, Ordering::SeqCst}, }; use enumflags2::{bitflags, BitFlags}; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use static_assertions::assert_impl_all; use zbus_names::{BusName, ErrorName, InterfaceName, MemberName, UniqueName}; use zvariant::{ serialized::{self, Context}, Endian, ObjectPath, Signature, Type as VariantType, }; use crate::{ message::{Field, FieldCode, Fields}, Error, }; pub(crate) const PRIMARY_HEADER_SIZE: usize = 12; pub(crate) const MIN_MESSAGE_SIZE: usize = PRIMARY_HEADER_SIZE + 4; pub(crate) const MAX_MESSAGE_SIZE: usize = 128 * 1024 * 1024; // 128 MiB /// D-Bus code for endianness. #[repr(u8)] #[derive(Debug, Copy, Clone, Deserialize_repr, PartialEq, Eq, Serialize_repr, VariantType)] pub enum EndianSig { /// The D-Bus message is in big-endian (network) byte order. Big = b'B', /// The D-Bus message is in little-endian byte order. Little = b'l', } assert_impl_all!(EndianSig: Send, Sync, Unpin); // Such a shame I've to do this manually impl TryFrom for EndianSig { type Error = Error; fn try_from(val: u8) -> Result { match val { b'B' => Ok(EndianSig::Big), b'l' => Ok(EndianSig::Little), _ => Err(Error::IncorrectEndian), } } } #[cfg(target_endian = "big")] /// Signature of the target's native endian. pub const NATIVE_ENDIAN_SIG: EndianSig = EndianSig::Big; #[cfg(target_endian = "little")] /// Signature of the target's native endian. pub const NATIVE_ENDIAN_SIG: EndianSig = EndianSig::Little; impl From for EndianSig { fn from(endian: Endian) -> Self { match endian { Endian::Little => EndianSig::Little, Endian::Big => EndianSig::Big, } } } impl From for Endian { fn from(endian_sig: EndianSig) -> Self { match endian_sig { EndianSig::Little => Endian::Little, EndianSig::Big => Endian::Big, } } } /// Message header representing the D-Bus type of the message. #[repr(u8)] #[derive( Debug, Copy, Clone, Deserialize_repr, PartialEq, Eq, Hash, Serialize_repr, VariantType, )] pub enum Type { /// Method call. This message type may prompt a reply (and typically does). MethodCall = 1, /// A reply to a method call. MethodReturn = 2, /// An error in response to a method call. Error = 3, /// Signal emission. Signal = 4, } assert_impl_all!(Type: Send, Sync, Unpin); /// Pre-defined flags that can be passed in Message header. #[bitflags] #[repr(u8)] #[derive(Debug, Copy, Clone, PartialEq, Eq, VariantType)] pub enum Flags { /// This message does not expect method return replies or error replies, even if it is of a /// type that can have a reply; the reply should be omitted. /// /// Note that `Type::MethodCall` is the only message type currently defined in the /// specification that can expect a reply, so the presence or absence of this flag in the other /// three message types that are currently documented is meaningless: replies to those message /// types should not be sent, whether this flag is present or not. NoReplyExpected = 0x1, /// The bus must not launch an owner for the destination name in response to this message. NoAutoStart = 0x2, /// This flag may be set on a method call message to inform the receiving side that the caller /// is prepared to wait for interactive authorization, which might take a considerable time to /// complete. For instance, if this flag is set, it would be appropriate to query the user for /// passwords or confirmation via Polkit or a similar framework. AllowInteractiveAuth = 0x4, } assert_impl_all!(Flags: Send, Sync, Unpin); /// The primary message header, which is present in all D-Bus messages. /// /// This header contains all the essential information about a message, regardless of its type. #[derive(Clone, Debug, Serialize, Deserialize, VariantType)] pub struct PrimaryHeader { endian_sig: EndianSig, msg_type: Type, flags: BitFlags, protocol_version: u8, body_len: u32, serial_num: NonZeroU32, } assert_impl_all!(PrimaryHeader: Send, Sync, Unpin); impl PrimaryHeader { /// Create a new `PrimaryHeader` instance. pub fn new(msg_type: Type, body_len: u32) -> Self { Self { endian_sig: NATIVE_ENDIAN_SIG, msg_type, flags: BitFlags::empty(), protocol_version: 1, body_len, serial_num: SERIAL_NUM.fetch_add(1, SeqCst).try_into().unwrap(), } } pub(crate) fn read(buf: &[u8]) -> Result<(PrimaryHeader, u32), Error> { let endian = Endian::from(EndianSig::try_from(buf[0])?); let ctx = Context::new_dbus(endian, 0); let data = serialized::Data::new(buf, ctx); Self::read_from_data(&data) } pub(crate) fn read_from_data( data: &serialized::Data<'_, '_>, ) -> Result<(PrimaryHeader, u32), Error> { let (primary_header, size) = data.deserialize()?; assert_eq!(size, PRIMARY_HEADER_SIZE); let (fields_len, _) = data.slice(PRIMARY_HEADER_SIZE..).deserialize()?; Ok((primary_header, fields_len)) } /// D-Bus code for endian encoding of the message. pub fn endian_sig(&self) -> EndianSig { self.endian_sig } /// Set the D-Bus code for endian encoding of the message. pub fn set_endian_sig(&mut self, sig: EndianSig) { self.endian_sig = sig; } /// The message type. pub fn msg_type(&self) -> Type { self.msg_type } /// Set the message type. pub fn set_msg_type(&mut self, msg_type: Type) { self.msg_type = msg_type; } /// The message flags. pub fn flags(&self) -> BitFlags { self.flags } /// Set the message flags. pub fn set_flags(&mut self, flags: BitFlags) { self.flags = flags; } /// The major version of the protocol the message is compliant to. /// /// Currently only `1` is valid. pub fn protocol_version(&self) -> u8 { self.protocol_version } /// Set the major version of the protocol the message is compliant to. /// /// Currently only `1` is valid. pub fn set_protocol_version(&mut self, version: u8) { self.protocol_version = version; } /// The byte length of the message body. pub fn body_len(&self) -> u32 { self.body_len } /// Set the byte length of the message body. pub fn set_body_len(&mut self, len: u32) { self.body_len = len; } /// The serial number of the message. /// /// This is used to match a reply to a method call. pub fn serial_num(&self) -> NonZeroU32 { self.serial_num } /// Set the serial number of the message. /// /// This is used to match a reply to a method call. pub fn set_serial_num(&mut self, serial_num: NonZeroU32) { self.serial_num = serial_num; } } /// The message header, containing all the metadata about the message. /// /// This includes both the [`PrimaryHeader`] and [`Fields`]. /// /// [`PrimaryHeader`]: struct.PrimaryHeader.html /// [`Fields`]: struct.Fields.html #[derive(Debug, Clone, Serialize, Deserialize, VariantType)] pub struct Header<'m> { primary: PrimaryHeader, #[serde(borrow)] fields: Fields<'m>, } assert_impl_all!(Header<'_>: Send, Sync, Unpin); macro_rules! get_field { ($self:ident, $kind:ident) => { get_field!($self, $kind, (|v| v)) }; ($self:ident, $kind:ident, $closure:tt) => { #[allow(clippy::redundant_closure_call)] match $self.fields().get_field(FieldCode::$kind) { Some(Field::$kind(value)) => Some($closure(value)), // SAFETY: `Deserialize` impl for `Field` ensures that the code and field match. Some(_) => unreachable!("FieldCode and Field mismatch"), None => None, } }; } macro_rules! get_field_u32 { ($self:ident, $kind:ident) => { get_field!($self, $kind, (|v: &u32| *v)) }; } impl<'m> Header<'m> { /// Create a new `Header` instance. pub(super) fn new(primary: PrimaryHeader, fields: Fields<'m>) -> Self { Self { primary, fields } } /// Get a reference to the primary header. pub fn primary(&self) -> &PrimaryHeader { &self.primary } /// Get a mutable reference to the primary header. pub fn primary_mut(&mut self) -> &mut PrimaryHeader { &mut self.primary } /// Get the primary header, consuming `self`. pub fn into_primary(self) -> PrimaryHeader { self.primary } /// Get a reference to the message fields. fn fields(&self) -> &Fields<'m> { &self.fields } /// Get a mutable reference to the message fields. pub(super) fn fields_mut(&mut self) -> &mut Fields<'m> { &mut self.fields } /// The message type pub fn message_type(&self) -> Type { self.primary().msg_type() } /// The object to send a call to, or the object a signal is emitted from. pub fn path(&self) -> Option<&ObjectPath<'m>> { get_field!(self, Path) } /// The interface to invoke a method call on, or that a signal is emitted from. pub fn interface(&self) -> Option<&InterfaceName<'m>> { get_field!(self, Interface) } /// The member, either the method name or signal name. pub fn member(&self) -> Option<&MemberName<'m>> { get_field!(self, Member) } /// The name of the error that occurred, for errors. pub fn error_name(&self) -> Option<&ErrorName<'m>> { get_field!(self, ErrorName) } /// The serial number of the message this message is a reply to. pub fn reply_serial(&self) -> Option { match self.fields().get_field(FieldCode::ReplySerial) { Some(Field::ReplySerial(value)) => Some(*value), // SAFETY: `Deserialize` impl for `Field` ensures that the code and field match. Some(_) => unreachable!("FieldCode and Field mismatch"), None => None, } } /// The name of the connection this message is intended for. pub fn destination(&self) -> Option<&BusName<'m>> { get_field!(self, Destination) } /// Unique name of the sending connection. pub fn sender(&self) -> Option<&UniqueName<'m>> { get_field!(self, Sender) } /// The signature of the message body. pub fn signature(&self) -> Option<&Signature<'m>> { get_field!(self, Signature) } /// The number of Unix file descriptors that accompany the message. pub fn unix_fds(&self) -> Option { get_field_u32!(self, UnixFDs) } } static SERIAL_NUM: AtomicU32 = AtomicU32::new(1); #[cfg(test)] mod tests { use crate::message::{Field, Fields, Header, PrimaryHeader, Type}; use std::error::Error; use test_log::test; use zbus_names::{InterfaceName, MemberName}; use zvariant::{ObjectPath, Signature}; #[test] fn header() -> Result<(), Box> { let path = ObjectPath::try_from("/some/path")?; let iface = InterfaceName::try_from("some.interface")?; let member = MemberName::try_from("Member")?; let mut f = Fields::new(); f.add(Field::Path(path.clone())); f.add(Field::Interface(iface.clone())); f.add(Field::Member(member.clone())); f.add(Field::Sender(":1.84".try_into()?)); let h = Header::new(PrimaryHeader::new(Type::Signal, 77), f); assert_eq!(h.message_type(), Type::Signal); assert_eq!(h.path(), Some(&path)); assert_eq!(h.interface(), Some(&iface)); assert_eq!(h.member(), Some(&member)); assert_eq!(h.error_name(), None); assert_eq!(h.destination(), None); assert_eq!(h.reply_serial(), None); assert_eq!(h.sender().unwrap(), ":1.84"); assert_eq!(h.signature(), None); assert_eq!(h.unix_fds(), None); let mut f = Fields::new(); f.add(Field::ErrorName("org.zbus.Error".try_into()?)); f.add(Field::Destination(":1.11".try_into()?)); f.add(Field::ReplySerial(88.try_into()?)); f.add(Field::Signature(Signature::from_str_unchecked("say"))); f.add(Field::UnixFDs(12)); let h = Header::new(PrimaryHeader::new(Type::MethodReturn, 77), f); assert_eq!(h.message_type(), Type::MethodReturn); assert_eq!(h.path(), None); assert_eq!(h.interface(), None); assert_eq!(h.member(), None); assert_eq!(h.error_name().unwrap(), "org.zbus.Error"); assert_eq!(h.destination().unwrap(), ":1.11"); assert_eq!(h.reply_serial().map(Into::into), Some(88)); assert_eq!(h.sender(), None); assert_eq!(h.signature(), Some(&Signature::from_str_unchecked("say"))); assert_eq!(h.unix_fds(), Some(12)); Ok(()) } } zbus-4.3.1/src/message/mod.rs000064400000000000000000000333311046102023000141570ustar 00000000000000//! D-Bus Message. use std::{fmt, num::NonZeroU32, sync::Arc}; use static_assertions::assert_impl_all; use zbus_names::{ErrorName, InterfaceName, MemberName}; use zvariant::{serialized, Endian}; use crate::{utils::padding_for_8_bytes, zvariant::ObjectPath, Error, Result}; mod builder; pub use builder::Builder; mod field; pub(crate) use field::{Field, FieldCode}; mod fields; pub(crate) use fields::Fields; use fields::QuickFields; mod body; pub use body::Body; pub(crate) mod header; use header::MIN_MESSAGE_SIZE; pub use header::{EndianSig, Flags, Header, PrimaryHeader, Type, NATIVE_ENDIAN_SIG}; /// A position in the stream of [`Message`] objects received by a single [`zbus::Connection`]. /// /// Note: the relative ordering of values obtained from distinct [`zbus::Connection`] objects is /// not specified; only sequence numbers originating from the same connection should be compared. #[derive(Debug, Default, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] pub struct Sequence { recv_seq: u64, } impl Sequence { /// A sequence number that is higher than any other; used by errors that terminate a stream. pub(crate) const LAST: Self = Self { recv_seq: u64::MAX }; } /// A D-Bus Message. /// /// The content of the message are stored in serialized format. To get the body of the message, use /// the [`Message::body`] method, and use [`Body`] methods to deserialize it. You may also access /// the header and other details with the various other getters. /// /// Also provided are constructors for messages of different types. These will mainly be useful for /// very advanced use cases as typically you will want to create a message for immediate dispatch /// and hence use the API provided by [`Connection`], even when using the low-level API. /// /// **Note**: The message owns the received FDs and will close them when dropped. You can /// deserialize to [`zvariant::OwnedFd`] the body (that you get using [`Message::body`]) if you want /// to keep the FDs around after the containing message is dropped. /// /// [`Connection`]: struct.Connection#method.call_method #[derive(Clone)] pub struct Message { pub(super) inner: Arc, } pub(super) struct Inner { pub(crate) primary_header: PrimaryHeader, pub(crate) quick_fields: QuickFields, pub(crate) bytes: serialized::Data<'static, 'static>, pub(crate) body_offset: usize, pub(crate) recv_seq: Sequence, } assert_impl_all!(Message: Send, Sync, Unpin); impl Message { /// Create a builder for message of type [`Type::MethodCall`]. pub fn method<'b, 'p: 'b, 'm: 'b, P, M>(path: P, method_name: M) -> Result> where P: TryInto>, M: TryInto>, P::Error: Into, M::Error: Into, { #[allow(deprecated)] Builder::method_call(path, method_name) } /// Create a builder for message of type [`Type::Signal`]. pub fn signal<'b, 'p: 'b, 'i: 'b, 'm: 'b, P, I, M>( path: P, iface: I, signal_name: M, ) -> Result> where P: TryInto>, I: TryInto>, M: TryInto>, P::Error: Into, I::Error: Into, M::Error: Into, { #[allow(deprecated)] Builder::signal(path, iface, signal_name) } /// Create a builder for message of type [`Type::MethodReturn`]. pub fn method_reply(call: &Self) -> Result> { #[allow(deprecated)] Builder::method_return(&call.header()) } /// Create a builder for message of type [`Type::Error`]. pub fn method_error<'b, 'e: 'b, E>(call: &Self, name: E) -> Result> where E: TryInto>, E::Error: Into, { #[allow(deprecated)] Builder::error(&call.header(), name) } /// Create a message from bytes. /// /// **Note:** Since the constructed message is not construct by zbus, the receive sequence, /// which can be acquired from [`Message::recv_position`], is not applicable and hence set /// to `0`. /// /// # Safety /// /// This method is unsafe as bytes may have an invalid encoding. pub unsafe fn from_bytes(bytes: serialized::Data<'static, 'static>) -> Result { Self::from_raw_parts(bytes, 0) } /// Create a message from its full contents pub(crate) fn from_raw_parts( bytes: serialized::Data<'static, 'static>, recv_seq: u64, ) -> Result { let endian = Endian::from(EndianSig::try_from(bytes[0])?); if endian != bytes.context().endian() { return Err(Error::IncorrectEndian); } let (primary_header, fields_len) = PrimaryHeader::read_from_data(&bytes)?; let (header, _) = bytes.deserialize()?; let header_len = MIN_MESSAGE_SIZE + fields_len as usize; let body_offset = header_len + padding_for_8_bytes(header_len); let quick_fields = QuickFields::new(&bytes, &header)?; Ok(Self { inner: Arc::new(Inner { primary_header, quick_fields, bytes, body_offset, recv_seq: Sequence { recv_seq }, }), }) } pub fn primary_header(&self) -> &PrimaryHeader { &self.inner.primary_header } /// The message header. /// /// Note: This method does not deserialize the header but it does currently allocate so its not /// zero-cost. While the allocation is small and will hopefully be removed in the future, it's /// best to keep the header around if you need to access it a lot. pub fn header(&self) -> Header<'_> { let mut fields = Fields::new(); let quick_fields = &self.inner.quick_fields; if let Some(p) = quick_fields.path(self) { fields.add(Field::Path(p)); } if let Some(i) = quick_fields.interface(self) { fields.add(Field::Interface(i)); } if let Some(m) = quick_fields.member(self) { fields.add(Field::Member(m)); } if let Some(e) = quick_fields.error_name(self) { fields.add(Field::ErrorName(e)); } if let Some(r) = quick_fields.reply_serial() { fields.add(Field::ReplySerial(r)); } if let Some(d) = quick_fields.destination(self) { fields.add(Field::Destination(d)); } if let Some(s) = quick_fields.sender(self) { fields.add(Field::Sender(s)); } if let Some(s) = quick_fields.signature(self) { fields.add(Field::Signature(s)); } if let Some(u) = quick_fields.unix_fds() { fields.add(Field::UnixFDs(u)); } Header::new(self.inner.primary_header.clone(), fields) } /// The message type. pub fn message_type(&self) -> Type { self.inner.primary_header.msg_type() } /// The object to send a call to, or the object a signal is emitted from. #[deprecated( since = "4.0.0", note = "Use `Message::header` with `message::Header::path` instead" )] pub fn path(&self) -> Option> { self.inner.quick_fields.path(self) } /// The interface to invoke a method call on, or that a signal is emitted from. #[deprecated( since = "4.0.0", note = "Use `Message::header` with `message::Header::interface` instead" )] pub fn interface(&self) -> Option> { self.inner.quick_fields.interface(self) } /// The member, either the method name or signal name. #[deprecated( since = "4.0.0", note = "Use `Message::header` with `message::Header::member` instead" )] pub fn member(&self) -> Option> { self.inner.quick_fields.member(self) } /// The serial number of the message this message is a reply to. #[deprecated( since = "4.0.0", note = "Use `Message::header` with `message::Header::reply_serial` instead" )] pub fn reply_serial(&self) -> Option { self.inner.quick_fields.reply_serial() } /// The body that you can deserialize using [`Body::deserialize`]. /// /// # Example /// /// ``` /// # use zbus::message::Message; /// # (|| -> zbus::Result<()> { /// let send_body = (7i32, (2i32, "foo"), vec!["bar"]); /// let message = Message::method("/", "ping")? /// .destination("zbus.test")? /// .interface("zbus.test")? /// .build(&send_body)?; /// let body = message.body(); /// let body: zbus::zvariant::Structure = body.deserialize()?; /// let fields = body.fields(); /// assert!(matches!(fields[0], zvariant::Value::I32(7))); /// assert!(matches!(fields[1], zvariant::Value::Structure(_))); /// assert!(matches!(fields[2], zvariant::Value::Array(_))); /// /// let reply_body = Message::method_reply(&message)?.build(&body)?.body(); /// let reply_value : (i32, (i32, &str), Vec) = reply_body.deserialize()?; /// /// assert_eq!(reply_value.0, 7); /// assert_eq!(reply_value.2.len(), 1); /// # Ok(()) })().unwrap() /// ``` pub fn body(&self) -> Body { Body::new( self.inner.bytes.slice(self.inner.body_offset..), self.clone(), ) } /// Get a reference to the underlying byte encoding of the message. pub fn data(&self) -> &serialized::Data<'static, 'static> { &self.inner.bytes } /// Get the receive ordering of a message. /// /// This may be used to identify how two events were ordered on the bus. It only produces a /// useful ordering for messages that were produced by the same [`zbus::Connection`]. /// /// This is completely unrelated to the serial number on the message, which is set by the peer /// and might not be ordered at all. pub fn recv_position(&self) -> Sequence { self.inner.recv_seq } } impl fmt::Debug for Message { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut msg = f.debug_struct("Msg"); let h = self.header(); msg.field("type", &h.message_type()); msg.field("serial", &self.primary_header().serial_num()); if let Some(sender) = h.sender() { msg.field("sender", &sender); } if let Some(serial) = h.reply_serial() { msg.field("reply-serial", &serial); } if let Some(path) = h.path() { msg.field("path", &path); } if let Some(iface) = h.interface() { msg.field("iface", &iface); } if let Some(member) = h.member() { msg.field("member", &member); } if let Some(s) = self.body().signature() { msg.field("body", &s); } #[cfg(unix)] { msg.field("fds", &self.data().fds()); } msg.finish() } } impl fmt::Display for Message { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let header = self.header(); let (ty, error_name, sender, member) = ( header.message_type(), header.error_name(), header.sender(), header.member(), ); match ty { Type::MethodCall => { write!(f, "Method call")?; if let Some(m) = member { write!(f, " {m}")?; } } Type::MethodReturn => { write!(f, "Method return")?; } Type::Error => { write!(f, "Error")?; if let Some(e) = error_name { write!(f, " {e}")?; } let body = self.body(); let msg = body.deserialize_unchecked::<&str>(); if let Ok(msg) = msg { write!(f, ": {msg}")?; } } Type::Signal => { write!(f, "Signal")?; if let Some(m) = member { write!(f, " {m}")?; } } } if let Some(s) = sender { write!(f, " from {s}")?; } Ok(()) } } #[cfg(test)] mod tests { #[cfg(unix)] use std::os::fd::{AsFd, AsRawFd}; use test_log::test; #[cfg(unix)] use zvariant::Fd; use super::Message; use crate::Error; #[test] fn test() { #[cfg(unix)] let stdout = std::io::stdout(); let m = Message::method("/", "do") .unwrap() .sender(":1.72") .unwrap() .build(&( #[cfg(unix)] Fd::from(&stdout), "foo", )) .unwrap(); assert_eq!( m.body().signature().unwrap().to_string(), if cfg!(unix) { "hs" } else { "s" } ); #[cfg(unix)] { let fds = m.data().fds(); assert_eq!(fds.len(), 1); // FDs get dup'ed so it has to be a different FD now. assert_ne!(fds[0].as_fd().as_raw_fd(), stdout.as_raw_fd()); } let body: Result = m.body().deserialize(); assert!(matches!( body.unwrap_err(), Error::Variant(zvariant::Error::SignatureMismatch { .. }) )); assert_eq!(m.to_string(), "Method call do from :1.72"); let r = Message::method_reply(&m) .unwrap() .build(&("all fine!")) .unwrap(); assert_eq!(r.to_string(), "Method return"); let e = Message::method_error(&m, "org.freedesktop.zbus.Error") .unwrap() .build(&("kaboom!", 32)) .unwrap(); assert_eq!(e.to_string(), "Error org.freedesktop.zbus.Error: kaboom!"); } } zbus-4.3.1/src/message_stream.rs000064400000000000000000000252121046102023000147520ustar 00000000000000use std::{ pin::Pin, sync::Arc, task::{Context, Poll}, }; use async_broadcast::Receiver as ActiveReceiver; use futures_core::stream; use futures_util::stream::FusedStream; use ordered_stream::{OrderedStream, PollResult}; use static_assertions::assert_impl_all; use tracing::warn; use crate::{ connection::ConnectionInner, message::{Message, Sequence}, AsyncDrop, Connection, MatchRule, OwnedMatchRule, Result, }; /// A [`stream::Stream`] implementation that yields [`Message`] items. /// /// You can convert a [`Connection`] to this type and back to [`Connection`]. /// /// **NOTE**: You must ensure a `MessageStream` is continuously polled or you will experience hangs. /// If you don't need to continuously poll the `MessageStream` but need to keep it around for later /// use, keep the connection around and convert it into a `MessageStream` when needed. The /// conversion is not an expensive operation so you don't need to worry about performance, unless /// you do it very frequently. If you need to convert back and forth frequently, you may want to /// consider keeping both a connection and stream around. #[derive(Clone, Debug)] #[must_use = "streams do nothing unless polled"] pub struct MessageStream { inner: Inner, } assert_impl_all!(MessageStream: Send, Sync, Unpin); impl MessageStream { /// Create a message stream for the given match rule. /// /// If `conn` is a bus connection and match rule is for a signal, the match rule will be /// registered with the bus and queued for deregistration when the stream is dropped. If you'd /// like immediate deregistration, use [`AsyncDrop::async_drop`]. The reason match rules are /// only registered with the bus for signals is that D-Bus specification only allows signals to /// be broadcasted and unicast messages are always sent to their destination (regardless of any /// match rules registered by the destination) by the bus. Hence there is no need to register /// match rules for non-signal messages with the bus. /// /// Having said that, stream created by this method can still very useful as it allows you to /// avoid needless task wakeups and simplify your stream consuming code. /// /// You can optionally also request the capacity of the underlying message queue through /// `max_queued`. If specified, the capacity is guaranteed to be at least `max_queued`. If not /// specified, the default of 64 is assumed. The capacity can also be changed later through /// [`MessageStream::set_max_queued`]. /// /// # Example /// /// ``` /// use async_io::Timer; /// use zbus::{AsyncDrop, Connection, MatchRule, MessageStream, fdo::NameOwnerChanged}; /// use futures_util::{TryStreamExt, future::select, future::Either::{Left, Right}, pin_mut}; /// /// # zbus::block_on(async { /// let conn = Connection::session().await?; /// let rule = MatchRule::builder() /// .msg_type(zbus::message::Type::Signal) /// .sender("org.freedesktop.DBus")? /// .interface("org.freedesktop.DBus")? /// .member("NameOwnerChanged")? /// .add_arg("org.freedesktop.zbus.MatchRuleStreamTest42")? /// .build(); /// let mut stream = MessageStream::for_match_rule( /// rule, /// &conn, /// // For such a specific match rule, we don't need a big queue. /// Some(1), /// ).await?; /// /// let rule_str = "type='signal',sender='org.freedesktop.DBus',\ /// interface='org.freedesktop.DBus',member='NameOwnerChanged',\ /// arg0='org.freedesktop.zbus.MatchRuleStreamTest42'"; /// assert_eq!( /// stream.match_rule().map(|r| r.to_string()).as_deref(), /// Some(rule_str), /// ); /// /// // We register 2 names, starting with the uninteresting one. If `stream` wasn't filtering /// // messages based on the match rule, we'd receive method return call for each of these 2 /// // calls first. /// // /// // Note that the `NameOwnerChanged` signal will not be sent by the bus for the first name /// // we register since we setup an arg filter. /// conn.request_name("org.freedesktop.zbus.MatchRuleStreamTest44") /// .await?; /// conn.request_name("org.freedesktop.zbus.MatchRuleStreamTest42") /// .await?; /// /// let msg = stream.try_next().await?.unwrap(); /// let signal = NameOwnerChanged::from_message(msg).unwrap(); /// assert_eq!(signal.args()?.name(), "org.freedesktop.zbus.MatchRuleStreamTest42"); /// stream.async_drop().await; /// /// // Ensure the match rule is deregistered and this connection doesn't receive /// // `NameOwnerChanged` signals. /// let stream = MessageStream::from(&conn).try_filter_map(|msg| async move { /// Ok(NameOwnerChanged::from_message(msg)) /// }); /// conn.release_name("org.freedesktop.zbus.MatchRuleStreamTest42").await?; /// /// pin_mut!(stream); /// let next = stream.try_next(); /// pin_mut!(next); /// let timeout = Timer::after(std::time::Duration::from_millis(50)); /// pin_mut!(timeout); /// match select(next, timeout).await { /// Left((msg, _)) => unreachable!("unexpected message: {:?}", msg), /// Right((_, _)) => (), /// } /// /// # Ok::<(), zbus::Error>(()) /// # }).unwrap(); /// ``` /// /// # Caveats /// /// Since this method relies on [`MatchRule::matches`], it inherits its caveats. pub async fn for_match_rule( rule: R, conn: &Connection, max_queued: Option, ) -> Result where R: TryInto, R::Error: Into, { let rule = rule.try_into().map_err(Into::into)?; let msg_receiver = conn.add_match(rule.clone(), max_queued).await?; Ok(Self::for_subscription_channel( msg_receiver, Some(rule), conn, )) } /// The associated match rule, if any. pub fn match_rule(&self) -> Option> { self.inner.match_rule.as_deref().cloned() } /// The maximum number of messages to queue for this stream. pub fn max_queued(&self) -> usize { self.inner.msg_receiver.capacity() } /// Set maximum number of messages to queue for this stream. /// /// After this call, the capacity is guaranteed to be at least `max_queued`. pub fn set_max_queued(&mut self, max_queued: usize) { if max_queued <= self.max_queued() { return; } self.inner.msg_receiver.set_capacity(max_queued); } pub(crate) fn for_subscription_channel( msg_receiver: ActiveReceiver>, rule: Option, conn: &Connection, ) -> Self { let conn_inner = conn.inner.clone(); Self { inner: Inner { conn_inner, msg_receiver, match_rule: rule, }, } } } impl stream::Stream for MessageStream { type Item = Result; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let this = self.get_mut(); Pin::new(&mut this.inner.msg_receiver).poll_next(cx) } } impl OrderedStream for MessageStream { type Data = Result; type Ordering = Sequence; fn poll_next_before( self: Pin<&mut Self>, cx: &mut Context<'_>, before: Option<&Self::Ordering>, ) -> Poll> { let this = self.get_mut(); match stream::Stream::poll_next(Pin::new(this), cx) { Poll::Pending if before.is_some() => { // Assume the provided Sequence in before was obtained from a Message // associated with our Connection (because that's the only supported use case). // Because there is only one socket-reader task, any messages that would have been // ordered before that message would have already been sitting in the broadcast // queue (and we would have seen Ready in our poll). Because we didn't, we can // guarantee that we won't ever produce a message whose sequence is before that // provided value, and so we can return NoneBefore. // // This ensures that ordered_stream::Join will never return Pending while it // has a message buffered. Poll::Ready(PollResult::NoneBefore) } Poll::Pending => Poll::Pending, Poll::Ready(Some(Ok(msg))) => Poll::Ready(PollResult::Item { ordering: msg.recv_position(), data: Ok(msg), }), Poll::Ready(Some(Err(e))) => Poll::Ready(PollResult::Item { ordering: Sequence::LAST, data: Err(e), }), Poll::Ready(None) => Poll::Ready(PollResult::Terminated), } } } impl FusedStream for MessageStream { fn is_terminated(&self) -> bool { self.inner.msg_receiver.is_terminated() } } impl From for MessageStream { fn from(conn: Connection) -> Self { let conn_inner = conn.inner; let msg_receiver = conn_inner.msg_receiver.activate_cloned(); Self { inner: Inner { conn_inner, msg_receiver, match_rule: None, }, } } } impl From<&Connection> for MessageStream { fn from(conn: &Connection) -> Self { Self::from(conn.clone()) } } impl From for Connection { fn from(stream: MessageStream) -> Connection { Connection::from(&stream) } } impl From<&MessageStream> for Connection { fn from(stream: &MessageStream) -> Connection { Connection { inner: stream.inner.conn_inner.clone(), } } } #[derive(Clone, Debug)] struct Inner { conn_inner: Arc, msg_receiver: ActiveReceiver>, match_rule: Option, } impl Drop for Inner { fn drop(&mut self) { let conn = Connection { inner: self.conn_inner.clone(), }; if let Some(rule) = self.match_rule.take() { conn.queue_remove_match(rule); } } } #[async_trait::async_trait] impl AsyncDrop for MessageStream { async fn async_drop(mut self) { let conn = Connection { inner: self.inner.conn_inner.clone(), }; if let Some(rule) = self.inner.match_rule.take() { if let Err(e) = conn.remove_match(rule).await { warn!("Failed to remove match rule: {}", e); } } } } zbus-4.3.1/src/object_server/interface.rs000064400000000000000000000152241046102023000165510ustar 00000000000000use std::{ any::{Any, TypeId}, collections::HashMap, fmt::{self, Write}, future::Future, pin::Pin, sync::Arc, }; use async_trait::async_trait; use zbus::message::Flags; use zbus_names::{InterfaceName, MemberName}; use zvariant::{DynamicType, OwnedValue, Value}; use crate::{ async_lock::RwLock, fdo, message::Message, object_server::SignalContext, Connection, ObjectServer, Result, }; use tracing::trace; /// A helper type returned by [`Interface`] callbacks. pub enum DispatchResult<'a> { /// This interface does not support the given method NotFound, /// Retry with [Interface::call_mut]. /// /// This is equivalent to NotFound if returned by call_mut. RequiresMut, /// The method was found and will be completed by running this Future Async(Pin> + Send + 'a>>), } impl<'a> DispatchResult<'a> { /// Helper for creating the Async variant pub fn new_async(conn: &'a Connection, msg: &'a Message, f: F) -> Self where F: Future> + Send + 'a, T: serde::Serialize + DynamicType + Send + Sync, E: zbus::DBusError + Send, { DispatchResult::Async(Box::pin(async move { let hdr = msg.header(); let ret = f.await; if !hdr.primary().flags().contains(Flags::NoReplyExpected) { match ret { Ok(r) => conn.reply(msg, &r).await, Err(e) => conn.reply_dbus_error(&hdr, e).await, } .map(|_seq| ()) } else { trace!("No reply expected for {:?} by the caller.", msg); Ok(()) } })) } } /// The trait is used to dispatch messages to an interface instance. /// /// This trait should be treated as unstable API and compatibility may break in minor /// version bumps. Because of this and other reasons, it is not recommended to manually implement /// this trait. The [`crate::dbus_interface`] macro implements it for you. /// /// If you have an advanced use case where `dbus_interface` is inadequate, consider using /// [`crate::MessageStream`] or [`crate::blocking::MessageIterator`] instead. #[async_trait] pub trait Interface: Any + Send + Sync { /// Return the name of the interface. Ex: "org.foo.MyInterface" fn name() -> InterfaceName<'static> where Self: Sized; /// Whether each method call will be handled from a different spawned task. /// /// Note: When methods are called from separate tasks, they may not be run in the order in which /// they were called. fn spawn_tasks_for_methods(&self) -> bool { true } /// Get a property value. Returns `None` if the property doesn't exist. async fn get(&self, property_name: &str) -> Option>; /// Return all the properties. async fn get_all(&self) -> fdo::Result>; /// Set a property value. /// /// Return [`DispatchResult::NotFound`] if the property doesn't exist, or /// [`DispatchResult::RequiresMut`] if `set_mut` should be used instead. The default /// implementation just returns `RequiresMut`. fn set<'call>( &'call self, property_name: &'call str, value: &'call Value<'_>, ctxt: &'call SignalContext<'_>, ) -> DispatchResult<'call> { let _ = (property_name, value, ctxt); DispatchResult::RequiresMut } /// Set a property value. /// /// Returns `None` if the property doesn't exist. /// /// This will only be invoked if `set` returned `RequiresMut`. async fn set_mut( &mut self, property_name: &str, value: &Value<'_>, ctxt: &SignalContext<'_>, ) -> Option>; /// Call a method. /// /// Return [`DispatchResult::NotFound`] if the method doesn't exist, or /// [`DispatchResult::RequiresMut`] if `call_mut` should be used instead. /// /// It is valid, though inefficient, for this to always return `RequiresMut`. fn call<'call>( &'call self, server: &'call ObjectServer, connection: &'call Connection, msg: &'call Message, name: MemberName<'call>, ) -> DispatchResult<'call>; /// Call a `&mut self` method. /// /// This will only be invoked if `call` returned `RequiresMut`. fn call_mut<'call>( &'call mut self, server: &'call ObjectServer, connection: &'call Connection, msg: &'call Message, name: MemberName<'call>, ) -> DispatchResult<'call>; /// Write introspection XML to the writer, with the given indentation level. fn introspect_to_writer(&self, writer: &mut dyn Write, level: usize); } /// A type for a reference counted Interface trait-object, with associated run-time details and a /// manual Debug impl. #[derive(Clone)] pub(crate) struct ArcInterface { pub instance: Arc>, pub spawn_tasks_for_methods: bool, } impl ArcInterface { pub fn new(iface: I) -> Self where I: Interface, { let spawn_tasks_for_methods = iface.spawn_tasks_for_methods(); Self { instance: Arc::new(RwLock::new(iface)), spawn_tasks_for_methods, } } } impl fmt::Debug for ArcInterface { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Arc>") .finish_non_exhaustive() } } // Note: while it is possible to implement this without `unsafe`, it currently requires a helper // trait with a blanket impl that creates `dyn Any` refs. It's simpler (and more performant) to // just check the type ID and do the downcast ourself. // // See https://github.com/rust-lang/rust/issues/65991 for a rustc feature that will make it // possible to get a `dyn Any` ref directly from a `dyn Interface` ref; once that is stable, we can // remove this unsafe code. impl dyn Interface { /// Return Any of self pub(crate) fn downcast_ref(&self) -> Option<&T> { if ::type_id(self) == TypeId::of::() { // SAFETY: If type ID matches, it means object is of type T Some(unsafe { &*(self as *const dyn Interface as *const T) }) } else { None } } /// Return Any of self pub(crate) fn downcast_mut(&mut self) -> Option<&mut T> { if ::type_id(self) == TypeId::of::() { // SAFETY: If type ID matches, it means object is of type T Some(unsafe { &mut *(self as *mut dyn Interface as *mut T) }) } else { None } } } zbus-4.3.1/src/object_server/mod.rs000064400000000000000000000717711046102023000154010ustar 00000000000000//! The object server API. use event_listener::{Event, EventListener}; use serde::{Deserialize, Serialize}; use std::{ collections::{hash_map::Entry, HashMap}, fmt::Write, marker::PhantomData, ops::{Deref, DerefMut}, sync::Arc, }; use tracing::{debug, instrument, trace, trace_span, Instrument}; use static_assertions::assert_impl_all; use zbus_names::InterfaceName; use zvariant::{ObjectPath, OwnedObjectPath, OwnedValue, Signature, Type, Value}; use crate::{ async_lock::{RwLock, RwLockReadGuard, RwLockWriteGuard}, connection::WeakConnection, fdo, fdo::{Introspectable, ManagedObjects, ObjectManager, Peer, Properties}, message::{Header, Message}, Connection, Error, Result, }; mod interface; pub(crate) use interface::ArcInterface; pub use interface::{DispatchResult, Interface}; mod signal_context; pub use signal_context::SignalContext; /// Opaque structure that derefs to an `Interface` type. pub struct InterfaceDeref<'d, I> { iface: RwLockReadGuard<'d, dyn Interface>, phantom: PhantomData, } impl Deref for InterfaceDeref<'_, I> where I: Interface, { type Target = I; fn deref(&self) -> &I { self.iface.downcast_ref::().unwrap() } } /// Opaque structure that mutably derefs to an `Interface` type. pub struct InterfaceDerefMut<'d, I> { iface: RwLockWriteGuard<'d, dyn Interface>, phantom: PhantomData, } impl Deref for InterfaceDerefMut<'_, I> where I: Interface, { type Target = I; fn deref(&self) -> &I { self.iface.downcast_ref::().unwrap() } } impl DerefMut for InterfaceDerefMut<'_, I> where I: Interface, { fn deref_mut(&mut self) -> &mut Self::Target { self.iface.downcast_mut::().unwrap() } } /// Wrapper over an interface, along with its corresponding `SignalContext` /// instance. A reference to the underlying interface may be obtained via /// [`InterfaceRef::get`] and [`InterfaceRef::get_mut`]. pub struct InterfaceRef { ctxt: SignalContext<'static>, lock: Arc>, phantom: PhantomData, } impl InterfaceRef where I: 'static, { /// Get a reference to the underlying interface. /// /// **WARNING:** If methods (e.g property setters) in `ObjectServer` require `&mut self` /// `ObjectServer` will not be able to access the interface in question until all references /// of this method are dropped, it is highly recommended that the scope of the interface /// returned is restricted. pub async fn get(&self) -> InterfaceDeref<'_, I> { let iface = self.lock.read().await; iface .downcast_ref::() .expect("Unexpected interface type"); InterfaceDeref { iface, phantom: PhantomData, } } /// Get a reference to the underlying interface. /// /// **WARNINGS:** Since the `ObjectServer` will not be able to access the interface in question /// until the return value of this method is dropped, it is highly recommended that the scope /// of the interface returned is restricted. /// /// # Errors /// /// If the interface at this instance's path is not valid, `Error::InterfaceNotFound` error is /// returned. /// /// # Examples /// /// ```no_run /// # use std::error::Error; /// # use async_io::block_on; /// # use zbus::{Connection, interface}; /// /// struct MyIface(u32); /// /// #[interface(name = "org.myiface.MyIface")] /// impl MyIface { /// #[zbus(property)] /// async fn count(&self) -> u32 { /// self.0 /// } /// } /// /// # block_on(async { /// // Setup connection and object_server etc here and then in another part of the code: /// # let connection = Connection::session().await?; /// # /// # let path = "/org/zbus/path"; /// # connection.object_server().at(path, MyIface(22)).await?; /// let object_server = connection.object_server(); /// let iface_ref = object_server.interface::<_, MyIface>(path).await?; /// let mut iface = iface_ref.get_mut().await; /// iface.0 = 42; /// iface.count_changed(iface_ref.signal_context()).await?; /// # Ok::<_, Box>(()) /// # })?; /// # /// # Ok::<_, Box>(()) /// ``` pub async fn get_mut(&self) -> InterfaceDerefMut<'_, I> { let mut iface = self.lock.write().await; iface .downcast_ref::() .expect("Unexpected interface type"); iface .downcast_mut::() .expect("Unexpected interface type"); InterfaceDerefMut { iface, phantom: PhantomData, } } pub fn signal_context(&self) -> &SignalContext<'static> { &self.ctxt } } impl Clone for InterfaceRef { fn clone(&self) -> Self { Self { ctxt: self.ctxt.clone(), lock: self.lock.clone(), phantom: PhantomData, } } } #[derive(Default, Debug)] pub(crate) struct Node { path: OwnedObjectPath, children: HashMap, interfaces: HashMap, ArcInterface>, } impl Node { pub(crate) fn new(path: OwnedObjectPath) -> Self { let mut node = Self { path, ..Default::default() }; assert!(node.add_interface(Peer)); assert!(node.add_interface(Introspectable)); assert!(node.add_interface(Properties)); node } // Get the child Node at path. pub(crate) fn get_child(&self, path: &ObjectPath<'_>) -> Option<&Node> { let mut node = self; for i in path.split('/').skip(1) { if i.is_empty() { continue; } match node.children.get(i) { Some(n) => node = n, None => return None, } } Some(node) } // Get the child Node at path. Optionally create one if it doesn't exist. // It also returns the path of parent node that implements ObjectManager (if any). If multiple // parents implement it (they shouldn't), then the closest one is returned. fn get_child_mut( &mut self, path: &ObjectPath<'_>, create: bool, ) -> (Option<&mut Node>, Option>) { let mut node = self; let mut node_path = String::new(); let mut obj_manager_path = None; for i in path.split('/').skip(1) { if i.is_empty() { continue; } if node.interfaces.contains_key(&ObjectManager::name()) { obj_manager_path = Some((*node.path).clone()); } write!(&mut node_path, "/{i}").unwrap(); match node.children.entry(i.into()) { Entry::Vacant(e) => { if create { let path = node_path.as_str().try_into().expect("Invalid Object Path"); node = e.insert(Node::new(path)); } else { return (None, obj_manager_path); } } Entry::Occupied(e) => node = e.into_mut(), } } (Some(node), obj_manager_path) } pub(crate) fn interface_lock(&self, interface_name: InterfaceName<'_>) -> Option { self.interfaces.get(&interface_name).cloned() } fn remove_interface(&mut self, interface_name: InterfaceName<'static>) -> bool { self.interfaces.remove(&interface_name).is_some() } fn is_empty(&self) -> bool { !self.interfaces.keys().any(|k| { *k != Peer::name() && *k != Introspectable::name() && *k != Properties::name() && *k != ObjectManager::name() }) } fn remove_node(&mut self, node: &str) -> bool { self.children.remove(node).is_some() } fn add_arc_interface(&mut self, name: InterfaceName<'static>, arc_iface: ArcInterface) -> bool { match self.interfaces.entry(name) { Entry::Vacant(e) => { e.insert(arc_iface); true } Entry::Occupied(_) => false, } } fn add_interface(&mut self, iface: I) -> bool where I: Interface, { self.add_arc_interface(I::name(), ArcInterface::new(iface)) } async fn introspect_to_writer(&self, writer: &mut W) { enum Fragment<'a> { /// Represent an unclosed node tree, could be further splitted into sub-`Fragment`s Node { name: &'a str, node: &'a Node, level: usize, }, /// Represent a closing `` End { level: usize }, } let mut stack = Vec::new(); stack.push(Fragment::Node { name: "", node: self, level: 0, }); // This can be seen as traversing the fragment tree in pre-order DFS with formatted XML // fragment, splitted `Fragment::Node`s and `Fragment::End` being current node, left // subtree and right leaf respectively. while let Some(fragment) = stack.pop() { match fragment { Fragment::Node { name, node, level } => { stack.push(Fragment::End { level }); for (name, node) in &node.children { stack.push(Fragment::Node { name, node, level: level + 2, }) } if level == 0 { writeln!( writer, r#" "# ) .unwrap(); } else { writeln!( writer, "{:indent$}", "", name, indent = level ) .unwrap(); } for iface in node.interfaces.values() { iface .instance .read() .await .introspect_to_writer(writer, level + 2); } } Fragment::End { level } => { writeln!(writer, "{:indent$}", "", indent = level).unwrap(); } } } } pub(crate) async fn introspect(&self) -> String { let mut xml = String::with_capacity(1024); self.introspect_to_writer(&mut xml).await; xml } pub(crate) async fn get_managed_objects(&self) -> fdo::Result { let mut managed_objects = ManagedObjects::new(); // Recursively get all properties of all interfaces of descendants. let mut node_list: Vec<_> = self.children.values().collect(); while let Some(node) = node_list.pop() { let mut interfaces = HashMap::new(); for iface_name in node.interfaces.keys().filter(|n| { // Filter standard interfaces. *n != &Peer::name() && *n != &Introspectable::name() && *n != &Properties::name() && *n != &ObjectManager::name() }) { let props = node.get_properties(iface_name.clone()).await?; interfaces.insert(iface_name.clone().into(), props); } managed_objects.insert(node.path.clone(), interfaces); node_list.extend(node.children.values()); } Ok(managed_objects) } async fn get_properties( &self, interface_name: InterfaceName<'_>, ) -> fdo::Result> { self.interface_lock(interface_name) .expect("Interface was added but not found") .instance .read() .await .get_all() .await } } /// An object server, holding server-side D-Bus objects & interfaces. /// /// Object servers hold interfaces on various object paths, and expose them over D-Bus. /// /// All object paths will have the standard interfaces implemented on your behalf, such as /// `org.freedesktop.DBus.Introspectable` or `org.freedesktop.DBus.Properties`. /// /// # Example /// /// This example exposes the `org.myiface.Example.Quit` method on the `/org/zbus/path` /// path. /// /// ```no_run /// # use std::error::Error; /// use zbus::{Connection, interface}; /// use event_listener::Event; /// # use async_io::block_on; /// /// struct Example { /// // Interfaces are owned by the ObjectServer. They can have /// // `&mut self` methods. /// quit_event: Event, /// } /// /// impl Example { /// fn new(quit_event: Event) -> Self { /// Self { quit_event } /// } /// } /// /// #[interface(name = "org.myiface.Example")] /// impl Example { /// // This will be the "Quit" D-Bus method. /// async fn quit(&mut self) { /// self.quit_event.notify(1); /// } /// /// // See `interface` documentation to learn /// // how to expose properties & signals as well. /// } /// /// # block_on(async { /// let connection = Connection::session().await?; /// /// let quit_event = Event::new(); /// let quit_listener = quit_event.listen(); /// let interface = Example::new(quit_event); /// connection /// .object_server() /// .at("/org/zbus/path", interface) /// .await?; /// /// quit_listener.await; /// # Ok::<_, Box>(()) /// # })?; /// # Ok::<_, Box>(()) /// ``` #[derive(Debug)] pub struct ObjectServer { conn: WeakConnection, root: RwLock, } assert_impl_all!(ObjectServer: Send, Sync, Unpin); impl ObjectServer { /// Creates a new D-Bus `ObjectServer`. pub(crate) fn new(conn: &Connection) -> Self { Self { conn: conn.into(), root: RwLock::new(Node::new("/".try_into().expect("zvariant bug"))), } } pub(crate) fn root(&self) -> &RwLock { &self.root } /// Register a D-Bus [`Interface`] at a given path. (see the example above) /// /// Typically you'd want your interfaces to be registered immediately after the associated /// connection is established and therefore use [`zbus::connection::Builder::serve_at`] instead. /// However, there are situations where you'd need to register interfaces dynamically and that's /// where this method becomes useful. /// /// If the interface already exists at this path, returns false. pub async fn at<'p, P, I>(&self, path: P, iface: I) -> Result where I: Interface, P: TryInto>, P::Error: Into, { self.add_arc_interface(path, I::name(), ArcInterface::new(iface)) .await } pub(crate) async fn add_arc_interface<'p, P>( &self, path: P, name: InterfaceName<'static>, arc_iface: ArcInterface, ) -> Result where P: TryInto>, P::Error: Into, { let path = path.try_into().map_err(Into::into)?; let mut root = self.root().write().await; let (node, manager_path) = root.get_child_mut(&path, true); let node = node.unwrap(); let added = node.add_arc_interface(name.clone(), arc_iface); if added { if name == ObjectManager::name() { // Just added an object manager. Need to signal all managed objects under it. let ctxt = SignalContext::new(&self.connection(), path)?; let objects = node.get_managed_objects().await?; for (path, owned_interfaces) in objects { let interfaces = owned_interfaces .iter() .map(|(i, props)| { let props = props .iter() .map(|(k, v)| Ok((k.as_str(), Value::try_from(v)?))) .collect::>(); Ok((i.into(), props?)) }) .collect::>()?; ObjectManager::interfaces_added(&ctxt, &path, &interfaces).await?; } } else if let Some(manager_path) = manager_path { let ctxt = SignalContext::new(&self.connection(), manager_path.clone())?; let mut interfaces = HashMap::new(); let owned_props = node.get_properties(name.clone()).await?; let props = owned_props .iter() .map(|(k, v)| Ok((k.as_str(), Value::try_from(v)?))) .collect::>()?; interfaces.insert(name, props); ObjectManager::interfaces_added(&ctxt, &path, &interfaces).await?; } } Ok(added) } /// Unregister a D-Bus [`Interface`] at a given path. /// /// If there are no more interfaces left at that path, destroys the object as well. /// Returns whether the object was destroyed. pub async fn remove<'p, I, P>(&self, path: P) -> Result where I: Interface, P: TryInto>, P::Error: Into, { let path = path.try_into().map_err(Into::into)?; let mut root = self.root.write().await; let (node, manager_path) = root.get_child_mut(&path, false); let node = node.ok_or(Error::InterfaceNotFound)?; if !node.remove_interface(I::name()) { return Err(Error::InterfaceNotFound); } if let Some(manager_path) = manager_path { let ctxt = SignalContext::new(&self.connection(), manager_path.clone())?; ObjectManager::interfaces_removed(&ctxt, &path, &[I::name()]).await?; } if node.is_empty() { let mut path_parts = path.rsplit('/').filter(|i| !i.is_empty()); let last_part = path_parts.next().unwrap(); let ppath = ObjectPath::from_string_unchecked( path_parts.fold(String::new(), |a, p| format!("/{p}{a}")), ); root.get_child_mut(&ppath, false) .0 .unwrap() .remove_node(last_part); return Ok(true); } Ok(false) } /// Get the interface at the given path. /// /// # Errors /// /// If the interface is not registered at the given path, `Error::InterfaceNotFound` error is /// returned. /// /// # Examples /// /// The typical use of this is property changes outside of a dispatched handler: /// /// ```no_run /// # use std::error::Error; /// # use zbus::{Connection, interface}; /// # use async_io::block_on; /// # /// struct MyIface(u32); /// /// #[interface(name = "org.myiface.MyIface")] /// impl MyIface { /// #[zbus(property)] /// async fn count(&self) -> u32 { /// self.0 /// } /// } /// /// # block_on(async { /// # let connection = Connection::session().await?; /// # /// # let path = "/org/zbus/path"; /// # connection.object_server().at(path, MyIface(0)).await?; /// let iface_ref = connection /// .object_server() /// .interface::<_, MyIface>(path).await?; /// let mut iface = iface_ref.get_mut().await; /// iface.0 = 42; /// iface.count_changed(iface_ref.signal_context()).await?; /// # Ok::<_, Box>(()) /// # })?; /// # /// # Ok::<_, Box>(()) /// ``` pub async fn interface<'p, P, I>(&self, path: P) -> Result> where I: Interface, P: TryInto>, P::Error: Into, { let path = path.try_into().map_err(Into::into)?; let root = self.root().read().await; let node = root.get_child(&path).ok_or(Error::InterfaceNotFound)?; let lock = node .interface_lock(I::name()) .ok_or(Error::InterfaceNotFound)? .instance .clone(); // Ensure what we return can later be dowcasted safely. lock.read() .await .downcast_ref::() .ok_or(Error::InterfaceNotFound)?; let conn = self.connection(); // SAFETY: We know that there is a valid path on the node as we already converted w/o error. let ctxt = SignalContext::new(&conn, path).unwrap().into_owned(); Ok(InterfaceRef { ctxt, lock, phantom: PhantomData, }) } async fn dispatch_call_to_iface( &self, iface: Arc>, connection: &Connection, msg: &Message, hdr: &Header<'_>, ) -> fdo::Result<()> { let member = hdr .member() .ok_or_else(|| fdo::Error::Failed("Missing member".into()))?; let iface_name = hdr .interface() .ok_or_else(|| fdo::Error::Failed("Missing interface".into()))?; trace!("acquiring read lock on interface `{}`", iface_name); let read_lock = iface.read().await; trace!("acquired read lock on interface `{}`", iface_name); match read_lock.call(self, connection, msg, member.as_ref()) { DispatchResult::NotFound => { return Err(fdo::Error::UnknownMethod(format!( "Unknown method '{member}'" ))); } DispatchResult::Async(f) => { return f.await.map_err(|e| match e { Error::FDO(e) => *e, e => fdo::Error::Failed(format!("{e}")), }); } DispatchResult::RequiresMut => {} } drop(read_lock); trace!("acquiring write lock on interface `{}`", iface_name); let mut write_lock = iface.write().await; trace!("acquired write lock on interface `{}`", iface_name); match write_lock.call_mut(self, connection, msg, member.as_ref()) { DispatchResult::NotFound => {} DispatchResult::RequiresMut => {} DispatchResult::Async(f) => { return f.await.map_err(|e| match e { Error::FDO(e) => *e, e => fdo::Error::Failed(format!("{e}")), }); } } drop(write_lock); Err(fdo::Error::UnknownMethod(format!( "Unknown method '{member}'" ))) } async fn dispatch_method_call_try( &self, connection: &Connection, msg: &Message, hdr: &Header<'_>, ) -> fdo::Result<()> { let path = hdr .path() .ok_or_else(|| fdo::Error::Failed("Missing object path".into()))?; let iface_name = hdr .interface() // TODO: In the absence of an INTERFACE field, if two or more interfaces on the same // object have a method with the same name, it is undefined which of those // methods will be invoked. Implementations may choose to either return an // error, or deliver the message as though it had an arbitrary one of those // interfaces. .ok_or_else(|| fdo::Error::Failed("Missing interface".into()))?; // Check that the message has a member before spawning. // Note that an unknown member will still spawn a task. We should instead gather // all the details for the call before spawning. // See also https://github.com/dbus2/zbus/issues/674 for future of Interface. let _ = hdr .member() .ok_or_else(|| fdo::Error::Failed("Missing member".into()))?; // Ensure the root lock isn't held while dispatching the message. That // way, the object server can be mutated during that time. let (iface, with_spawn) = { let root = self.root.read().await; let node = root .get_child(path) .ok_or_else(|| fdo::Error::UnknownObject(format!("Unknown object '{path}'")))?; let iface = node.interface_lock(iface_name.as_ref()).ok_or_else(|| { fdo::Error::UnknownInterface(format!("Unknown interface '{iface_name}'")) })?; (iface.instance, iface.spawn_tasks_for_methods) }; if with_spawn { let executor = connection.executor().clone(); let task_name = format!("`{msg}` method dispatcher"); let connection = connection.clone(); let msg = msg.clone(); executor .spawn( async move { let server = connection.object_server(); let hdr = msg.header(); server .dispatch_call_to_iface(iface, &connection, &msg, &hdr) .await } .instrument(trace_span!("{}", task_name)), &task_name, ) .detach(); Ok(()) } else { self.dispatch_call_to_iface(iface, connection, msg, hdr) .await } } /// Dispatch an incoming message to a registered interface. /// /// The object server will handle the message by: /// /// - looking up the called object path & interface, /// /// - calling the associated method if one exists, /// /// - returning a message (responding to the caller with either a return or error message) to /// the caller through the associated server connection. /// /// Returns an error if the message is malformed. #[instrument(skip(self))] pub(crate) async fn dispatch_call(&self, msg: &Message, hdr: &Header<'_>) -> Result<()> { let conn = self.connection(); if let Err(e) = self.dispatch_method_call_try(&conn, msg, hdr).await { debug!("Returning error: {}", e); conn.reply_dbus_error(hdr, e).await?; } trace!("Handled: {}", msg); Ok(()) } pub(crate) fn connection(&self) -> Connection { self.conn .upgrade() .expect("ObjectServer can't exist w/o an associated Connection") } } impl From for ObjectServer { fn from(server: crate::blocking::ObjectServer) -> Self { server.into_inner() } } /// A response wrapper that notifies after response has been sent. /// /// Sometimes in [`interface`] method implemenations we need to do some other work after the /// response has been sent off. This wrapper type allows us to do that. Instead of returning your /// intended response type directly, wrap it in this type and return it from your method. The /// returned `EventListener` from `new` method will be notified when the response has been sent. /// /// A typical use case is sending off signals after the response has been sent. The easiest way to /// do that is to spawn a task from the method that sends the signal but only after being notified /// of the response dispatch. /// /// # Caveats /// /// The notification indicates that the response has been sent off, not that destination peer has /// received it. That can only be guaranteed for a peer-to-peer connection. /// /// [`interface`]: crate::interface #[derive(Debug)] pub struct ResponseDispatchNotifier { response: R, event: Option, } impl ResponseDispatchNotifier { /// Create a new `NotifyResponse`. pub fn new(response: R) -> (Self, EventListener) { let event = Event::new(); let listener = event.listen(); ( Self { response, event: Some(event), }, listener, ) } /// Get the response. pub fn response(&self) -> &R { &self.response } } impl Serialize for ResponseDispatchNotifier where R: Serialize, { fn serialize(&self, serializer: S) -> std::result::Result where S: serde::Serializer, { self.response.serialize(serializer) } } impl<'de, R> Deserialize<'de> for ResponseDispatchNotifier where R: Deserialize<'de>, { fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { Ok(Self { response: R::deserialize(deserializer)?, event: None, }) } } impl Type for ResponseDispatchNotifier where R: Type, { fn signature() -> Signature<'static> { R::signature() } } impl Drop for ResponseDispatchNotifier { fn drop(&mut self) { if let Some(event) = self.event.take() { event.notify(usize::MAX); } } } zbus-4.3.1/src/object_server/signal_context.rs000064400000000000000000000051171046102023000176320ustar 00000000000000use zbus_names::BusName; use crate::{zvariant::ObjectPath, Connection, Error, Result}; /// A signal emission context. /// /// For signal emission using the high-level API, you'll need instances of this type. /// /// See [`crate::InterfaceRef::signal_context`] and [`crate::interface`] /// documentation for details and examples of this type in use. #[derive(Clone, Debug)] pub struct SignalContext<'s> { conn: Connection, path: ObjectPath<'s>, destination: Option>, } impl<'s> SignalContext<'s> { /// Create a new signal context for the given connection and object path. pub fn new

(conn: &Connection, path: P) -> Result where P: TryInto>, P::Error: Into, { path.try_into() .map(|p| Self { conn: conn.clone(), path: p, destination: None, }) .map_err(Into::into) } /// Create a new signal context for the given connection and object path. pub fn from_parts(conn: Connection, path: ObjectPath<'s>) -> Self { Self { conn, path, destination: None, } } /// Set the destination for the signal emission. /// /// Signals are typically broadcasted and thus don't have a destination. However, there are /// cases where you need to unicast signals to specific peers. This method allows you to set the /// destination for the signals emitted with this context. pub fn set_destination(mut self, destination: BusName<'s>) -> Self { self.destination = Some(destination); self } /// Get a reference to the associated connection. pub fn connection(&self) -> &Connection { &self.conn } /// Get a reference to the associated object path. pub fn path(&self) -> &ObjectPath<'s> { &self.path } /// Get a reference to the associated destination (if any). pub fn destination(&self) -> Option<&BusName<'s>> { self.destination.as_ref() } /// Creates an owned clone of `self`. pub fn to_owned(&self) -> SignalContext<'static> { SignalContext { conn: self.conn.clone(), path: self.path.to_owned(), destination: self.destination.as_ref().map(|d| d.to_owned()), } } /// Creates an owned clone of `self`. pub fn into_owned(self) -> SignalContext<'static> { SignalContext { conn: self.conn, path: self.path.into_owned(), destination: self.destination.map(|d| d.into_owned()), } } } zbus-4.3.1/src/proxy/builder.rs000064400000000000000000000146631046102023000145720ustar 00000000000000use std::{collections::HashSet, marker::PhantomData, sync::Arc}; use static_assertions::assert_impl_all; use zbus_names::{BusName, InterfaceName}; use zvariant::{ObjectPath, Str}; use crate::{proxy::ProxyInner, Connection, Error, Proxy, Result}; /// The properties caching mode. #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] #[non_exhaustive] pub enum CacheProperties { /// Cache properties. The properties will be cached upfront as part of the proxy /// creation. Yes, /// Don't cache properties. No, /// Cache properties but only populate the cache on the first read of a property (default). #[default] Lazily, } /// Builder for proxies. #[derive(Debug)] pub struct Builder<'a, T = ()> { conn: Connection, destination: Option>, path: Option>, interface: Option>, proxy_type: PhantomData, cache: CacheProperties, uncached_properties: Option>>, } impl<'a, T> Clone for Builder<'a, T> { fn clone(&self) -> Self { Self { conn: self.conn.clone(), destination: self.destination.clone(), path: self.path.clone(), interface: self.interface.clone(), cache: self.cache, uncached_properties: self.uncached_properties.clone(), proxy_type: PhantomData, } } } assert_impl_all!(Builder<'_>: Send, Sync, Unpin); impl<'a, T> Builder<'a, T> { /// Set the proxy destination address. pub fn destination(mut self, destination: D) -> Result where D: TryInto>, D::Error: Into, { self.destination = Some(destination.try_into().map_err(Into::into)?); Ok(self) } /// Set the proxy path. pub fn path

(mut self, path: P) -> Result where P: TryInto>, P::Error: Into, { self.path = Some(path.try_into().map_err(Into::into)?); Ok(self) } /// Set the proxy interface. pub fn interface(mut self, interface: I) -> Result where I: TryInto>, I::Error: Into, { self.interface = Some(interface.try_into().map_err(Into::into)?); Ok(self) } /// Set the properties caching mode. #[must_use] pub fn cache_properties(mut self, cache: CacheProperties) -> Self { self.cache = cache; self } /// Specify a set of properties (by name) which should be excluded from caching. #[must_use] pub fn uncached_properties(mut self, properties: &[&'a str]) -> Self { self.uncached_properties .replace(properties.iter().map(|p| Str::from(*p)).collect()); self } pub(crate) fn build_internal(self) -> Result> { let conn = self.conn; let destination = self .destination .ok_or(Error::MissingParameter("destination"))?; let path = self.path.ok_or(Error::MissingParameter("path"))?; let interface = self.interface.ok_or(Error::MissingParameter("interface"))?; let cache = self.cache; let uncached_properties = self.uncached_properties.unwrap_or_default(); Ok(Proxy { inner: Arc::new(ProxyInner::new( conn, destination, path, interface, cache, uncached_properties, )), }) } /// Build a proxy from the builder. /// /// # Errors /// /// If the builder is lacking the necessary parameters to build a proxy, /// [`Error::MissingParameter`] is returned. pub async fn build(self) -> Result where T: From>, { let cache_upfront = self.cache == CacheProperties::Yes; let proxy = self.build_internal()?; if cache_upfront { proxy .get_property_cache() .expect("properties cache not initialized") .ready() .await?; } Ok(proxy.into()) } } impl<'a, T> Builder<'a, T> where T: ProxyDefault, { /// Create a new [`Builder`] for the given connection. #[must_use] pub fn new(conn: &Connection) -> Self { Self { conn: conn.clone(), destination: T::DESTINATION .map(|d| BusName::from_static_str(d).expect("invalid bus name")), path: T::PATH.map(|p| ObjectPath::from_static_str(p).expect("invalid default path")), interface: T::INTERFACE .map(|i| InterfaceName::from_static_str(i).expect("invalid interface name")), cache: CacheProperties::default(), uncached_properties: None, proxy_type: PhantomData, } } /// Create a new [`Builder`] for the given connection. #[must_use] #[deprecated( since = "4.0.0", note = "use `Builder::new` instead, which is now generic over the proxy type" )] pub fn new_bare(conn: &Connection) -> Self { Self::new(conn) } } /// Trait for the default associated values of a proxy. /// /// The trait is automatically implemented by the [`dbus_proxy`] macro on your behalf, and may be /// later used to retrieve the associated constants. /// /// [`dbus_proxy`]: attr.dbus_proxy.html pub trait ProxyDefault { const INTERFACE: Option<&'static str>; const DESTINATION: Option<&'static str>; const PATH: Option<&'static str>; } impl ProxyDefault for Proxy<'_> { const INTERFACE: Option<&'static str> = None; const DESTINATION: Option<&'static str> = None; const PATH: Option<&'static str> = None; } #[cfg(test)] mod tests { use super::*; use test_log::test; #[test] #[ntest::timeout(15000)] fn builder() { crate::utils::block_on(builder_async()); } async fn builder_async() { let conn = Connection::session().await.unwrap(); let builder = Builder::>::new(&conn) .destination("org.freedesktop.DBus") .unwrap() .path("/some/path") .unwrap() .interface("org.freedesktop.Interface") .unwrap() .cache_properties(CacheProperties::No); assert!(matches!( builder.clone().destination.unwrap(), BusName::Unique(_), )); let proxy = builder.build().await.unwrap(); assert!(matches!(proxy.inner.destination, BusName::Unique(_))); } } zbus-4.3.1/src/proxy/mod.rs000064400000000000000000001476751046102023000137350ustar 00000000000000//! The client-side proxy API. use enumflags2::{bitflags, BitFlags}; use event_listener::{Event, EventListener}; use futures_core::{ready, stream}; use futures_util::{future::Either, stream::Map}; use ordered_stream::{join as join_streams, FromFuture, Join, OrderedStream, PollResult}; use static_assertions::assert_impl_all; use std::{ collections::{HashMap, HashSet}, fmt, future::Future, ops::Deref, pin::Pin, sync::{Arc, OnceLock, RwLock, RwLockReadGuard}, task::{Context, Poll}, }; use tracing::{debug, info_span, instrument, trace, Instrument}; use zbus_names::{BusName, InterfaceName, MemberName, UniqueName}; use zvariant::{ObjectPath, OwnedValue, Str, Value}; use crate::{ fdo::{self, IntrospectableProxy, NameOwnerChanged, PropertiesChangedStream, PropertiesProxy}, message::{Flags, Message, Sequence, Type}, AsyncDrop, Connection, Error, Executor, MatchRule, MessageStream, OwnedMatchRule, Result, Task, }; mod builder; pub use builder::{Builder, CacheProperties, ProxyDefault}; /// A client-side interface proxy. /// /// A `Proxy` is a helper to interact with an interface on a remote object. /// /// # Example /// /// ``` /// use std::result::Result; /// use std::error::Error; /// use zbus::{Connection, Proxy}; /// /// #[tokio::main] /// async fn main() -> Result<(), Box> { /// let connection = Connection::session().await?; /// let p = Proxy::new( /// &connection, /// "org.freedesktop.DBus", /// "/org/freedesktop/DBus", /// "org.freedesktop.DBus", /// ).await?; /// // owned return value /// let _id: String = p.call("GetId", &()).await?; /// // borrowed return value /// let body = p.call_method("GetId", &()).await?.body(); /// let _id: &str = body.deserialize()?; /// /// Ok(()) /// } /// ``` /// /// # Note /// /// It is recommended to use the [`proxy`] macro, which provides a more convenient and /// type-safe *façade* `Proxy` derived from a Rust trait. /// /// [`futures` crate]: https://crates.io/crates/futures /// [`proxy`]: attr.proxy.html #[derive(Clone, Debug)] pub struct Proxy<'a> { pub(crate) inner: Arc>, } assert_impl_all!(Proxy<'_>: Send, Sync, Unpin); /// This is required to avoid having the Drop impl extend the lifetime 'a, which breaks zbus_xmlgen /// (and possibly other crates). pub(crate) struct ProxyInnerStatic { pub(crate) conn: Connection, dest_owner_change_match_rule: OnceLock, } impl fmt::Debug for ProxyInnerStatic { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ProxyInnerStatic") .field( "dest_owner_change_match_rule", &self.dest_owner_change_match_rule, ) .finish_non_exhaustive() } } #[derive(Debug)] pub(crate) struct ProxyInner<'a> { inner_without_borrows: ProxyInnerStatic, pub(crate) destination: BusName<'a>, pub(crate) path: ObjectPath<'a>, pub(crate) interface: InterfaceName<'a>, /// Cache of property values. property_cache: Option, Task<()>)>>, /// Set of properties which do not get cached, by name. /// This overrides proxy-level caching behavior. uncached_properties: HashSet>, } impl Drop for ProxyInnerStatic { fn drop(&mut self) { if let Some(rule) = self.dest_owner_change_match_rule.take() { self.conn.queue_remove_match(rule); } } } /// A property changed event. /// /// The property changed event generated by [`PropertyStream`]. pub struct PropertyChanged<'a, T> { name: &'a str, properties: Arc, proxy: Proxy<'a>, phantom: std::marker::PhantomData, } impl<'a, T> PropertyChanged<'a, T> { // The name of the property that changed. pub fn name(&self) -> &str { self.name } // Get the raw value of the property that changed. // // If the notification signal contained the new value, it has been cached already and this call // will return that value. Otherwise (i-e invalidated property), a D-Bus call is made to fetch // and cache the new value. pub async fn get_raw<'p>(&'p self) -> Result> + 'p> { struct Wrapper<'w> { name: &'w str, values: RwLockReadGuard<'w, HashMap>, } impl<'w> Deref for Wrapper<'w> { type Target = Value<'static>; fn deref(&self) -> &Self::Target { self.values .get(self.name) .expect("PropertyStream with no corresponding property") .value .as_ref() .expect("PropertyStream with no corresponding property") } } { let values = self.properties.values.read().expect("lock poisoned"); if values .get(self.name) .expect("PropertyStream with no corresponding property") .value .is_some() { return Ok(Wrapper { name: self.name, values, }); } } // The property was invalidated, so we need to fetch the new value. let properties_proxy = self.proxy.properties_proxy(); let value = properties_proxy .get(self.proxy.inner.interface.clone(), self.name) .await .map_err(crate::Error::from)?; // Save the new value { let mut values = self.properties.values.write().expect("lock poisoned"); values .get_mut(self.name) .expect("PropertyStream with no corresponding property") .value = Some(value); } Ok(Wrapper { name: self.name, values: self.properties.values.read().expect("lock poisoned"), }) } } impl PropertyChanged<'_, T> where T: TryFrom, T::Error: Into, { // Get the value of the property that changed. // // If the notification signal contained the new value, it has been cached already and this call // will return that value. Otherwise (i-e invalidated property), a D-Bus call is made to fetch // and cache the new value. pub async fn get(&self) -> Result { self.get_raw() .await .and_then(|v| T::try_from(OwnedValue::try_from(&*v)?).map_err(Into::into)) } } /// A [`stream::Stream`] implementation that yields property change notifications. /// /// Use [`Proxy::receive_property_changed`] to create an instance of this type. #[derive(Debug)] pub struct PropertyStream<'a, T> { name: &'a str, proxy: Proxy<'a>, changed_listener: EventListener, phantom: std::marker::PhantomData, } impl<'a, T> stream::Stream for PropertyStream<'a, T> where T: Unpin, { type Item = PropertyChanged<'a, T>; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let m = self.get_mut(); let properties = match m.proxy.get_property_cache() { Some(properties) => properties.clone(), // With no cache, we will get no updates; return immediately None => return Poll::Ready(None), }; ready!(Pin::new(&mut m.changed_listener).poll(cx)); m.changed_listener = properties .values .read() .expect("lock poisoned") .get(m.name) .expect("PropertyStream with no corresponding property") .event .listen(); Poll::Ready(Some(PropertyChanged { name: m.name, properties, proxy: m.proxy.clone(), phantom: std::marker::PhantomData, })) } } #[derive(Debug)] pub(crate) struct PropertiesCache { values: RwLock>, caching_result: RwLock, } #[derive(Debug)] enum CachingResult { Caching { ready: Event }, Cached { result: Result<()> }, } impl PropertiesCache { #[instrument(skip_all)] fn new( proxy: PropertiesProxy<'static>, interface: InterfaceName<'static>, executor: &Executor<'_>, uncached_properties: HashSet>, ) -> (Arc, Task<()>) { let cache = Arc::new(PropertiesCache { values: Default::default(), caching_result: RwLock::new(CachingResult::Caching { ready: Event::new(), }), }); let cache_clone = cache.clone(); let task_name = format!("{interface} proxy caching"); let proxy_caching = async move { let result = cache_clone .init(proxy, interface, uncached_properties) .await; let (prop_changes, interface, uncached_properties) = { let mut caching_result = cache_clone.caching_result.write().expect("lock poisoned"); let ready = match &*caching_result { CachingResult::Caching { ready } => ready, // SAFETY: This is the only part of the code that changes this state and it's // only run once. _ => unreachable!(), }; match result { Ok((prop_changes, interface, uncached_properties)) => { ready.notify(usize::MAX); *caching_result = CachingResult::Cached { result: Ok(()) }; (prop_changes, interface, uncached_properties) } Err(e) => { ready.notify(usize::MAX); *caching_result = CachingResult::Cached { result: Err(e) }; return; } } }; if let Err(e) = cache_clone .keep_updated(prop_changes, interface, uncached_properties) .await { debug!("Error keeping properties cache updated: {e}"); } } .instrument(info_span!("{}", task_name)); let task = executor.spawn(proxy_caching, &task_name); (cache, task) } // new() runs this in a task it spawns for initialization of properties cache. async fn init( &self, proxy: PropertiesProxy<'static>, interface: InterfaceName<'static>, uncached_properties: HashSet>, ) -> Result<( PropertiesChangedStream<'static>, InterfaceName<'static>, HashSet>, )> { use ordered_stream::OrderedStreamExt; let prop_changes = proxy.receive_properties_changed().await?.map(Either::Left); let get_all = proxy .inner() .connection() .call_method_raw( Some(proxy.inner().destination()), proxy.inner().path(), Some(proxy.inner().interface()), "GetAll", BitFlags::empty(), &interface, ) .await .map(|r| FromFuture::from(r.expect("no reply")).map(Either::Right))?; let mut join = join_streams(prop_changes, get_all); loop { match join.next().await { Some(Either::Left(_update)) => { // discard updates prior to the initial population } Some(Either::Right(populate)) => { populate?.body().deserialize().map(|values| { self.update_cache(&uncached_properties, &values, Vec::new(), &interface); })?; break; } None => break, } } if let Some((Either::Left(update), _)) = Pin::new(&mut join).take_buffered() { // if an update was buffered, then it happened after the get_all returned and needs to // be applied before we discard the join if let Ok(args) = update.args() { if args.interface_name == interface { self.update_cache( &uncached_properties, &args.changed_properties, args.invalidated_properties, &interface, ); } } } // This is needed to avoid a "implementation of `OrderedStream` is not general enough" // error that occurs if you apply the map and join to Pin::new(&mut prop_changes) instead // of directly to the stream. let prop_changes = join.into_inner().0.into_inner(); Ok((prop_changes, interface, uncached_properties)) } // new() runs this in a task it spawns for keeping the cache in sync. #[instrument(skip_all)] async fn keep_updated( &self, mut prop_changes: PropertiesChangedStream<'static>, interface: InterfaceName<'static>, uncached_properties: HashSet>, ) -> Result<()> { use futures_util::StreamExt; trace!("Listening for property changes on {interface}..."); while let Some(update) = prop_changes.next().await { if let Ok(args) = update.args() { if args.interface_name == interface { self.update_cache( &uncached_properties, &args.changed_properties, args.invalidated_properties, &interface, ); } } } Ok(()) } fn update_cache( &self, uncached_properties: &HashSet>, changed: &HashMap<&str, Value<'_>>, invalidated: Vec<&str>, interface: &InterfaceName<'_>, ) { let mut values = self.values.write().expect("lock poisoned"); for inval in invalidated { if uncached_properties.contains(&Str::from(inval)) { debug!( "Ignoring invalidation of uncached property `{}.{}`", interface, inval ); continue; } trace!("Property `{interface}.{inval}` invalidated"); if let Some(entry) = values.get_mut(inval) { entry.value = None; entry.event.notify(usize::MAX); } } for (property_name, value) in changed { if uncached_properties.contains(&Str::from(*property_name)) { debug!( "Ignoring update of uncached property `{}.{}`", interface, property_name ); continue; } trace!("Property `{interface}.{property_name}` updated"); let entry = values.entry(property_name.to_string()).or_default(); let value = match OwnedValue::try_from(value) { Ok(value) => value, Err(e) => { debug!( "Failed to convert property `{interface}.{property_name}` to OwnedValue: {e}" ); continue; } }; entry.value = Some(value); entry.event.notify(usize::MAX); } } /// Wait for the cache to be populated and return any error encountered during population pub(crate) async fn ready(&self) -> Result<()> { let listener = match &*self.caching_result.read().expect("lock poisoned") { CachingResult::Caching { ready } => ready.listen(), CachingResult::Cached { result } => return result.clone(), }; listener.await; // It must be ready now. match &*self.caching_result.read().expect("lock poisoned") { // SAFETY: We were just notified that state has changed to `Cached` and we never go back // to `Caching` once in `Cached`. CachingResult::Caching { .. } => unreachable!(), CachingResult::Cached { result } => result.clone(), } } } impl<'a> ProxyInner<'a> { pub(crate) fn new( conn: Connection, destination: BusName<'a>, path: ObjectPath<'a>, interface: InterfaceName<'a>, cache: CacheProperties, uncached_properties: HashSet>, ) -> Self { let property_cache = match cache { CacheProperties::Yes | CacheProperties::Lazily => Some(OnceLock::new()), CacheProperties::No => None, }; Self { inner_without_borrows: ProxyInnerStatic { conn, dest_owner_change_match_rule: OnceLock::new(), }, destination, path, interface, property_cache, uncached_properties, } } /// Subscribe to the "NameOwnerChanged" signal on the bus for our destination. /// /// If the destination is a unique name, we will not subscribe to the signal. pub(crate) async fn subscribe_dest_owner_change(&self) -> Result<()> { if !self.inner_without_borrows.conn.is_bus() { // Names don't mean much outside the bus context. return Ok(()); } let well_known_name = match &self.destination { BusName::WellKnown(well_known_name) => well_known_name, BusName::Unique(_) => return Ok(()), }; if self .inner_without_borrows .dest_owner_change_match_rule .get() .is_some() { // Already watching over the bus for any name updates so nothing to do here. return Ok(()); } let conn = &self.inner_without_borrows.conn; let signal_rule: OwnedMatchRule = MatchRule::builder() .msg_type(Type::Signal) .sender("org.freedesktop.DBus")? .path("/org/freedesktop/DBus")? .interface("org.freedesktop.DBus")? .member("NameOwnerChanged")? .add_arg(well_known_name.as_str())? .build() .to_owned() .into(); conn.add_match( signal_rule.clone(), Some(MAX_NAME_OWNER_CHANGED_SIGNALS_QUEUED), ) .await?; if self .inner_without_borrows .dest_owner_change_match_rule .set(signal_rule.clone()) .is_err() { // we raced another destination_unique_name call and added it twice conn.remove_match(signal_rule).await?; } Ok(()) } } const MAX_NAME_OWNER_CHANGED_SIGNALS_QUEUED: usize = 8; impl<'a> Proxy<'a> { /// Create a new `Proxy` for the given destination/path/interface. pub async fn new( conn: &Connection, destination: D, path: P, interface: I, ) -> Result> where D: TryInto>, P: TryInto>, I: TryInto>, D::Error: Into, P::Error: Into, I::Error: Into, { Builder::new(conn) .destination(destination)? .path(path)? .interface(interface)? .build() .await } /// Create a new `Proxy` for the given destination/path/interface, taking ownership of all /// passed arguments. pub async fn new_owned( conn: Connection, destination: D, path: P, interface: I, ) -> Result> where D: TryInto>, P: TryInto>, I: TryInto>, D::Error: Into, P::Error: Into, I::Error: Into, { Builder::new(&conn) .destination(destination)? .path(path)? .interface(interface)? .build() .await } /// Get a reference to the associated connection. pub fn connection(&self) -> &Connection { &self.inner.inner_without_borrows.conn } /// Get a reference to the destination service name. pub fn destination(&self) -> &BusName<'_> { &self.inner.destination } /// Get a reference to the object path. pub fn path(&self) -> &ObjectPath<'_> { &self.inner.path } /// Get a reference to the interface. pub fn interface(&self) -> &InterfaceName<'_> { &self.inner.interface } /// Introspect the associated object, and return the XML description. /// /// See the [xml](xml/index.html) module for parsing the /// result. pub async fn introspect(&self) -> fdo::Result { let proxy = IntrospectableProxy::builder(&self.inner.inner_without_borrows.conn) .destination(&self.inner.destination)? .path(&self.inner.path)? .build() .await?; proxy.introspect().await } fn properties_proxy(&self) -> PropertiesProxy<'_> { PropertiesProxy::builder(&self.inner.inner_without_borrows.conn) // Safe because already checked earlier .destination(self.inner.destination.as_ref()) .unwrap() // Safe because already checked earlier .path(self.inner.path.as_ref()) .unwrap() // does not have properties .cache_properties(CacheProperties::No) .build_internal() .unwrap() .into() } fn owned_properties_proxy(&self) -> PropertiesProxy<'static> { PropertiesProxy::builder(&self.inner.inner_without_borrows.conn) // Safe because already checked earlier .destination(self.inner.destination.to_owned()) .unwrap() // Safe because already checked earlier .path(self.inner.path.to_owned()) .unwrap() // does not have properties .cache_properties(CacheProperties::No) .build_internal() .unwrap() .into() } /// Get the cache, starting it in the background if needed. /// /// Use PropertiesCache::ready() to wait for the cache to be populated and to get any errors /// encountered in the population. pub(crate) fn get_property_cache(&self) -> Option<&Arc> { let cache = match &self.inner.property_cache { Some(cache) => cache, None => return None, }; let (cache, _) = &cache.get_or_init(|| { let proxy = self.owned_properties_proxy(); let interface = self.interface().to_owned(); let uncached_properties: HashSet> = self .inner .uncached_properties .iter() .map(|s| s.to_owned()) .collect(); let executor = self.connection().executor(); PropertiesCache::new(proxy, interface, executor, uncached_properties) }); Some(cache) } /// Get the cached value of the property `property_name`. /// /// This returns `None` if the property is not in the cache. This could be because the cache /// was invalidated by an update, because caching was disabled for this property or proxy, or /// because the cache has not yet been populated. Use `get_property` to fetch the value from /// the peer. pub fn cached_property(&self, property_name: &str) -> Result> where T: TryFrom, T::Error: Into, { self.cached_property_raw(property_name) .as_deref() .map(|v| T::try_from(OwnedValue::try_from(v)?).map_err(Into::into)) .transpose() } /// Get the cached value of the property `property_name`. /// /// Same as `cached_property`, but gives you access to the raw value stored in the cache. This /// is useful if you want to avoid allocations and cloning. pub fn cached_property_raw<'p>( &'p self, property_name: &'p str, ) -> Option> + 'p> { if let Some(values) = self .inner .property_cache .as_ref() .and_then(OnceLock::get) .map(|c| c.0.values.read().expect("lock poisoned")) { // ensure that the property is in the cache. values .get(property_name) // if the property value has not yet been cached, this will return None. .and_then(|e| e.value.as_ref())?; struct Wrapper<'a> { values: RwLockReadGuard<'a, HashMap>, property_name: &'a str, } impl Deref for Wrapper<'_> { type Target = Value<'static>; fn deref(&self) -> &Self::Target { self.values .get(self.property_name) .and_then(|e| e.value.as_ref()) .map(|v| v.deref()) .expect("inexistent property") } } Some(Wrapper { values, property_name, }) } else { None } } async fn get_proxy_property(&self, property_name: &str) -> Result { Ok(self .properties_proxy() .get(self.inner.interface.as_ref(), property_name) .await?) } /// Get the property `property_name`. /// /// Get the property value from the cache (if caching is enabled) or call the /// `Get` method of the `org.freedesktop.DBus.Properties` interface. pub async fn get_property(&self, property_name: &str) -> Result where T: TryFrom, T::Error: Into, { if let Some(cache) = self.get_property_cache() { cache.ready().await?; } if let Some(value) = self.cached_property(property_name)? { return Ok(value); } let value = self.get_proxy_property(property_name).await?; value.try_into().map_err(Into::into) } /// Set the property `property_name`. /// /// Effectively, call the `Set` method of the `org.freedesktop.DBus.Properties` interface. pub async fn set_property<'t, T>(&self, property_name: &str, value: T) -> fdo::Result<()> where T: 't + Into>, { self.properties_proxy() .set(self.inner.interface.as_ref(), property_name, &value.into()) .await } /// Call a method and return the reply. /// /// Typically, you would want to use [`call`] method instead. Use this method if you need to /// deserialize the reply message manually (this way, you can avoid the memory /// allocation/copying, by deserializing the reply to an unowned type). /// /// [`call`]: struct.Proxy.html#method.call pub async fn call_method<'m, M, B>(&self, method_name: M, body: &B) -> Result where M: TryInto>, M::Error: Into, B: serde::ser::Serialize + zvariant::DynamicType, { self.inner .inner_without_borrows .conn .call_method( Some(&self.inner.destination), self.inner.path.as_str(), Some(&self.inner.interface), method_name, body, ) .await } /// Call a method and return the reply body. /// /// Use [`call_method`] instead if you need to deserialize the reply manually/separately. /// /// [`call_method`]: struct.Proxy.html#method.call_method pub async fn call<'m, M, B, R>(&self, method_name: M, body: &B) -> Result where M: TryInto>, M::Error: Into, B: serde::ser::Serialize + zvariant::DynamicType, R: for<'d> zvariant::DynamicDeserialize<'d>, { let reply = self.call_method(method_name, body).await?; reply.body().deserialize() } /// Call a method and return the reply body, optionally supplying a set of /// method flags to control the way the method call message is sent and handled. /// /// Use [`call`] instead if you do not need any special handling via additional flags. /// If the `NoReplyExpected` flag is passed , this will return None immediately /// after sending the message, similar to [`call_noreply`] /// /// [`call`]: struct.Proxy.html#method.call /// [`call_noreply`]: struct.Proxy.html#method.call_noreply pub async fn call_with_flags<'m, M, B, R>( &self, method_name: M, flags: BitFlags, body: &B, ) -> Result> where M: TryInto>, M::Error: Into, B: serde::ser::Serialize + zvariant::DynamicType, R: for<'d> zvariant::DynamicDeserialize<'d>, { let flags = flags.iter().map(Flags::from).collect::>(); match self .inner .inner_without_borrows .conn .call_method_raw( Some(self.destination()), self.path(), Some(self.interface()), method_name, flags, body, ) .await? { Some(reply) => reply.await?.body().deserialize().map(Some), None => Ok(None), } } /// Call a method without expecting a reply /// /// This sets the `NoReplyExpected` flag on the calling message and does not wait for a reply. pub async fn call_noreply<'m, M, B>(&self, method_name: M, body: &B) -> Result<()> where M: TryInto>, M::Error: Into, B: serde::ser::Serialize + zvariant::DynamicType, { self.call_with_flags::<_, _, ()>(method_name, MethodFlags::NoReplyExpected.into(), body) .await?; Ok(()) } /// Create a stream for signal named `signal_name`. pub async fn receive_signal<'m, M>(&self, signal_name: M) -> Result> where M: TryInto>, M::Error: Into, { self.receive_signal_with_args(signal_name, &[]).await } /// Same as [`Proxy::receive_signal`] but with a filter. /// /// The D-Bus specification allows you to filter signals by their arguments, which helps avoid /// a lot of unnecessary traffic and processing since the filter is run on the server side. Use /// this method where possible. Note that this filtering is limited to arguments of string /// types. /// /// The arguments are passed as a tuples of argument index and expected value. pub async fn receive_signal_with_args<'m, M>( &self, signal_name: M, args: &[(u8, &str)], ) -> Result> where M: TryInto>, M::Error: Into, { let signal_name = signal_name.try_into().map_err(Into::into)?; self.receive_signals(Some(signal_name), args).await } async fn receive_signals<'m>( &self, signal_name: Option>, args: &[(u8, &str)], ) -> Result> { self.inner.subscribe_dest_owner_change().await?; SignalStream::new(self.clone(), signal_name, args).await } /// Create a stream for all signals emitted by this service. pub async fn receive_all_signals(&self) -> Result> { self.receive_signals(None, &[]).await } /// Get a stream to receive property changed events. /// /// Note that zbus doesn't queue the updates. If the listener is slower than the receiver, it /// will only receive the last update. /// /// If caching is not enabled on this proxy, the resulting stream will not return any events. pub async fn receive_property_changed<'name: 'a, T>( &self, name: &'name str, ) -> PropertyStream<'a, T> { let properties = self.get_property_cache(); let changed_listener = if let Some(properties) = &properties { let mut values = properties.values.write().expect("lock poisoned"); let entry = values .entry(name.to_string()) .or_insert_with(PropertyValue::default); entry.event.listen() } else { Event::new().listen() }; PropertyStream { name, proxy: self.clone(), changed_listener, phantom: std::marker::PhantomData, } } /// Get a stream to receive destination owner changed events. /// /// If the proxy destination is a unique name, the stream will be notified of the peer /// disconnection from the bus (with a `None` value). /// /// If the proxy destination is a well-known name, the stream will be notified whenever the name /// owner is changed, either by a new peer being granted ownership (`Some` value) or when the /// name is released (with a `None` value). /// /// Note that zbus doesn't queue the updates. If the listener is slower than the receiver, it /// will only receive the last update. pub async fn receive_owner_changed(&self) -> Result> { use futures_util::StreamExt; let dbus_proxy = fdo::DBusProxy::builder(self.connection()) .cache_properties(CacheProperties::No) .build() .await?; Ok(OwnerChangedStream { stream: dbus_proxy .receive_name_owner_changed_with_args(&[(0, self.destination().as_str())]) .await? .map(Box::new(move |signal| { let args = signal.args().unwrap(); let new_owner = args.new_owner().as_ref().map(|owner| owner.to_owned()); new_owner })), name: self.destination().clone(), }) } } #[derive(Debug, Default)] struct PropertyValue { value: Option, event: Event, } /// Flags to use with [`Proxy::call_with_flags`]. #[bitflags] #[repr(u8)] #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum MethodFlags { /// No response is expected from this method call, regardless of whether the /// signature for the interface method indicates a reply type. When passed, /// `call_with_flags` will return `Ok(None)` immediately after successfully /// sending the method call. /// /// Errors encountered while *making* the call will still be returned as /// an `Err` variant, but any errors that are triggered by the receiver's /// handling of the call will not be delivered. NoReplyExpected = 0x1, /// When set on a call whose destination is a message bus, this flag will instruct /// the bus not to [launch][al] a service to handle the call if no application /// on the bus owns the requested name. /// /// This flag is ignored when using a peer-to-peer connection. /// /// [al]: https://dbus.freedesktop.org/doc/dbus-specification.html#message-bus-starting-services NoAutoStart = 0x2, /// Indicates to the receiver that this client is prepared to wait for interactive /// authorization, which might take a considerable time to complete. For example, the receiver /// may query the user for confirmation via [polkit] or a similar framework. /// /// [polkit]: https://gitlab.freedesktop.org/polkit/polkit/ AllowInteractiveAuth = 0x4, } assert_impl_all!(MethodFlags: Send, Sync, Unpin); impl From for Flags { fn from(method_flag: MethodFlags) -> Self { match method_flag { MethodFlags::NoReplyExpected => Self::NoReplyExpected, MethodFlags::NoAutoStart => Self::NoAutoStart, MethodFlags::AllowInteractiveAuth => Self::AllowInteractiveAuth, } } } type OwnerChangedStreamMap<'a> = Map< fdo::NameOwnerChangedStream<'a>, Box Option> + Send + Sync + Unpin>, >; /// A [`stream::Stream`] implementation that yields `UniqueName` when the bus owner changes. /// /// Use [`Proxy::receive_owner_changed`] to create an instance of this type. pub struct OwnerChangedStream<'a> { stream: OwnerChangedStreamMap<'a>, name: BusName<'a>, } assert_impl_all!(OwnerChangedStream<'_>: Send, Sync, Unpin); impl OwnerChangedStream<'_> { /// The bus name being tracked. pub fn name(&self) -> &BusName<'_> { &self.name } } impl<'a> stream::Stream for OwnerChangedStream<'a> { type Item = Option>; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { use futures_util::StreamExt; self.get_mut().stream.poll_next_unpin(cx) } } /// A [`stream::Stream`] implementation that yields signal [messages](`Message`). /// /// Use [`Proxy::receive_signal`] to create an instance of this type. /// /// This type uses a [`MessageStream::for_match_rule`] internally and therefore the note about match /// rule registration and [`AsyncDrop`] in its documentation applies here as well. #[derive(Debug)] pub struct SignalStream<'a> { stream: Join>, src_unique_name: Option>, signal_name: Option>, } impl<'a> SignalStream<'a> { /// The signal name. pub fn name(&self) -> Option<&MemberName<'a>> { self.signal_name.as_ref() } async fn new( proxy: Proxy<'_>, signal_name: Option>, args: &[(u8, &str)], ) -> Result> { let mut rule_builder = MatchRule::builder() .msg_type(Type::Signal) .sender(proxy.destination())? .path(proxy.path())? .interface(proxy.interface())?; if let Some(name) = &signal_name { rule_builder = rule_builder.member(name)?; } for (i, arg) in args { rule_builder = rule_builder.arg(*i, *arg)?; } let signal_rule: OwnedMatchRule = rule_builder.build().to_owned().into(); let conn = proxy.connection(); let (src_unique_name, stream) = match proxy.destination().to_owned() { BusName::Unique(name) => ( Some(name), join_streams( MessageStream::for_match_rule(signal_rule, conn, None).await?, None, ), ), BusName::WellKnown(name) => { use ordered_stream::OrderedStreamExt; let name_owner_changed_rule = MatchRule::builder() .msg_type(Type::Signal) .sender("org.freedesktop.DBus")? .path("/org/freedesktop/DBus")? .interface("org.freedesktop.DBus")? .member("NameOwnerChanged")? .add_arg(name.as_str())? .build(); let name_owner_changed_stream = MessageStream::for_match_rule( name_owner_changed_rule, conn, Some(MAX_NAME_OWNER_CHANGED_SIGNALS_QUEUED), ) .await? .map(Either::Left); let get_name_owner = conn .call_method_raw( Some("org.freedesktop.DBus"), "/org/freedesktop/DBus", Some("org.freedesktop.DBus"), "GetNameOwner", BitFlags::empty(), &name, ) .await .map(|r| FromFuture::from(r.expect("no reply")).map(Either::Right))?; let mut join = join_streams(name_owner_changed_stream, get_name_owner); let mut src_unique_name = loop { match join.next().await { Some(Either::Left(Ok(msg))) => { let signal = NameOwnerChanged::from_message(msg) .expect("`NameOwnerChanged` signal stream got wrong message"); { break signal .args() // SAFETY: The filtering code couldn't have let this through if // args were not in order. .expect("`NameOwnerChanged` signal has no args") .new_owner() .as_ref() .map(UniqueName::to_owned); } } Some(Either::Left(Err(_))) => (), Some(Either::Right(Ok(response))) => { break Some(response.body().deserialize::>()?.to_owned()) } Some(Either::Right(Err(e))) => { // Probably the name is not owned. Not a problem but let's still log it. debug!("Failed to get owner of {name}: {e}"); break None; } None => { return Err(Error::InputOutput( std::io::Error::new( std::io::ErrorKind::BrokenPipe, "connection closed", ) .into(), )) } } }; // Let's take into account any buffered NameOwnerChanged signal. let (stream, _, queued) = join.into_inner(); if let Some(msg) = queued.and_then(|e| match e.0 { Either::Left(Ok(msg)) => Some(msg), Either::Left(Err(_)) | Either::Right(_) => None, }) { if let Some(signal) = NameOwnerChanged::from_message(msg) { if let Ok(args) = signal.args() { match (args.name(), args.new_owner().deref()) { (BusName::WellKnown(n), Some(new_owner)) if n == &name => { src_unique_name = Some(new_owner.to_owned()); } _ => (), } } } } let name_owner_changed_stream = stream.into_inner(); let stream = join_streams( MessageStream::for_match_rule(signal_rule, conn, None).await?, Some(name_owner_changed_stream), ); (src_unique_name, stream) } }; Ok(SignalStream { stream, src_unique_name, signal_name, }) } fn filter(&mut self, msg: &Message) -> Result { let header = msg.header(); let sender = header.sender(); if sender == self.src_unique_name.as_ref() { return Ok(true); } // The src_unique_name must be maintained in lock-step with the applied filter if let Some(signal) = NameOwnerChanged::from_message(msg.clone()) { let args = signal.args()?; self.src_unique_name = args.new_owner().as_ref().map(|n| n.to_owned()); } Ok(false) } } assert_impl_all!(SignalStream<'_>: Send, Sync, Unpin); impl<'a> stream::Stream for SignalStream<'a> { type Item = Message; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { OrderedStream::poll_next_before(self, cx, None).map(|res| res.into_data()) } } impl<'a> OrderedStream for SignalStream<'a> { type Data = Message; type Ordering = Sequence; fn poll_next_before( self: Pin<&mut Self>, cx: &mut Context<'_>, before: Option<&Self::Ordering>, ) -> Poll> { let this = self.get_mut(); loop { match ready!(OrderedStream::poll_next_before( Pin::new(&mut this.stream), cx, before )) { PollResult::Item { data, ordering } => { if let Ok(msg) = data { if let Ok(true) = this.filter(&msg) { return Poll::Ready(PollResult::Item { data: msg, ordering, }); } } } PollResult::Terminated => return Poll::Ready(PollResult::Terminated), PollResult::NoneBefore => return Poll::Ready(PollResult::NoneBefore), } } } } impl<'a> stream::FusedStream for SignalStream<'a> { fn is_terminated(&self) -> bool { ordered_stream::FusedOrderedStream::is_terminated(&self.stream) } } #[async_trait::async_trait] impl AsyncDrop for SignalStream<'_> { async fn async_drop(self) { let (signals, names, _buffered) = self.stream.into_inner(); signals.async_drop().await; if let Some(names) = names { names.async_drop().await; } } } impl<'a> From> for Proxy<'a> { fn from(proxy: crate::blocking::Proxy<'a>) -> Self { proxy.into_inner() } } /// This trait is implemented by all async proxies, which are generated with the /// [`dbus_proxy`](zbus::dbus_proxy) macro. pub trait ProxyImpl<'c> where Self: Sized, { /// Returns a customizable builder for this proxy. fn builder(conn: &Connection) -> Builder<'c, Self>; /// Consumes `self`, returning the underlying `zbus::Proxy`. fn into_inner(self) -> Proxy<'c>; /// The reference to the underlying `zbus::Proxy`. fn inner(&self) -> &Proxy<'c>; } #[cfg(test)] mod tests { use super::*; use crate::{connection, interface, object_server::SignalContext, proxy, utils::block_on}; use futures_util::StreamExt; use ntest::timeout; use test_log::test; #[test] #[timeout(15000)] fn signal() { block_on(test_signal()).unwrap(); } async fn test_signal() -> Result<()> { // Register a well-known name with the session bus and ensure we get the appropriate // signals called for that. let conn = Connection::session().await?; let dest_conn = Connection::session().await?; let unique_name = dest_conn.unique_name().unwrap().clone(); let well_known = "org.freedesktop.zbus.async.ProxySignalStreamTest"; let proxy: Proxy<'_> = Builder::new(&conn) .destination(well_known)? .path("/does/not/matter")? .interface("does.not.matter")? .build() .await?; let mut owner_changed_stream = proxy.receive_owner_changed().await?; let proxy = fdo::DBusProxy::new(&dest_conn).await?; let mut name_acquired_stream = proxy .inner() .receive_signal_with_args("NameAcquired", &[(0, well_known)]) .await?; let prop_stream = proxy .inner() .receive_property_changed("SomeProp") .await .filter_map(|changed| async move { let v: Option = changed.get().await.ok(); dbg!(v) }); drop(proxy); drop(prop_stream); dest_conn.request_name(well_known).await?; let (new_owner, acquired_signal) = futures_util::join!(owner_changed_stream.next(), name_acquired_stream.next(),); assert_eq!(&new_owner.unwrap().unwrap(), &*unique_name); let acquired_signal = acquired_signal.unwrap(); assert_eq!( acquired_signal.body().deserialize::<&str>().unwrap(), well_known ); let proxy = Proxy::new(&conn, &unique_name, "/does/not/matter", "does.not.matter").await?; let mut unique_name_changed_stream = proxy.receive_owner_changed().await?; drop(dest_conn); name_acquired_stream.async_drop().await; // There shouldn't be an owner anymore. let new_owner = owner_changed_stream.next().await; assert!(new_owner.unwrap().is_none()); let new_unique_owner = unique_name_changed_stream.next().await; assert!(new_unique_owner.unwrap().is_none()); Ok(()) } #[test] #[timeout(15000)] fn signal_stream_deadlock() { block_on(test_signal_stream_deadlock()).unwrap(); } /// Tests deadlocking in signal reception when the message queue is full. /// /// Creates a connection with a small message queue, and a service that /// emits signals at a high rate. First a listener is created that listens /// for that signal which should fill the small queue. Then another signal /// signal listener is created against another signal. Previously, this second /// call to add the match rule never resolved and resulted in a deadlock. async fn test_signal_stream_deadlock() -> Result<()> { #[proxy( gen_blocking = false, default_path = "/org/zbus/Test", default_service = "org.zbus.Test.MR501", interface = "org.zbus.Test" )] trait Test { #[zbus(signal)] fn my_signal(&self, msg: &str) -> Result<()>; } struct TestIface; #[interface(name = "org.zbus.Test")] impl TestIface { #[zbus(signal)] async fn my_signal(context: &SignalContext<'_>, msg: &'static str) -> Result<()>; } let test_iface = TestIface; let server_conn = connection::Builder::session()? .name("org.zbus.Test.MR501")? .serve_at("/org/zbus/Test", test_iface)? .build() .await?; let client_conn = connection::Builder::session()? .max_queued(1) .build() .await?; let test_proxy = TestProxy::new(&client_conn).await?; let test_prop_proxy = PropertiesProxy::builder(&client_conn) .destination("org.zbus.Test.MR501")? .path("/org/zbus/Test")? .build() .await?; let (tx, mut rx) = tokio::sync::mpsc::channel(1); let handle = { let tx = tx.clone(); let conn = server_conn.clone(); let server_fut = async move { use std::time::Duration; #[cfg(not(feature = "tokio"))] use async_io::Timer; #[cfg(feature = "tokio")] use tokio::time::sleep; let iface_ref = conn .object_server() .interface::<_, TestIface>("/org/zbus/Test") .await .unwrap(); let context = iface_ref.signal_context(); while !tx.is_closed() { for _ in 0..10 { TestIface::my_signal(context, "This is a test") .await .unwrap(); } #[cfg(not(feature = "tokio"))] Timer::after(Duration::from_millis(5)).await; #[cfg(feature = "tokio")] sleep(Duration::from_millis(5)).await; } }; server_conn.executor().spawn(server_fut, "server_task") }; let signal_fut = async { let mut signal_stream = test_proxy.receive_my_signal().await.unwrap(); tx.send(()).await.unwrap(); while let Some(_signal) = signal_stream.next().await {} }; let prop_fut = async move { rx.recv().await.unwrap(); let _prop_stream = test_prop_proxy.receive_properties_changed().await.unwrap(); }; futures_util::pin_mut!(signal_fut); futures_util::pin_mut!(prop_fut); futures_util::future::select(signal_fut, prop_fut).await; handle.await; Ok(()) } } zbus-4.3.1/src/utils.rs000064400000000000000000000032271046102023000131150ustar 00000000000000#[cfg(unix)] pub(crate) const FDS_MAX: usize = 1024; // this is hardcoded in sdbus - nothing in the spec pub(crate) fn padding_for_8_bytes(value: usize) -> usize { padding_for_n_bytes(value, 8) } pub(crate) fn padding_for_n_bytes(value: usize, align: usize) -> usize { let len_rounded_up = value.wrapping_add(align).wrapping_sub(1) & !align.wrapping_sub(1); len_rounded_up.wrapping_sub(value) } /// Helper trait for macro-generated code. /// /// This trait allows macros to refer to the `Ok` and `Err` types of a [Result] that is behind a /// type alias. This is currently required because the macros for properties expect a Result /// return value, but the macro-generated `receive_` functions need to refer to the actual /// type without the associated error. #[doc(hidden)] pub trait ResultAdapter { type Ok; type Err; } impl ResultAdapter for Result { type Ok = T; type Err = E; } #[cfg(not(feature = "tokio"))] #[doc(hidden)] pub fn block_on(future: F) -> F::Output { async_io::block_on(future) } #[cfg(feature = "tokio")] #[doc(hidden)] pub fn block_on(future: F) -> F::Output { use std::sync::OnceLock; static TOKIO_RT: OnceLock = OnceLock::new(); let runtime = TOKIO_RT.get_or_init(|| { tokio::runtime::Builder::new_current_thread() .enable_io() .enable_time() .build() .expect("launch of single-threaded tokio runtime") }); runtime.block_on(future) } // If we're running inside a Flatpak sandbox. pub(crate) fn is_flatpak() -> bool { std::env::var("FLATPAK_ID").is_ok() } zbus-4.3.1/src/win32.rs000064400000000000000000000235701046102023000127220ustar 00000000000000use std::{ ffi::{CStr, OsStr}, io::{Error, ErrorKind}, net::SocketAddr, os::windows::{ io::{AsRawHandle, FromRawHandle, OwnedHandle, RawHandle}, prelude::OsStrExt, }, ptr, }; use windows_sys::Win32::{ Foundation::{ LocalFree, ERROR_INSUFFICIENT_BUFFER, FALSE, HANDLE, NO_ERROR, WAIT_ABANDONED, WAIT_OBJECT_0, }, NetworkManagement::IpHelper::{GetTcpTable2, MIB_TCPTABLE2, MIB_TCP_STATE_ESTAB}, Networking::WinSock::INADDR_LOOPBACK, Security::{ Authorization::ConvertSidToStringSidA, GetTokenInformation, IsValidSid, TokenUser, TOKEN_QUERY, TOKEN_USER, }, System::{ Memory::{MapViewOfFile, OpenFileMappingW, FILE_MAP_READ}, Threading::{ CreateMutexW, GetCurrentProcess, OpenProcess, OpenProcessToken, ReleaseMutex, WaitForSingleObject, INFINITE, PROCESS_ACCESS_RIGHTS, PROCESS_QUERY_LIMITED_INFORMATION, }, }, }; use crate::Address; #[cfg(not(feature = "tokio"))] use uds_windows::UnixStream; struct Mutex(OwnedHandle); impl Mutex { pub fn new(name: &str) -> Result { let name_wide = OsStr::new(name) .encode_wide() .chain([0]) .collect::>(); let handle = unsafe { CreateMutexW(ptr::null_mut(), FALSE, name_wide.as_ptr()) }; // SAFETY: We have exclusive ownership over the mutex handle Ok(Self(unsafe { OwnedHandle::from_raw_handle(handle as RawHandle) })) } pub fn lock(&self) -> MutexGuard<'_> { match unsafe { WaitForSingleObject(self.0.as_raw_handle() as isize, INFINITE) } { WAIT_ABANDONED | WAIT_OBJECT_0 => MutexGuard(self), err => panic!("WaitForSingleObject() failed: return code {}", err), } } } struct MutexGuard<'a>(&'a Mutex); impl Drop for MutexGuard<'_> { fn drop(&mut self) { unsafe { ReleaseMutex(self.0 .0.as_raw_handle() as isize) }; } } // A process handle pub struct ProcessHandle(OwnedHandle); impl ProcessHandle { // Open the process associated with the process_id (if None, the current process) pub fn open( process_id: Option, desired_access: PROCESS_ACCESS_RIGHTS, ) -> Result { let process = if let Some(process_id) = process_id { unsafe { OpenProcess(desired_access, false.into(), process_id) } } else { unsafe { GetCurrentProcess() } }; if process == 0 { Err(Error::last_os_error()) } else { // SAFETY: We have exclusive ownership over the process handle Ok(Self(unsafe { OwnedHandle::from_raw_handle(process as RawHandle) })) } } } // A process token // // See MSDN documentation: // https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocesstoken // // Get the process security identifier with the `sid()` function. pub struct ProcessToken(OwnedHandle); impl ProcessToken { // Open the access token associated with the process_id (if None, the current process) pub fn open(process_id: Option) -> Result { let mut process_token: HANDLE = HANDLE::default(); let process = ProcessHandle::open(process_id, PROCESS_QUERY_LIMITED_INFORMATION)?; if unsafe { OpenProcessToken( process.0.as_raw_handle() as isize, TOKEN_QUERY, ptr::addr_of_mut!(process_token), ) == 0 } { Err(Error::last_os_error()) } else { // SAFETY: We have exclusive ownership over the process handle Ok(Self(unsafe { OwnedHandle::from_raw_handle(process_token as RawHandle) })) } } // Return the process SID (security identifier) as a string pub fn sid(&self) -> Result { let mut len = 256; let mut token_info; loop { token_info = vec![0u8; len as usize]; let result = unsafe { GetTokenInformation( self.0.as_raw_handle() as isize, TokenUser, token_info.as_mut_ptr().cast(), len, ptr::addr_of_mut!(len), ) }; if result != 0 { break; } let last_error = Error::last_os_error(); if last_error.raw_os_error() == Some(ERROR_INSUFFICIENT_BUFFER as i32) { continue; } return Err(last_error); } let sid = unsafe { (*token_info.as_ptr().cast::()).User.Sid }; if unsafe { IsValidSid(sid.cast()) == FALSE } { return Err(Error::new(ErrorKind::Other, "Invalid SID")); } let mut pstr = ptr::null_mut(); if unsafe { ConvertSidToStringSidA(sid, ptr::addr_of_mut!(pstr)) == 0 } { return Err(Error::last_os_error()); } let sid = unsafe { CStr::from_ptr(pstr.cast()) }; let ret = sid .to_str() .map_err(|_| Error::new(ErrorKind::Other, "Invalid SID"))? .to_owned(); unsafe { LocalFree(pstr.cast()); } Ok(ret) } } // Get the process ID of the local socket address // TODO: add ipv6 support pub fn socket_addr_get_pid(addr: &SocketAddr) -> Result { let mut len = 4096; let mut tcp_table = vec![]; let res = loop { tcp_table.resize(len as usize, 0); let res = unsafe { GetTcpTable2( tcp_table.as_mut_ptr().cast::(), ptr::addr_of_mut!(len), 0, ) }; if res != ERROR_INSUFFICIENT_BUFFER { break res; } }; if res != NO_ERROR { return Err(Error::last_os_error()); } let tcp_table = tcp_table.as_mut_ptr().cast::(); let entries = unsafe { std::slice::from_raw_parts( (*tcp_table).table.as_ptr(), (*tcp_table).dwNumEntries as usize, ) }; for entry in entries { let port = (entry.dwLocalPort & 0xFFFF) as u16; let port = u16::from_be(port); if entry.dwState == MIB_TCP_STATE_ESTAB as u32 && u32::from_be(entry.dwLocalAddr) == INADDR_LOOPBACK && u32::from_be(entry.dwRemoteAddr) == INADDR_LOOPBACK && port == addr.port() { return Ok(entry.dwOwningPid); } } Err(Error::new(ErrorKind::Other, "PID of TCP address not found")) } // Get the process ID of the connected peer #[cfg(any(test, not(feature = "tokio")))] pub fn tcp_stream_get_peer_pid(stream: &std::net::TcpStream) -> Result { let peer_addr = stream.peer_addr()?; socket_addr_get_pid(&peer_addr) } #[cfg(not(feature = "tokio"))] fn last_err() -> std::io::Error { use windows_sys::Win32::Networking::WinSock::WSAGetLastError; let err = unsafe { WSAGetLastError() }; std::io::Error::from_raw_os_error(err) } // Get the process ID of the connected peer #[cfg(not(feature = "tokio"))] pub fn unix_stream_get_peer_pid(stream: &UnixStream) -> Result { use std::os::windows::io::AsRawSocket; use windows_sys::Win32::Networking::WinSock::{WSAIoctl, IOC_OUT, IOC_VENDOR, SOCKET_ERROR}; macro_rules! _WSAIOR { ($x:expr, $y:expr) => { IOC_OUT | $x | $y }; } let socket = stream.as_raw_socket(); const SIO_AF_UNIX_GETPEERPID: u32 = _WSAIOR!(IOC_VENDOR, 256); let mut ret = 0; let mut bytes = 0; let r = unsafe { WSAIoctl( socket as _, SIO_AF_UNIX_GETPEERPID, ptr::null_mut(), 0, ptr::addr_of_mut!(ret).cast(), std::mem::size_of_val(&ret) as u32, ptr::addr_of_mut!(bytes), ptr::null_mut(), None, ) }; if r == SOCKET_ERROR { return Err(last_err()); } Ok(ret) } fn read_shm(name: &str) -> Result, crate::Error> { let handle = { let wide_name = OsStr::new(name) .encode_wide() .chain([0]) .collect::>(); let res = unsafe { OpenFileMappingW(FILE_MAP_READ, FALSE, wide_name.as_ptr()) }; if res != 0 { // SAFETY: We have exclusive ownership over the file mapping handle unsafe { OwnedHandle::from_raw_handle(res as RawHandle) } } else { return Err(crate::Error::Address( "Unable to open shared memory".to_owned(), )); } }; let addr = unsafe { MapViewOfFile(handle.as_raw_handle() as isize, FILE_MAP_READ, 0, 0, 0) }; if addr.Value.is_null() { return Err(crate::Error::Address("MapViewOfFile() failed".to_owned())); } let data = unsafe { CStr::from_ptr(addr.Value.cast()) }; Ok(data.to_bytes().to_owned()) } pub fn autolaunch_bus_address() -> Result { let mutex = Mutex::new("DBusAutolaunchMutex")?; let _guard = mutex.lock(); let addr = read_shm("DBusDaemonAddressInfo")?; let addr = String::from_utf8(addr) .map_err(|e| crate::Error::Address(format!("Unable to parse address as UTF-8: {}", e)))?; addr.parse() } #[cfg(test)] mod tests { use super::*; #[test] fn socket_pid_and_sid() { let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap(); let addr = listener.local_addr().unwrap(); let client = std::net::TcpStream::connect(addr).unwrap(); let _server = listener.incoming().next().unwrap().unwrap(); let pid = tcp_stream_get_peer_pid(&client).unwrap(); let process_token = ProcessToken::open(if pid != 0 { Some(pid) } else { None }).unwrap(); let sid = process_token.sid().unwrap(); assert!(!sid.is_empty()); } } zbus-4.3.1/tests/e2e.rs000064400000000000000000001003361046102023000130020ustar 00000000000000#![allow(clippy::disallowed_names)] use std::collections::HashMap; #[cfg(all(unix, not(feature = "tokio"), feature = "p2p"))] use std::os::unix::net::UnixStream; #[cfg(all(unix, feature = "tokio", feature = "p2p"))] use tokio::net::UnixStream; use event_listener::Event; use futures_util::{StreamExt, TryStreamExt}; use ntest::timeout; use serde::{Deserialize, Serialize}; use test_log::test; use tokio::sync::mpsc::{channel, Sender}; use tracing::{debug, instrument}; use zbus::{ block_on, fdo::{ObjectManager, ObjectManagerProxy}, message, object_server::ResponseDispatchNotifier, DBusError, Error, Message, MessageStream, }; use zvariant::{DeserializeDict, Optional, OwnedValue, SerializeDict, Str, Type, Value}; use zbus::{ connection, interface, message::Header, object_server::{InterfaceRef, SignalContext}, proxy::CacheProperties, Connection, ObjectServer, }; #[derive(Debug, Deserialize, Serialize, Type)] pub struct ArgStructTest { foo: i32, bar: String, } // Mimic a NetworkManager interface property that's a dict. This tests ability to use a custom // dict type using the `Type` And `*Dict` macros (issue #241). #[derive(DeserializeDict, SerializeDict, Type, Debug, Value, OwnedValue, PartialEq, Eq)] #[zvariant(signature = "dict")] pub struct IP4Adress { prefix: u32, address: String, } // To test property setter for types with lifetimes. #[derive(Serialize, Deserialize, Type, Debug, Value, OwnedValue, PartialEq, Eq)] pub struct RefType<'a> { #[serde(borrow)] field1: Str<'a>, } #[derive(Debug, Clone)] enum NextAction { Quit, CreateObj(String), DestroyObj(String), } #[derive(Debug)] struct MyIface { next_tx: Sender, count: u32, emits_changed_default: u32, emits_changed_true: u32, emits_changed_invalidates: u32, emits_changed_const: u32, emits_changed_false: u32, } impl MyIface { fn new(next_tx: Sender) -> Self { Self { next_tx, count: 0, emits_changed_default: 0, emits_changed_true: 0, emits_changed_invalidates: 0, emits_changed_const: 0, emits_changed_false: 0, } } } /// Custom D-Bus error type. #[derive(Debug, DBusError, PartialEq)] #[zbus(prefix = "org.freedesktop.MyIface.Error")] enum MyIfaceError { SomethingWentWrong(String), #[zbus(error)] ZBus(zbus::Error), } #[interface( interface = "org.freedesktop.MyIface", proxy(gen_blocking = true, assume_defaults = true) )] impl MyIface { #[instrument] async fn ping(&mut self, #[zbus(signal_context)] ctxt: SignalContext<'_>) -> u32 { self.count += 1; if self.count % 3 == 0 { MyIface::alert_count(&ctxt, self.count) .await .expect("Failed to emit signal"); debug!("emitted `AlertCount` signal."); } else { debug!("Didn't emit `AlertCount` signal."); } self.count } #[instrument] async fn quit(&self) { debug!("Client asked to quit."); self.next_tx.send(NextAction::Quit).await.unwrap(); } #[instrument] fn test_header(&self, #[zbus(header)] header: Header<'_>) { debug!("`TestHeader` called."); assert_eq!(header.message_type(), message::Type::MethodCall); assert_eq!(header.member().unwrap(), "TestHeader"); } #[instrument] fn test_error(&self) -> zbus::fdo::Result<()> { debug!("`TestError` called."); Err(zbus::fdo::Error::Failed("error raised".to_string())) } #[instrument] fn test_custom_error(&self) -> Result<(), MyIfaceError> { debug!("`TestCustomError` called."); Err(MyIfaceError::SomethingWentWrong("oops".to_string())) } #[instrument] fn test_single_struct_arg( &self, arg: ArgStructTest, #[zbus(header)] header: Header<'_>, ) -> zbus::fdo::Result<()> { debug!("`TestSingleStructArg` called."); assert_eq!(header.signature().unwrap(), "(is)"); assert_eq!(arg.foo, 1); assert_eq!(arg.bar, "TestString"); Ok(()) } #[instrument] fn test_single_struct_ret(&self) -> zbus::fdo::Result { debug!("`TestSingleStructRet` called."); Ok(ArgStructTest { foo: 42, bar: String::from("Meaning of life"), }) } #[instrument] #[zbus(out_args("foo", "bar"))] fn test_multi_ret(&self) -> zbus::fdo::Result<(i32, String)> { debug!("`TestMultiRet` called."); Ok((42, String::from("Meaning of life"))) } #[instrument] fn test_response_notify( &self, #[zbus(connection)] conn: &Connection, #[zbus(signal_context)] ctxt: SignalContext<'_>, ) -> zbus::fdo::Result> { debug!("`TestResponseNotify` called."); let (response, listener) = ResponseDispatchNotifier::new(String::from("Meaning of life")); let ctxt = ctxt.to_owned(); conn.executor() .spawn( async move { listener.await; Self::test_response_notified(ctxt).await.unwrap(); }, "TestResponseNotify", ) .detach(); Ok(response) } #[zbus(signal)] async fn test_response_notified(ctxt: SignalContext<'_>) -> zbus::Result<()>; #[instrument] async fn test_hashmap_return(&self) -> zbus::fdo::Result> { debug!("`TestHashmapReturn` called."); let mut map = HashMap::new(); map.insert("hi".into(), "hello".into()); map.insert("bye".into(), "now".into()); Ok(map) } #[instrument] async fn create_obj(&self, key: &str) { debug!("`CreateObj` called."); self.next_tx .send(NextAction::CreateObj(key.into())) .await .unwrap(); } #[instrument] async fn create_obj_inside( &self, #[zbus(object_server)] object_server: &ObjectServer, key: String, ) { debug!("`CreateObjInside` called."); object_server .at( format!("/zbus/test/{key}"), MyIface::new(self.next_tx.clone()), ) .await .unwrap(); } #[instrument] async fn destroy_obj(&self, key: &str) { debug!("`DestroyObj` called."); self.next_tx .send(NextAction::DestroyObj(key.into())) .await .unwrap(); } #[cfg(feature = "option-as-array")] #[instrument] async fn optional_args(&self, arg: Option<&str>) -> zbus::fdo::Result> { debug!("`OptionalArgs` called."); Ok(arg.map(|s| format!("Hello {}", s))) } #[instrument] #[zbus(property)] fn set_count(&mut self, val: u32) -> zbus::fdo::Result<()> { debug!("`Count` setter called."); if val == 42 { return Err(zbus::fdo::Error::InvalidArgs("Tsss tsss!".to_string())); } self.count = val; Ok(()) } #[instrument] #[zbus(property)] fn count(&self) -> u32 { debug!("`Count` getter called."); self.count } #[instrument] #[zbus(property)] async fn hash_map(&self) -> HashMap { debug!("`HashMap` getter called."); self.test_hashmap_return().await.unwrap() } #[instrument] #[zbus(property)] async fn fail_property(&self) -> zbus::fdo::Result { Err(zbus::fdo::Error::UnknownProperty( "FailProperty".to_string(), )) } #[instrument] #[zbus(property)] fn optional_property(&self) -> Optional { debug!("`OptionalAsProp` getter called."); Some(42).into() } #[instrument] #[zbus(property)] fn address_data(&self) -> IP4Adress { debug!("`AddressData` getter called."); IP4Adress { address: "127.0.0.1".to_string(), prefix: 1234, } } #[instrument] #[zbus(property)] fn set_address_data(&self, addr: IP4Adress) { debug!("`AddressData` setter called with {:?}", addr); } // On the bus, this should return the same value as address_data above. We want to test if // this works both ways. #[instrument] #[zbus(property)] fn address_data2(&self) -> HashMap { debug!("`AddressData2` getter called."); let mut map = HashMap::new(); map.insert( "address".into(), Value::from("127.0.0.1").try_into().unwrap(), ); map.insert("prefix".into(), 1234u32.into()); map } #[instrument] #[zbus(property)] fn str_prop(&self) -> String { "Hello".to_string() } #[instrument] #[zbus(property)] fn set_str_prop(&self, str_prop: &str) { debug!("`SetStrRef` called with {:?}", str_prop); } #[instrument] #[zbus(property)] fn ref_prop(&self) -> RefType<'_> { RefType { field1: "Hello".into(), } } #[instrument] #[zbus(property)] fn set_ref_prop(&self, ref_type: RefType<'_>) { debug!("`SetRefType` called with {:?}", ref_type); } #[instrument] #[zbus(proxy(no_reply))] fn test_no_reply(&self, #[zbus(header)] header: Header<'_>) { debug!("`TestNoReply` called"); assert_eq!(header.message_type(), zbus::message::Type::MethodCall); assert!(header .primary() .flags() .contains(zbus::message::Flags::NoReplyExpected)); } #[instrument] #[zbus(proxy(no_autostart))] fn test_no_autostart(&self, #[zbus(header)] header: Header<'_>) { debug!("`TestNoAutostart` called"); assert_eq!(header.message_type(), zbus::message::Type::MethodCall); assert!(header .primary() .flags() .contains(zbus::message::Flags::NoAutoStart)); } #[instrument] #[zbus(proxy(allow_interactive_auth))] fn test_interactive_auth(&self, #[zbus(header)] header: Header<'_>) { debug!("`TestInteractiveAuth` called"); assert_eq!(header.message_type(), zbus::message::Type::MethodCall); assert!(header .primary() .flags() .contains(zbus::message::Flags::AllowInteractiveAuth)); } #[zbus(signal)] async fn alert_count(ctxt: &SignalContext<'_>, val: u32) -> zbus::Result<()>; #[instrument] #[zbus(property)] fn emits_changed_default(&self) -> u32 { debug!("`EmitsChangedDefault` getter called."); self.emits_changed_default } #[instrument] #[zbus(property)] fn set_emits_changed_default(&mut self, val: u32) -> zbus::fdo::Result<()> { debug!("`EmitsChangedDefault` setter called."); self.emits_changed_default = val; Ok(()) } #[instrument] #[zbus(property(emits_changed_signal = "true"))] fn emits_changed_true(&self) -> u32 { debug!("`EmitsChangedTrue` getter called."); self.emits_changed_true } #[instrument] #[zbus(property)] fn set_emits_changed_true(&mut self, val: u32) -> zbus::fdo::Result<()> { debug!("`EmitsChangedTrue` setter called."); self.emits_changed_true = val; Ok(()) } #[instrument] #[zbus(property(emits_changed_signal = "invalidates"))] fn emits_changed_invalidates(&self) -> u32 { debug!("`EmitsChangedInvalidates` getter called."); self.emits_changed_invalidates } #[instrument] #[zbus(property)] fn set_emits_changed_invalidates(&mut self, val: u32) -> zbus::fdo::Result<()> { debug!("`EmitsChangedInvalidates` setter called."); self.emits_changed_invalidates = val; Ok(()) } #[instrument] #[zbus(property(emits_changed_signal = "const"))] fn emits_changed_const(&self) -> u32 { debug!("`EmitsChangedConst` getter called."); self.emits_changed_const } #[instrument] #[zbus(property)] fn set_emits_changed_const(&mut self, val: u32) -> zbus::fdo::Result<()> { debug!("`EmitsChangedConst` setter called."); self.emits_changed_const = val; Ok(()) } #[instrument] #[zbus(property(emits_changed_signal = "false"))] fn emits_changed_false(&self) -> u32 { debug!("`EmitsChangedFalse` getter called."); self.emits_changed_false } #[instrument] #[zbus(property)] fn set_emits_changed_false(&mut self, val: u32) -> zbus::fdo::Result<()> { debug!("`EmitsChangedFalse` setter called."); self.emits_changed_false = val; Ok(()) } } fn check_hash_map(map: HashMap) { assert_eq!(map["hi"], "hello"); assert_eq!(map["bye"], "now"); } fn check_ipv4_address(address: IP4Adress) { assert_eq!( address, IP4Adress { address: "127.0.0.1".to_string(), prefix: 1234, } ); } fn check_ipv4_address_hashmap(address: HashMap) { assert_eq!(**address.get("address").unwrap(), Value::from("127.0.0.1")); assert_eq!(**address.get("prefix").unwrap(), Value::from(1234u32)); } #[instrument] async fn my_iface_test(conn: Connection, event: Event) -> zbus::Result { debug!("client side starting.."); // Use low-level API for `TestResponseNotify` because we need to ensure that the signal is // always received after the response. let mut stream = MessageStream::from(&conn); let method = Message::method("/org/freedesktop/MyService", "TestResponseNotify")? .interface("org.freedesktop.MyIface")? .destination("org.freedesktop.MyService")? .build(&())?; let serial = method.primary_header().serial_num(); conn.send(&method).await?; let mut method_returned = false; let mut signal_received = false; while !method_returned && !signal_received { let msg = stream.try_next().await?.unwrap(); let hdr = msg.header(); if hdr.message_type() == message::Type::MethodReturn && hdr.reply_serial() == Some(serial) { assert!(!signal_received); method_returned = true; } else if hdr.message_type() == message::Type::Signal && hdr.interface().unwrap() == "org.freedesktop.MyService" && hdr.member().unwrap() == "TestResponseNotified" { assert!(method_returned); signal_received = true; } } drop(stream); let root_introspect_proxy = zbus::fdo::IntrospectableProxy::builder(&conn) .destination("org.freedesktop.MyService")? .path("/")? .build() .await?; debug!("Created: {:?}", root_introspect_proxy); let root_xml = root_introspect_proxy.introspect().await?; let root_node = zbus_xml::Node::from_reader(root_xml.as_bytes()) .map_err(|e| Error::Failure(e.to_string()))?; let mut node = &root_node; for name in ["org", "freedesktop", "MyService"] { node = node .nodes() .iter() .find(|&n| n.name().is_some_and(|n| n == name)) .expect("Child node not exist"); } assert!(node .interfaces() .iter() .any(|i| i.name() == "org.freedesktop.MyIface")); let proxy = MyIfaceProxy::builder(&conn) .destination("org.freedesktop.MyService")? .path("/org/freedesktop/MyService")? // the server isn't yet running .cache_properties(CacheProperties::No) .build() .await?; debug!("Created: {:?}", proxy); let props_proxy = zbus::fdo::PropertiesProxy::builder(&conn) .destination("org.freedesktop.MyService")? .path("/org/freedesktop/MyService")? .build() .await?; debug!("Created: {:?}", props_proxy); let mut props_changed_stream = props_proxy.receive_properties_changed().await?; debug!("Created: {:?}", props_changed_stream); event.notify(1); debug!("Notified service that client is ready"); match props_changed_stream.next().await { Some(changed) => { assert_eq!( *changed.args()?.changed_properties().keys().next().unwrap(), "Count" ); } None => panic!(""), }; drop(props_changed_stream); proxy.ping().await?; assert_eq!(proxy.count().await?, 1); assert_eq!(proxy.cached_count()?, None); proxy.test_header().await?; proxy .test_single_struct_arg(ArgStructTest { foo: 1, bar: "TestString".into(), }) .await?; proxy.test_error().await.unwrap_err(); assert_eq!( proxy.test_custom_error().await.unwrap_err(), MyIfaceError::SomethingWentWrong("oops".to_string()) ); check_hash_map(proxy.test_hashmap_return().await?); check_hash_map(proxy.hash_map().await?); proxy .set_address_data(IP4Adress { address: "localhost".to_string(), prefix: 1234, }) .await?; proxy.set_str_prop("This is an str ref").await?; check_ipv4_address(proxy.address_data().await?); check_ipv4_address_hashmap(proxy.address_data2().await?); proxy.test_no_reply().await?; proxy.test_no_autostart().await?; proxy.test_interactive_auth().await?; let err = proxy.fail_property().await; assert_eq!( err.unwrap_err(), zbus::Error::FDO(Box::new(zbus::fdo::Error::UnknownProperty( "FailProperty".into() ))) ); assert_eq!(proxy.optional_property().await?, Some(42).into()); let xml = proxy.inner().introspect().await?; debug!("Introspection: {}", xml); let node = zbus_xml::Node::from_reader(xml.as_bytes()).map_err(|e| Error::Failure(e.to_string()))?; let ifaces = node.interfaces(); let iface = ifaces .iter() .find(|i| i.name() == "org.freedesktop.MyIface") .unwrap(); let methods = iface.methods(); for method in methods { if method.name() != "TestSingleStructRet" && method.name() != "TestMultiRet" { continue; } let args = method.args(); let mut out_args = args .iter() .filter(|a| a.direction().unwrap() == zbus_xml::ArgDirection::Out); if method.name() == "TestSingleStructRet" { assert_eq!(args.len(), 1); assert_eq!(out_args.next().unwrap().ty().signature(), "(is)"); assert!(out_args.next().is_none()); } else { assert_eq!(args.len(), 2); let foo = out_args.find(|a| a.name() == Some("foo")).unwrap(); assert_eq!(foo.ty().signature(), "i"); let bar = out_args.find(|a| a.name() == Some("bar")).unwrap(); assert_eq!(bar.ty().signature(), "s"); } } // build-time check to see if macro is doing the right thing. let _ = proxy.test_single_struct_ret().await?.foo; let _ = proxy.test_multi_ret().await?.1; let val = proxy.ping().await?; let obj_manager_proxy = ObjectManagerProxy::builder(&conn) .destination("org.freedesktop.MyService")? .path("/zbus/test")? .build() .await?; debug!("Created: {:?}", obj_manager_proxy); let mut ifaces_added_stream = obj_manager_proxy.receive_interfaces_added().await?; debug!("Created: {:?}", ifaces_added_stream); // Must process in parallel, so the stream listener does not block receiving // the method return message. let (ifaces_added, _) = futures_util::future::join( async { let ret = ifaces_added_stream.next().await.unwrap(); drop(ifaces_added_stream); ret }, async { proxy.create_obj("MyObj").await.unwrap(); }, ) .await; #[cfg(feature = "option-as-array")] { assert!(proxy.optional_args(None).await.unwrap().is_none()); assert_eq!( proxy.optional_args(Some("🚌")).await.unwrap().unwrap(), "Hello 🚌", ); } assert_eq!(ifaces_added.args()?.object_path(), "/zbus/test/MyObj"); let args = ifaces_added.args()?; let ifaces = args.interfaces_and_properties(); let _ = ifaces.get("org.freedesktop.MyIface").unwrap(); // TODO: Check if the properties are correct. // issue#207: interface panics on incorrect number of args. assert!(proxy.inner().call_method("CreateObj", &()).await.is_err()); let my_obj_proxy = MyIfaceProxy::builder(&conn) .destination("org.freedesktop.MyService")? .path("/zbus/test/MyObj")? .build() .await?; debug!("Created: {:?}", my_obj_proxy); my_obj_proxy.receive_count_changed().await; // Calling this after creating the stream was panicking if the property doesn't get cached // before the call (MR !460). my_obj_proxy.cached_count()?; assert_eq!(my_obj_proxy.count().await?, 0); assert_eq!(my_obj_proxy.cached_count()?, Some(0)); assert_eq!( my_obj_proxy.inner().cached_property_raw("Count").as_deref(), Some(&Value::from(0u32)) ); my_obj_proxy.ping().await?; let mut ifaces_removed_stream = obj_manager_proxy.receive_interfaces_removed().await?; debug!("Created: {:?}", ifaces_removed_stream); // Must process in parallel, so the stream listener does not block receiving // the method return message. let (ifaces_removed, _) = futures_util::future::join( async { let ret = ifaces_removed_stream.next().await.unwrap(); drop(ifaces_removed_stream); ret }, async { proxy.destroy_obj("MyObj").await.unwrap(); }, ) .await; let args = ifaces_removed.args()?; assert_eq!(args.object_path(), "/zbus/test/MyObj"); assert_eq!(args.interfaces(), &["org.freedesktop.MyIface"]); assert!(my_obj_proxy.inner().introspect().await.is_err()); assert!(my_obj_proxy.ping().await.is_err()); // Make sure methods modifying the ObjectServer can be called without // deadlocks. proxy .inner() .call_method("CreateObjInside", &("CreatedInside")) .await?; let created_inside_proxy = MyIfaceProxy::builder(&conn) .destination("org.freedesktop.MyService")? .path("/zbus/test/CreatedInside")? .build() .await?; created_inside_proxy.ping().await?; proxy.destroy_obj("CreatedInside").await?; // Test that interfaces emit signals when properties change // according to their emits_changed_signal flags. let mut props_changed = props_proxy.receive_properties_changed().await?; let expected_property_value = 4; proxy .set_emits_changed_default(expected_property_value) .await?; let changed = props_changed.next().await.unwrap(); let expected_property_key = "EmitsChangedDefault"; let args = changed.args()?; assert!(args.invalidated_properties().is_empty()); let changed_prop_key = *args.changed_properties().keys().next().unwrap(); assert_eq!(changed_prop_key, expected_property_key); let changed_value = &args.changed_properties()[expected_property_key]; assert_eq!( <&Value as TryInto>::try_into(changed_value).unwrap(), expected_property_value ); assert!(args.invalidated_properties().is_empty()); proxy .set_emits_changed_true(expected_property_value) .await?; let changed = props_changed.next().await.unwrap(); let expected_property_key = "EmitsChangedTrue"; let args = changed.args()?; assert!(args.invalidated_properties().is_empty()); let changed_prop_key = *args.changed_properties().keys().next().unwrap(); assert_eq!(changed_prop_key, expected_property_key); let changed_value = &args.changed_properties()[expected_property_key]; assert_eq!( <&Value as TryInto>::try_into(changed_value).unwrap(), expected_property_value ); assert!(args.invalidated_properties().is_empty()); proxy .set_emits_changed_invalidates(expected_property_value) .await?; let changed = props_changed.next().await.unwrap(); let expected_property_key = "EmitsChangedInvalidates"; let args = changed.args()?; assert_eq!( args.invalidated_properties(), &vec![expected_property_key.to_string()] ); assert!(args.changed_properties().is_empty()); // First set a property for which we don't expect a signal // then set a property for which we do (and we checked above // that we receive it. The next item in the iter should correspond // to the second property we set. proxy .set_emits_changed_const(expected_property_value) .await?; proxy .set_emits_changed_true(expected_property_value) .await?; let changed = props_changed.next().await.unwrap(); let unexpected_property_key = "EmitsChangedConst"; let args = changed.args()?; assert_ne!( args.invalidated_properties(), &vec![unexpected_property_key.to_string()] ); assert!(!args.changed_properties().is_empty()); assert!(args.invalidated_properties().is_empty()); proxy .set_emits_changed_false(expected_property_value) .await?; proxy .set_emits_changed_true(expected_property_value) .await?; let changed = props_changed.next().await.unwrap(); let unexpected_property_key = "EmitsChangedFalse"; let args = changed.args()?; assert_ne!( args.invalidated_properties(), &vec![unexpected_property_key.to_string()] ); assert!(!args.changed_properties().is_empty()); assert!(args.invalidated_properties().is_empty()); proxy.quit().await?; Ok(val) } #[test] #[timeout(15000)] fn iface_and_proxy() { block_on(iface_and_proxy_(false)); } #[cfg(feature = "p2p")] #[cfg(unix)] #[test] #[timeout(15000)] fn iface_and_proxy_unix_p2p() { block_on(iface_and_proxy_(true)); } #[instrument] async fn iface_and_proxy_(#[allow(unused)] p2p: bool) { let event = event_listener::Event::new(); #[cfg(feature = "p2p")] let guid = zbus::Guid::generate(); let session_conns_build = || { let service_conn_builder = connection::Builder::session() .unwrap() .name("org.freedesktop.MyService") .unwrap() .name("org.freedesktop.MyService.foo") .unwrap() .name("org.freedesktop.MyService.bar") .unwrap() .name("org.freedesktop.MyEmitsChangedSignalIface") .unwrap(); let client_conn_builder = connection::Builder::session().unwrap(); (service_conn_builder, client_conn_builder) }; #[cfg(feature = "p2p")] let (service_conn_builder, client_conn_builder) = if p2p { #[cfg(unix)] { let (p0, p1) = UnixStream::pair().unwrap(); ( connection::Builder::unix_stream(p0) .server(guid) .unwrap() .p2p(), connection::Builder::unix_stream(p1).p2p(), ) } #[cfg(windows)] { #[cfg(not(feature = "tokio"))] { let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap(); let addr = listener.local_addr().unwrap(); let p1 = std::net::TcpStream::connect(addr).unwrap(); let p0 = listener.incoming().next().unwrap().unwrap(); ( connection::Builder::tcp_stream(p0) .server(guid) .unwrap() .p2p(), connection::Builder::tcp_stream(p1).p2p(), ) } #[cfg(feature = "tokio")] { let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); let addr = listener.local_addr().unwrap(); let p1 = tokio::net::TcpStream::connect(addr).await.unwrap(); let p0 = listener.accept().await.unwrap().0; ( connection::Builder::tcp_stream(p0) .server(guid) .unwrap() .p2p(), connection::Builder::tcp_stream(p1).p2p(), ) } } } else { session_conns_build() }; #[cfg(not(feature = "p2p"))] let (service_conn_builder, client_conn_builder) = session_conns_build(); debug!( "Client connection builder created: {:?}", client_conn_builder ); debug!( "Service connection builder created: {:?}", service_conn_builder ); let (next_tx, mut next_rx) = channel(64); let iface = MyIface::new(next_tx.clone()); let service_conn_builder = service_conn_builder .serve_at("/org/freedesktop/MyService", iface) .unwrap() .serve_at("/zbus/test", ObjectManager) .unwrap(); debug!("ObjectServer set-up."); let (service_conn, client_conn) = futures_util::try_join!(service_conn_builder.build(), client_conn_builder.build(),) .unwrap(); debug!("Client connection created: {:?}", client_conn); debug!("Service connection created: {:?}", service_conn); let listen = event.listen(); let child = client_conn .executor() .spawn(my_iface_test(client_conn.clone(), event), "client_task"); debug!("Child task spawned."); // Wait for the listener to be ready listen.await; debug!("Child task signaled it's ready."); let iface: InterfaceRef = service_conn .object_server() .interface("/org/freedesktop/MyService") .await .unwrap(); iface .get() .await .count_changed(iface.signal_context()) .await .unwrap(); debug!("`PropertiesChanged` emitted for `Count` property."); loop { MyIface::alert_count(iface.signal_context(), 51) .await .unwrap(); debug!("`AlertCount` signal emitted."); match next_rx.recv().await.unwrap() { NextAction::Quit => break, NextAction::CreateObj(key) => { let path = format!("/zbus/test/{key}"); service_conn .object_server() .at(path.clone(), MyIface::new(next_tx.clone())) .await .unwrap(); debug!("Object `{path}` added."); } NextAction::DestroyObj(key) => { let path = format!("/zbus/test/{key}"); service_conn .object_server() .remove::(path.clone()) .await .unwrap(); debug!("Object `{path}` removed."); } } } debug!("Server done."); // don't close the connection before we end the loop drop(client_conn); debug!("Connection closed."); let val = child.await.unwrap(); debug!("Client task done."); assert_eq!(val, 2); if p2p { debug!("p2p connection, no need to release names.."); return; } // Release primary name explicitly and let others be released implicitly. assert_eq!( service_conn.release_name("org.freedesktop.MyService").await, Ok(true) ); debug!("Bus name `org.freedesktop.MyService` released."); assert_eq!( service_conn .release_name("org.freedesktop.MyService.foo") .await, Ok(true) ); debug!("Bus name `org.freedesktop.MyService.foo` released."); assert_eq!( service_conn .release_name("org.freedesktop.MyService.bar") .await, Ok(true) ); debug!("Bus name `org.freedesktop.MyService.bar` released."); // Let's ensure all names were released. let proxy = zbus::fdo::DBusProxy::new(&service_conn).await.unwrap(); debug!("DBusProxy created to ensure all names were released."); assert_eq!( proxy .name_has_owner("org.freedesktop.MyService".try_into().unwrap()) .await, Ok(false) ); assert_eq!( proxy .name_has_owner("org.freedesktop.MyService.foo".try_into().unwrap()) .await, Ok(false) ); assert_eq!( proxy .name_has_owner("org.freedesktop.MyService.bar".try_into().unwrap()) .await, Ok(false) ); debug!("Bus confirmed that all names were definitely released."); }

(self, path: P) -> Result where P: TryInto>, P::Error: Into, { crate::proxy::Builder::path(self.0, path).map(Self) } /// Set the proxy interface. pub fn interface(self, interface: I) -> Result where I: TryInto>, I::Error: Into, { crate::proxy::Builder::interface(self.0, interface).map(Self) } /// Set whether to cache properties. #[must_use] pub fn cache_properties(self, cache: CacheProperties) -> Self { Self(self.0.cache_properties(cache)) } /// Specify a set of properties (by name) which should be excluded from caching. #[must_use] pub fn uncached_properties(self, properties: &[&'a str]) -> Self { Self(self.0.uncached_properties(properties)) } /// Build a proxy from the builder. /// /// # Panics /// /// Panics if the builder is lacking the necessary details to build a proxy. pub fn build(self) -> Result where T: From>, { block_on(self.0.build()) } } impl<'a, T> Builder<'a, T> where T: ProxyDefault, { /// Create a new [`Builder`] for the given connection. #[must_use] pub fn new(conn: &Connection) -> Self { Self(crate::proxy::Builder::new(&conn.clone().into())) } /// Create a new [`Builder`] for the given connection. #[must_use] #[deprecated( since = "4.0.0", note = "use `Builder::new` instead, which is now generic over the proxy type" )] pub fn new_bare(conn: &Connection) -> Self { Self::new(conn) } } zbus-4.3.1/src/blocking/proxy/mod.rs000064400000000000000000000441041046102023000155040ustar 00000000000000//! The client-side proxy API. use enumflags2::BitFlags; use futures_util::StreamExt; use static_assertions::assert_impl_all; use std::{fmt, ops::Deref}; use zbus_names::{BusName, InterfaceName, MemberName, UniqueName}; use zvariant::{ObjectPath, OwnedValue, Value}; use crate::{ blocking::Connection, message::Message, proxy::{MethodFlags, ProxyDefault}, utils::block_on, Error, Result, }; use crate::fdo; mod builder; pub use builder::Builder; /// A blocking wrapper of [`crate::Proxy`]. /// /// This API is mostly the same as [`crate::Proxy`], except that all its methods block to /// completion. /// /// # Example /// /// ``` /// use std::result::Result; /// use std::error::Error; /// use zbus::blocking::{Connection, Proxy}; /// /// fn main() -> Result<(), Box> { /// let connection = Connection::session()?; /// let p = Proxy::new( /// &connection, /// "org.freedesktop.DBus", /// "/org/freedesktop/DBus", /// "org.freedesktop.DBus", /// )?; /// // owned return value /// let _id: String = p.call("GetId", &())?; /// // borrowed return value /// let body = p.call_method("GetId", &())?.body(); /// let _id: &str = body.deserialize()?; /// Ok(()) /// } /// ``` /// /// # Note /// /// It is recommended to use the [`proxy`] macro, which provides a more convenient and /// type-safe *façade* `Proxy` derived from a Rust trait. /// /// ## Current limitations: /// /// At the moment, `Proxy` doesn't prevent [auto-launching][al]. /// /// [`proxy`]: attr.proxy.html /// [al]: https://github.com/dbus2/zbus/issues/54 #[derive(Clone)] pub struct Proxy<'a> { conn: Connection, // Wrap it in an `Option` to ensure the proxy is dropped in a `block_on` call. This is needed // for tokio because the proxy spawns a task in its `Drop` impl and that needs a runtime // context in case of tokio. azync: Option>, } impl fmt::Debug for Proxy<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Proxy") .field("azync", &self.azync) .finish_non_exhaustive() } } assert_impl_all!(Proxy<'_>: Send, Sync, Unpin); impl<'a> Proxy<'a> { /// Create a new `Proxy` for the given destination/path/interface. pub fn new( conn: &Connection, destination: D, path: P, interface: I, ) -> Result> where D: TryInto>, P: TryInto>, I: TryInto>, D::Error: Into, P::Error: Into, I::Error: Into, { let proxy = block_on(crate::Proxy::new( conn.inner(), destination, path, interface, ))?; Ok(Self { conn: conn.clone(), azync: Some(proxy), }) } /// Create a new `Proxy` for the given destination/path/interface, taking ownership of all /// passed arguments. pub fn new_owned( conn: Connection, destination: D, path: P, interface: I, ) -> Result> where D: TryInto>, P: TryInto>, I: TryInto>, D::Error: Into, P::Error: Into, I::Error: Into, { let proxy = block_on(crate::Proxy::new_owned( conn.clone().into_inner(), destination, path, interface, ))?; Ok(Self { conn, azync: Some(proxy), }) } /// Get a reference to the associated connection. pub fn connection(&self) -> &Connection { &self.conn } /// Get a reference to the destination service name. pub fn destination(&self) -> &BusName<'_> { self.inner().destination() } /// Get a reference to the object path. pub fn path(&self) -> &ObjectPath<'_> { self.inner().path() } /// Get a reference to the interface. pub fn interface(&self) -> &InterfaceName<'_> { self.inner().interface() } /// Introspect the associated object, and return the XML description. /// /// See the [xml](xml/index.html) module for parsing the result. pub fn introspect(&self) -> fdo::Result { block_on(self.inner().introspect()) } /// Get the cached value of the property `property_name`. /// /// This returns `None` if the property is not in the cache. This could be because the cache /// was invalidated by an update, because caching was disabled for this property or proxy, or /// because the cache has not yet been populated. Use `get_property` to fetch the value from /// the peer. pub fn cached_property(&self, property_name: &str) -> Result> where T: TryFrom, T::Error: Into, { self.inner().cached_property(property_name) } /// Get the cached value of the property `property_name`. /// /// Same as `cached_property`, but gives you access to the raw value stored in the cache. This /// is useful if you want to avoid allocations and cloning. pub fn cached_property_raw<'p>( &'p self, property_name: &'p str, ) -> Option> + 'p> { self.inner().cached_property_raw(property_name) } /// Get the property `property_name`. /// /// Get the property value from the cache or call the `Get` method of the /// `org.freedesktop.DBus.Properties` interface. pub fn get_property(&self, property_name: &str) -> Result where T: TryFrom, T::Error: Into, { block_on(self.inner().get_property(property_name)) } /// Set the property `property_name`. /// /// Effectively, call the `Set` method of the `org.freedesktop.DBus.Properties` interface. pub fn set_property<'t, T>(&self, property_name: &str, value: T) -> fdo::Result<()> where T: 't + Into>, { block_on(self.inner().set_property(property_name, value)) } /// Call a method and return the reply. /// /// Typically, you would want to use [`call`] method instead. Use this method if you need to /// deserialize the reply message manually (this way, you can avoid the memory /// allocation/copying, by deserializing the reply to an unowned type). /// /// [`call`]: struct.Proxy.html#method.call pub fn call_method<'m, M, B>(&self, method_name: M, body: &B) -> Result where M: TryInto>, M::Error: Into, B: serde::ser::Serialize + zvariant::DynamicType, { block_on(self.inner().call_method(method_name, body)) } /// Call a method and return the reply body. /// /// Use [`call_method`] instead if you need to deserialize the reply manually/separately. /// /// [`call_method`]: struct.Proxy.html#method.call_method pub fn call<'m, M, B, R>(&self, method_name: M, body: &B) -> Result where M: TryInto>, M::Error: Into, B: serde::ser::Serialize + zvariant::DynamicType, R: for<'d> zvariant::DynamicDeserialize<'d>, { block_on(self.inner().call(method_name, body)) } /// Call a method and return the reply body, optionally supplying a set of /// method flags to control the way the method call message is sent and handled. /// /// Use [`call`] instead if you do not need any special handling via additional flags. /// If the `NoReplyExpected` flag is passed , this will return None immediately /// after sending the message, similar to [`call_noreply`] /// /// [`call`]: struct.Proxy.html#method.call /// [`call_noreply`]: struct.Proxy.html#method.call_noreply pub fn call_with_flags<'m, M, B, R>( &self, method_name: M, flags: BitFlags, body: &B, ) -> Result> where M: TryInto>, M::Error: Into, B: serde::ser::Serialize + zvariant::DynamicType, R: for<'d> zvariant::DynamicDeserialize<'d>, { block_on(self.inner().call_with_flags(method_name, flags, body)) } /// Call a method without expecting a reply /// /// This sets the `NoReplyExpected` flag on the calling message and does not wait for a reply. pub fn call_noreply<'m, M, B>(&self, method_name: M, body: &B) -> Result<()> where M: TryInto>, M::Error: Into, B: serde::ser::Serialize + zvariant::DynamicType, { block_on(self.inner().call_noreply(method_name, body)) } /// Create a stream for signal named `signal_name`. /// /// # Errors /// /// Apart from general I/O errors that can result from socket communications, calling this /// method will also result in an error if the destination service has not yet registered its /// well-known name with the bus (assuming you're using the well-known name as destination). pub fn receive_signal<'m, M>(&self, signal_name: M) -> Result> where M: TryInto>, M::Error: Into, { self.receive_signal_with_args(signal_name, &[]) } /// Same as [`Proxy::receive_signal`] but with a filter. /// /// The D-Bus specification allows you to filter signals by their arguments, which helps avoid /// a lot of unnecessary traffic and processing since the filter is run on the server side. Use /// this method where possible. Note that this filtering is limited to arguments of string /// types. /// /// The arguments are passed as a tuples of argument index and expected value. pub fn receive_signal_with_args<'m, M>( &self, signal_name: M, args: &[(u8, &str)], ) -> Result> where M: TryInto>, M::Error: Into, { block_on(self.inner().receive_signal_with_args(signal_name, args)) .map(Some) .map(SignalIterator) } /// Create a stream for all signals emitted by this service. /// /// # Errors /// /// Apart from general I/O errors that can result from socket communications, calling this /// method will also result in an error if the destination service has not yet registered its /// well-known name with the bus (assuming you're using the well-known name as destination). pub fn receive_all_signals(&self) -> Result> { block_on(self.inner().receive_all_signals()) .map(Some) .map(SignalIterator) } /// Get an iterator to receive owner changed events. /// /// If the proxy destination is a unique name, the stream will be notified of the peer /// disconnection from the bus (with a `None` value). /// /// If the proxy destination is a well-known name, the stream will be notified whenever the name /// owner is changed, either by a new peer being granted ownership (`Some` value) or when the /// name is released (with a `None` value). /// /// Note that zbus doesn't queue the updates. If the listener is slower than the receiver, it /// will only receive the last update. pub fn receive_property_changed<'name: 'a, T>( &self, name: &'name str, ) -> PropertyIterator<'a, T> { PropertyIterator(block_on(self.inner().receive_property_changed(name))) } /// Get an iterator to receive property changed events. /// /// Note that zbus doesn't queue the updates. If the listener is slower than the receiver, it /// will only receive the last update. pub fn receive_owner_changed(&self) -> Result> { block_on(self.inner().receive_owner_changed()).map(OwnerChangedIterator) } /// Get a reference to the underlying async Proxy. pub fn inner(&self) -> &crate::Proxy<'a> { self.azync.as_ref().expect("Inner proxy is `None`") } /// Get the underlying async Proxy, consuming `self`. pub fn into_inner(mut self) -> crate::Proxy<'a> { self.azync.take().expect("Inner proxy is `None`") } } impl ProxyDefault for Proxy<'_> { const INTERFACE: Option<&'static str> = None; const DESTINATION: Option<&'static str> = None; const PATH: Option<&'static str> = None; } impl<'a> std::convert::AsRef> for Proxy<'a> { fn as_ref(&self) -> &Proxy<'a> { self } } impl<'a> From> for Proxy<'a> { fn from(proxy: crate::Proxy<'a>) -> Self { Self { conn: proxy.connection().clone().into(), azync: Some(proxy), } } } impl std::ops::Drop for Proxy<'_> { fn drop(&mut self) { block_on(async { self.azync.take(); }); } } /// An [`std::iter::Iterator`] implementation that yields signal [messages](`Message`). /// /// Use [`Proxy::receive_signal`] to create an instance of this type. #[derive(Debug)] pub struct SignalIterator<'a>(Option>); impl<'a> SignalIterator<'a> { /// The signal name. pub fn name(&self) -> Option<&MemberName<'a>> { self.0.as_ref().expect("`SignalStream` is `None`").name() } } assert_impl_all!(SignalIterator<'_>: Send, Sync, Unpin); impl std::iter::Iterator for SignalIterator<'_> { type Item = Message; fn next(&mut self) -> Option { block_on(self.0.as_mut().expect("`SignalStream` is `None`").next()) } } impl std::ops::Drop for SignalIterator<'_> { fn drop(&mut self) { block_on(async { if let Some(azync) = self.0.take() { crate::AsyncDrop::async_drop(azync).await; } }); } } /// An [`std::iter::Iterator`] implementation that yields property change notifications. /// /// Use [`Proxy::receive_property_changed`] to create an instance of this type. pub struct PropertyIterator<'a, T>(crate::proxy::PropertyStream<'a, T>); impl<'a, T> std::iter::Iterator for PropertyIterator<'a, T> where T: Unpin, { type Item = PropertyChanged<'a, T>; fn next(&mut self) -> Option { block_on(self.0.next()).map(PropertyChanged) } } /// A property changed event. /// /// The property changed event generated by [`PropertyIterator`]. pub struct PropertyChanged<'a, T>(crate::proxy::PropertyChanged<'a, T>); // split this out to avoid the trait bound on `name` method impl<'a, T> PropertyChanged<'a, T> { /// Get the name of the property that changed. pub fn name(&self) -> &str { self.0.name() } // Get the raw value of the property that changed. // // If the notification signal contained the new value, it has been cached already and this call // will return that value. Otherwise (i-e invalidated property), a D-Bus call is made to fetch // and cache the new value. pub fn get_raw(&self) -> Result> + '_> { block_on(self.0.get_raw()) } } impl<'a, T> PropertyChanged<'a, T> where T: TryFrom, T::Error: Into, { // Get the value of the property that changed. // // If the notification signal contained the new value, it has been cached already and this call // will return that value. Otherwise (i-e invalidated property), a D-Bus call is made to fetch // and cache the new value. pub fn get(&self) -> Result { block_on(self.0.get()) } } /// An [`std::iter::Iterator`] implementation that yields owner change notifications. /// /// Use [`Proxy::receive_owner_changed`] to create an instance of this type. pub struct OwnerChangedIterator<'a>(crate::proxy::OwnerChangedStream<'a>); impl OwnerChangedIterator<'_> { /// The bus name being tracked. pub fn name(&self) -> &BusName<'_> { self.0.name() } } impl<'a> std::iter::Iterator for OwnerChangedIterator<'a> { type Item = Option>; fn next(&mut self) -> Option { block_on(self.0.next()) } } /// This trait is implemented by all blocking proxies, which are generated with the /// [`dbus_proxy`](zbus::dbus_proxy) macro. pub trait ProxyImpl<'p> where Self: Sized, { /// Returns a customizable builder for this proxy. fn builder(conn: &Connection) -> Builder<'p, Self>; /// Consumes `self`, returning the underlying `zbus::Proxy`. fn into_inner(self) -> Proxy<'p>; /// The reference to the underlying `zbus::Proxy`. fn inner(&self) -> &Proxy<'p>; } #[cfg(test)] mod tests { use super::*; use crate::blocking; use ntest::timeout; use test_log::test; #[test] #[timeout(15000)] fn signal() { // Register a well-known name with the session bus and ensure we get the appropriate // signals called for that. let conn = Connection::session().unwrap(); let unique_name = conn.unique_name().unwrap().to_string(); let proxy = blocking::fdo::DBusProxy::new(&conn).unwrap(); let well_known = "org.freedesktop.zbus.ProxySignalTest"; let mut owner_changed = proxy .receive_name_owner_changed_with_args(&[(0, well_known), (2, unique_name.as_str())]) .unwrap(); let mut name_acquired = proxy .receive_name_acquired_with_args(&[(0, well_known)]) .unwrap(); blocking::fdo::DBusProxy::new(&conn) .unwrap() .request_name( well_known.try_into().unwrap(), fdo::RequestNameFlags::ReplaceExisting.into(), ) .unwrap(); let signal = owner_changed.next().unwrap(); let args = signal.args().unwrap(); assert!(args.name() == well_known); assert!(*args.new_owner().as_ref().unwrap() == *unique_name); let signal = name_acquired.next().unwrap(); // `NameAcquired` is emitted twice, first when the unique name is assigned on // connection and secondly after we ask for a specific name. Let's make sure we only get the // one we subscribed to. assert!(signal.args().unwrap().name() == well_known); } } zbus-4.3.1/src/connection/builder.rs000064400000000000000000000503031046102023000155370ustar 00000000000000#[cfg(not(feature = "tokio"))] use async_io::Async; use event_listener::Event; use static_assertions::assert_impl_all; #[cfg(not(feature = "tokio"))] use std::net::TcpStream; #[cfg(all(unix, not(feature = "tokio")))] use std::os::unix::net::UnixStream; use std::{ collections::{HashMap, HashSet, VecDeque}, vec, }; #[cfg(feature = "tokio")] use tokio::net::TcpStream; #[cfg(all(unix, feature = "tokio"))] use tokio::net::UnixStream; #[cfg(feature = "tokio-vsock")] use tokio_vsock::VsockStream; #[cfg(all(windows, not(feature = "tokio")))] use uds_windows::UnixStream; #[cfg(all(feature = "vsock", not(feature = "tokio")))] use vsock::VsockStream; use zvariant::{ObjectPath, Str}; use crate::{ address::{self, Address}, names::{InterfaceName, WellKnownName}, object_server::{ArcInterface, Interface}, Connection, Error, Executor, Guid, OwnedGuid, Result, }; use super::{ handshake::{AuthMechanism, Authenticated}, socket::{BoxedSplit, ReadHalf, Split, WriteHalf}, }; const DEFAULT_MAX_QUEUED: usize = 64; #[derive(Debug)] enum Target { #[cfg(any(unix, not(feature = "tokio")))] UnixStream(UnixStream), TcpStream(TcpStream), #[cfg(any( all(feature = "vsock", not(feature = "tokio")), feature = "tokio-vsock" ))] VsockStream(VsockStream), Address(Address), Socket(Split, Box>), AuthenticatedSocket(Split, Box>), } type Interfaces<'a> = HashMap, HashMap, ArcInterface>>; /// A builder for [`zbus::Connection`]. #[derive(Debug)] #[must_use] pub struct Builder<'a> { target: Option, max_queued: Option, // This is only set for p2p server case or pre-authenticated sockets. guid: Option>, #[cfg(feature = "p2p")] p2p: bool, internal_executor: bool, interfaces: Interfaces<'a>, names: HashSet>, auth_mechanisms: Option>, #[cfg(feature = "bus-impl")] unique_name: Option>, cookie_context: Option>, cookie_id: Option, } assert_impl_all!(Builder<'_>: Send, Sync, Unpin); impl<'a> Builder<'a> { /// Create a builder for the session/user message bus connection. pub fn session() -> Result { Ok(Self::new(Target::Address(Address::session()?))) } /// Create a builder for the system-wide message bus connection. pub fn system() -> Result { Ok(Self::new(Target::Address(Address::system()?))) } /// Create a builder for connection that will use the given [D-Bus bus address]. /// /// # Example /// /// Here is an example of connecting to an IBus service: /// /// ```no_run /// # use std::error::Error; /// # use zbus::connection::Builder; /// # use zbus::block_on; /// # /// # block_on(async { /// let addr = "unix:\ /// path=/home/zeenix/.cache/ibus/dbus-ET0Xzrk9,\ /// guid=fdd08e811a6c7ebe1fef0d9e647230da"; /// let conn = Builder::address(addr)? /// .build() /// .await?; /// /// // Do something useful with `conn`.. /// # drop(conn); /// # Ok::<(), zbus::Error>(()) /// # }).unwrap(); /// # /// # Ok::<_, Box>(()) /// ``` /// /// **Note:** The IBus address is different for each session. You can find the address for your /// current session using `ibus address` command. /// /// [D-Bus bus address]: https://dbus.freedesktop.org/doc/dbus-specification.html#addresses pub fn address(address: A) -> Result where A: TryInto