zbus-1.9.3/.cargo_vcs_info.json0000644000000001420000000000100120260ustar { "git": { "sha1": "7d676e1de100ff545daed1cc831608f59d032d5c" }, "path_in_vcs": "zbus" }zbus-1.9.3/Cargo.lock0000644000000410020000000000100100010ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "async-io" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5e18f61464ae81cde0a23e713ae8fd299580c54d697a35820cfd0625b8b0e07" dependencies = [ "concurrent-queue", "futures-lite", "libc", "log", "once_cell", "parking", "polling", "slab", "socket2", "waker-fn", "winapi", ] [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "byteorder" version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "cache-padded" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" [[package]] name = "cc" version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" [[package]] name = "cfg-if" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "concurrent-queue" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" dependencies = [ "cache-padded", ] [[package]] name = "derivative" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "doc-comment" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "enumflags2" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83c8d82922337cd23a15f88b70d8e4ef5f11da38dd7cdb55e84dd5de99695da0" dependencies = [ "enumflags2_derive", "serde", ] [[package]] name = "enumflags2_derive" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "946ee94e3dbf58fdd324f9ce245c7b238d46a66f00e86a020b71996349e46cce" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "fastrand" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" dependencies = [ "instant", ] [[package]] name = "futures" version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" dependencies = [ "futures-channel", "futures-core", "futures-executor", "futures-io", "futures-sink", "futures-task", "futures-util", ] [[package]] name = "futures-channel" version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" dependencies = [ "futures-core", "futures-sink", ] [[package]] name = "futures-core" version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" [[package]] name = "futures-executor" version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" dependencies = [ "futures-core", "futures-task", "futures-util", ] [[package]] name = "futures-io" version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" [[package]] name = "futures-lite" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" dependencies = [ "fastrand", "futures-core", "futures-io", "memchr", "parking", "pin-project-lite", "waker-fn", ] [[package]] name = "futures-macro" version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "futures-sink" version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" [[package]] name = "futures-task" version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" [[package]] name = "futures-util" version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" dependencies = [ "futures-channel", "futures-core", "futures-io", "futures-macro", "futures-sink", "futures-task", "memchr", "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "instant" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if 1.0.0", ] [[package]] name = "libc" version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" [[package]] name = "log" version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if 1.0.0", ] [[package]] name = "memchr" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memoffset" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" dependencies = [ "autocfg", ] [[package]] name = "nb-connect" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1bb540dc6ef51cfe1916ec038ce7a620daf3a111e2502d745197cd53d6bca15" dependencies = [ "libc", "socket2", ] [[package]] name = "nix" version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363" dependencies = [ "bitflags", "cc", "cfg-if 0.1.10", "libc", "void", ] [[package]] name = "nix" version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4916f159ed8e5de0082076562152a76b7a1f64a01fd9d1e0fea002c37624faf" dependencies = [ "bitflags", "cc", "cfg-if 1.0.0", "libc", "memoffset", ] [[package]] name = "ntest" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c544e496c816f0a59645c0bb69097e453df203954ae2ed4b3ac4251fad69d44" dependencies = [ "ntest_proc_macro_helper", "ntest_test_cases", "ntest_timeout", ] [[package]] name = "ntest_proc_macro_helper" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f52e34b414605b77efc95c3f0ecef01df0c324bcc7f68d9a9cb7a7552777e52" [[package]] name = "ntest_test_cases" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99a81eb400abc87063f829560bc5c5c835177703b83d1cd991960db0b2a00abe" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "ntest_timeout" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b10db009e117aca57cbfb70ac332348f9a89d09ff7204497c283c0f7a0c96323" dependencies = [ "ntest_proc_macro_helper", "proc-macro-crate 1.1.3", "proc-macro2", "quote", "syn", ] [[package]] name = "once_cell" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" [[package]] name = "parking" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" [[package]] name = "pin-project-lite" version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "polling" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259" dependencies = [ "cfg-if 1.0.0", "libc", "log", "wepoll-ffi", "winapi", ] [[package]] name = "proc-macro-crate" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" dependencies = [ "toml", ] [[package]] name = "proc-macro-crate" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" dependencies = [ "thiserror", "toml", ] [[package]] name = "proc-macro2" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" dependencies = [ "proc-macro2", ] [[package]] name = "scoped-tls" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" [[package]] name = "serde" version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" dependencies = [ "serde_derive", ] [[package]] name = "serde-xml-rs" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0bf1ba0696ccf0872866277143ff1fd14d22eec235d2b23702f95e6660f7dfa" dependencies = [ "log", "serde", "thiserror", "xml-rs", ] [[package]] name = "serde_derive" version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_repr" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2ad84e47328a31223de7fed7a4f5087f2d6ddfe586cf3ca25b7a165bc0a5aed" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "slab" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" [[package]] name = "socket2" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" dependencies = [ "libc", "winapi", ] [[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.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "thiserror" version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "toml" version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" dependencies = [ "serde", ] [[package]] name = "unicode-ident" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" [[package]] name = "void" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" [[package]] name = "waker-fn" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" [[package]] name = "wepoll-ffi" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" dependencies = [ "cc", ] [[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 = "xml-rs" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" [[package]] name = "zbus" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9722b364071ef25d3402b9c1daeb38b23796051fc36efdfc5a271813215c3f8" dependencies = [ "byteorder", "derivative", "enumflags2", "fastrand", "nix 0.17.0", "once_cell", "scoped-tls", "serde", "serde_repr", "zbus_macros", "zvariant", "zvariant_derive", ] [[package]] name = "zbus" version = "1.9.3" dependencies = [ "async-io", "byteorder", "derivative", "doc-comment", "enumflags2", "fastrand", "futures", "nb-connect", "nix 0.22.3", "ntest", "once_cell", "polling", "scoped-tls", "serde", "serde-xml-rs", "serde_repr", "zbus_macros", "zbus_polkit", "zvariant", ] [[package]] name = "zbus_macros" version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa3959a7847cf95e3d51e312856617c5b1b77191176c65a79a5f14d778bbe0a6" dependencies = [ "proc-macro-crate 0.1.5", "proc-macro2", "quote", "syn", ] [[package]] name = "zbus_polkit" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac86e28aa88ed90509b1cb10b985e6b05abadbb6c59ab54f824313f534d2ebfb" dependencies = [ "enumflags2", "serde", "serde_repr", "zbus 1.1.0", "zvariant", ] [[package]] name = "zvariant" version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a68c7b55f2074489b7e8e07d2d0a6ee6b4f233867a653c664d8020ba53692525" dependencies = [ "byteorder", "enumflags2", "libc", "serde", "static_assertions", "zvariant_derive", ] [[package]] name = "zvariant_derive" version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4ca5e22593eb4212382d60d26350065bf2a02c34b85bc850474a74b589a3de9" dependencies = [ "proc-macro-crate 1.1.3", "proc-macro2", "quote", "syn", ] zbus-1.9.3/Cargo.toml0000644000000035500000000000100100320ustar # 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 = "2018" name = "zbus" version = "1.9.3" authors = ["Zeeshan Ali "] description = "API for D-Bus communication" documentation = "http://docs.rs/zbus" readme = "../README.md" keywords = [ "D-Bus", "DBus", "IPC", ] categories = ["os::unix-apis"] license = "MIT" repository = "https://gitlab.freedesktop.org/dbus/zbus/" [package.metadata.docs.rs] all-features = true targets = ["x86_64-unknown-linux-gnu"] [dependencies.async-io] version = "1.3.1" [dependencies.byteorder] version = "1.3.1" [dependencies.derivative] version = "2.1" [dependencies.enumflags2] version = "0.6.4" features = ["serde"] [dependencies.fastrand] version = "1.2.4" [dependencies.futures] version = "0.3.8" [dependencies.nb-connect] version = "1.0.2" [dependencies.nix] version = "0.22.3" [dependencies.once_cell] version = "1.4.0" [dependencies.polling] version = "2.0.2" [dependencies.scoped-tls] version = "1.0.0" [dependencies.serde] version = "1.0" features = ["derive"] [dependencies.serde-xml-rs] version = "0.4.0" optional = true [dependencies.serde_repr] version = "0.1" [dependencies.zbus_macros] version = "=1.9.3" [dependencies.zvariant] version = "2" features = ["enumflags2"] default-features = false [dev-dependencies.doc-comment] version = "0.3.3" [dev-dependencies.ntest] version = "0.7.1" [dev-dependencies.zbus_polkit] version = "1" [features] xml = ["serde-xml-rs"] zbus-1.9.3/Cargo.toml.orig000064400000000000000000000021700072674642500135400ustar 00000000000000[package] name = "zbus" version = "1.9.3" authors = ["Zeeshan Ali "] edition = "2018" description = "API for D-Bus communication" repository = "https://gitlab.freedesktop.org/dbus/zbus/" documentation = "http://docs.rs/zbus" keywords = ["D-Bus", "DBus", "IPC"] license = "MIT" categories = ["os::unix-apis"] readme = "../README.md" [features] xml = ["serde-xml-rs"] [dependencies] byteorder = "1.3.1" nix = "0.22.3" serde = { version = "1.0", features = ["derive"] } serde_repr = "0.1" zvariant = { path = "../zvariant", version = "2", default-features = false, features = ["enumflags2"] } zbus_macros = { path = "../zbus_macros", version = "=1.9.3" } enumflags2 = { version = "0.6.4", features = ["serde"] } serde-xml-rs = { version = "0.4.0", optional = true } derivative = "2.1" scoped-tls = "1.0.0" fastrand = "1.2.4" once_cell = "1.4.0" nb-connect = "1.0.2" polling = "2.0.2" async-io = "1.3.1" futures = "0.3.8" [dev-dependencies] zbus_polkit = { path = "../zbus_polkit", version = "1" } doc-comment = "0.3.3" ntest = "0.7.1" [package.metadata.docs.rs] all-features = true targets = ["x86_64-unknown-linux-gnu"] zbus-1.9.3/LICENSE000064400000000000000000000017770072674642500116720ustar 00000000000000Permission 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-1.9.3/examples/screen-brightness.rs000064400000000000000000000020550072674642500164640ustar 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 incresses // the brightness by 5%. Pass '-' for decreasing it by 5%. fn main() { let connection = zbus::Connection::new_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::<(u32, &str)>().unwrap(); println!("New level: {}%", percent); } zbus-1.9.3/src/address.rs000064400000000000000000000103210072674642500134300ustar 00000000000000#![allow(deprecated)] use crate::{Error, Result}; use async_io::Async; use nb_connect::unix; use nix::unistd::Uid; use polling::{Event, Poller}; use std::{env, ffi::OsString, os::unix::net::UnixStream, str::FromStr}; /// A bus address #[derive(Debug, PartialEq)] pub(crate) enum Address { /// A path on the filesystem Unix(OsString), } #[derive(Debug)] pub(crate) enum Stream { Unix(UnixStream), } #[derive(Debug)] pub(crate) enum AsyncStream { Unix(Async), } impl Address { pub(crate) fn connect(&self, nonblocking: bool) -> Result { match self { Address::Unix(p) => { let stream = unix(p)?; let poller = Poller::new()?; poller.add(&stream, Event::writable(0))?; poller.wait(&mut Vec::new(), None)?; stream.set_nonblocking(nonblocking)?; Ok(Stream::Unix(stream)) } } } pub(crate) async fn connect_async(&self) -> Result { match self { Address::Unix(p) => Async::::connect(p) .await .map(AsyncStream::Unix) .map_err(Error::Io), } } /// 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 /// /run/user/UID/bus pub(crate) fn session() -> Result { match env::var("DBUS_SESSION_BUS_ADDRESS") { Ok(val) => Self::from_str(&val), _ => { let uid = Uid::current(); let path = format!("unix:path=/run/user/{}/bus", uid); Self::from_str(&path) } } } /// 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(crate) fn system() -> Result { match env::var("DBUS_SYSTEM_BUS_ADDRESS") { Ok(val) => Self::from_str(&val), _ => Self::from_str("unix:path=/var/run/dbus/system_bus_socket"), } } } impl FromStr for Address { type Err = Error; /// Parse a D-BUS address and return its path if we recognize it fn from_str(address: &str) -> Result { // Options are given separated by commas let first = address.split(',').next().unwrap(); let parts = first.split(':').collect::>(); if parts.len() != 2 { return Err(Error::Address("address has no colon".into())); } if parts[0] != "unix" { return Err(Error::Address(format!( "unsupported transport '{}'", parts[0] ))); } let pathparts = parts[1].split('=').collect::>(); if pathparts.len() != 2 { return Err(Error::Address("address is missing '='".into())); } let path = match pathparts[0] { "path" => OsString::from(pathparts[1]), "abstract" => { let mut s = OsString::from("\0"); s.push(pathparts[1]); s } _ => { return Err(Error::Address( "unix address is missing path or abstract".to_owned(), )) } }; Ok(Address::Unix(path)) } } #[cfg(test)] mod tests { use super::Address; use crate::Error; use std::str::FromStr; #[test] fn parse_dbus_addresses() { match Address::from_str("foo").unwrap_err() { Error::Address(e) => assert_eq!(e, "address has no colon"), _ => panic!(), } match Address::from_str("tcp:localhost").unwrap_err() { Error::Address(e) => assert_eq!(e, "unsupported transport 'tcp'"), _ => panic!(), } assert_eq!( Address::Unix("/tmp/dbus-foo".into()), Address::from_str("unix:path=/tmp/dbus-foo").unwrap() ); assert_eq!( Address::Unix("/tmp/dbus-foo".into()), Address::from_str("unix:path=/tmp/dbus-foo,guid=123").unwrap() ); } } zbus-1.9.3/src/azync/connection.rs000064400000000000000000000534400072674642500152770ustar 00000000000000use async_io::Async; use once_cell::sync::OnceCell; use std::{ io::{self, ErrorKind}, os::unix::{io::AsRawFd, net::UnixStream}, pin::Pin, task::{Context, Poll}, }; use futures::{ sink::{Sink, SinkExt}, stream::{Stream, TryStreamExt}, }; use crate::{ azync::{Authenticated, AuthenticatedType}, raw::{Connection as RawConnection, Socket}, Error, Guid, Message, MessageType, Result, DEFAULT_MAX_QUEUED, }; /// The asynchronous sibling of [`zbus::Connection`]. /// /// Most of the API is very similar to [`zbus::Connection`], except it's asynchronous. However, /// there are a few differences: /// /// ### Generic over Socket /// /// This type is generic over [`zbus::raw::Socket`] so that support for new socket types can be /// added with the same type easily later on. /// /// ### Cloning and Mutability /// /// Unlike [`zbus::Connection`], this type does not implement [`std::clone::Clone`]. The reason is /// that implementation will be very difficult (and still prone to deadlocks) if connection is /// owned by multiple tasks/threads. Create separate connection instances or use /// [`futures::stream::StreamExt::split`] to split reading and writing between two separate async /// tasks. /// /// Also notice that unlike [`zbus::Connection`], most methods take a `&mut self`, rather than a /// `&self`. If they'd take `&self`, `Connection` will need to manage mutability internally, which /// is not a very good match with the general async/await machinery and runtimes in Rust and could /// easily lead into some hard-to-debug deadlocks. You can use [`std::cell::Cell`], /// [`std::sync::Mutex`] or other related API combined with [`std::rc::Rc`] or [`std::sync::Arc`] /// for sharing a mutable `Connection` instance between different parts of your code (or threads). /// /// ### Sending Messages /// /// For sending messages you can either use [`Connection::send_message`] method or make use of the /// [`Sink`] implementation. For latter, you might find [`SinkExt`] API very useful. Keep in mind /// that [`Connection`] will not manage the serial numbers (cookies) on the messages for you when /// they are sent through the [`Sink`] implementation. You can manually assign unique serial numbers /// to them using the [`Connection::assign_serial_num`] method before sending them off, if needed. /// Having said that, [`Sink`] is mainly useful for sending out signals, as they do not expect a /// reply, and serial numbers are not very useful for signals either for the same reason. /// /// ### Receiving Messages /// /// Unlike [`zbus::Connection`], there is no direct async equivalent of /// [`zbus::Connection::receive_message`] method provided. This is because the `futures` crate /// already provides a nice rich API that makes use of the [`Stream`] implementation. /// /// ### Examples /// /// #### Get the session bus ID /// /// ``` ///# use zvariant::Type; ///# ///# futures::executor::block_on(async { /// use zbus::azync::Connection; /// /// let mut connection = Connection::new_session().await?; /// /// let reply = connection /// .call_method( /// Some("org.freedesktop.DBus"), /// "/org/freedesktop/DBus", /// Some("org.freedesktop.DBus"), /// "GetId", /// &(), /// ) /// .await?; /// /// let id: &str = reply.body()?; /// println!("Unique ID of the bus: {}", id); ///# Ok::<(), zbus::Error>(()) ///# }); /// ``` /// /// #### Monitoring all messages /// /// Let's eavesdrop on the session bus 😈 using the [Monitor] interface: /// /// ```rust,no_run ///# futures::executor::block_on(async { /// use futures::TryStreamExt; /// use zbus::azync::Connection; /// /// let mut connection = Connection::new_session().await?; /// /// connection /// .call_method( /// Some("org.freedesktop.DBus"), /// "/org/freedesktop/DBus", /// Some("org.freedesktop.DBus.Monitoring"), /// "BecomeMonitor", /// &(&[] as &[&str], 0u32), /// ) /// .await?; /// /// while let Some(msg) = connection.try_next().await? { /// println!("Got message: {}", msg); /// } /// ///# Ok::<(), zbus::Error>(()) ///# }); /// ``` /// /// 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(Debug)] pub struct Connection { server_guid: Guid, cap_unix_fd: bool, unique_name: OnceCell, raw_conn: RawConnection>, // Serial number for next outgoing message serial: u32, // Queue of incoming messages incoming_queue: Vec, // Max number of messages to queue max_queued: usize, } impl Connection where S: AsRawFd + std::fmt::Debug + Unpin + Socket, Async: Socket, { /// Create and open a D-Bus connection from the given `stream`. /// /// The connection may either be set up for a *bus* connection, or not (for peer-to-peer /// communications). /// /// Upon successful return, the connection is fully established and negotiated: D-Bus messages /// can be sent and received. pub async fn new_client(stream: S, bus_connection: bool) -> Result { // SASL Handshake let auth = Authenticated::client(Async::new(stream)?).await?; if bus_connection { Connection::new_authenticated_bus(auth).await } else { Ok(Connection::new_authenticated(auth)) } } /// Create a server `Connection` for the given `stream` and the server `guid`. /// /// The connection will wait for incoming client authentication handshake & negotiation messages, /// for peer-to-peer communications. /// /// Upon successful return, the connection is fully established and negotiated: D-Bus messages /// can be sent and received. pub async fn new_server(stream: S, guid: &Guid) -> Result { use nix::sys::socket::{getsockopt, sockopt::PeerCredentials}; // FIXME: Could and should this be async? let creds = getsockopt(stream.as_raw_fd(), PeerCredentials) .map_err(|e| Error::Handshake(format!("Failed to get peer credentials: {}", e)))?; let auth = Authenticated::server(Async::new(stream)?, guid.clone(), creds.uid()).await?; Ok(Self::new_authenticated(auth)) } /// Send `msg` to the peer. /// /// Unlike [`Sink`] implementation, this method sets a unique (to this connection) serial /// number on the message before sending it off, for you. /// /// On successfully sending off `msg`, the assigned serial number is returned. pub async fn send_message(&mut self, mut msg: Message) -> Result { let serial = self.assign_serial_num(&mut msg)?; self.send(msg).await?; Ok(serial) } /// Send a method call. /// /// Create a method-call message, send it over the connection, then wait for the reply. /// /// On succesful 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( &mut self, destination: Option<&str>, path: &str, iface: Option<&str>, method_name: &str, body: &B, ) -> Result where B: serde::ser::Serialize + zvariant::Type, { let m = Message::method( self.unique_name(), destination, path, iface, method_name, body, )?; let serial = self.send_message(m).await?; let mut tmp_queue = vec![]; while let Some(m) = self.try_next().await? { let h = m.header()?; if h.reply_serial()? != Some(serial) { if self.incoming_queue.len() + tmp_queue.len() < self.max_queued() { // We first push to a temporary queue as otherwise it'll create an infinite loop // since subsequent `receive_message` call will pick up the message from the main // queue. tmp_queue.push(m); } continue; } else { self.incoming_queue.append(&mut tmp_queue); } match h.message_type()? { MessageType::Error => return Err(m.into()), MessageType::MethodReturn => return Ok(m), _ => (), } } // If Stream gives us None, that means the socket was closed Err(Error::Io(io::Error::new( ErrorKind::BrokenPipe, "socket closed", ))) } /// Emit a signal. /// /// Create a signal message, and send it over the connection. pub async fn emit_signal( &mut self, destination: Option<&str>, path: &str, iface: &str, signal_name: &str, body: &B, ) -> Result<()> where B: serde::ser::Serialize + zvariant::Type, { let m = Message::signal( self.unique_name(), destination, path, iface, signal_name, body, )?; self.send_message(m).await.map(|_| ()) } /// Reply to a message. /// /// Given an existing message (likely a method call), send a reply back to the caller with the /// given `body`. /// /// Returns the message serial number. pub async fn reply(&mut self, call: &Message, body: &B) -> Result where B: serde::ser::Serialize + zvariant::Type, { let m = Message::method_reply(self.unique_name(), call, body)?; self.send_message(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`. /// /// Returns the message serial number. pub async fn reply_error( &mut self, call: &Message, error_name: &str, body: &B, ) -> Result where B: serde::ser::Serialize + zvariant::Type, { let m = Message::method_error(self.unique_name(), call, error_name, body)?; self.send_message(m).await } /// Sets the unique name for this connection. /// /// This method should only be used when initializing a client *bus* connection with /// [`Connection::new_authenticated`]. Setting the unique name to anything other than the return /// value of the bus hello is a protocol violation. /// /// Returns and error if the name has already been set. pub fn set_unique_name(self, name: String) -> std::result::Result { self.unique_name.set(name).map(|_| self) } /// Assigns a serial number to `msg` that is unique to this connection. /// /// This method can fail if `msg` is corrupt. pub fn assign_serial_num(&mut self, msg: &mut Message) -> Result { let serial = self.next_serial(); msg.modify_primary_header(|primary| { primary.set_serial_num(serial); Ok(()) })?; Ok(serial) } /// The unique name as assigned by the message bus or `None` if not a message bus connection. pub fn unique_name(&self) -> Option<&str> { self.unique_name.get().map(|s| s.as_str()) } /// Max number of messages to queue. pub fn max_queued(&self) -> usize { self.max_queued } /// Set the max number of messages to queue. /// /// Since typically you'd want to set this at instantiation time, this method takes ownership /// of `self` and returns an owned `Connection` instance so you can use the builder pattern to /// set the value. /// /// # Example /// /// ``` ///# use std::error::Error; ///# use zbus::azync::Connection; /// use futures::executor::block_on; /// /// let conn = block_on(Connection::new_session())?.set_max_queued(30); /// assert_eq!(conn.max_queued(), 30); /// /// // Do something usefull with `conn`.. ///# Ok::<_, Box>(()) /// ``` pub fn set_max_queued(mut self, max: usize) -> Self { self.max_queued = max; self } /// The server's GUID. pub fn server_guid(&self) -> &str { self.server_guid.as_str() } /// Create a `Connection` from an already authenticated unix socket. /// /// This method can be used in conjunction with [`crate::azync::Authenticated`] to handle /// the initial handshake of the D-Bus connection asynchronously. /// /// If the aim is to initialize a client *bus* connection, you need to send the client hello and assign /// the resulting unique name using [`set_unique_name`] before doing anything else. /// /// [`set_unique_name`]: struct.Connection.html#method.set_unique_name fn new_authenticated(auth: Authenticated>) -> Self { let auth = auth.into_inner(); Self { raw_conn: auth.conn, server_guid: auth.server_guid, cap_unix_fd: auth.cap_unix_fd, serial: 1, unique_name: OnceCell::new(), incoming_queue: vec![], max_queued: DEFAULT_MAX_QUEUED, } } async fn new_authenticated_bus(auth: Authenticated>) -> Result { let mut connection = Connection::new_authenticated(auth); // Now that the server has approved us, we must send the bus Hello, as per specs // TODO: Use fdo module once it's async. let name: String = connection .call_method( Some("org.freedesktop.DBus"), "/org/freedesktop/DBus", Some("org.freedesktop.DBus"), "Hello", &(), ) .await? .body()?; Ok(connection .set_unique_name(name) // programmer (probably our) error if this fails. .expect("Attempted to set unique_name twice")) } fn next_serial(&mut self) -> u32 { let serial = self.serial; self.serial = serial + 1; serial } // Used by Sink impl. fn flush(&mut self, cx: &mut Context<'_>) -> Poll> { loop { match self.raw_conn.try_flush() { Ok(()) => return Poll::Ready(Ok(())), Err(e) => { if e.kind() == ErrorKind::WouldBlock { let poll = self.raw_conn.socket().poll_writable(cx); match poll { Poll::Pending => return Poll::Pending, // Guess socket became ready already so let's try it again. Poll::Ready(Ok(_)) => continue, Poll::Ready(Err(e)) => return Poll::Ready(Err(e.into())), } } else { return Poll::Ready(Err(Error::Io(e))); } } } } } } impl Connection { /// Create a `Connection` to the session/user message bus. /// /// Although, session bus hardly ever runs on anything other than UNIX domain sockets, if you /// want your code to be able to handle those rare cases, use [`ConnectionType::new_session`] /// instead. pub async fn new_session() -> Result { Self::new_authenticated_bus(Authenticated::session().await?).await } /// Create a `Connection` to the system-wide message bus. /// /// Although, system bus hardly ever runs on anything other than UNIX domain sockets, if you /// want your code to be able to handle those rare cases, use [`ConnectionType::new_system`] /// instead. pub async fn new_system() -> Result { Self::new_authenticated_bus(Authenticated::system().await?).await } } impl Sink for Connection where S: AsRawFd + std::fmt::Debug + Unpin + Socket, Async: Socket, { type Error = Error; fn poll_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { // TODO: We should have a max queue length in raw::Socket for outgoing messages. Poll::Ready(Ok(())) } fn start_send(self: Pin<&mut Self>, msg: Message) -> Result<()> { let conn = self.get_mut(); if !msg.fds().is_empty() && !conn.cap_unix_fd { return Err(Error::Unsupported); } conn.raw_conn.enqueue_message(msg); Ok(()) } fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.get_mut().flush(cx) } fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let conn = self.get_mut(); match conn.flush(cx) { Poll::Ready(Ok(_)) => (), Poll::Ready(Err(e)) => return Poll::Ready(Err(e)), Poll::Pending => return Poll::Pending, } Poll::Ready((conn.raw_conn).close()) } } impl Stream for Connection where S: Socket, Async: Socket, { type Item = Result; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let conn = self.get_mut(); if let Some(msg) = conn.incoming_queue.pop() { return Poll::Ready(Some(Ok(msg))); } loop { match conn.raw_conn.try_receive_message() { Ok(m) => return Poll::Ready(Some(Ok(m))), Err(Error::Io(e)) if e.kind() == ErrorKind::WouldBlock => { let poll = conn.raw_conn.socket().poll_readable(cx); match poll { Poll::Pending => return Poll::Pending, // Guess socket became ready already so let's try it again. Poll::Ready(Ok(_)) => continue, Poll::Ready(Err(e)) => return Poll::Ready(Some(Err(e.into()))), } } Err(Error::Io(e)) if e.kind() == ErrorKind::BrokenPipe => return Poll::Ready(None), Err(e) => return Poll::Ready(Some(Err(e))), } } } } /// Type representing all concrete [`Connection`] types, provided by zbus. /// /// For maximum portability, use constructor method provided by this type instead of ones provided /// by [`Connection`]. pub enum ConnectionType { Unix(Connection), } impl ConnectionType { /// Create a `ConnectionType` for the given [D-Bus address]. /// /// [D-Bus address]: https://dbus.freedesktop.org/doc/dbus-specification.html#addresses pub async fn new_for_address(address: &str, bus_connection: bool) -> Result { match AuthenticatedType::for_address(address).await? { AuthenticatedType::Unix(auth) => { let conn = if bus_connection { Connection::new_authenticated_bus(auth).await? } else { Connection::new_authenticated(auth) }; Ok(ConnectionType::Unix(conn)) } } } /// Create a `ConnectionType` to the session/user message bus. pub async fn new_session() -> Result { match AuthenticatedType::session().await? { AuthenticatedType::Unix(auth) => { let conn = Connection::new_authenticated_bus(auth).await?; Ok(ConnectionType::Unix(conn)) } } } /// Create a `ConnectionType` to the system-wide message bus. pub async fn new_system() -> Result { match AuthenticatedType::system().await? { AuthenticatedType::Unix(auth) => { let conn = Connection::new_authenticated_bus(auth).await?; Ok(ConnectionType::Unix(conn)) } } } } #[cfg(test)] mod tests { use std::os::unix::net::UnixStream; use super::*; #[test] fn unix_p2p() { futures::executor::block_on(test_unix_p2p()).unwrap(); } async fn test_unix_p2p() -> Result<()> { let guid = Guid::generate(); let (p0, p1) = UnixStream::pair().unwrap(); let server = Connection::new_server(p0, &guid); let client = Connection::new_client(p1, false); let (mut client_conn, mut server_conn) = futures::try_join!(client, server)?; let server_future = async { let mut method: Option = None; while let Some(m) = server_conn.try_next().await? { if m.to_string() == "Method call Test" { method.replace(m); break; } } let method = method.unwrap(); // Send another message first to check the queueing function on client side. server_conn .emit_signal(None, "/", "org.zbus.p2p", "ASignalForYou", &()) .await?; server_conn.reply(&method, &("yay")).await }; let client_future = async { let reply = client_conn .call_method(None, "/", Some("org.zbus.p2p"), "Test", &()) .await?; assert_eq!(reply.to_string(), "Method return"); // Check we didn't miss the signal that was sent during the call. let m = client_conn.try_next().await?.unwrap(); assert_eq!(m.to_string(), "Signal ASignalForYou"); reply.body::().map_err(|e| e.into()) }; let (val, _) = futures::try_join!(client_future, server_future)?; assert_eq!(val, "yay"); Ok(()) } } zbus-1.9.3/src/azync/handshake.rs000064400000000000000000000154230072674642500150650ustar 00000000000000use async_io::Async; use std::{ fmt::Debug, future::Future, marker::PhantomData, ops::Deref, os::unix::net::UnixStream, pin::Pin, str::FromStr, task::{Context, Poll}, }; use crate::{ address::{self, Address}, guid::Guid, handshake::{self, Handshake as SyncHandshake, IoOperation}, raw::Socket, Error, Result, }; /// The asynchronous sibling of [`handshake::Handshake`]. /// /// The underlying socket is in nonblocking mode. Enabling blocking mode on it, will lead to /// undefined behaviour. pub(crate) struct Authenticated(handshake::Authenticated); impl Authenticated where S: Socket, { /// Unwraps the inner [`handshake::Authenticated`]. pub fn into_inner(self) -> handshake::Authenticated { self.0 } } impl Deref for Authenticated { type Target = handshake::Authenticated; fn deref(&self) -> &Self::Target { &self.0 } } impl Authenticated> where S: Debug + Unpin, Async: Socket, { /// Create a client-side `Authenticated` for the given `socket`. pub async fn client(socket: Async) -> Result { Handshake { handshake: Some(handshake::ClientHandshake::new(socket)), phantom: PhantomData, } .await } /// Create a server-side `Authenticated` for the given `socket`. pub async fn server(socket: Async, guid: Guid, client_uid: u32) -> Result { Handshake { handshake: Some(handshake::ServerHandshake::new(socket, guid, client_uid)), phantom: PhantomData, } .await } } impl Authenticated> { /// Create a `Authenticated` for the session/user message bus. /// /// Although, session bus hardly ever runs on anything other than UNIX domain sockets, if you /// want your code to be able to handle those rare cases, use [`AuthenticatedType::session`] /// instead. pub async fn session() -> Result { match Address::session()?.connect_async().await? { address::AsyncStream::Unix(a) => Self::client(a).await, } } /// Create a `Authenticated` for the system-wide message bus. /// /// Although, system bus hardly ever runs on anything other than UNIX domain sockets, if you /// want your code to be able to handle those rare cases, use [`AuthenticatedType::system`] /// instead. pub async fn system() -> Result { match Address::system()?.connect_async().await? { address::AsyncStream::Unix(a) => Self::client(a).await, } } } struct Handshake { handshake: Option, phantom: PhantomData, } impl Future for Handshake where H: SyncHandshake> + Unpin + Debug, S: Unpin, { type Output = Result>>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let self_mut = &mut self.get_mut(); let handshake = self_mut .handshake .as_mut() .expect("ClientHandshake::poll() called unexpectedly"); loop { match handshake.advance_handshake() { Ok(()) => { let handshake = self_mut .handshake .take() .expect("::poll() called unexpectedly"); let authenticated = handshake .try_finish() .expect("Failed to finish a successfull handshake"); return Poll::Ready(Ok(Authenticated(authenticated))); } Err(Error::Io(e)) => { if e.kind() == std::io::ErrorKind::WouldBlock { let poll = match handshake.next_io_operation() { IoOperation::Read => handshake.socket().poll_readable(cx), IoOperation::Write => handshake.socket().poll_writable(cx), IoOperation::None => panic!("Invalid handshake state"), }; match poll { Poll::Pending => return Poll::Pending, // Guess socket became ready already so let's try it again. Poll::Ready(Ok(_)) => continue, Poll::Ready(Err(e)) => return Poll::Ready(Err(e.into())), } } else { return Poll::Ready(Err(Error::Io(e))); } } Err(e) => return Poll::Ready(Err(e)), } } } } /// Type representing all concrete [`Authenticated`] types, provided by zbus. /// /// For maximum portability, use constructor methods provided by this type instead of ones provided /// by [`Authenticated`]. pub(crate) enum AuthenticatedType { Unix(Authenticated>), } impl AuthenticatedType { /// Create a `AuthenticatedType` for the given [D-Bus address]. /// /// [D-Bus address]: https://dbus.freedesktop.org/doc/dbus-specification.html#addresses pub async fn for_address(address: &str) -> Result { match Address::from_str(address)?.connect_async().await? { address::AsyncStream::Unix(a) => Authenticated::client(a).await.map(Self::Unix), } } /// Create a `AuthenticatedType` for the session/user message bus. pub async fn session() -> Result { match Address::session()?.connect_async().await? { address::AsyncStream::Unix(a) => Authenticated::client(a).await.map(Self::Unix), } } /// Create a `AuthenticatedType` for the system-wide message bus. pub async fn system() -> Result { match Address::system()?.connect_async().await? { address::AsyncStream::Unix(a) => Authenticated::client(a).await.map(Self::Unix), } } } #[cfg(test)] mod tests { use nix::unistd::Uid; use std::os::unix::net::UnixStream; use super::*; use crate::{Guid, Result}; #[test] fn async_handshake() { futures::executor::block_on(handshake()).unwrap(); } async fn handshake() -> Result<()> { // a pair of non-blocking connection UnixStream let (p0, p1) = UnixStream::pair()?; // initialize both handshakes let client = Authenticated::client(Async::new(p0)?); let server = Authenticated::server(Async::new(p1)?, Guid::generate(), Uid::current().into()); // proceed to the handshakes let (client_auth, server_auth) = futures::try_join!(client, server)?; assert_eq!(client_auth.server_guid, server_auth.server_guid); assert_eq!(client_auth.cap_unix_fd, server_auth.cap_unix_fd); Ok(()) } } zbus-1.9.3/src/azync/mod.rs000064400000000000000000000002450072674642500137120ustar 00000000000000//! The asynchronous API. //! //! This module host all our asynchronous API. mod handshake; pub(crate) use handshake::*; pub mod connection; pub use connection::*; zbus-1.9.3/src/connection.rs000064400000000000000000000540150072674642500141520ustar 00000000000000use std::{ os::unix::{ io::{AsRawFd, RawFd}, net::UnixStream, }, sync::{Arc, Mutex, RwLock}, }; use nix::poll::PollFlags; use once_cell::sync::OnceCell; use crate::{ fdo, handshake::{Authenticated, ClientHandshake, ServerHandshake}, raw::Connection as RawConnection, utils::wait_on, Error, Guid, Message, MessageType, Result, }; type MessageHandlerFn = Box<(dyn FnMut(Message) -> Option + Send)>; pub(crate) const DEFAULT_MAX_QUEUED: usize = 32; const LOCK_FAIL_MSG: &str = "Failed to lock a mutex or read-write lock"; #[derive(derivative::Derivative)] #[derivative(Debug)] struct ConnectionInner { server_guid: Guid, cap_unix_fd: bool, bus_conn: bool, unique_name: OnceCell, raw_conn: RwLock>, // Serial number for next outgoing message serial: Mutex, // Queue of incoming messages incoming_queue: Mutex>, // Max number of messages to queue max_queued: RwLock, #[derivative(Debug = "ignore")] default_msg_handler: Mutex>, } /// 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 /// [`dbus_proxy`] and [`dbus_interface`] macros instead of doing it directly on a `Connection`. /// /// For lower-level handling of the connection (such as nonblocking socket handling), see the /// documentation of the [`new_authenticated_unix`] constructor. /// /// Typically, a connection is made to the session bus with [`new_session`], or to the system bus /// with [`new_system`]. Then the connection is shared with the [`Proxy`] and [`ObjectServer`] /// instances. /// /// `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. /// /// NB: If you want to send and receive messages from multiple threads at the same time, it's /// usually better to create unique connections for each thread. Otherwise you can end up with /// deadlocks. For example, if one thread tries to send a message on a connection, while another is /// waiting to receive a message on the bus, the former will block until the latter receives a /// message. /// /// Since there are times when important messages arrive between a method call message is sent and /// its reply is received, `Connection` keeps an internal queue of incoming messages so that these /// messages are not lost and subsequent calls to [`receive_message`] will retreive messages from /// this queue first. The size of this queue is configurable through the [`set_max_queued`] method. /// The default size is 32. All messages that are received after the queue is full, are dropped. /// /// [method calls]: struct.Connection.html#method.call_method /// [signals]: struct.Connection.html#method.emit_signal /// [`new_system`]: struct.Connection.html#method.new_system /// [`new_session`]: struct.Connection.html#method.new_session /// [`new_authenticated_unix`]: struct.Connection.html#method.new_authenticated_unix /// [`Proxy`]: struct.Proxy.html /// [`ObjectServer`]: struct.ObjectServer.html /// [`dbus_proxy`]: attr.dbus_proxy.html /// [`dbus_interface`]: attr.dbus_interface.html /// [`Clone`]: https://doc.rust-lang.org/std/clone/trait.Clone.html /// [file an issue]: https://gitlab.freedesktop.org/dbus/zbus/-/issues/new /// [`receive_message`]: struct.Connection.html#method.receive_message /// [`set_max_queued`]: struct.Connection.html#method.set_max_queued #[derive(Debug, Clone)] pub struct Connection(Arc>); impl AsRawFd for Connection { fn as_raw_fd(&self) -> RawFd { self.0 .raw_conn .read() .expect(LOCK_FAIL_MSG) .socket() .as_raw_fd() } } impl Connection { /// Create and open a D-Bus connection from a `UnixStream`. /// /// The connection may either be set up for a *bus* connection, or not (for peer-to-peer /// communications). /// /// Upon successful return, the connection is fully established and negotiated: D-Bus messages /// can be sent and received. pub fn new_unix_client(stream: UnixStream, bus_connection: bool) -> Result { // SASL Handshake let auth = ClientHandshake::new(stream).blocking_finish()?; if bus_connection { Connection::new_authenticated_unix_bus(auth) } else { Ok(Connection::new_authenticated_unix(auth)) } } /// Create a `Connection` to the session/user message bus. pub fn new_session() -> Result { ClientHandshake::new_session()? .blocking_finish() .and_then(Self::new_authenticated_unix_bus) } /// Create a `Connection` to the system-wide message bus. pub fn new_system() -> Result { ClientHandshake::new_system()? .blocking_finish() .and_then(Self::new_authenticated_unix_bus) } /// Create a `Connection` for the given [D-Bus address]. /// /// [D-Bus address]: https://dbus.freedesktop.org/doc/dbus-specification.html#addresses pub fn new_for_address(address: &str, bus_connection: bool) -> Result { let auth = ClientHandshake::new_for_address(address)?.blocking_finish()?; if bus_connection { Connection::new_authenticated_unix_bus(auth) } else { Ok(Connection::new_authenticated_unix(auth)) } } /// Create a server `Connection` for the given `UnixStream` and the server `guid`. /// /// The connection will wait for incoming client authentication handshake & negotiation messages, /// for peer-to-peer communications. /// /// Upon successful return, the connection is fully established and negotiated: D-Bus messages /// can be sent and received. pub fn new_unix_server(stream: UnixStream, guid: &Guid) -> Result { use nix::sys::socket::{getsockopt, sockopt::PeerCredentials}; let creds = getsockopt(stream.as_raw_fd(), PeerCredentials) .map_err(|e| Error::Handshake(format!("Failed to get peer credentials: {}", e)))?; let handshake = ServerHandshake::new(stream, guid.clone(), creds.uid()); handshake .blocking_finish() .map(Connection::new_authenticated_unix) } /// Max number of messages to queue. pub fn max_queued(&self) -> usize { *self.0.max_queued.read().expect(LOCK_FAIL_MSG) } /// Set the max number of messages to queue. /// /// Since typically you'd want to set this at instantiation time, this method takes ownership /// of `self` and returns an owned `Connection` instance so you can use the builder pattern to /// set the value. /// /// # Example /// /// ``` ///# use std::error::Error; ///# /// let conn = zbus::Connection::new_session()?.set_max_queued(30); /// assert_eq!(conn.max_queued(), 30); /// /// // Do something usefull with `conn`.. ///# Ok::<_, Box>(()) /// ``` pub fn set_max_queued(self, max: usize) -> Self { *self.0.max_queued.write().expect(LOCK_FAIL_MSG) = max; self } /// The server's GUID. pub fn server_guid(&self) -> &str { self.0.server_guid.as_str() } /// The unique name as assigned by the message bus or `None` if not a message bus connection. pub fn unique_name(&self) -> Option<&str> { self.0.unique_name.get().map(|s| s.as_str()) } /// Fetch the next message from the connection. /// /// Read from the connection until a message is received or an error is reached. Return the /// message on success. If the connection is in non-blocking mode, this will return a /// `WouldBlock` error instead of blocking. If there are pending messages in the queue, the /// first one from the queue is returned instead of attempting to read the connection. /// /// If a default message handler has been registered on this connection through /// [`set_default_message_handler`], it will first get to decide the fate of the received /// message. /// /// [`set_default_message_handler`]: struct.Connection.html#method.set_default_message_handler pub fn receive_message(&self) -> Result { loop { let mut queue = self.0.incoming_queue.lock().expect(LOCK_FAIL_MSG); if let Some(msg) = queue.pop() { return Ok(msg); } if let Some(msg) = self.receive_message_raw()? { return Ok(msg); } } } /// Receive a specific message. /// /// This is the same as [`Self::receive_message`], except that it takes a predicate function that /// decides if the message received should be returned by this method or not. Message received /// during this call that are not returned by it, are pushed to the queue to be picked by the /// susubsequent call to `receive_message`] or this method. pub fn receive_specific

(&self, predicate: P) -> Result where P: Fn(&Message) -> Result, { loop { let mut queue = self.0.incoming_queue.lock().expect(LOCK_FAIL_MSG); for (i, msg) in queue.iter().enumerate() { if predicate(msg)? { return Ok(queue.remove(i)); } } let msg = match self.receive_message_raw()? { Some(msg) => msg, None => continue, }; if predicate(&msg)? { return Ok(msg); } else if queue.len() < *self.0.max_queued.read().expect(LOCK_FAIL_MSG) { queue.push(msg); } } } /// Send `msg` to the peer. /// /// The connection sets a unique serial number on the message before sending it off. /// /// On successfully sending off `msg`, the assigned serial number is returned. /// /// **Note:** if this connection is in non-blocking mode, the message may not actually /// have been sent when this method returns, and you need to call the [`flush`] method /// so that pending messages are written to the socket. /// /// [`flush`]: struct.Connection.html#method.flush pub fn send_message(&self, mut msg: Message) -> Result { if !msg.fds().is_empty() && !self.0.cap_unix_fd { return Err(Error::Unsupported); } let serial = self.next_serial(); msg.modify_primary_header(|primary| { primary.set_serial_num(serial); Ok(()) })?; let mut conn = self.0.raw_conn.write().expect(LOCK_FAIL_MSG); conn.enqueue_message(msg); // Swallow a potential WouldBLock error, but propagate the others if let Err(e) = conn.try_flush() { if e.kind() != std::io::ErrorKind::WouldBlock { return Err(e.into()); } } Ok(serial) } /// Flush pending outgoing messages to the server /// /// This method is only useful if the connection is in non-blocking mode. It will /// write as many pending outgoing messages as possible to the socket. /// /// It will return `Ok(())` if all messages could be sent, and error otherwise. A /// `WouldBlock` error means that the internal buffer of the connection transport is /// full, and you need to wait for write-readiness before calling this method again. /// /// If the connection is in blocking mode, this will return `Ok(())` and do nothing. pub fn flush(&self) -> Result<()> { self.0.raw_conn.write().expect(LOCK_FAIL_MSG).try_flush()?; Ok(()) } /// Send a method call. /// /// Create a method-call message, send it over the connection, then wait for the reply. Incoming /// messages are received through [`receive_message`] (and by the default message handler) /// until the matching method reply (error or return) is received. /// /// On succesful reply, an `Ok(Message)` is returned. On error, an `Err` is returned. D-Bus /// error replies are returned as [`MethodError`]. /// /// *Note:* This method will block until the response is received even if the connection is /// in non-blocking mode. If you don't want to block like this, use [`Connection::send_message`]. /// /// [`receive_message`]: struct.Connection.html#method.receive_message /// [`MethodError`]: enum.Error.html#variant.MethodError /// [`sent_message`]: struct.Connection.html#method.send_message pub fn call_method( &self, destination: Option<&str>, path: &str, iface: Option<&str>, method_name: &str, body: &B, ) -> Result where B: serde::ser::Serialize + zvariant::Type, { let m = Message::method( self.unique_name(), destination, path, iface, method_name, body, )?; let serial = self.send_message(m)?; // loop & sleep until the message is completely sent loop { match self.flush() { Ok(()) => break, Err(Error::Io(e)) if e.kind() == std::io::ErrorKind::WouldBlock => { wait_on(self.as_raw_fd(), PollFlags::POLLOUT)?; } Err(e) => return Err(e), } } loop { match self.receive_specific(|m| { let h = m.header()?; Ok(h.reply_serial()? == Some(serial)) }) { Ok(m) => match m.header()?.message_type()? { MessageType::Error => return Err(m.into()), MessageType::MethodReturn => return Ok(m), _ => (), }, Err(Error::Io(e)) if e.kind() == std::io::ErrorKind::WouldBlock => { wait_on(self.as_raw_fd(), PollFlags::POLLIN)?; } Err(e) => return Err(e), }; } } /// Emit a signal. /// /// Create a signal message, and send it over the connection. pub fn emit_signal( &self, destination: Option<&str>, path: &str, iface: &str, signal_name: &str, body: &B, ) -> Result<()> where B: serde::ser::Serialize + zvariant::Type, { let m = Message::signal( self.unique_name(), destination, path, iface, signal_name, body, )?; self.send_message(m)?; Ok(()) } /// Reply to a message. /// /// Given an existing message (likely a method call), send a reply back to the caller with the /// given `body`. /// /// Returns the message serial number. pub fn reply(&self, call: &Message, body: &B) -> Result where B: serde::ser::Serialize + zvariant::Type, { let m = Message::method_reply(self.unique_name(), call, body)?; self.send_message(m) } /// 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(&self, call: &Message, error_name: &str, body: &B) -> Result where B: serde::ser::Serialize + zvariant::Type, { let m = Message::method_error(self.unique_name(), call, error_name, body)?; self.send_message(m) } /// Set a default handler for incoming messages on this connection. /// /// This is the handler that will be called on all messages received during [`receive_message`] /// call. If the handler callback returns a message (which could be a different message than it /// was given), `receive_message` will return it to its caller. /// /// [`receive_message`]: struct.Connection.html#method.receive_message #[deprecated( since = "1.4.0", note = "You shouldn't need this anymore since Connection queues messages" )] pub fn set_default_message_handler(&mut self, handler: MessageHandlerFn) { self.0 .default_msg_handler .lock() .expect(LOCK_FAIL_MSG) .replace(handler); } /// Reset the default message handler. /// /// Remove the previously set message handler from `set_default_message_handler`. #[deprecated( since = "1.4.0", note = "You shouldn't need this anymore since Connection queues messages" )] pub fn reset_default_message_handler(&mut self) { self.0 .default_msg_handler .lock() .expect(LOCK_FAIL_MSG) .take(); } /// Create a `Connection` from an already authenticated unix socket /// /// This method can be used in conjunction with [`ClientHandshake`] or [`ServerHandshake`] to handle /// the initial handshake of the D-Bus connection asynchronously. The [`Authenticated`] argument required /// by this method is the result provided by these handshake utilities. /// /// If the aim is to initialize a client *bus* connection, you need to send the [client hello] and assign /// the resulting unique name using [`set_unique_name`] before doing anything else. /// /// [`ClientHandshake`]: ./handshake/struct.ClientHandshake.html /// [`ServerHandshake`]: ./handshake/struct.ServerHandshake.html /// [`Authenticated`]: ./handshake/struct.Authenticated.html /// [client hello]: ./fdo/struct.DBusProxy.html#method.hello /// [`set_unique_name`]: struct.Connection.html#method.set_unique_name pub fn new_authenticated_unix(auth: Authenticated) -> Self { Self::new_authenticated_unix_(auth, false) } /// Sets the unique name for this connection /// /// This method should only be used when initializing a client *bus* connection with /// [`new_authenticated_unix`]. Setting the unique name to anything other than the return value of the bus /// hello is a protocol violation. /// /// Returns and error if the name has already been set. /// /// [`new_authenticated_unix`]: struct.Connection.html#method.new_authenticated_unix pub fn set_unique_name(&self, name: String) -> std::result::Result<(), String> { self.0.unique_name.set(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.0.bus_conn } fn new_authenticated_unix_bus(auth: Authenticated) -> Result { let connection = Connection::new_authenticated_unix_(auth, true); // Now that the server has approved us, we must send the bus Hello, as per specs let name = fdo::DBusProxy::new(&connection)? .hello() .map_err(|e| Error::Handshake(format!("Hello failed: {}", e)))?; connection .0 .unique_name .set(name) // programmer (probably our) error if this fails. .expect("Attempted to set unique_name twice"); Ok(connection) } fn new_authenticated_unix_(auth: Authenticated, bus_conn: bool) -> Self { Self(Arc::new(ConnectionInner { raw_conn: RwLock::new(auth.conn), server_guid: auth.server_guid, cap_unix_fd: auth.cap_unix_fd, bus_conn, serial: Mutex::new(1), unique_name: OnceCell::new(), incoming_queue: Mutex::new(vec![]), max_queued: RwLock::new(DEFAULT_MAX_QUEUED), default_msg_handler: Mutex::new(None), })) } fn next_serial(&self) -> u32 { let mut serial = self.0.serial.lock().expect(LOCK_FAIL_MSG); let current = *serial; *serial = current + 1; current } // Get the message directly from the socket (ignoring the queue). fn receive_message_raw(&self) -> Result> { let incoming = self .0 .raw_conn .write() .expect(LOCK_FAIL_MSG) .try_receive_message()?; if let Some(ref mut handler) = &mut *self.0.default_msg_handler.lock().expect(LOCK_FAIL_MSG) { // Let's see if the default handler wants the message first Ok(handler(incoming)) } else { Ok(Some(incoming)) } } } #[cfg(test)] mod tests { use std::{os::unix::net::UnixStream, thread}; use crate::{Connection, Guid}; #[test] fn unix_p2p() { let guid = Guid::generate(); let (p0, p1) = UnixStream::pair().unwrap(); let server_thread = thread::spawn(move || { let c = Connection::new_unix_server(p0, &guid).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().unwrap(); val }); let c = Connection::new_unix_client(p1, false).unwrap(); let m = c.receive_message().unwrap(); assert_eq!(m.to_string(), "Method call Test"); c.reply(&m, &("yay")).unwrap(); let val = server_thread.join().expect("failed to join server thread"); assert_eq!(val, "yay"); } #[test] fn serial_monotonically_increases() { let c = Connection::new_session().unwrap(); let serial = c.next_serial() + 1; for next in serial..serial + 10 { assert_eq!(next, c.next_serial()); } } } zbus-1.9.3/src/error.rs000064400000000000000000000121030072674642500131340ustar 00000000000000use std::{error, fmt, io}; use zvariant::Error as VariantError; use crate::{fdo, Message, MessageError, MessageType}; /// The error type for `zbus`. /// /// The various errors that can be reported by this crate. #[derive(Debug)] pub enum Error { /// Interface not found InterfaceNotFound, /// Invalid D-Bus address. Address(String), /// An I/O error. Io(io::Error), /// Message parsing error. Message(MessageError), /// A [zvariant](../zvariant/index.html) error. Variant(VariantError), /// 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(String, Option, Message), /// Invalid D-Bus GUID. InvalidGUID, /// Unsupported function, or support currently lacking. Unsupported, /// Thread-local connection is not set. #[deprecated(since = "1.1.2", note = "No longer returned by any of our API")] NoTLSConnection, /// Thread-local node is not set. #[deprecated(since = "1.1.2", note = "No longer returned by any of our API")] NoTLSNode, /// A [`fdo::Error`] tranformed into [`Error`]. FDO(Box), } impl PartialEq for Error { fn eq(&self, other: &Self) -> bool { match self { Error::Io(_) => false, _ => self == other, } } } impl error::Error for Error { fn source(&self) -> Option<&(dyn error::Error + 'static)> { match self { Error::InterfaceNotFound => None, Error::Address(_) => None, Error::Io(e) => Some(e), Error::Handshake(_) => None, Error::Message(e) => Some(e), Error::Variant(e) => Some(e), Error::InvalidReply => None, Error::MethodError(_, _, _) => None, Error::InvalidGUID => None, Error::Unsupported => None, #[allow(deprecated)] Error::NoTLSConnection => None, #[allow(deprecated)] Error::NoTLSNode => None, Error::FDO(e) => Some(e), } } } 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::Io(e) => write!(f, "I/O error: {}", e), Error::Handshake(e) => write!(f, "D-Bus handshake failed: {}", e), Error::Message(e) => write!(f, "Message creation error: {}", e), Error::Variant(e) => write!(f, "{}", e), Error::InvalidReply => write!(f, "Invalid D-Bus method reply"), 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"), #[allow(deprecated)] Error::NoTLSConnection => write!(f, "No TLS connection"), #[allow(deprecated)] Error::NoTLSNode => write!(f, "No TLS node"), Error::FDO(e) => write!(f, "{}", e), } } } impl From for Error { fn from(val: io::Error) -> Self { Error::Io(val) } } 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: MessageError) -> Self { Error::Message(val) } } impl From for Error { fn from(val: VariantError) -> Self { Error::Variant(val) } } impl From for Error { fn from(val: fdo::Error) -> Self { match val { fdo::Error::ZBus(e) => e, e => Error::FDO(Box::new(e)), } } } // 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 = match message.header() { Ok(header) => header, Err(e) => { return Error::Message(e); } }; if header.primary().msg_type() != MessageType::Error { return Error::InvalidReply; } if let Ok(Some(name)) = header.error_name() { let name = String::from(name); match message.body_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-1.9.3/src/fdo.rs000064400000000000000000000574640072674642500125760ustar 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; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use std::collections::HashMap; use zvariant::{derive::Type, ObjectPath, OwnedObjectPath, OwnedValue, Value}; use crate::{dbus_proxy, DBusError}; /// Proxy for the `org.freedesktop.DBus.Introspectable` interface. #[dbus_proxy(interface = "org.freedesktop.DBus.Introspectable", default_path = "/")] 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; } /// Proxy for the `org.freedesktop.DBus.Properties` interface. #[dbus_proxy(interface = "org.freedesktop.DBus.Properties")] trait Properties { /// Get a property value. fn get(&self, interface_name: &str, property_name: &str) -> Result; /// Set a property value. fn set(&self, interface_name: &str, property_name: &str, value: &Value<'_>) -> Result<()>; /// Get all properties. fn get_all(&self, interface_name: &str) -> Result>; #[dbus_proxy(signal)] fn properties_changed( &self, interface_name: &str, changed_properties: HashMap<&str, Value<'_>>, invalidated_properties: Vec<&str>, ) -> Result<()>; } type ManagedObjects = HashMap>>; /// Proxy for the `org.freedesktop.DBus.ObjectManager` interface. /// /// **NB:** Changes to properties on existing interfaces are not reported using this interface. /// Please use [`PropertiesProxy::connect_properties_changed`] to monitor changes to properties on /// objects. #[dbus_proxy(interface = "org.freedesktop.DBus.ObjectManager")] 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. #[dbus_proxy(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. #[dbus_proxy(signal)] fn interfaces_removed(&self, object_path: ObjectPath<'_>, interfaces: Vec<&str>) -> Result<()>; } /// Proxy for the `org.freedesktop.DBus.Peer` interface. #[dbus_proxy(interface = "org.freedesktop.DBus.Peer")] 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; } /// Proxy for the `org.freedesktop.DBus.Monitoring` interface. #[dbus_proxy(interface = "org.freedesktop.DBus.Monitoring")] trait Monitoring { /// Converts the connection into a monitor connection which can be used as a /// debugging/monitoring tool. fn become_monitor(&self, n1: &[&str], n2: u32) -> Result<()>; } /// Proxy for the `org.freedesktop.DBus.Stats` interface. #[dbus_proxy(interface = "org.freedesktop.DBus.Debug.Stats")] trait Stats { /// GetStats (undocumented) fn get_stats(&self) -> Result>>; /// GetConnectionStats (undocumented) fn get_connection_stats(&self, n1: &str) -> Result>>; /// GetAllMatchRules (undocumented) fn get_all_match_rules(&self) -> Result>>>; } /// The flags used by the bus [`request_name`] method. /// /// [`request_name`]: struct.DBusProxy.html#method.request_name #[repr(u32)] #[derive(Type, BitFlags, Debug, PartialEq, 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, } /// 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)] 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, } /// The return code of the [`releaes_name`] method. /// /// [`release_name`]: struct.DBusProxy.html#method.release_name #[repr(u32)] #[derive(Deserialize_repr, Serialize_repr, Type, Debug, PartialEq)] 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, } /// Proxy for the `org.freedesktop.DBus` interface. #[dbus_proxy(interface = "org.freedesktop.DBus")] trait DBus { /// Adds a match rule to match messages going through the message bus fn add_match(&self, rule: &str) -> Result<()>; /// Returns auditing data used by Solaris ADT, in an unspecified binary format. fn get_adt_audit_session_data(&self, bus_name: &str) -> Result>; /// Returns as many credentials as possible for the process connected to the server. fn get_connection_credentials(&self, bus_name: &str) -> Result>; /// Returns the security context used by SELinux, in an unspecified format. #[dbus_proxy(name = "GetConnectionSELinuxSecurityContext")] fn get_connection_selinux_security_context(&self, bus_name: &str) -> Result>; /// Returns the Unix process ID of the process connected to the server. #[dbus_proxy(name = "GetConnectionUnixProcessID")] fn get_connection_unix_process_id(&self, bus_name: &str) -> Result; /// Returns the Unix user ID of the process connected to the server. fn get_connection_unix_user(&self, bus_name: &str) -> 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: &str) -> 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: &str) -> Result>; /// Checks if the specified name exists (currently has an owner). fn name_has_owner(&self, name: &str) -> Result; /// Ask the message bus to release the method caller's claim to the given name. fn release_name(&self, name: &str) -> Result; /// Reload server configuration. fn reload_config(&self) -> Result<()>; /// Removes the first rule that matches. fn remove_match(&self, rule: &str) -> Result<()>; /// Ask the message bus to assign the given name to the method caller. fn request_name( &self, name: &str, 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: &str, 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. #[dbus_proxy(signal)] fn name_owner_changed(&self, name: &str, old_owner: &str, new_owner: &str); /// This signal is sent to a specific application when it loses ownership of a name. #[dbus_proxy(signal)] fn name_lost(&self, name: &str); /// This signal is sent to a specific application when it gains ownership of a name. #[dbus_proxy(signal)] fn name_acquired(&self, name: &str); /// 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. #[dbus_proxy(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 ot /// 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. #[dbus_proxy(property)] fn interfaces(&self) -> Result>; } /// Errors from https://gitlab.freedesktop.org/dbus/dbus/-/blob/master/dbus/dbus-protocol.h #[derive(Debug, DBusError, PartialEq)] #[dbus_error(prefix = "org.freedesktop.DBus.Error")] pub enum Error { /// Unknown or fall-through 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. #[dbus_error(name = "Spawn.ExecFailed")] SpawnExecFailed(String), /// While starting a new process, the fork() call failed. #[dbus_error(name = "Spawn.ForkFailed")] SpawnForkFailed(String), /// While starting a new process, the child exited with a status code. #[dbus_error(name = "Spawn.ChildExited")] SpawnChildExited(String), /// While starting a new process, the child exited on a signal. #[dbus_error(name = "Spawn.ChildSignaled")] SpawnChildSignaled(String), /// While starting a new process, something went wrong. #[dbus_error(name = "Spawn.Failed")] SpawnFailed(String), /// We failed to setup the environment correctly. #[dbus_error(name = "Spawn.FailedToSetup")] SpawnFailedToSetup(String), /// We failed to setup the config parser correctly. #[dbus_error(name = "Spawn.ConfigInvalid")] SpawnConfigInvalid(String), /// Bus name was not valid. #[dbus_error(name = "Spawn.ServiceNotValid")] SpawnServiceNotValid(String), /// Service file not found in system-services directory. #[dbus_error(name = "Spawn.ServiceNotFound")] SpawnServiceNotFound(String), /// Permissions are incorrect on the setuid helper. #[dbus_error(name = "Spawn.PermissionsInvalid")] SpawnPermissionsInvalid(String), /// Service file invalid (Name, User or Exec missing). #[dbus_error(name = "Spawn.FileInvalid")] SpawnFileInvalid(String), /// There was not enough memory to complete the operation. #[dbus_error(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), } /// Alias for a `Result` with the error type [`zbus::fdo::Error`]. /// /// [`zbus::fdo::Error`]: enum.Error.html pub type Result = std::result::Result; impl From for Error { fn from(val: zbus::MessageError) -> Self { match val { zbus::MessageError::InsufficientData => { Self::InconsistentMessage("insufficient data".to_string()) } zbus::MessageError::ExcessData => Self::InconsistentMessage("excess data".to_string()), zbus::MessageError::IncorrectEndian => { Self::InconsistentMessage("incorrect endian".to_string()) } zbus::MessageError::Io(e) => Self::IOError(e.to_string()), zbus::MessageError::UnmatchedBodySignature => { Self::InvalidArgs("incorrect body signature".to_string()) } zbus::MessageError::NoBodySignature => { Self::InvalidSignature("missing body signature".to_string()) } zbus::MessageError::InvalidField => { Self::InconsistentMessage("invalid message field".to_string()) } zbus::MessageError::Variant(e) => Self::InconsistentMessage(e.to_string()), zbus::MessageError::MissingField => { Self::InconsistentMessage("Required message field missing".to_string()) } } } } #[cfg(test)] mod tests { use crate::{fdo, Error, Message}; use std::{ convert::TryInto, sync::{Arc, Mutex}, }; #[test] fn error_from_zerror() { let m = Message::method(Some(":1.2"), None, "/", None, "foo", &()).unwrap(); let m = Message::method_error( None, &m, "org.freedesktop.DBus.Error.TimedOut", &("so long"), ) .unwrap(); let e: Error = m.into(); let e: fdo::Error = e.try_into().unwrap(); assert_eq!(e, fdo::Error::TimedOut("so long".to_string())); } #[test] fn signal() { // Register a well-known name with the session bus and ensure we get the appropriate // signals called for that. let conn = crate::Connection::new_session().unwrap(); let owner_change_signaled = Arc::new(Mutex::new(false)); let name_acquired_signaled = Arc::new(Mutex::new(false)); let proxy = fdo::DBusProxy::new(&conn).unwrap(); let well_known = "org.freedesktop.zbus.FdoSignalTest"; let unique_name = conn.unique_name().unwrap().to_string(); { let well_known = well_known.clone(); let signaled = owner_change_signaled.clone(); proxy .connect_name_owner_changed(move |name, _, new_owner| { if name != well_known { // Meant for the other testcase then return Ok(()); } assert_eq!(new_owner, unique_name); *signaled.lock().unwrap() = true; Ok(()) }) .unwrap(); } { let signaled = name_acquired_signaled.clone(); // `NameAcquired` is emitted twice, first when the unique name is assigned on // connection and secondly after we ask for a specific name. proxy .connect_name_acquired(move |name| { if name == well_known { *signaled.lock().unwrap() = true; } Ok(()) }) .unwrap(); } proxy .request_name(&well_known, fdo::RequestNameFlags::ReplaceExisting.into()) .unwrap(); loop { proxy.next_signal().unwrap(); if *owner_change_signaled.lock().unwrap() && *name_acquired_signaled.lock().unwrap() { break; } } let result = proxy.release_name(&well_known).unwrap(); assert_eq!(result, fdo::ReleaseNameReply::Released); let result = proxy.release_name(&well_known).unwrap(); assert_eq!(result, fdo::ReleaseNameReply::NonExistent); } } zbus-1.9.3/src/guid.rs000064400000000000000000000042620072674642500127420ustar 00000000000000use std::{ convert::TryFrom, fmt, iter::repeat_with, time::{SystemTime, UNIX_EPOCH}, }; /// 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, Hash)] pub struct Guid(String); impl Guid { /// Generate a D-Bus GUID that can be used with e.g. [`Connection::new_unix_server`]. /// /// [`Connection::new_unix_server`]: struct.Connection.html#method.new_unix_server pub fn generate() -> Self { let r: Vec = repeat_with(|| fastrand::u32(..)).take(3).collect(); let r3 = match SystemTime::now().duration_since(UNIX_EPOCH) { Ok(n) => n.as_secs() as u32, Err(_) => fastrand::u32(..), }; let s = format!("{:08x}{:08x}{:08x}{:08x}", r[0], r[1], r[2], r3); Self(s) } /// Returns a string slice for the GUID. pub fn as_str(&self) -> &str { self.0.as_str() } } impl fmt::Display for Guid { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.as_str()) } } impl TryFrom<&str> for Guid { 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) -> std::result::Result { if value.as_bytes().len() != 32 || !value.chars().all(|c| char::is_ascii_hexdigit(&c)) { Err(crate::Error::InvalidGUID) } else { Ok(Guid(value.to_string())) } } } #[cfg(test)] mod tests { use crate::Guid; #[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-1.9.3/src/handshake.rs000064400000000000000000000600500072674642500137350ustar 00000000000000use std::{ convert::TryInto, io::BufRead, os::unix::{io::AsRawFd, net::UnixStream}, str::FromStr, }; use nix::{poll::PollFlags, unistd::Uid}; use crate::{ address::{self, Address}, guid::Guid, raw::{Connection, Socket}, utils::wait_on, Error, Result, }; /* * Client-side handshake logic */ #[derive(Debug)] enum ClientHandshakeStep { Init, SendingOauth, WaitOauth, SendingNegociateFd, WaitNegociateFd, SendingBegin, Done, } pub enum IoOperation { None, Read, Write, } /// 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. To use it, you should call the [`advance_handshake`] method whenever the /// underlying socket becomes ready (tracking the readiness itself is not managed by this abstraction) until /// it returns `Ok(())`, at which point you can invoke the [`try_finish`] method to get an [`Authenticated`], /// which can be given to [`Connection::new_authenticated`]. /// /// If handling the handshake asynchronously is not necessary, the [`blocking_finish`] method is provided /// which blocks until the handshake is completed or an error occurs. /// /// [`advance_handshake`]: struct.ClientHandshake.html#method.advance_handshake /// [`try_finish`]: struct.ClientHandshake.html#method.try_finish /// [`Authenticated`]: struct.AUthenticated.html /// [`Connection::new_authenticated`]: ../struct.Connection.html#method.new_authenticated /// [`blocking_finish`]: struct.ClientHandshake.html#method.blocking_finish #[derive(Debug)] pub struct ClientHandshake { socket: S, buffer: Vec, step: ClientHandshakeStep, server_guid: Option, cap_unix_fd: bool, } /// The result of a finalized handshake /// /// The result of a finalized [`ClientHandshake`] or [`ServerHandshake`]. It can be passed to /// [`Connection::new_authenticated`] to initialize a connection. /// /// [`ClientHandshake`]: struct.ClientHandshake.html /// [`ServerHandshake`]: struct.ServerHandshake.html /// [`Connection::new_authenticated`]: ../struct.Connection.html#method.new_authenticated #[derive(Debug)] pub struct Authenticated { pub(crate) conn: Connection, /// The server Guid pub(crate) server_guid: Guid, /// Whether file descriptor passing has been accepted by both sides pub(crate) cap_unix_fd: bool, } pub trait Handshake { /// The next I/O operation needed for advancing the handshake. /// /// If [`Handshake::advance_handshake`] returns a `std::io::ErrorKind::WouldBlock` error, you /// can use this to figure out which operation to poll for, before calling `advance_handshake` /// again. fn next_io_operation(&self) -> IoOperation; /// Attempt to advance the handshake /// /// In non-blocking mode, you need to invoke this method repeatedly /// until it returns `Ok(())`. Once it does, the handshake is finished /// and you can invoke the [`Handshake::try_finish`] method. /// /// Note that only the intial handshake is done. If you need to send a /// Bus Hello, this remains to be done. fn advance_handshake(&mut self) -> Result<()>; /// Attempt to finalize this handshake into an initialized client. /// /// This method should only be called once `advance_handshake()` has /// returned `Ok(())`. Otherwise it'll error and return you the object. fn try_finish(self) -> std::result::Result, Self> where Self: Sized; /// Access the socket backing this handshake /// /// Would typically be used to register it for readiness. fn socket(&self) -> &S; } impl ClientHandshake { /// Start a handsake on this client socket pub fn new(socket: S) -> ClientHandshake { ClientHandshake { socket, buffer: Vec::new(), step: ClientHandshakeStep::Init, server_guid: None, cap_unix_fd: false, } } fn flush_buffer(&mut self) -> Result<()> { while !self.buffer.is_empty() { let written = self.socket.sendmsg(&self.buffer, &[])?; self.buffer.drain(..written); } Ok(()) } fn read_command(&mut self) -> Result<()> { while !self.buffer.ends_with(b"\r\n") { let mut buf = [0; 40]; let (read, _) = self.socket.recvmsg(&mut buf)?; self.buffer.extend(&buf[..read]); } Ok(()) } /// Same as [`Handshake::advance_handshake`]. Only exists for backwards compatibility. pub fn advance_handshake(&mut self) -> Result<()> { Handshake::advance_handshake(self) } /// Same as [`Handshake::try_finish`]. Only exists for backwards compatibility. pub fn try_finish(self) -> std::result::Result, Self> { Handshake::try_finish(self) } /// Same as [`Handshake::socket`]. Only exists for backwards compatibility. pub fn socket(&self) -> &S { Handshake::socket(self) } } impl Handshake for ClientHandshake { fn next_io_operation(&self) -> IoOperation { match self.step { ClientHandshakeStep::Init | ClientHandshakeStep::Done => IoOperation::None, ClientHandshakeStep::WaitNegociateFd | ClientHandshakeStep::WaitOauth => { IoOperation::Read } ClientHandshakeStep::SendingOauth | ClientHandshakeStep::SendingNegociateFd | ClientHandshakeStep::SendingBegin => IoOperation::Write, } } fn advance_handshake(&mut self) -> Result<()> { loop { match self.step { ClientHandshakeStep::Init => { // send the SASL handshake let uid_str = Uid::current() .to_string() .chars() .map(|c| format!("{:x}", c as u32)) .collect::(); self.buffer = format!("\0AUTH EXTERNAL {}\r\n", uid_str).into(); self.step = ClientHandshakeStep::SendingOauth; } ClientHandshakeStep::SendingOauth => { self.flush_buffer()?; self.step = ClientHandshakeStep::WaitOauth; } ClientHandshakeStep::WaitOauth => { self.read_command()?; let mut reply = String::new(); (&self.buffer[..]).read_line(&mut reply)?; let mut words = reply.split_whitespace(); // We expect a 2 words answer "OK" and the server Guid let guid = match (words.next(), words.next(), words.next()) { (Some("OK"), Some(guid), None) => guid.try_into()?, _ => { return Err(Error::Handshake( "Unexpected server AUTH reply".to_string(), )) } }; self.server_guid = Some(guid); self.buffer = Vec::from(&b"NEGOTIATE_UNIX_FD\r\n"[..]); self.step = ClientHandshakeStep::SendingNegociateFd; } ClientHandshakeStep::SendingNegociateFd => { self.flush_buffer()?; self.step = ClientHandshakeStep::WaitNegociateFd; } ClientHandshakeStep::WaitNegociateFd => { self.read_command()?; if self.buffer.starts_with(b"AGREE_UNIX_FD") { self.cap_unix_fd = true; } else if self.buffer.starts_with(b"ERROR") { self.cap_unix_fd = false; } else { return Err(Error::Handshake( "Unexpected server UNIX_FD reply".to_string(), )); } self.buffer = Vec::from(&b"BEGIN\r\n"[..]); self.step = ClientHandshakeStep::SendingBegin; } ClientHandshakeStep::SendingBegin => { self.flush_buffer()?; self.step = ClientHandshakeStep::Done; } ClientHandshakeStep::Done => return Ok(()), } } } fn try_finish(self) -> std::result::Result, Self> { if let ClientHandshakeStep::Done = self.step { Ok(Authenticated { conn: Connection::wrap(self.socket), server_guid: self.server_guid.unwrap(), cap_unix_fd: self.cap_unix_fd, }) } else { Err(self) } } fn socket(&self) -> &S { &self.socket } } impl ClientHandshake { /// Initialize a handshake to the session/user message bus. /// /// The socket backing this connection is created in blocking mode. pub fn new_session() -> Result { session_socket(false).map(Self::new) } /// Initialize a handshake to the session/user message bus. /// /// The socket backing this connection is created in non-blocking mode. pub fn new_session_nonblock() -> Result { let socket = session_socket(true)?; Ok(Self::new(socket)) } /// Initialize a handshake to the system-wide message bus. /// /// The socket backing this connection is created in blocking mode. pub fn new_system() -> Result { system_socket(false).map(Self::new) } /// Initialize a handshake to the system-wide message bus. /// /// The socket backing this connection is created in non-blocking mode. pub fn new_system_nonblock() -> Result { let socket = system_socket(true)?; Ok(Self::new(socket)) } /// Create a handshake for the given [D-Bus address]. /// /// The socket backing this connection is created in blocking mode. /// /// [D-Bus address]: https://dbus.freedesktop.org/doc/dbus-specification.html#addresses pub fn new_for_address(address: &str) -> Result { match Address::from_str(address)?.connect(false)? { address::Stream::Unix(s) => Ok(Self::new(s)), } } /// Create a handshake for the given [D-Bus address]. /// /// The socket backing this connection is created in non-blocking mode. /// /// [D-Bus address]: https://dbus.freedesktop.org/doc/dbus-specification.html#addresses pub fn new_for_address_nonblock(address: &str) -> Result { match Address::from_str(address)?.connect(true)? { address::Stream::Unix(s) => Ok(Self::new(s)), } } /// Block and automatically drive the handshake for this client /// /// This method will block until the handshake is finalized, even if the /// socket is in non-blocking mode. pub fn blocking_finish(mut self) -> Result> { loop { match self.advance_handshake() { Ok(()) => return Ok(self.try_finish().unwrap_or_else(|_| unreachable!())), Err(Error::Io(e)) if e.kind() == std::io::ErrorKind::WouldBlock => { // we raised a WouldBlock error, this means this is a non-blocking socket // we use poll to wait until the action we need is available let flags = match self.step { ClientHandshakeStep::SendingOauth | ClientHandshakeStep::SendingNegociateFd | ClientHandshakeStep::SendingBegin => PollFlags::POLLOUT, ClientHandshakeStep::WaitOauth | ClientHandshakeStep::WaitNegociateFd => { PollFlags::POLLIN } ClientHandshakeStep::Init | ClientHandshakeStep::Done => unreachable!(), }; wait_on(self.socket.as_raw_fd(), flags)?; } Err(e) => return Err(e), } } } } /* * Server-side handshake logic */ #[derive(Debug)] enum ServerHandshakeStep { WaitingForNull, WaitingForAuth, SendingAuthOK, SendingAuthError, WaitingForBegin, SendingBeginMessage, 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. /// /// This struct is an async-compatible representation of the initial handshake that must be performed before /// a D-Bus connection can be used. To use it, you should call the [`advance_handshake`] method whenever the /// underlying socket becomes ready (tracking the readiness itself is not managed by this abstraction) until /// it returns `Ok(())`, at which point you can invoke the [`try_finish`] method to get an [`Authenticated`], /// which can be given to [`Connection::new_authenticated`]. /// /// If handling the handshake asynchronously is not necessary, the [`blocking_finish`] method is provided /// which blocks until the handshake is completed or an error occurs. /// /// [`advance_handshake`]: struct.ServerHandshake.html#method.advance_handshake /// [`try_finish`]: struct.ServerHandshake.html#method.try_finish /// [`Authenticated`]: struct.Authenticated.html /// [`Connection::new_authenticated`]: ../struct.Connection.html#method.new_authenticated /// [`blocking_finish`]: struct.ServerHandshake.html#method.blocking_finish #[derive(Debug)] pub struct ServerHandshake { socket: S, buffer: Vec, step: ServerHandshakeStep, server_guid: Guid, cap_unix_fd: bool, client_uid: u32, } impl ServerHandshake { pub fn new(socket: S, guid: Guid, client_uid: u32) -> ServerHandshake { ServerHandshake { socket, buffer: Vec::new(), step: ServerHandshakeStep::WaitingForNull, server_guid: guid, cap_unix_fd: false, client_uid, } } fn flush_buffer(&mut self) -> Result<()> { while !self.buffer.is_empty() { let written = self.socket.sendmsg(&self.buffer, &[])?; self.buffer.drain(..written); } Ok(()) } fn read_command(&mut self) -> Result<()> { while !self.buffer.ends_with(b"\r\n") { let mut buf = [0; 40]; let (read, _) = self.socket.recvmsg(&mut buf)?; self.buffer.extend(&buf[..read]); } Ok(()) } /// Same as [`Handshake::advance_handshake`]. Only exists for backwards compatibility. pub fn advance_handshake(&mut self) -> Result<()> { Handshake::advance_handshake(self) } /// Same as [`Handshake::try_finish`]. Only exists for backwards compatibility. pub fn try_finish(self) -> std::result::Result, Self> { Handshake::try_finish(self) } /// Same as [`Handshake::socket`]. Only exists for backwards compatibility. pub fn socket(&self) -> &S { Handshake::socket(self) } } impl Handshake for ServerHandshake { fn next_io_operation(&self) -> IoOperation { match self.step { ServerHandshakeStep::Done => IoOperation::None, ServerHandshakeStep::WaitingForNull | ServerHandshakeStep::WaitingForAuth | ServerHandshakeStep::WaitingForBegin => IoOperation::Read, ServerHandshakeStep::SendingAuthOK | ServerHandshakeStep::SendingAuthError | ServerHandshakeStep::SendingBeginMessage => IoOperation::Write, } } fn advance_handshake(&mut self) -> Result<()> { loop { match self.step { ServerHandshakeStep::WaitingForNull => { let mut buffer = [0; 1]; let (read, _) = self.socket.recvmsg(&mut buffer)?; // recvmsg cannot return anything else than Ok(1) or Err debug_assert!(read == 1); if buffer[0] != 0 { return Err(Error::Handshake( "First client byte is not NUL!".to_string(), )); } self.step = ServerHandshakeStep::WaitingForAuth; } ServerHandshakeStep::WaitingForAuth => { self.read_command()?; let mut reply = String::new(); (&self.buffer[..]).read_line(&mut reply)?; let mut words = reply.split_whitespace(); match (words.next(), words.next(), words.next(), words.next()) { (Some("AUTH"), Some("EXTERNAL"), Some(uid), None) => { let uid = id_from_str(uid) .map_err(|e| Error::Handshake(format!("Invalid UID: {}", e)))?; if uid == self.client_uid { self.buffer = format!("OK {}\r\n", self.server_guid).into(); self.step = ServerHandshakeStep::SendingAuthOK; } else { self.buffer = Vec::from(&b"REJECTED EXTERNAL\r\n"[..]); self.step = ServerHandshakeStep::SendingAuthError; } } (Some("AUTH"), _, _, _) | (Some("ERROR"), _, _, _) => { self.buffer = Vec::from(&b"REJECTED EXTERNAL\r\n"[..]); self.step = ServerHandshakeStep::SendingAuthError; } (Some("BEGIN"), None, None, None) => { return Err(Error::Handshake( "Received BEGIN while not authenticated".to_string(), )); } _ => { self.buffer = Vec::from(&b"ERROR Unsupported command\r\n"[..]); self.step = ServerHandshakeStep::SendingAuthError; } } } ServerHandshakeStep::SendingAuthError => { self.flush_buffer()?; self.step = ServerHandshakeStep::WaitingForAuth; } ServerHandshakeStep::SendingAuthOK => { self.flush_buffer()?; self.step = ServerHandshakeStep::WaitingForBegin; } ServerHandshakeStep::WaitingForBegin => { self.read_command()?; let mut reply = String::new(); (&self.buffer[..]).read_line(&mut reply)?; let mut words = reply.split_whitespace(); match (words.next(), words.next()) { (Some("BEGIN"), None) => { self.step = ServerHandshakeStep::Done; } (Some("CANCEL"), None) => { self.buffer = Vec::from(&b"REJECTED EXTERNAL\r\n"[..]); self.step = ServerHandshakeStep::SendingAuthError; } (Some("ERROR"), _) => { self.buffer = Vec::from(&b"REJECTED EXTERNAL\r\n"[..]); self.step = ServerHandshakeStep::SendingAuthError; } (Some("NEGOTIATE_UNIX_FD"), None) => { self.cap_unix_fd = true; self.buffer = Vec::from(&b"AGREE_UNIX_FD\r\n"[..]); self.step = ServerHandshakeStep::SendingBeginMessage; } _ => { self.buffer = Vec::from(&b"ERROR Unsupported command\r\n"[..]); self.step = ServerHandshakeStep::SendingBeginMessage; } } } ServerHandshakeStep::SendingBeginMessage => { self.flush_buffer()?; self.step = ServerHandshakeStep::WaitingForBegin; } ServerHandshakeStep::Done => return Ok(()), } } } fn try_finish(self) -> std::result::Result, Self> { if let ServerHandshakeStep::Done = self.step { Ok(Authenticated { conn: Connection::wrap(self.socket), server_guid: self.server_guid, cap_unix_fd: self.cap_unix_fd, }) } else { Err(self) } } fn socket(&self) -> &S { &self.socket } } impl ServerHandshake { /// Block and automatically drive the handshake for this server /// /// This method will block until the handshake is finalized, even if the /// socket is in non-blocking mode. pub fn blocking_finish(mut self) -> Result> { loop { match self.advance_handshake() { Ok(()) => return Ok(self.try_finish().unwrap_or_else(|_| unreachable!())), Err(Error::Io(e)) if e.kind() == std::io::ErrorKind::WouldBlock => { // we raised a WouldBlock error, this means this is a non-blocking socket // we use poll to wait until the action we need is available let flags = match self.step { ServerHandshakeStep::SendingAuthError | ServerHandshakeStep::SendingAuthOK | ServerHandshakeStep::SendingBeginMessage => PollFlags::POLLOUT, ServerHandshakeStep::WaitingForNull | ServerHandshakeStep::WaitingForBegin | ServerHandshakeStep::WaitingForAuth => PollFlags::POLLIN, ServerHandshakeStep::Done => unreachable!(), }; wait_on(self.socket.as_raw_fd(), flags)?; } Err(e) => return Err(e), } } } } fn session_socket(nonblocking: bool) -> Result { match Address::session()?.connect(nonblocking)? { address::Stream::Unix(s) => Ok(s), } } fn system_socket(nonblocking: bool) -> Result { match Address::system()?.connect(nonblocking)? { address::Stream::Unix(s) => Ok(s), } } fn id_from_str(s: &str) -> std::result::Result> { let mut id = String::new(); for s in s.as_bytes().chunks(2) { let c = char::from(u8::from_str_radix(std::str::from_utf8(s)?, 16)?); id.push(c); } Ok(id.parse::()?) } #[cfg(test)] mod tests { use std::os::unix::net::UnixStream; use super::*; use crate::Guid; #[test] fn handshake() { // a pair of non-blocking connection UnixStream let (p0, p1) = UnixStream::pair().unwrap(); p0.set_nonblocking(true).unwrap(); p1.set_nonblocking(true).unwrap(); // initialize both handshakes let mut client = ClientHandshake::new(p0); let mut server = ServerHandshake::new(p1, Guid::generate(), Uid::current().into()); // proceed to the handshakes let mut client_done = false; let mut server_done = false; while !(client_done && server_done) { match client.advance_handshake() { Ok(()) => client_done = true, Err(Error::Io(e)) => assert!(e.kind() == std::io::ErrorKind::WouldBlock), Err(e) => panic!("Unexpected error: {:?}", e), } match server.advance_handshake() { Ok(()) => server_done = true, Err(Error::Io(e)) => assert!(e.kind() == std::io::ErrorKind::WouldBlock), Err(e) => panic!("Unexpected error: {:?}", e), } } let client = client.try_finish().unwrap(); let server = server.try_finish().unwrap(); assert_eq!(client.server_guid, server.server_guid); assert_eq!(client.cap_unix_fd, server.cap_unix_fd); } } zbus-1.9.3/src/lib.rs000064400000000000000000000457100072674642500125630ustar 00000000000000#![deny(rust_2018_idioms)] #![doc( html_logo_url = "https://storage.googleapis.com/fdo-gitlab-uploads/project/avatar/3213/zbus-logomark.png" )] //! This crate provides the main API you will use to interact with D-Bus from Rust. 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. //! //! zbus crate is currently Linux-specific[^otheros]. //! //! ### Getting Started //! //! The best way to get started with zbus is the [book], where we start with basic D-Bus concepts //! and explain with code samples, how zbus makes D-Bus easy. //! //! ### Example code //! //! #### Client //! //! This code display a notification on your Freedesktop.org-compatible OS: //! //! ```rust,no_run //! use std::collections::HashMap; //! use std::error::Error; //! //! use zbus::dbus_proxy; //! use zvariant::Value; //! //! #[dbus_proxy] //! trait Notifications { //! fn notify( //! &self, //! app_name: &str, //! replaces_id: u32, //! app_icon: &str, //! summary: &str, //! body: &str, //! actions: &[&str], //! hints: HashMap<&str, &Value>, //! expire_timeout: i32, //! ) -> zbus::Result; //! } //! //! fn main() -> Result<(), Box> { //! let connection = zbus::Connection::new_session()?; //! //! let proxy = NotificationsProxy::new(&connection)?; //! let reply = proxy.notify( //! "my-app", //! 0, //! "dialog-information", //! "A summary", //! "Some body", //! &[], //! HashMap::new(), //! 5000, //! )?; //! dbg!(reply); //! //! Ok(()) //! } //! ``` //! //! #### Server //! //! A simple service that politely greets whoever calls its `SayHello` method: //! //! ```rust,no_run //! use std::error::Error; //! use std::convert::TryInto; //! use zbus::{dbus_interface, fdo}; //! //! struct Greeter { //! count: u64 //! } //! //! #[dbus_interface(name = "org.zbus.MyGreeter1")] //! impl Greeter { //! fn say_hello(&mut self, name: &str) -> String { //! self.count += 1; //! format!("Hello {}! I have been called: {}", name, self.count) //! } //! } //! //! fn main() -> Result<(), Box> { //! let connection = zbus::Connection::new_session()?; //! fdo::DBusProxy::new(&connection)?.request_name( //! "org.zbus.MyGreeter", //! fdo::RequestNameFlags::ReplaceExisting.into(), //! )?; //! //! let mut object_server = zbus::ObjectServer::new(&connection); //! let mut greeter = Greeter { count: 0 }; //! object_server.at(&"/org/zbus/MyGreeter".try_into()?, greeter)?; //! loop { //! if let Err(err) = object_server.try_handle_next() { //! eprintln!("{}", err); //! } //! } //! } //! ``` //! //! 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" //! Hello Maria! //! $ //! ``` //! //! #### Asynchronous API //! //! Currently, only [low-level asynchronous API] is provided. You can do everything you can through //! it that you can do through the high-level asynchronous API (when it exists), it's [not at all as //! hard to use](azync::Connection#monitoring-all-messages) as it may sound. //! //! [book]: https://dbus.pages.freedesktop.org/zbus/ //! [low-level asynchronous API]: azync::Connection //! //! [^otheros]: Support for other OS exist, but it is not supported to the same extent. D-Bus //! clients in javascript (running from any browser) do exist though. And zbus may also be //! working from the browser sometime in the future too, thanks to Rust 🦀 and WebAssembly 🕸. //! #[cfg(doctest)] mod doctests { doc_comment::doctest!("../../README.md"); // Book markdown checks doc_comment::doctest!("../../book/src/client.md"); doc_comment::doctest!("../../book/src/concepts.md"); 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/server.md"); } mod error; pub use error::*; mod address; mod guid; pub use guid::*; mod message; pub use message::*; mod message_header; pub use message_header::*; mod message_field; pub use message_field::*; mod message_fields; pub use message_fields::*; mod connection; pub use connection::*; mod proxy; pub use proxy::*; mod signal_receiver; pub use signal_receiver::*; mod owned_fd; pub use owned_fd::*; mod utils; mod object_server; pub use object_server::*; pub mod fdo; pub mod raw; pub mod azync; pub mod handshake; pub mod xml; pub use zbus_macros::{dbus_interface, dbus_proxy, DBusError}; // 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 zvariant; } pub use zvariant; #[cfg(test)] mod tests { use std::{ collections::HashMap, convert::TryInto, fs::File, os::unix::io::{AsRawFd, FromRawFd}, }; use enumflags2::BitFlags; use ntest::timeout; use serde_repr::{Deserialize_repr, Serialize_repr}; use zvariant::{derive::Type, Fd, OwnedValue, Type}; use crate::{azync::ConnectionType, Connection, Message, MessageFlags, Result}; #[test] fn msg() { let mut m = Message::method( None, Some("org.freedesktop.DBus"), "/org/freedesktop/DBus", Some("org.freedesktop.DBus.Peer"), "GetMachineId", &(), ) .unwrap(); m.modify_primary_header(|primary| { primary.set_flags(BitFlags::from(MessageFlags::NoAutoStart)); primary.set_serial_num(11); Ok(()) }) .unwrap(); let primary = m.primary_header().unwrap(); assert!(primary.serial_num() == 11); assert!(primary.flags() == MessageFlags::NoAutoStart); } #[test] fn basic_connection() { let connection = crate::Connection::new_session() .map_err(|e| { println!("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] fn basic_connection_async() { futures::executor::block_on(test_basic_connection()).unwrap(); } async fn test_basic_connection() -> Result<()> { let mut connection = match ConnectionType::new_session().await? { ConnectionType::Unix(c) => c, }; 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(()) } #[test] fn fdpass_systemd() { let connection = crate::Connection::new_system().unwrap(); let mut reply = connection .call_method( Some("org.freedesktop.systemd1"), "/org/freedesktop/systemd1", Some("org.freedesktop.systemd1.Manager"), "DumpByFileDescriptor", &(), ) .unwrap(); assert!(reply .body_signature() .map(|s| s == ::signature()) .unwrap()); let fd: Fd = reply.body().unwrap(); reply.disown_fds(); assert!(fd.as_raw_fd() >= 0); let f = unsafe { File::from_raw_fd(fd.as_raw_fd()) }; f.metadata().unwrap(); } // Let's try getting us a fancy name on the bus #[repr(u32)] #[derive(Type, BitFlags, Debug, PartialEq, Copy, Clone)] enum RequestNameFlags { AllowReplacement = 0x01, ReplaceExisting = 0x02, DoNotQueue = 0x04, } #[repr(u32)] #[derive(Deserialize_repr, Serialize_repr, Type, Debug, PartialEq)] enum RequestNameReply { PrimaryOwner = 0x01, InQueue = 0x02, Exists = 0x03, AlreadyOwner = 0x04, } #[test] fn freedesktop_api() { let mut connection = crate::Connection::new_session() .map_err(|e| { println!("error: {}", e); e }) .unwrap(); #[allow(deprecated)] connection.set_default_message_handler(Box::new(|msg| { // Debug implementation will test it a bit println!("Received while waiting for a reply: {}", msg); Some(msg) })); 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(); assert!(reply.body_signature().map(|s| s == "u").unwrap()); let reply: RequestNameReply = reply.body().unwrap(); assert_eq!(reply, RequestNameReply::PrimaryOwner); let reply = connection .call_method( Some("org.freedesktop.DBus"), "/org/freedesktop/DBus", Some("org.freedesktop.DBus"), "GetId", &(), ) .unwrap(); assert!(reply .body_signature() .map(|s| s == <&str>::signature()) .unwrap()); let id: &str = reply.body().unwrap(); println!("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(); assert!(reply .body_signature() .map(|s| s == bool::signature()) .unwrap()); assert!(reply.body::().unwrap()); let reply = connection .call_method( Some("org.freedesktop.DBus"), "/org/freedesktop/DBus", Some("org.freedesktop.DBus"), "GetNameOwner", &"org.freedesktop.zbus.sync", ) .unwrap(); assert!(reply .body_signature() .map(|s| s == <&str>::signature()) .unwrap()); assert_eq!( Some(reply.body::<&str>().unwrap()), connection.unique_name() ); let reply = connection .call_method( Some("org.freedesktop.DBus"), "/org/freedesktop/DBus", Some("org.freedesktop.DBus"), "GetConnectionCredentials", &"org.freedesktop.DBus", ) .unwrap(); assert!(reply.body_signature().map(|s| s == "a{sv}").unwrap()); let hashmap: HashMap<&str, OwnedValue> = reply.body().unwrap(); let pid: u32 = (&hashmap["ProcessID"]).try_into().unwrap(); println!("DBus bus PID: {}", pid); let uid: u32 = (&hashmap["UnixUserID"]).try_into().unwrap(); println!("DBus bus UID: {}", uid); } #[test] fn freedesktop_api_async() { futures::executor::block_on(test_freedesktop_api()).unwrap(); } async fn test_freedesktop_api() -> Result<()> { let mut connection = match ConnectionType::new_session().await? { ConnectionType::Unix(c) => c, }; 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(); assert!(reply.body_signature().map(|s| s == "u").unwrap()); let reply: RequestNameReply = reply.body().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(); assert!(reply .body_signature() .map(|s| s == <&str>::signature()) .unwrap()); let id: &str = reply.body().unwrap(); println!("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(); assert!(reply .body_signature() .map(|s| s == bool::signature()) .unwrap()); assert!(reply.body::().unwrap()); let reply = connection .call_method( Some("org.freedesktop.DBus"), "/org/freedesktop/DBus", Some("org.freedesktop.DBus"), "GetNameOwner", &"org.freedesktop.zbus.async", ) .await .unwrap(); assert!(reply .body_signature() .map(|s| s == <&str>::signature()) .unwrap()); assert_eq!( Some(reply.body::<&str>().unwrap()), connection.unique_name() ); let reply = connection .call_method( Some("org.freedesktop.DBus"), "/org/freedesktop/DBus", Some("org.freedesktop.DBus"), "GetConnectionCredentials", &"org.freedesktop.DBus", ) .await .unwrap(); assert!(reply.body_signature().map(|s| s == "a{sv}").unwrap()); let hashmap: HashMap<&str, OwnedValue> = reply.body().unwrap(); let pid: u32 = (&hashmap["ProcessID"]).try_into().unwrap(); println!("DBus bus PID: {}", pid); let uid: u32 = (&hashmap["UnixUserID"]).try_into().unwrap(); println!("DBus bus UID: {}", uid); Ok(()) } #[test] #[timeout(1000)] fn issue_68() { // Tests the fix for https://gitlab.freedesktop.org/dbus/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 = Connection::new_session().unwrap(); // Send a message as client before service starts to process messages let client_conn = Connection::new_session().unwrap(); let msg = Message::method( None, conn.unique_name(), "/org/freedesktop/Issue68", Some("org.freedesktop.Issue68"), "Ping", &(), ) .unwrap(); let serial = client_conn.send_message(msg).unwrap(); crate::fdo::DBusProxy::new(&conn).unwrap().get_id().unwrap(); loop { let msg = conn.receive_message().unwrap(); if msg.primary_header().unwrap().serial_num() == serial { break; } } } #[test] #[timeout(1000)] fn issue104() { // Tests the fix for https://gitlab.freedesktop.org/dbus/zbus/-/issues/104 // // The issue is caused by `dbus_proxy` macro adding `()` around the return value of methods // with multiple out arguments, ending up with double paranthesis 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 std::{cell::RefCell, convert::TryFrom, rc::Rc}; use zvariant::{ObjectPath, OwnedObjectPath, Value}; let conn = Connection::new_session().unwrap(); let service_name = conn.unique_name().unwrap().to_string(); let mut object_server = super::ObjectServer::new(&conn); struct Secret(Rc>); #[super::dbus_interface(name = "org.freedesktop.Secret.Service")] impl Secret { fn open_session( &self, _algorithm: &str, input: Value<'_>, ) -> zbus::fdo::Result<(OwnedValue, OwnedObjectPath)> { *self.0.borrow_mut() = true; Ok(( OwnedValue::from(input), ObjectPath::try_from("/org/freedesktop/secrets/Blah") .unwrap() .into(), )) } } let quit = Rc::new(RefCell::new(false)); let secret = Secret(quit.clone()); object_server .at(&"/org/freedesktop/secrets".try_into().unwrap(), secret) .unwrap(); let child = std::thread::spawn(move || { let conn = Connection::new_session().unwrap(); #[super::dbus_proxy(interface = "org.freedesktop.Secret.Service")] trait Secret { fn open_session( &self, algorithm: &str, input: &zvariant::Value<'_>, ) -> zbus::Result<(zvariant::OwnedValue, zvariant::OwnedObjectPath)>; } let proxy = SecretProxy::new_for(&conn, &service_name, "/org/freedesktop/secrets").unwrap(); proxy.open_session("plain", &Value::from("")).unwrap(); 2u32 }); loop { let m = conn.receive_message().unwrap(); if let Err(e) = object_server.dispatch_message(&m) { eprintln!("{}", e); } if *quit.borrow() { break; } } let val = child.join().expect("failed to join"); assert_eq!(val, 2); } #[test] fn connection_is_send_and_sync() { accept_send_and_sync::(); } fn accept_send_and_sync() {} } zbus-1.9.3/src/message.rs000064400000000000000000000474210072674642500134420ustar 00000000000000use std::{ convert::{TryFrom, TryInto}, error, fmt, io::{Cursor, Error as IOError}, os::unix::io::{AsRawFd, IntoRawFd, RawFd}, }; use zvariant::{EncodingContext, Error as VariantError, Signature, Type}; use crate::{ owned_fd::OwnedFd, utils::padding_for_8_bytes, EndianSig, MessageField, MessageFieldCode, MessageFields, MessageHeader, MessagePrimaryHeader, MessageType, MIN_MESSAGE_SIZE, NATIVE_ENDIAN_SIG, PRIMARY_HEADER_SIZE, }; const FIELDS_LEN_START_OFFSET: usize = 12; macro_rules! dbus_context { ($n_bytes_before: expr) => { EncodingContext::::new_dbus($n_bytes_before) }; } /// Error type returned by [`Message`] methods. /// /// [`Message`]: struct.Message.html #[derive(Debug)] pub enum MessageError { /// Insufficient data provided. InsufficientData, /// Data too large. ExcessData, /// Endian signature invalid or doesn't match expectation. IncorrectEndian, /// An I/O error. Io(IOError), /// Missing body signature. NoBodySignature, /// Unmatching/bad body signature. UnmatchedBodySignature, /// Invalid message field. InvalidField, /// Data serializing/deserializing error. Variant(VariantError), /// A required field is missing in the headers. MissingField, } impl PartialEq for MessageError { fn eq(&self, other: &Self) -> bool { match (self, other) { (Self::InsufficientData, Self::InsufficientData) => true, (Self::ExcessData, Self::ExcessData) => true, (Self::IncorrectEndian, Self::IncorrectEndian) => true, // Io is false (Self::NoBodySignature, Self::NoBodySignature) => true, (Self::UnmatchedBodySignature, Self::UnmatchedBodySignature) => true, (Self::InvalidField, Self::InvalidField) => true, (Self::Variant(s), Self::Variant(o)) => s == o, (_, _) => false, } } } impl error::Error for MessageError { fn source(&self) -> Option<&(dyn error::Error + 'static)> { match self { MessageError::Io(e) => Some(e), MessageError::Variant(e) => Some(e), _ => None, } } } impl fmt::Display for MessageError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { MessageError::InsufficientData => write!(f, "insufficient data"), MessageError::Io(e) => e.fmt(f), MessageError::ExcessData => write!(f, "excess data"), MessageError::IncorrectEndian => write!(f, "incorrect endian"), MessageError::InvalidField => write!(f, "invalid message field"), MessageError::NoBodySignature => write!(f, "missing body signature"), MessageError::UnmatchedBodySignature => write!(f, "unmatched body signature"), MessageError::Variant(e) => write!(f, "{}", e), MessageError::MissingField => write!(f, "A required field is missing"), } } } impl From for MessageError { fn from(val: VariantError) -> MessageError { MessageError::Variant(val) } } impl From for MessageError { fn from(val: IOError) -> MessageError { MessageError::Io(val) } } #[derive(Debug)] struct MessageBuilder<'a, B> { ty: MessageType, body: &'a B, body_len: u32, reply_to: Option>, fields: MessageFields<'a>, } impl<'a, B> MessageBuilder<'a, B> where B: serde::ser::Serialize + Type, { fn new(ty: MessageType, sender: Option<&'a str>, body: &'a B) -> Result { let ctxt = dbus_context!(0); let (body_len, fds_len) = zvariant::serialized_size_fds(ctxt, body)?; let body_len = u32::try_from(body_len).map_err(|_| MessageError::ExcessData)?; let mut fields = MessageFields::new(); let mut signature = B::signature(); 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); } fields.add(MessageField::Signature(signature)); } if let Some(sender) = sender { fields.add(MessageField::Sender(sender.into())); } if fds_len > 0 { fields.add(MessageField::UnixFDs(fds_len as u32)); } Ok(Self { ty, body, body_len, fields, reply_to: None, }) } fn build(self) -> Result { let MessageBuilder { ty, body, body_len, mut fields, reply_to, } = self; if let Some(reply_to) = reply_to.as_ref() { let serial = reply_to.primary().serial_num(); fields.add(MessageField::ReplySerial(serial)); if let Some(sender) = reply_to.sender()? { fields.add(MessageField::Destination(sender.into())); } } let primary = MessagePrimaryHeader::new(ty, body_len); let header = MessageHeader::new(primary, fields); let ctxt = dbus_context!(0); // 1K for all the fields should be enough for most messages? let mut bytes: Vec = Vec::with_capacity(PRIMARY_HEADER_SIZE + 1024 + (body_len as usize)); let mut cursor = Cursor::new(&mut bytes); zvariant::to_writer(&mut cursor, ctxt, &header)?; let (_, fds) = zvariant::to_writer_fds(&mut cursor, ctxt, body)?; Ok(Message { bytes, fds: Fds::Raw(fds), }) } fn set_reply_to(mut self, reply_to: &'a Message) -> Result { self.reply_to = Some(reply_to.header()?); Ok(self) } fn set_field(mut self, field: MessageField<'a>) -> Self { self.fields.add(field); self } fn reply( sender: Option<&'a str>, reply_to: &'a Message, body: &'a B, ) -> Result { Self::new(MessageType::MethodReturn, sender, body)?.set_reply_to(reply_to) } fn error( sender: Option<&'a str>, reply_to: &'a Message, error_name: &'a str, body: &'a B, ) -> Result { Ok(Self::new(MessageType::Error, sender, body)? .set_reply_to(reply_to)? .set_field(MessageField::ErrorName(error_name.into()))) } fn method( sender: Option<&'a str>, path: &'a str, method_name: &'a str, body: &'a B, ) -> Result { let path = path.try_into()?; Ok(Self::new(MessageType::MethodCall, sender, body)? .set_field(MessageField::Path(path)) .set_field(MessageField::Member(method_name.into()))) } fn signal( sender: Option<&'a str>, path: &'a str, iface: &'a str, signal_name: &'a str, body: &'a B, ) -> Result { let path = path.try_into()?; Ok(Self::new(MessageType::Signal, sender, body)? .set_field(MessageField::Path(path)) .set_field(MessageField::Interface(iface.into())) .set_field(MessageField::Member(signal_name.into()))) } } #[derive(Debug, Eq, PartialEq)] enum Fds { Owned(Vec), Raw(Vec), } /// A D-Bus Message. /// /// The content of the message are stored in serialized format. To deserialize the body of the /// message, use the [`body`] method. 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 call /// [`disown_fds`] after deserializing to `RawFD` using [`body`] if you want to take the ownership. /// /// [`body`]: #method.body /// [`disown_fds`]: #method.disown_fds /// [`Connection`]: struct.Connection#method.call_method pub struct Message { bytes: Vec, fds: Fds, } // TODO: Handle non-native byte order: https://gitlab.freedesktop.org/dbus/zbus/-/issues/19 impl Message { /// Create a message of type [`MessageType::MethodCall`]. /// /// [`MessageType::MethodCall`]: enum.MessageType.html#variant.MethodCall pub fn method( sender: Option<&str>, destination: Option<&str>, path: &str, iface: Option<&str>, method_name: &str, body: &B, ) -> Result where B: serde::ser::Serialize + Type, { let mut b = MessageBuilder::method(sender, path, method_name, body)?; if let Some(destination) = destination { b = b.set_field(MessageField::Destination(destination.into())); } if let Some(iface) = iface { b = b.set_field(MessageField::Interface(iface.into())); } b.build() } /// Create a message of type [`MessageType::Signal`]. /// /// [`MessageType::Signal`]: enum.MessageType.html#variant.Signal pub fn signal( sender: Option<&str>, destination: Option<&str>, path: &str, iface: &str, signal_name: &str, body: &B, ) -> Result where B: serde::ser::Serialize + Type, { let mut b = MessageBuilder::signal(sender, path, iface, signal_name, body)?; if let Some(destination) = destination { b = b.set_field(MessageField::Destination(destination.into())); } b.build() } /// Create a message of type [`MessageType::MethodReturn`]. /// /// [`MessageType::MethodReturn`]: enum.MessageType.html#variant.MethodReturn pub fn method_reply( sender: Option<&str>, call: &Self, body: &B, ) -> Result where B: serde::ser::Serialize + Type, { MessageBuilder::reply(sender, call, body)?.build() } /// Create a message of type [`MessageType::MethodError`]. /// /// [`MessageType::MethodError`]: enum.MessageType.html#variant.MethodError pub fn method_error( sender: Option<&str>, call: &Self, name: &str, body: &B, ) -> Result where B: serde::ser::Serialize + Type, { MessageBuilder::error(sender, call, name, body)?.build() } pub(crate) fn from_bytes(bytes: &[u8]) -> Result { if bytes.len() < MIN_MESSAGE_SIZE { return Err(MessageError::InsufficientData); } if EndianSig::try_from(bytes[0])? != NATIVE_ENDIAN_SIG { return Err(MessageError::IncorrectEndian); } let bytes = bytes.to_vec(); let fds = Fds::Raw(vec![]); Ok(Self { bytes, fds }) } pub(crate) fn add_bytes(&mut self, bytes: &[u8]) -> Result<(), MessageError> { if bytes.len() > self.bytes_to_completion()? { return Err(MessageError::ExcessData); } self.bytes.extend(bytes); Ok(()) } pub(crate) fn set_owned_fds(&mut self, fds: Vec) { self.fds = Fds::Owned(fds); } /// Disown the associated file descriptors. /// /// When a message is received over a AF_UNIX socket, it may /// contain associated FDs. To prevent the message from closing /// those FDs on drop, you may remove the ownership thanks to this /// method, after that you are responsible for closing them. pub fn disown_fds(&mut self) { if let Fds::Owned(ref mut fds) = &mut self.fds { // From now on, it's the caller responsability to close the fds self.fds = Fds::Raw(fds.drain(..).map(|fd| fd.into_raw_fd()).collect()); } } pub(crate) fn bytes_to_completion(&self) -> Result { let header_len = MIN_MESSAGE_SIZE + self.fields_len()?; let body_padding = padding_for_8_bytes(header_len); let body_len = self.primary_header()?.body_len(); let required = header_len + body_padding + body_len as usize; Ok(required - self.bytes.len()) } /// 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 body_signature<'b, 's: 'b>(&'s self) -> Result, MessageError> { match self .header()? .into_fields() .into_field(MessageFieldCode::Signature) .ok_or(MessageError::NoBodySignature)? { MessageField::Signature(signature) => Ok(signature), _ => Err(MessageError::InvalidField), } } /// Deserialize the primary header. pub fn primary_header(&self) -> Result { zvariant::from_slice(&self.bytes, dbus_context!(0)).map_err(MessageError::from) } pub(crate) fn modify_primary_header(&mut self, mut modifier: F) -> Result<(), MessageError> where F: FnMut(&mut MessagePrimaryHeader) -> Result<(), MessageError>, { let mut primary = self.primary_header()?; modifier(&mut primary)?; let mut cursor = Cursor::new(&mut self.bytes); zvariant::to_writer(&mut cursor, dbus_context!(0), &primary) .map(|_| ()) .map_err(MessageError::from) } /// Deserialize the header. pub fn header<'h, 'm: 'h>(&'m self) -> Result, MessageError> { zvariant::from_slice(&self.bytes, dbus_context!(0)).map_err(MessageError::from) } /// Deserialize the fields. pub fn fields<'f, 'm: 'f>(&'m self) -> Result, MessageError> { let ctxt = dbus_context!(crate::PRIMARY_HEADER_SIZE); zvariant::from_slice(&self.bytes[crate::PRIMARY_HEADER_SIZE..], ctxt) .map_err(MessageError::from) } /// Deserialize the body (without checking signature matching). pub fn body_unchecked<'d, 'm: 'd, B>(&'m self) -> Result where B: serde::de::Deserialize<'d> + Type, { if self.bytes_to_completion()? != 0 { return Err(MessageError::InsufficientData); } let mut header_len = MIN_MESSAGE_SIZE + self.fields_len()?; header_len = header_len + padding_for_8_bytes(header_len); zvariant::from_slice_fds( &self.bytes[header_len..], Some(&self.fds()), dbus_context!(0), ) .map_err(MessageError::from) } /// Check the signature and deserialize the body. pub fn body<'d, 'm: 'd, B>(&'m self) -> Result where B: serde::de::Deserialize<'d> + Type, { let b_sig = B::signature(); let sig = match self.body_signature() { Ok(sig) => sig, Err(MessageError::NoBodySignature) => Signature::from_str_unchecked(""), Err(e) => return Err(e), }; let c = zvariant::STRUCT_SIG_START_CHAR; let b_sig = if b_sig.len() >= 2 && b_sig.starts_with(c) && !sig.starts_with(c) { &b_sig[1..b_sig.len() - 1] } else { &b_sig }; if b_sig != sig.as_str() { return Err(MessageError::UnmatchedBodySignature); } self.body_unchecked() } pub(crate) fn fds(&self) -> Vec { match &self.fds { Fds::Raw(fds) => fds.clone(), Fds::Owned(fds) => fds.iter().map(|f| f.as_raw_fd()).collect(), } } /// Get a reference to the byte encoding of the message. pub fn as_bytes(&self) -> &[u8] { &self.bytes } fn fields_len(&self) -> Result { zvariant::from_slice(&self.bytes[FIELDS_LEN_START_OFFSET..], dbus_context!(0)) .map(|v: u32| v as usize) .map_err(MessageError::from) } } impl fmt::Debug for Message { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut msg = f.debug_struct("Msg"); let _ = self.header().map(|h| { if let Ok(t) = h.message_type() { msg.field("type", &t); } if let Ok(Some(sender)) = h.sender() { msg.field("sender", &sender); } if let Ok(Some(serial)) = h.reply_serial() { msg.field("reply-serial", &serial); } if let Ok(Some(path)) = h.path() { msg.field("path", &path); } if let Ok(Some(iface)) = h.interface() { msg.field("iface", &iface); } if let Ok(Some(member)) = h.member() { msg.field("member", &member); } }); if let Ok(s) = self.body_signature() { msg.field("body", &s); } if !self.fds().is_empty() { msg.field("fds", &self.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) = if let Ok(h) = header.as_ref() { ( h.message_type().ok(), h.error_name().ok().flatten(), h.sender().ok().flatten(), h.member().ok().flatten(), ) } else { (None, None, None, None) }; match ty { Some(MessageType::MethodCall) => { write!(f, "Method call")?; if let Some(m) = member { write!(f, " {}", m)?; } } Some(MessageType::MethodReturn) => { write!(f, "Method return")?; } Some(MessageType::Error) => { write!(f, "Error")?; if let Some(e) = error_name { write!(f, " {}", e)?; } let msg = self.body_unchecked::<&str>(); if let Ok(msg) = msg { write!(f, ": {}", msg)?; } } Some(MessageType::Signal) => { write!(f, "Signal")?; if let Some(m) = member { write!(f, " {}", m)?; } } _ => { write!(f, "Unknown message")?; } } if let Some(s) = sender { write!(f, " from {}", s)?; } Ok(()) } } #[cfg(test)] mod tests { use super::{Fds, Message, MessageError}; use std::os::unix::io::AsRawFd; use zvariant::Fd; #[test] fn test() { let stdout = std::io::stdout(); let m = Message::method( Some(":1.72"), None, "/", None, "do", &(Fd::from(&stdout), "foo"), ) .unwrap(); assert_eq!(m.body_signature().unwrap().to_string(), "hs"); assert_eq!(m.fds, Fds::Raw(vec![stdout.as_raw_fd()])); let body: Result = m.body(); assert_eq!(body.unwrap_err(), MessageError::UnmatchedBodySignature); assert_eq!(m.to_string(), "Method call do from :1.72"); let r = Message::method_reply(None, &m, &("all fine!")).unwrap(); assert_eq!(r.to_string(), "Method return"); let e = Message::method_error(None, &m, "org.freedesktop.zbus.Error", &("kaboom!", 32)) .unwrap(); assert_eq!(e.to_string(), "Error org.freedesktop.zbus.Error: kaboom!"); } } zbus-1.9.3/src/message_field.rs000064400000000000000000000200510072674642500145730ustar 00000000000000use std::convert::TryFrom; use serde::{ de::{Deserialize, Deserializer, Error}, ser::{Serialize, Serializer}, }; use serde_repr::{Deserialize_repr, Serialize_repr}; use zvariant::{derive::Type, ObjectPath, Signature, Str, Type, Value}; /// The message field code. /// /// Every [`MessageField`] 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 [`MessageFields`]. /// /// [`MessageField`]: enum.MessageField.html /// [retrieve a specific field]: struct.MessageFields.html#method.get_field /// [`MessageFields`]: struct.MessageFields.html #[repr(u8)] #[derive(Copy, Clone, Debug, Deserialize_repr, PartialEq, Serialize_repr, Type)] pub enum MessageFieldCode { /// Code for [`MessageField::Invalid`](enum.MessageField.html#variant.Invalid) Invalid = 0, /// Code for [`MessageField::Path`](enum.MessageField.html#variant.Path) Path = 1, /// Code for [`MessageField::Interface`](enum.MessageField.html#variant.Interface) Interface = 2, /// Code for [`MessageField::Member`](enum.MessageField.html#variant.Member) Member = 3, /// Code for [`MessageField::ErrorName`](enum.MessageField.html#variant.ErrorName) ErrorName = 4, /// Code for [`MessageField::ReplySerial`](enum.MessageField.html#variant.ReplySerial) ReplySerial = 5, /// Code for [`MessageField::Destinatione`](enum.MessageField.html#variant.Destination) Destination = 6, /// Code for [`MessageField::Sender`](enum.MessageField.html#variant.Sender) Sender = 7, /// Code for [`MessageField::Signature`](enum.MessageField.html#variant.Signature) Signature = 8, /// Code for [`MessageField::UnixFDs`](enum.MessageField.html#variant.UnixFDs) UnixFDs = 9, } impl From for MessageFieldCode { fn from(val: u8) -> MessageFieldCode { match val { 1 => MessageFieldCode::Path, 2 => MessageFieldCode::Interface, 3 => MessageFieldCode::Member, 4 => MessageFieldCode::ErrorName, 5 => MessageFieldCode::ReplySerial, 6 => MessageFieldCode::Destination, 7 => MessageFieldCode::Sender, 8 => MessageFieldCode::Signature, 9 => MessageFieldCode::UnixFDs, _ => MessageFieldCode::Invalid, } } } impl<'f> MessageField<'f> { /// Get the associated code for this field. pub fn code(&self) -> MessageFieldCode { match self { MessageField::Path(_) => MessageFieldCode::Path, MessageField::Interface(_) => MessageFieldCode::Interface, MessageField::Member(_) => MessageFieldCode::Member, MessageField::ErrorName(_) => MessageFieldCode::ErrorName, MessageField::ReplySerial(_) => MessageFieldCode::ReplySerial, MessageField::Destination(_) => MessageFieldCode::Destination, MessageField::Sender(_) => MessageFieldCode::Sender, MessageField::Signature(_) => MessageFieldCode::Signature, MessageField::UnixFDs(_) => MessageFieldCode::UnixFDs, MessageField::Invalid => MessageFieldCode::Invalid, } } } /// 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.MessageHeader.html /// [are fixed]: struct.MessagePrimaryHeader.html /// [Message Format]: https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-messages #[derive(Clone, Debug, PartialEq)] pub enum MessageField<'f> { /// Not a valid field. Invalid, /// 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(Str<'f>), /// The member, either the method name or signal name. Member(Str<'f>), /// The name of the error that occurred, for errors ErrorName(Str<'f>), /// The serial number of the message this message is a reply to. ReplySerial(u32), /// The name of the connection this message is intended for. Destination(Str<'f>), /// Unique name of the sending connection. Sender(Str<'f>), /// The signature of the message body. Signature(Signature<'f>), /// The number of Unix file descriptors that accompany the message. UnixFDs(u32), } impl<'f> Type for MessageField<'f> { fn signature() -> Signature<'static> { Signature::from_str_unchecked("(yv)") } } impl<'f> Serialize for MessageField<'f> { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let tuple: (MessageFieldCode, Value<'_>) = match self { MessageField::Path(value) => (MessageFieldCode::Path, value.clone().into()), MessageField::Interface(value) => (MessageFieldCode::Interface, value.as_str().into()), MessageField::Member(value) => (MessageFieldCode::Member, value.as_str().into()), MessageField::ErrorName(value) => (MessageFieldCode::ErrorName, value.as_str().into()), MessageField::ReplySerial(value) => (MessageFieldCode::ReplySerial, (*value).into()), MessageField::Destination(value) => { (MessageFieldCode::Destination, value.as_str().into()) } MessageField::Sender(value) => (MessageFieldCode::Sender, value.as_str().into()), MessageField::Signature(value) => (MessageFieldCode::Signature, value.clone().into()), MessageField::UnixFDs(value) => (MessageFieldCode::UnixFDs, (*value).into()), // This is a programmer error MessageField::Invalid => panic!("Attempt to serialize invalid MessageField"), }; tuple.serialize(serializer) } } impl<'de: 'f, 'f> Deserialize<'de> for MessageField<'f> { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { let (code, value) = <(MessageFieldCode, Value<'_>)>::deserialize(deserializer)?; Ok(match code { MessageFieldCode::Path => { MessageField::Path(ObjectPath::try_from(value).map_err(D::Error::custom)?) } MessageFieldCode::Interface => { MessageField::Interface(Str::try_from(value).map_err(D::Error::custom)?) } MessageFieldCode::Member => { MessageField::Member(Str::try_from(value).map_err(D::Error::custom)?) } MessageFieldCode::ErrorName => MessageField::ErrorName( Str::try_from(value) .map(Into::into) .map_err(D::Error::custom)?, ), MessageFieldCode::ReplySerial => { MessageField::ReplySerial(u32::try_from(value).map_err(D::Error::custom)?) } MessageFieldCode::Destination => MessageField::Destination( Str::try_from(value) .map(Into::into) .map_err(D::Error::custom)?, ), MessageFieldCode::Sender => MessageField::Sender( Str::try_from(value) .map(Into::into) .map_err(D::Error::custom)?, ), MessageFieldCode::Signature => { MessageField::Signature(Signature::try_from(value).map_err(D::Error::custom)?) } MessageFieldCode::UnixFDs => { MessageField::UnixFDs(u32::try_from(value).map_err(D::Error::custom)?) } MessageFieldCode::Invalid => { return Err(Error::invalid_value( serde::de::Unexpected::Unsigned(code as u64), &"A valid D-Bus message field code", )); } }) } } zbus-1.9.3/src/message_fields.rs000064400000000000000000000040360072674642500147630ustar 00000000000000use serde::{Deserialize, Serialize}; use zvariant::derive::Type; use crate::{MessageField, MessageFieldCode}; // 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 [`MessageField`] instances. /// /// [`MessageField`]: enum.MessageField.html #[derive(Debug, Serialize, Deserialize, Type)] pub struct MessageFields<'m>(#[serde(borrow)] Vec>); impl<'m> MessageFields<'m> { /// Creates an empty collection of fields. pub fn new() -> Self { Self::default() } /// Appends a [`MessageField`] to the collection of fields in the message. /// /// [`MessageField`]: enum.MessageField.html pub fn add<'f: 'm>(&mut self, field: MessageField<'f>) { self.0.push(field); } /// Returns a slice with all the [`MessageField`] in the message. /// /// [`MessageField`]: enum.MessageField.html pub fn get(&self) -> &[MessageField<'m>] { &self.0 } /// Gets a reference to a specific [`MessageField`] by its code. /// /// Returns `None` if the message has no such field. /// /// [`MessageField`]: enum.MessageField.html pub fn get_field(&self, code: MessageFieldCode) -> Option<&MessageField<'m>> { self.0.iter().find(|f| f.code() == code) } /// Consumes the `MessageFields` and returns a specific [`MessageField`] by its code. /// /// Returns `None` if the message has no such field. /// /// [`MessageField`]: enum.MessageField.html pub fn into_field(self, code: MessageFieldCode) -> Option> { for field in self.0 { if field.code() == code { return Some(field); } } None } } impl<'m> Default for MessageFields<'m> { fn default() -> Self { Self(Vec::with_capacity(MAX_FIELDS_IN_MESSAGE)) } } impl<'m> std::ops::Deref for MessageFields<'m> { type Target = [MessageField<'m>]; fn deref(&self) -> &Self::Target { self.get() } } zbus-1.9.3/src/message_header.rs000064400000000000000000000301500072674642500147410ustar 00000000000000use std::convert::TryFrom; use enumflags2::BitFlags; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use zvariant::{derive::Type, ObjectPath, Signature}; use crate::{MessageError, MessageField, MessageFieldCode, MessageFields}; pub(crate) const PRIMARY_HEADER_SIZE: usize = 12; pub(crate) const MIN_MESSAGE_SIZE: usize = PRIMARY_HEADER_SIZE + 4; /// D-Bus code for endianness. #[repr(u8)] #[derive(Debug, Copy, Clone, Deserialize_repr, PartialEq, Serialize_repr, Type)] 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', } // Such a shame I've to do this manually impl TryFrom for EndianSig { type Error = MessageError; fn try_from(val: u8) -> Result { match val { b'B' => Ok(EndianSig::Big), b'l' => Ok(EndianSig::Little), _ => Err(MessageError::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; /// Message header representing the D-Bus type of the message. #[repr(u8)] #[derive(Debug, Copy, Clone, Deserialize_repr, PartialEq, Serialize_repr, Type)] pub enum MessageType { /// Invalid message type. All unknown types on received messages are treated as invalid. Invalid = 0, /// 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, } // Such a shame I've to do this manually impl From for MessageType { fn from(val: u8) -> MessageType { match val { 1 => MessageType::MethodCall, 2 => MessageType::MethodReturn, 3 => MessageType::Error, 4 => MessageType::Signal, _ => MessageType::Invalid, } } } /// Pre-defined flags that can be passed in Message header. #[repr(u8)] #[derive(Debug, Copy, Clone, PartialEq, BitFlags, Type)] pub enum MessageFlags { /// 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 `MessageType::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, } /// 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(Debug, Serialize, Deserialize, Type)] pub struct MessagePrimaryHeader { endian_sig: EndianSig, msg_type: MessageType, flags: BitFlags, protocol_version: u8, body_len: u32, serial_num: u32, } impl MessagePrimaryHeader { /// Create a new `MessagePrimaryHeader` instance. pub fn new(msg_type: MessageType, body_len: u32) -> Self { Self { endian_sig: NATIVE_ENDIAN_SIG, msg_type, flags: BitFlags::empty(), protocol_version: 1, body_len, serial_num: u32::max_value(), } } /// D-Bus code for bytorder encoding of the message. pub fn endian_sig(&self) -> EndianSig { self.endian_sig } /// Set the D-Bus code for bytorder 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) -> MessageType { self.msg_type } /// Set the message type. pub fn set_msg_type(&mut self, msg_type: MessageType) { 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. /// /// **Note:** There is no setter provided for this in the public API since this is set by the /// [`Connection`](struct.Connection.html) the message is sent over. pub fn serial_num(&self) -> u32 { self.serial_num } pub(crate) fn set_serial_num(&mut self, serial: u32) { self.serial_num = serial; } } /// The message header, containing all the metadata about the message. /// /// This includes both the [`MessagePrimaryHeader`] and [`MessageFields`]. /// /// [`MessagePrimaryHeader`]: struct.MessagePrimaryHeader.html /// [`MessageFields`]: struct.MessageFields.html #[derive(Debug, Serialize, Deserialize, Type)] pub struct MessageHeader<'m> { primary: MessagePrimaryHeader, #[serde(borrow)] fields: MessageFields<'m>, end: ((),), // To ensure header end on 8-byte boundry } 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(MessageFieldCode::$kind) { Some(MessageField::$kind(value)) => Ok(Some($closure(value))), Some(_) => Err(MessageError::InvalidField), None => Ok(None), } }; } macro_rules! get_field_str { ($self:ident, $kind:ident) => { get_field!($self, $kind, (|v: &'s zvariant::Str<'m>| v.as_str())) }; } macro_rules! get_field_u32 { ($self:ident, $kind:ident) => { get_field!($self, $kind, (|v: &u32| *v)) }; } impl<'m> MessageHeader<'m> { /// Create a new `MessageHeader` instance. pub fn new(primary: MessagePrimaryHeader, fields: MessageFields<'m>) -> Self { Self { primary, fields, end: ((),), } } /// Get a reference to the primary header. pub fn primary(&self) -> &MessagePrimaryHeader { &self.primary } /// Get a mutable reference to the primary header. pub fn primary_mut(&mut self) -> &mut MessagePrimaryHeader { &mut self.primary } /// Get the primary header, consuming `self`. pub fn into_primary(self) -> MessagePrimaryHeader { self.primary } /// Get a reference to the message fields. pub fn fields<'s>(&'s self) -> &'s MessageFields<'m> { &self.fields } /// Get a mutable reference to the message fields. pub fn fields_mut<'s>(&'s mut self) -> &'s mut MessageFields<'m> { &mut self.fields } /// Get the message fields, consuming `self`. pub fn into_fields(self) -> MessageFields<'m> { self.fields } /// The message type pub fn message_type(&self) -> Result { Ok(self.primary().msg_type()) } /// The object to send a call to, or the object a signal is emitted from. pub fn path<'s>(&'s self) -> Result>, MessageError> { get_field!(self, Path) } /// The interface to invoke a method call on, or that a signal is emitted from. pub fn interface<'s>(&'s self) -> Result, MessageError> { get_field_str!(self, Interface) } /// The member, either the method name or signal name. pub fn member<'s>(&'s self) -> Result, MessageError> { get_field_str!(self, Member) } /// The name of the error that occurred, for errors. pub fn error_name<'s>(&'s self) -> Result, MessageError> { get_field_str!(self, ErrorName) } /// The serial number of the message this message is a reply to. pub fn reply_serial(&self) -> Result, MessageError> { get_field_u32!(self, ReplySerial) } /// The name of the connection this message is intended for. pub fn destination<'s>(&'s self) -> Result, MessageError> { get_field_str!(self, Destination) } /// Unique name of the sending connection. pub fn sender<'s>(&'s self) -> Result, MessageError> { get_field_str!(self, Sender) } /// The signature of the message body. pub fn signature(&self) -> Result>, MessageError> { get_field!(self, Signature) } /// The number of Unix file descriptors that accompany the message. pub fn unix_fds(&self) -> Result, MessageError> { get_field_u32!(self, UnixFDs) } } #[cfg(test)] mod tests { use crate::{MessageField, MessageFields, MessageHeader, MessagePrimaryHeader, MessageType}; use std::{convert::TryFrom, error::Error, result::Result}; use zvariant::{ObjectPath, Signature}; #[test] fn header() -> Result<(), Box> { let path = ObjectPath::try_from("/some/path")?; let mut f = MessageFields::new(); f.add(MessageField::Path(path.clone())); f.add(MessageField::Interface("some.interface".into())); f.add(MessageField::Member("Member".into())); f.add(MessageField::Sender(":1.84".into())); let h = MessageHeader::new(MessagePrimaryHeader::new(MessageType::Signal, 77), f); assert_eq!(h.message_type()?, MessageType::Signal); assert_eq!(h.path()?, Some(&path)); assert_eq!(h.interface()?, Some("some.interface")); 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()?, Some(":1.84")); assert_eq!(h.signature()?, None); assert_eq!(h.unix_fds()?, None); let mut f = MessageFields::new(); f.add(MessageField::ErrorName("org.zbus.Error".into())); f.add(MessageField::Destination(":1.11".into())); f.add(MessageField::ReplySerial(88)); f.add(MessageField::Signature(Signature::from_str_unchecked( "say", ))); f.add(MessageField::UnixFDs(12)); let h = MessageHeader::new(MessagePrimaryHeader::new(MessageType::MethodReturn, 77), f); assert_eq!(h.message_type()?, MessageType::MethodReturn); assert_eq!(h.path()?, None); assert_eq!(h.interface()?, None); assert_eq!(h.member()?, None); assert_eq!(h.error_name()?, Some("org.zbus.Error")); assert_eq!(h.destination()?, Some(":1.11")); assert_eq!(h.reply_serial()?, 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-1.9.3/src/object_server.rs000064400000000000000000000645360072674642500146600ustar 00000000000000use std::{ any::{Any, TypeId}, cell::RefCell, collections::{hash_map::Entry, HashMap}, fmt::Write, marker::PhantomData, rc::Rc, }; use scoped_tls::scoped_thread_local; use zvariant::{ObjectPath, OwnedValue, Value}; use crate::{dbus_interface, fdo, Connection, Error, Message, MessageHeader, MessageType, Result}; scoped_thread_local!(static LOCAL_NODE: Node); scoped_thread_local!(static LOCAL_CONNECTION: Connection); /// The trait used to dispatch messages to an interface instance. /// /// Note: It is not recommended to manually implement this trait. The [`dbus_interface`] macro /// implements it for you. /// /// [`dbus_interface`]: attr.dbus_interface.html pub trait Interface: Any { /// Return the name of the interface. Ex: "org.foo.MyInterface" fn name() -> &'static str where Self: Sized; /// Get a property value. Returns `None` if the property doesn't exist. fn get(&self, property_name: &str) -> Option>; /// Return all the properties. fn get_all(&self) -> HashMap; /// Set a property value. Returns `None` if the property doesn't exist. fn set(&mut self, property_name: &str, value: &Value<'_>) -> Option>; /// Call a `&self` method. Returns `None` if the method doesn't exist. fn call(&self, connection: &Connection, msg: &Message, name: &str) -> Option>; /// Call a `&mut self` method. Returns `None` if the method doesn't exist. fn call_mut( &mut self, connection: &Connection, msg: &Message, name: &str, ) -> Option>; /// Write introspection XML to the writer, with the given indentation level. fn introspect_to_writer(&self, writer: &mut dyn Write, level: usize); } impl dyn Interface { /// Return Any of self 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 } } } struct Introspectable; #[dbus_interface(name = "org.freedesktop.DBus.Introspectable")] impl Introspectable { fn introspect(&self) -> String { LOCAL_NODE.with(|node| node.introspect()) } } struct Peer; #[dbus_interface(name = "org.freedesktop.DBus.Peer")] impl Peer { fn ping(&self) {} fn get_machine_id(&self) -> fdo::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(fdo::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) } } struct Properties; #[dbus_interface(name = "org.freedesktop.DBus.Properties")] impl Properties { fn get(&self, interface_name: &str, property_name: &str) -> fdo::Result { LOCAL_NODE.with(|node| { let iface = node.get_interface(interface_name).ok_or_else(|| { fdo::Error::UnknownInterface(format!("Unknown interface '{}'", interface_name)) })?; let res = iface.borrow().get(property_name); res.ok_or_else(|| { fdo::Error::UnknownProperty(format!("Unknown property '{}'", property_name)) })? }) } // TODO: should be able to take a &Value instead (but obscure deserialize error for now..) fn set( &mut self, interface_name: &str, property_name: &str, value: OwnedValue, ) -> fdo::Result<()> { LOCAL_NODE.with(|node| { let iface = node.get_interface(interface_name).ok_or_else(|| { fdo::Error::UnknownInterface(format!("Unknown interface '{}'", interface_name)) })?; let res = iface.borrow_mut().set(property_name, &value); res.ok_or_else(|| { fdo::Error::UnknownProperty(format!("Unknown property '{}'", property_name)) })? }) } fn get_all(&self, interface_name: &str) -> fdo::Result> { LOCAL_NODE.with(|node| { let iface = node.get_interface(interface_name).ok_or_else(|| { fdo::Error::UnknownInterface(format!("Unknown interface '{}'", interface_name)) })?; let res = iface.borrow().get_all(); Ok(res) }) } #[dbus_interface(signal)] fn properties_changed( &self, interface_name: &str, changed_properties: &HashMap<&str, &Value<'_>>, invalidated_properties: &[&str], ) -> Result<()>; } #[derive(Default, derivative::Derivative)] #[derivative(Debug)] struct Node { path: String, children: HashMap, #[derivative(Debug = "ignore")] interfaces: HashMap<&'static str, Rc>>, } impl Node { fn new(path: &str) -> Self { let mut node = Self { path: path.to_string(), ..Default::default() }; node.at(Peer::name(), Peer); node.at(Introspectable::name(), Introspectable); node.at(Properties::name(), Properties); node } fn get_interface(&self, iface: &str) -> Option>> { self.interfaces.get(iface).cloned() } fn remove_interface(&mut self, iface: &str) -> bool { self.interfaces.remove(iface).is_some() } fn is_empty(&self) -> bool { self.interfaces .keys() .find(|k| { *k != &Peer::name() && *k != &Introspectable::name() && *k != &Properties::name() }) .is_none() } fn remove_node(&mut self, node: &str) -> bool { self.children.remove(node).is_some() } fn at(&mut self, name: &'static str, iface: I) -> bool where I: Interface, { match self.interfaces.entry(name) { Entry::Vacant(e) => e.insert(Rc::new(RefCell::new(iface))), Entry::Occupied(_) => return false, }; true } fn with_iface_func(&self, func: F) -> Result<()> where F: Fn(&I) -> Result<()>, I: Interface, { let iface = self .interfaces .get(I::name()) .ok_or(Error::InterfaceNotFound)? .borrow(); let iface = iface.downcast_ref::().ok_or(Error::InterfaceNotFound)?; func(iface) } fn introspect_to_writer(&self, writer: &mut W, level: usize) { if level == 0 { writeln!( writer, r#" "# ) .unwrap(); } for iface in self.interfaces.values() { iface.borrow().introspect_to_writer(writer, level + 2); } for (path, node) in &self.children { let level = level + 2; writeln!( writer, "{:indent$}", "", path, indent = level ) .unwrap(); node.introspect_to_writer(writer, level); writeln!(writer, "{:indent$}", "", indent = level).unwrap(); } if level == 0 { writeln!(writer, "").unwrap(); } } fn introspect(&self) -> String { let mut xml = String::with_capacity(1024); self.introspect_to_writer(&mut xml, 0); xml } fn emit_signal( &self, dest: Option<&str>, iface: &str, signal_name: &str, body: &B, ) -> Result<()> where B: serde::ser::Serialize + zvariant::Type, { if !LOCAL_CONNECTION.is_set() { panic!("emit_signal: Connection TLS not set"); } LOCAL_CONNECTION.with(|conn| conn.emit_signal(dest, &self.path, iface, signal_name, body)) } } /// 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`. /// /// **NOTE:** The lifetime `'a` on the `ObjectServer` struct is bogus and only exists for /// backwards-compatibility and will be dropped in the next major release (i-e 2.0). This means /// that `'a` can be considered `'static` for all intents and purposes. /// /// # Example /// /// This example exposes the `org.myiface.Example.Quit` method on the `/org/zbus/path` /// path. /// /// ```no_run ///# use std::error::Error; ///# use std::convert::TryInto; /// use zbus::{Connection, ObjectServer, dbus_interface}; /// use std::rc::Rc; /// use std::cell::RefCell; /// /// struct Example { /// // Interfaces are owned by the ObjectServer. They can have /// // `&mut self` methods. /// // /// // If you need a shared state, you can use a RefCell for ex: /// quit: Rc>, /// } /// /// impl Example { /// fn new(quit: Rc>) -> Self { /// Self { quit } /// } /// } /// /// #[dbus_interface(name = "org.myiface.Example")] /// impl Example { /// // This will be the "Quit" D-Bus method. /// fn quit(&self) { /// *self.quit.borrow_mut() = true; /// } /// /// // See `dbus_interface` documentation to learn /// // how to expose properties & signals as well. /// } /// /// let connection = Connection::new_session()?; /// let mut object_server = ObjectServer::new(&connection); /// let quit = Rc::new(RefCell::new(false)); /// /// let interface = Example::new(quit.clone()); /// object_server.at(&"/org/zbus/path".try_into()?, interface)?; /// /// loop { /// if let Err(err) = object_server.try_handle_next() { /// eprintln!("{}", err); /// } /// /// if *quit.borrow() { /// break; /// } /// } ///# Ok::<_, Box>(()) /// ``` #[derive(Debug)] pub struct ObjectServer<'a> { conn: Connection, root: Node, phantom: PhantomData<&'a ()>, } impl<'a> ObjectServer<'a> { /// Creates a new D-Bus `ObjectServer` for a given connection. pub fn new(connection: &Connection) -> Self { Self { conn: connection.clone(), root: Node::new("/"), phantom: PhantomData, } } // Get the Node at path. fn get_node(&self, path: &ObjectPath<'_>) -> Option<&Node> { let mut node = &self.root; let mut node_path = String::new(); for i in path.split('/').skip(1) { if i.is_empty() { continue; } write!(&mut node_path, "/{}", i).unwrap(); match node.children.get(i) { Some(n) => node = n, None => return None, } } Some(node) } // Get the Node at path. Optionally create one if it doesn't exist. fn get_node_mut(&mut self, path: &ObjectPath<'_>, create: bool) -> Option<&mut Node> { let mut node = &mut self.root; let mut node_path = String::new(); for i in path.split('/').skip(1) { if i.is_empty() { continue; } write!(&mut node_path, "/{}", i).unwrap(); match node.children.entry(i.into()) { Entry::Vacant(e) => { if create { node = e.insert(Node::new(&node_path)); } else { return None; } } Entry::Occupied(e) => node = e.into_mut(), } } Some(node) } /// Register a D-Bus [`Interface`] at a given path. (see the example above) /// /// If the interface already exists at this path, returns false. /// /// [`Interface`]: trait.Interface.html pub fn at(&mut self, path: &ObjectPath<'_>, iface: I) -> Result where I: Interface, { Ok(self.get_node_mut(path, true).unwrap().at(I::name(), 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(&mut self, path: &ObjectPath<'_>) -> Result where I: Interface, { let node = self .get_node_mut(path, false) .ok_or(Error::InterfaceNotFound)?; if !node.remove_interface(I::name()) { return Err(Error::InterfaceNotFound); } 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)), ); self.get_node_mut(&ppath, false) .unwrap() .remove_node(last_part); return Ok(true); } Ok(false) } /// Run `func` with the given path & interface. /// /// Run the function `func` with the interface at path. If the interface was not found, return /// `Error::InterfaceNotFound`. /// /// This function is useful to emit signals outside of a dispatched handler: /// ```no_run ///# use std::error::Error; ///# use std::convert::TryInto; ///# use zbus::{Connection, ObjectServer, dbus_interface}; /// ///# struct MyIface; ///# #[dbus_interface(name = "org.myiface.MyIface")] ///# impl MyIface { ///# #[dbus_interface(signal)] ///# fn emit_signal(&self) -> zbus::Result<()>; ///# } ///# ///# let connection = Connection::new_session()?; ///# let mut object_server = ObjectServer::new(&connection); ///# ///# let path = &"/org/zbus/path".try_into()?; ///# object_server.at(path, MyIface)?; /// object_server.with(path, |iface: &MyIface| { /// iface.emit_signal() /// })?; ///# ///# ///# Ok::<_, Box>(()) /// ``` pub fn with(&self, path: &ObjectPath<'_>, func: F) -> Result<()> where F: Fn(&I) -> Result<()>, I: Interface, { let node = self.get_node(path).ok_or(Error::InterfaceNotFound)?; LOCAL_CONNECTION.set(&self.conn, || { LOCAL_NODE.set(node, || node.with_iface_func(func)) }) } /// Emit a signal on the currently dispatched node. /// /// This is an internal helper function to emit a signal on on the current node. You shouldn't /// call this method directly, rather with the derived signal implementation from /// [`dbus_interface`]. /// /// # Panics /// /// This method will panic if called from outside of a node context. Use [`ObjectServer::with`] /// to bring a node into the current context. /// /// [`dbus_interface`]: attr.dbus_interface.html pub fn local_node_emit_signal( destination: Option<&str>, iface: &str, signal_name: &str, body: &B, ) -> Result<()> where B: serde::ser::Serialize + zvariant::Type, { if !LOCAL_NODE.is_set() { panic!("emit_signal: Node TLS not set"); } LOCAL_NODE.with(|n| n.emit_signal(destination, iface, signal_name, body)) } fn dispatch_method_call_try( &mut self, msg_header: &MessageHeader<'_>, msg: &Message, ) -> fdo::Result> { let conn = self.conn.clone(); let path = msg_header .path() .ok() .flatten() .ok_or_else(|| fdo::Error::Failed("Missing object path".into()))?; let iface = msg_header .interface() .ok() .flatten() // 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()))?; let member = msg_header .member() .ok() .flatten() .ok_or_else(|| fdo::Error::Failed("Missing member".into()))?; let node = self .get_node_mut(&path, false) .ok_or_else(|| fdo::Error::UnknownObject(format!("Unknown object '{}'", path)))?; let iface = node.get_interface(iface).ok_or_else(|| { fdo::Error::UnknownInterface(format!("Unknown interface '{}'", iface)) })?; LOCAL_CONNECTION.set(&conn, || { LOCAL_NODE.set(node, || { let res = iface.borrow().call(&conn, &msg, member); res.or_else(|| iface.borrow_mut().call_mut(&conn, &msg, member)) .ok_or_else(|| { fdo::Error::UnknownMethod(format!("Unknown method '{}'", member)) }) }) }) } fn dispatch_method_call( &mut self, msg_header: &MessageHeader<'_>, msg: &Message, ) -> Result { match self.dispatch_method_call_try(msg_header, msg) { Err(e) => e.reply(&self.conn, msg), Ok(r) => r, } } /// 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, true if it's handled, false otherwise. /// /// # Note /// /// This API is subject to change, or becoming internal-only once zbus provides a general /// mechanism to dispatch messages. pub fn dispatch_message(&mut self, msg: &Message) -> Result { let msg_header = msg.header()?; match msg_header.message_type()? { MessageType::MethodCall => { self.dispatch_method_call(&msg_header, &msg)?; Ok(true) } _ => Ok(false), } } /// Receive and handle the next message from the associated connection. /// /// This function will read the incoming message from /// [`receive_message()`](Connection::receive_message) of the associated connection and pass it /// to [`dispatch_message()`](Self::dispatch_message). If the message was handled by an an /// interface, it returns `Ok(None)`. If not, it returns the received message. /// /// Returns an error if the message is malformed or an error occured. /// /// # Note /// /// This API is subject to change, or becoming internal-only once zbus provides a general /// mechanism to dispatch messages. pub fn try_handle_next(&mut self) -> Result> { let msg = self.conn.receive_message()?; if !self.dispatch_message(&msg)? { return Ok(Some(msg)); } Ok(None) } } #[cfg(test)] mod tests { use std::{cell::Cell, collections::HashMap, convert::TryInto, error::Error, rc::Rc, thread}; use ntest::timeout; use serde::{Deserialize, Serialize}; use zvariant::derive::Type; use crate::{ dbus_interface, dbus_proxy, fdo, Connection, MessageHeader, MessageType, ObjectServer, }; #[derive(Deserialize, Serialize, Type)] pub struct ArgStructTest { foo: i32, bar: String, } #[dbus_proxy] trait MyIface { fn ping(&self) -> zbus::Result; fn quit(&self) -> zbus::Result<()>; fn test_header(&self) -> zbus::Result<()>; fn test_error(&self) -> zbus::Result<()>; fn test_single_struct_arg(&self, arg: ArgStructTest) -> zbus::Result<()>; fn test_hashmap_return(&self) -> zbus::Result>; fn create_obj(&self, key: &str) -> zbus::Result<()>; fn destroy_obj(&self, key: &str) -> zbus::Result<()>; #[dbus_proxy(property)] fn count(&self) -> zbus::Result; #[dbus_proxy(property)] fn set_count(&self, count: u32) -> zbus::Result<()>; #[dbus_proxy(property)] fn hash_map(&self) -> zbus::Result>; } #[derive(Debug, Clone)] enum NextAction { Nothing, Quit, CreateObj(String), DestroyObj(String), } struct MyIfaceImpl { action: Rc>, count: u32, } impl MyIfaceImpl { fn new(action: Rc>) -> Self { Self { action, count: 0 } } } #[dbus_interface(interface = "org.freedesktop.MyIface")] impl MyIfaceImpl { fn ping(&mut self) -> u32 { self.count += 1; if self.count % 3 == 0 { self.alert_count(self.count).expect("Failed to emit signal"); } self.count } fn quit(&mut self) { self.action.set(NextAction::Quit); } fn test_header(&self, #[zbus(header)] header: MessageHeader<'_>) { assert_eq!(header.message_type().unwrap(), MessageType::MethodCall); assert_eq!(header.member().unwrap(), Some("TestHeader")); } fn test_error(&self) -> zbus::fdo::Result<()> { Err(zbus::fdo::Error::Failed("error raised".to_string())) } fn test_single_struct_arg(&self, arg: ArgStructTest) { assert_eq!(arg.foo, 1); assert_eq!(arg.bar, "TestString"); } fn test_hashmap_return(&self) -> zbus::Result> { let mut map = HashMap::new(); map.insert("hi".into(), "hello".into()); map.insert("bye".into(), "now".into()); Ok(map) } fn create_obj(&self, key: String) { self.action.set(NextAction::CreateObj(key)); } fn destroy_obj(&self, key: String) { self.action.set(NextAction::DestroyObj(key)); } #[dbus_interface(property)] fn set_count(&mut self, val: u32) -> zbus::fdo::Result<()> { if val == 42 { return Err(zbus::fdo::Error::InvalidArgs("Tsss tsss!".to_string())); } self.count = val; Ok(()) } #[dbus_interface(property)] fn count(&self) -> u32 { self.count } #[dbus_interface(property)] fn hash_map(&self) -> HashMap { self.test_hashmap_return().unwrap() } #[dbus_interface(signal)] fn alert_count(&self, val: u32) -> zbus::Result<()>; } fn check_hash_map(map: HashMap) { assert_eq!(map["hi"], "hello"); assert_eq!(map["bye"], "now"); } fn my_iface_test() -> std::result::Result> { let conn = Connection::new_session()?; let proxy = MyIfaceProxy::new_for( &conn, "org.freedesktop.MyService", "/org/freedesktop/MyService", )?; proxy.ping()?; assert_eq!(proxy.count()?, 1); proxy.test_header()?; proxy.test_single_struct_arg(ArgStructTest { foo: 1, bar: "TestString".into(), })?; check_hash_map(proxy.test_hashmap_return()?); check_hash_map(proxy.hash_map()?); proxy.introspect()?; let val = proxy.ping()?; proxy.create_obj("MyObj")?; let my_obj_proxy = MyIfaceProxy::new_for(&conn, "org.freedesktop.MyService", "/zbus/test/MyObj")?; my_obj_proxy.ping()?; proxy.destroy_obj("MyObj")?; assert!(my_obj_proxy.introspect().is_err()); assert!(my_obj_proxy.ping().is_err()); proxy.quit()?; Ok(val) } #[test] #[timeout(2000)] fn basic_iface() { let conn = Connection::new_session().unwrap(); let mut object_server = ObjectServer::new(&conn); let action = Rc::new(Cell::new(NextAction::Nothing)); fdo::DBusProxy::new(&conn) .unwrap() .request_name( "org.freedesktop.MyService", fdo::RequestNameFlags::ReplaceExisting.into(), ) .unwrap(); let iface = MyIfaceImpl::new(action.clone()); object_server .at(&"/org/freedesktop/MyService".try_into().unwrap(), iface) .unwrap(); let child = thread::spawn(|| my_iface_test().expect("child failed")); loop { let m = conn.receive_message().unwrap(); if let Err(e) = object_server.dispatch_message(&m) { eprintln!("{}", e); } object_server .with( &"/org/freedesktop/MyService".try_into().unwrap(), |iface: &MyIfaceImpl| iface.alert_count(51), ) .unwrap(); match action.replace(NextAction::Nothing) { NextAction::Nothing => (), NextAction::Quit => break, NextAction::CreateObj(key) => { let path = format!("/zbus/test/{}", key); object_server .at(&path.try_into().unwrap(), MyIfaceImpl::new(action.clone())) .unwrap(); } NextAction::DestroyObj(key) => { let path = format!("/zbus/test/{}", key); object_server .remove::(&path.try_into().unwrap()) .unwrap(); } } } let val = child.join().expect("failed to join"); assert_eq!(val, 2); } } zbus-1.9.3/src/owned_fd.rs000064400000000000000000000016540072674642500136010ustar 00000000000000use std::{ mem::forget, os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}, }; /// An owned representation of a file descriptor /// /// When it is dropped, the underlying frile descriptor will be dropped. /// You can take ownership of the file descriptor (and avoid it being closed) /// by using the /// [`IntoRawFd`](https://doc.rust-lang.org/stable/std/os/unix/io/trait.IntoRawFd.html) /// implementation. #[derive(Debug, PartialEq, Eq)] pub struct OwnedFd { inner: RawFd, } impl FromRawFd for OwnedFd { unsafe fn from_raw_fd(fd: RawFd) -> OwnedFd { OwnedFd { inner: fd } } } impl AsRawFd for OwnedFd { fn as_raw_fd(&self) -> RawFd { self.inner } } impl IntoRawFd for OwnedFd { fn into_raw_fd(self) -> RawFd { let v = self.inner; forget(self); v } } impl Drop for OwnedFd { fn drop(&mut self) { let _ = nix::unistd::close(self.inner); } } zbus-1.9.3/src/proxy.rs000064400000000000000000000316430072674642500131760ustar 00000000000000use std::{ borrow::Cow, collections::HashMap, convert::{TryFrom, TryInto}, sync::Mutex, }; use zvariant::{ObjectPath, OwnedValue, Value}; use crate::{Connection, Error, Message, Result}; use crate::fdo::{self, IntrospectableProxy, PropertiesProxy}; const LOCK_FAIL_MSG: &str = "Failed to lock a mutex or read-write lock"; type SignalHandler = Box Result<()> + Send>; /// 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}; /// /// fn main() -> Result<(), Box> { /// let connection = Connection::new_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 _id: &str = p.call_method("GetId", &())?.body()?; /// Ok(()) /// } /// ``` /// /// # Note /// /// It is recommended to use the [`dbus_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: /// /// * cache properties /// * track the current name owner /// * prevent auto-launching /// /// [`dbus_proxy`]: attr.dbus_proxy.html pub struct Proxy<'a> { conn: Connection, destination: Cow<'a, str>, path: Cow<'a, str>, interface: Cow<'a, str>, sig_handlers: Mutex>, } impl<'a> Proxy<'a> { /// Create a new `Proxy` for the given destination/path/interface. pub fn new( conn: &Connection, destination: &'a str, path: &'a str, interface: &'a str, ) -> Result { Ok(Self { conn: conn.clone(), destination: Cow::from(destination), path: Cow::from(path), interface: Cow::from(interface), sig_handlers: Mutex::new(HashMap::new()), }) } /// Create a new `Proxy` for the given destination/path/interface, taking ownership of all /// passed arguments. pub fn new_owned( conn: Connection, destination: String, path: String, interface: String, ) -> Result { Ok(Self { conn, destination: Cow::from(destination), path: Cow::from(path), interface: Cow::from(interface), sig_handlers: Mutex::new(HashMap::new()), }) } /// 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) -> &str { &self.destination } /// Get a reference to the object path. pub fn path(&self) -> &str { &self.path } /// Get a reference to the interface. pub fn interface(&self) -> &str { &self.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 { IntrospectableProxy::new_for(&self.conn, &self.destination, &self.path)?.introspect() } /// Get the property `property_name`. /// /// Effectively, call the `Get` method of the `org.freedesktop.DBus.Properties` interface. pub fn get_property(&self, property_name: &str) -> fdo::Result where T: TryFrom, { PropertiesProxy::new_for(&self.conn, &self.destination, &self.path)? .get(&self.interface, property_name)? .try_into() .map_err(|_| Error::InvalidReply.into()) } /// Set the property `property_name`. /// /// Effectively, call the `Set` method of the `org.freedesktop.DBus.Properties` interface. pub fn set_property<'t, T: 't>(&self, property_name: &str, value: T) -> fdo::Result<()> where T: Into>, { PropertiesProxy::new_for(&self.conn, &self.destination, &self.path)?.set( &self.interface, property_name, &value.into(), ) } /// 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(&self, method_name: &str, body: &B) -> Result where B: serde::ser::Serialize + zvariant::Type, { let reply = self.conn.call_method( Some(&self.destination), &self.path, Some(&self.interface), method_name, body, ); match reply { Ok(mut reply) => { reply.disown_fds(); Ok(reply) } Err(e) => Err(e), } } /// 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(&self, method_name: &str, body: &B) -> Result where B: serde::ser::Serialize + zvariant::Type, R: serde::de::DeserializeOwned + zvariant::Type, { Ok(self.call_method(method_name, body)?.body()?) } /// Register a handler for signal named `signal_name`. /// /// Once a handler is successfully registered, call [`Self::next_signal`] to wait for the next /// signal to arrive and be handled by its registered handler. /// /// If the associated connnection is to a bus, a match rule is added for the signal on the bus /// so that the bus sends us the signals. /// /// ### Errors /// /// This method can fail if addition of the relevant match rule on the bus fails. You can /// safely `unwrap` the `Result` if you're certain that associated connnection is not a bus /// connection. pub fn connect_signal(&self, signal_name: &'static str, handler: H) -> fdo::Result<()> where H: FnMut(&Message) -> Result<()> + Send + 'static, { if self .sig_handlers .lock() .expect(LOCK_FAIL_MSG) .insert(signal_name, Box::new(handler)) .is_none() && self.conn.is_bus() { let rule = self.match_rule_for_signal(signal_name); fdo::DBusProxy::new(&self.conn)?.add_match(&rule)?; } Ok(()) } /// Deregister the handler for the signal named `signal_name`. /// /// If the associated connnection is to a bus, the match rule is removed for the signal on the /// bus so that the bus stops sending us the signal. This method returns `Ok(true)` if a /// handler was registered for `signal_name` and was removed by this call; `Ok(false)` /// otherwise. /// /// ### Errors /// /// This method can fail if removal of the relevant match rule on the bus fails. You can /// safely `unwrap` the `Result` if you're certain that associated connnection is not a bus /// connection. pub fn disconnect_signal(&self, signal_name: &'static str) -> fdo::Result { if self .sig_handlers .lock() .expect(LOCK_FAIL_MSG) .remove(signal_name) .is_some() && self.conn.is_bus() { let rule = self.match_rule_for_signal(signal_name); fdo::DBusProxy::new(&self.conn)?.remove_match(&rule)?; Ok(true) } else { Ok(false) } } /// Receive and handle the next incoming signal on the associated connection. /// /// This method will wait for signal messages on the associated connection and call any /// handlers registered through the [`Self::connect_signal`] method. Signal handlers can be /// registered and deregistered from another threads during the call to this method. /// /// If the signal message was handled by a handler, `Ok(None)` is returned. Otherwise, the /// received message is returned. pub fn next_signal(&self) -> Result> { let msg = self.conn.receive_specific(|msg| { let handlers = self.sig_handlers.lock().expect(LOCK_FAIL_MSG); if handlers.is_empty() { // No signal handers associated anymore so no need to continue. return Ok(true); } let hdr = msg.header()?; let member = match hdr.member()? { Some(m) => m, None => return Ok(false), }; Ok(hdr.interface()? == Some(&self.interface) && hdr.path()? == Some(&ObjectPath::try_from(self.path.as_ref())?) && hdr.message_type()? == crate::MessageType::Signal && handlers.contains_key(member)) })?; if self.handle_signal(&msg)? { Ok(None) } else { Ok(Some(msg)) } } /// Handle the provided signal message. /// /// Call any handlers registered through the [`Self::connect_signal`] method for the provided /// signal message. /// /// If no errors are encountered, `Ok(true)` is returned if a handler was found and called for, /// the signal; `Ok(false)` otherwise. pub fn handle_signal(&self, msg: &Message) -> Result { let mut handlers = self.sig_handlers.lock().expect(LOCK_FAIL_MSG); if handlers.is_empty() { return Ok(false); } let hdr = msg.header()?; if let Some(name) = hdr.member()? { if let Some(handler) = handlers.get_mut(name) { handler(&msg)?; return Ok(true); } } Ok(false) } pub(crate) fn has_signal_handler(&self, signal_name: &str) -> bool { self.sig_handlers .lock() .expect(LOCK_FAIL_MSG) .contains_key(signal_name) } fn match_rule_for_signal(&self, signal_name: &'static str) -> String { // FIXME: Use the API to create this once we've it (issue#69). format!( "type='signal',path_namespace='{}',interface='{}',member='{}'", self.path, self.interface, signal_name, ) } } impl<'asref, 'p: 'asref> std::convert::AsRef> for Proxy<'p> { fn as_ref(&self) -> &Proxy<'asref> { &self } } #[cfg(test)] mod tests { use super::*; use std::sync::Arc; #[test] fn signal() { // Register a well-known name with the session bus and ensure we get the appropriate // signals called for that. let conn = Connection::new_session().unwrap(); let owner_change_signaled = Arc::new(Mutex::new(false)); let name_acquired_signaled = Arc::new(Mutex::new(false)); let proxy = Proxy::new( &conn, "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", ) .unwrap(); let well_known = "org.freedesktop.zbus.ProxySignalTest"; let unique_name = conn.unique_name().unwrap().to_string(); { let well_known = well_known.clone(); let signaled = owner_change_signaled.clone(); proxy .connect_signal("NameOwnerChanged", move |m| { let (name, _, new_owner) = m.body::<(&str, &str, &str)>()?; if name != well_known { // Meant for the other testcase then return Ok(()); } assert_eq!(new_owner, unique_name); *signaled.lock().unwrap() = true; Ok(()) }) .unwrap(); } { let signaled = name_acquired_signaled.clone(); // `NameAcquired` is emitted twice, first when the unique name is assigned on // connection and secondly after we ask for a specific name. proxy .connect_signal("NameAcquired", move |m| { if m.body::<&str>()? == well_known { *signaled.lock().unwrap() = true; } Ok(()) }) .unwrap(); } fdo::DBusProxy::new(&conn) .unwrap() .request_name(&well_known, fdo::RequestNameFlags::ReplaceExisting.into()) .unwrap(); loop { proxy.next_signal().unwrap(); if *owner_change_signaled.lock().unwrap() && *name_acquired_signaled.lock().unwrap() { break; } } } } zbus-1.9.3/src/raw/connection.rs000064400000000000000000000160700072674642500147420ustar 00000000000000use std::{collections::VecDeque, io}; use crate::{message::Message, message_header::MIN_MESSAGE_SIZE, raw::Socket, OwnedFd}; /// A low-level representation of a D-Bus connection /// /// This wrapper is agnostic on the actual transport, using the `Socket` trait /// to abstract it. It is compatible with sockets both in blocking or non-blocking /// mode. /// /// This wrapper abstracts away the serialization & buffering considerations of the /// protocol, and allows interaction based on messages, rather than bytes. #[derive(derivative::Derivative)] #[derivative(Debug)] pub struct Connection { #[derivative(Debug = "ignore")] socket: S, raw_in_buffer: Vec, raw_in_fds: Vec, msg_in_buffer: Option, raw_out_buffer: VecDeque, msg_out_buffer: VecDeque, } impl Connection { pub(crate) fn wrap(socket: S) -> Connection { Connection { socket, raw_in_buffer: vec![], raw_in_fds: vec![], msg_in_buffer: None, raw_out_buffer: VecDeque::new(), msg_out_buffer: VecDeque::new(), } } /// Attempt to flush the outgoing buffer /// /// This will try to write as many messages as possible from the /// outgoing buffer into the socket, until an error is encountered. /// /// This method will thus only block if the socket is in blocking mode. pub fn try_flush(&mut self) -> io::Result<()> { // first, empty the raw_out_buffer of any partially-sent message while !self.raw_out_buffer.is_empty() { let (front, _) = self.raw_out_buffer.as_slices(); // VecDeque should never return an empty front buffer if the VecDeque // itself is not empty debug_assert!(!front.is_empty()); let written = self.socket.sendmsg(front, &[])?; self.raw_out_buffer.drain(..written); } // now, try to drain the msg_out_buffer while let Some(msg) = self.msg_out_buffer.front() { let mut data = msg.as_bytes(); let fds = msg.fds(); let written = self.socket.sendmsg(data, &fds)?; // at least some part of the message has been sent, see if we can/need to send more // now the message must be removed from msg_out_buffer and any leftover bytes // must be stored into raw_out_buffer let msg = self.msg_out_buffer.pop_front().unwrap(); data = &msg.as_bytes()[written..]; while !data.is_empty() { match self.socket.sendmsg(data, &[]) { Ok(n) => data = &data[n..], Err(e) => { // an error occured, we cannot send more, store the remaining into // raw_out_buffer and forward the error self.raw_out_buffer.extend(data); return Err(e); } } } } Ok(()) } /// Enqueue a message to be sent out to the socket /// /// This method will *not* write anything to the socket, you need to call /// `try_flush()` afterwards so that your message is actually sent out. pub fn enqueue_message(&mut self, msg: Message) { self.msg_out_buffer.push_back(msg); } /// Attempt to read a message from the socket /// /// This methods will read from the socket until either a full D-Bus message is /// read or an error is encountered. /// /// If the socket is in non-blocking mode, it may read a partial message. In such case it /// will buffer it internally and try to complete it the next time you call `try_receive_message`. pub fn try_receive_message(&mut self) -> crate::Result { if self.msg_in_buffer.is_none() { // 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 self.raw_in_buffer.len() < MIN_MESSAGE_SIZE { let current_bytes = self.raw_in_buffer.len(); let mut buf = vec![0; MIN_MESSAGE_SIZE - current_bytes]; let (read, fds) = self.socket.recvmsg(&mut buf)?; self.raw_in_buffer.extend(&buf[..read]); self.raw_in_fds.extend(fds); } // We now have a full message header, so let us construct the Message self.msg_in_buffer = Some(Message::from_bytes(&self.raw_in_buffer)?); self.raw_in_buffer.clear(); } // At this point, we must have a partial message in self.msg_in_buffer, and we // need to complete it { let msg = self.msg_in_buffer.as_mut().unwrap(); loop { match msg.bytes_to_completion() { Ok(0) => { // the message is now complete, we can return break; } Ok(needed) => { // we need to read more data let mut buf = vec![0; needed]; let (read, fds) = self.socket.recvmsg(&mut buf)?; msg.add_bytes(&buf[..read])?; self.raw_in_fds.extend(fds); } Err(e) => { // the message is invalid, return the error return Err(e.into()); } } } } // If we reach here, the message is complete, return it let mut msg = self.msg_in_buffer.take().unwrap(); msg.set_owned_fds(std::mem::replace(&mut self.raw_in_fds, vec![])); Ok(msg) } /// Close the connection. /// /// After this call, all reading and writing operations will fail. pub fn close(&self) -> crate::Result<()> { self.socket().close().map_err(|e| e.into()) } /// Access the underlying socket /// /// This method is intended to provide access to the socket in order to register it /// to you event loop, for async integration. /// /// You should not try to read or write from it directly, as it may /// corrupt the internal state of this wrapper. pub fn socket(&self) -> &S { &self.socket } } #[cfg(test)] mod tests { use super::Connection; use crate::message::Message; use std::os::unix::net::UnixStream; #[test] fn raw_send_receive() { let (p0, p1) = UnixStream::pair().unwrap(); let mut conn0 = Connection::wrap(p0); let mut conn1 = Connection::wrap(p1); let msg = Message::method(None, None, "/", Some("org.zbus.p2p"), "Test", &()).unwrap(); conn0.enqueue_message(msg); conn0.try_flush().unwrap(); let ret = conn1.try_receive_message().unwrap(); assert_eq!(ret.to_string(), "Method call Test"); } } zbus-1.9.3/src/raw/mod.rs000064400000000000000000000001250072674642500133540ustar 00000000000000mod connection; mod socket; pub use connection::Connection; pub use socket::Socket; zbus-1.9.3/src/raw/socket.rs000064400000000000000000000105350072674642500140730ustar 00000000000000use async_io::Async; use std::{ io, os::unix::{ io::{AsRawFd, FromRawFd, RawFd}, net::UnixStream, }, }; use nix::{ cmsg_space, sys::{ socket::{recvmsg, sendmsg, ControlMessage, ControlMessageOwned, MsgFlags}, uio::IoVec, }, }; use crate::{utils::FDS_MAX, OwnedFd}; /// Trait representing some transport layer over which the DBus protocol can be used /// /// The crate provides an implementation of it for std's `UnixStream` on unix platforms. /// You will want to implement this trait to integrate zbus with a async-runtime-aware /// implementation of the socket, for example. pub trait Socket { /// Whether this transport supports file descriptor passing const SUPPORTS_FD_PASSING: bool; /// Attempt to receive a message from the socket /// /// On success, returns the number of bytes read as well as a `Vec` containing /// any associated file descriptors. /// /// This method may return an error of kind `WouldBlock` instead if blocking for /// non-blocking sockets. fn recvmsg(&mut self, buffer: &mut [u8]) -> io::Result<(usize, Vec)>; /// 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 `Err(Errorkind::Wouldblock)`, none of the provided file descriptors were sent. /// /// If the underlying transport does not support transmitting file descriptors, this /// will return `Err(ErrorKind::InvalidInput)`. fn sendmsg(&mut self, buffer: &[u8], fds: &[RawFd]) -> io::Result; /// Close the socket. /// /// After this call, all reading and writing operations will fail. /// /// NB: All currently implementations don't block so this method will never return /// `Err(Errorkind::Wouldblock)`. fn close(&self) -> io::Result<()>; } impl Socket for UnixStream { const SUPPORTS_FD_PASSING: bool = true; fn recvmsg(&mut self, buffer: &mut [u8]) -> io::Result<(usize, Vec)> { let iov = [IoVec::from_mut_slice(buffer)]; let mut cmsgspace = cmsg_space!([RawFd; FDS_MAX]); match recvmsg( self.as_raw_fd(), &iov, Some(&mut cmsgspace), MsgFlags::empty(), ) { Ok(msg) => { let mut fds = vec![]; for cmsg in msg.cmsgs() { 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)) } Err(e) => Err(e.into()), } } fn sendmsg(&mut self, buffer: &[u8], fds: &[RawFd]) -> io::Result { let cmsg = if !fds.is_empty() { vec![ControlMessage::ScmRights(fds)] } else { vec![] }; let iov = [IoVec::from_slice(buffer)]; match sendmsg(self.as_raw_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()), } } fn close(&self) -> io::Result<()> { self.shutdown(std::net::Shutdown::Both) } } impl Socket for Async where S: Socket, { const SUPPORTS_FD_PASSING: bool = true; fn recvmsg(&mut self, buffer: &mut [u8]) -> io::Result<(usize, Vec)> { self.get_mut().recvmsg(buffer) } fn sendmsg(&mut self, buffer: &[u8], fds: &[RawFd]) -> io::Result { self.get_mut().sendmsg(buffer, fds) } fn close(&self) -> io::Result<()> { self.get_ref().close() } } zbus-1.9.3/src/signal_receiver.rs000064400000000000000000000172420072674642500151550ustar 00000000000000use crate::{Connection, Error, MessageHeader, Proxy, Result}; use std::{ borrow::Cow, collections::HashMap, convert::{AsRef, TryFrom}, }; use zvariant::ObjectPath; #[derive(Hash, Eq, PartialEq)] struct ProxyKey<'key> { interface: Cow<'key, str>, path: ObjectPath<'key>, } impl<'p, P> From<&P> for ProxyKey<'_> where P: AsRef>, { fn from(proxy: &P) -> Self { let proxy = proxy.as_ref(); ProxyKey { interface: Cow::from(proxy.interface().to_owned()), path: ObjectPath::try_from(proxy.path()) .expect("invalid object path") .to_owned(), } } } impl<'key> TryFrom<&'key MessageHeader<'_>> for ProxyKey<'key> { type Error = Error; fn try_from(hdr: &'key MessageHeader<'_>) -> Result { match (hdr.interface()?, hdr.path()?.cloned()) { (Some(interface), Some(path)) => Ok(ProxyKey { interface: Cow::from(interface), path, }), (_, _) => Err(Error::Message(crate::MessageError::MissingField)), } } } /// Receives signals for [`Proxy`] instances. /// /// Use this to receive signals on a given connection for a bunch of proxies at the same time. pub struct SignalReceiver<'r, 'p> { conn: Connection, proxies: HashMap, &'r Proxy<'p>>, } impl<'r, 'p> SignalReceiver<'r, 'p> { /// Create a new `SignalReceiver` instance. pub fn new(conn: Connection) -> Self { Self { conn, proxies: HashMap::new(), } } /// Get a reference to the associated connection. pub fn connection(&self) -> &Connection { &self.conn } /// Get a iterator for all the proxies in this receiver. pub fn proxies(&self) -> impl Iterator> { self.proxies.values() } /// Watch for signals relevant to the `proxy`. /// /// # Panics /// /// This method will panic if you try to add a proxy with a different associated connection than /// the one associated with this receiver. pub fn receive_for<'a: 'p, 'b: 'r, P>(&mut self, proxy: &'b P) where P: AsRef>, { let proxy = proxy.as_ref(); assert_eq!(proxy.connection().unique_name(), self.conn.unique_name()); let key = ProxyKey::from(proxy); self.proxies.insert(key, proxy); } /// Received and handle the next incoming signal on the associated connection. /// /// This method will wait for signal messages on the associated connection and call any /// handler registered (through [`Proxy::connect_signal`]) with a proxy in this receiver. /// /// If the signal message was handled by a handler, `Ok(None)` is returned. Otherwise, the /// received message is returned. pub fn next_signal(&self) -> Result> { let msg = self.conn.receive_specific(|msg| { let hdr = msg.header()?; if hdr.message_type()? != crate::MessageType::Signal { return Ok(false); } let member = match hdr.member()? { Some(m) => m, None => return Ok(false), }; let key = ProxyKey::try_from(&hdr)?; if let Some(proxy) = self.proxies.get(&key) { if proxy.has_signal_handler(member) { return Ok(true); } } Ok(false) })?; if self.handle_signal(&msg)? { Ok(None) } else { Ok(Some(msg)) } } /// Handle the provided signal message. /// /// Call any handler registered (through [`Proxy::connect_signal`]) with a proxy in this receiver. /// /// If no errors are encountered, `Ok(true)` is returned if a handler was found and called for, /// the signal; `Ok(false)` otherwise. pub fn handle_signal(&self, msg: &crate::Message) -> Result { let hdr = msg.header()?; let key = ProxyKey::try_from(&hdr)?; match self.proxies.get(&key) { Some(proxy) => proxy.handle_signal(&msg), None => Ok(false), } } } #[cfg(test)] mod tests { use super::*; use crate::{dbus_interface, dbus_proxy, fdo}; use std::{ cell::RefCell, convert::TryInto, rc::Rc, sync::{Arc, Mutex}, }; fn multiple_signal_iface_test() -> std::result::Result> { #[dbus_proxy(interface = "org.freedesktop.zbus.MultiSignal")] trait MultiSignal { #[dbus_proxy(signal)] fn some_signal(&self, sig_arg: &str) -> Result<()>; fn emit_it(&self, arg: &str) -> Result<()>; } let conn = Connection::new_session()?; let mut receiver = SignalReceiver::new(conn.clone()); let proxy1 = MultiSignalProxy::new_for( &conn, "org.freedesktop.zbus.MultiSignal", "/org/freedesktop/zbus/MultiSignal/1", )?; let proxy1_str = Arc::new(Mutex::new(None)); let clone = proxy1_str.clone(); proxy1.connect_some_signal(move |s| { *clone.lock().unwrap() = Some(s.to_string()); Ok(()) })?; receiver.receive_for(&proxy1); let proxy2 = MultiSignalProxy::new_for( &conn, "org.freedesktop.zbus.MultiSignal", "/org/freedesktop/zbus/MultiSignal/2", )?; let proxy2_str = Arc::new(Mutex::new(None)); let clone = proxy2_str.clone(); proxy2.connect_some_signal(move |s| { *clone.lock().unwrap() = Some(s.to_string()); Ok(()) })?; receiver.receive_for(&proxy2); proxy1.emit_it("hi")?; proxy2.emit_it("bye")?; loop { receiver.next_signal()?; if proxy1_str.lock().unwrap().is_some() && proxy2_str.lock().unwrap().is_some() { break; } } Ok(99) } #[test] fn multiple_proxy_signals() { struct MultiSignal { times_called: Rc>, } #[dbus_interface(interface = "org.freedesktop.zbus.MultiSignal")] impl MultiSignal { #[dbus_interface(signal)] fn some_signal(&self, sig_arg: &str) -> Result<()>; fn emit_it(&mut self, arg: &str) -> Result<()> { *self.times_called.borrow_mut() += 1; self.some_signal(arg) } } let conn = Connection::new_session().unwrap(); fdo::DBusProxy::new(&conn) .unwrap() .request_name( "org.freedesktop.zbus.MultiSignal", fdo::RequestNameFlags::ReplaceExisting.into(), ) .unwrap(); let mut object_server = crate::ObjectServer::new(&conn); let times_called = Rc::new(RefCell::new(0)); let iface = MultiSignal { times_called: times_called.clone(), }; object_server .at( &"/org/freedesktop/zbus/MultiSignal/1".try_into().unwrap(), iface, ) .unwrap(); let iface = MultiSignal { times_called: times_called.clone(), }; object_server .at( &"/org/freedesktop/zbus/MultiSignal/2".try_into().unwrap(), iface, ) .unwrap(); let child = std::thread::spawn(|| multiple_signal_iface_test().unwrap()); while *times_called.borrow() < 2 { object_server.try_handle_next().unwrap(); } let val = child.join().expect("failed to join"); assert_eq!(val, 99); } } zbus-1.9.3/src/utils.rs000064400000000000000000000017640072674642500131560ustar 00000000000000use nix::{ errno::Errno, poll::{poll, PollFd, PollFlags}, }; use std::os::unix::io::RawFd; 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) } pub(crate) fn wait_on(fd: RawFd, flags: PollFlags) -> std::io::Result<()> { let pollfd = PollFd::new(fd, flags); loop { match poll(&mut [pollfd], -1) { Ok(_) => break, Err(e) => { if e == Errno::EAGAIN || e == Errno::EINTR { // we got interupted, try polling again continue; } else { return Err(std::io::Error::from(e)); } } } } Ok(()) } zbus-1.9.3/src/xml.rs000064400000000000000000000211430072674642500126070ustar 00000000000000#![cfg(feature = "xml")] //! Introspection XML support (`xml` feature) //! //! Thanks to the [`org.freedesktop.DBus.Introspectable`] interface, objects may be introspected at //! runtime, returning an XML string that describes the object. //! //! This optional `xml` module provides facilities to parse the XML data into more convenient Rust //! structures. The XML string may be parsed to a tree with [`Node.from_reader()`]. //! //! See also: //! //! * [Introspection format] in the DBus specification //! //! [`Node.from_reader()`]: struct.Node.html#method.from_reader //! [Introspection format]: https://dbus.freedesktop.org/doc/dbus-specification.html#introspection-format //! [`org.freedesktop.DBus.Introspectable`]: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-introspectable use serde::{Deserialize, Serialize}; use serde_xml_rs::{from_reader, from_str, to_writer, Error}; use std::{ io::{Read, Write}, result::Result, }; // note: serde-xml-rs doesnt handle nicely interleaved elements, so we have to use enums: // https://github.com/RReverser/serde-xml-rs/issues/55 macro_rules! get_vec { ($vec:expr, $kind:path) => { $vec.iter() .filter_map(|e| if let $kind(m) = e { Some(m) } else { None }) .collect() }; } /// Annotations are generic key/value pairs of metadata. #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Annotation { name: String, value: String, } impl Annotation { /// Return the annotation name/key. pub fn name(&self) -> &str { &self.name } /// Return the annotation value. pub fn value(&self) -> &str { &self.value } } /// An argument #[derive(Debug, Deserialize, Serialize, Clone)] pub struct Arg { name: Option, r#type: String, direction: Option, #[serde(rename = "annotation", default)] annotations: Vec, } impl Arg { /// Return the argument name, if any. pub fn name(&self) -> Option<&str> { self.name.as_deref() } /// Return the argument type. pub fn ty(&self) -> &str { &self.r#type } /// Return the argument direction (should be "in" or "out"), if any. pub fn direction(&self) -> Option<&str> { self.direction.as_deref() } /// Return the associated annotations. pub fn annotations(&self) -> Vec<&Annotation> { self.annotations.iter().collect() } } #[derive(Debug, Deserialize, Serialize, Clone)] #[serde(rename_all = "lowercase")] enum MethodElement { Arg(Arg), Annotation(Annotation), } /// A method #[derive(Debug, Deserialize, Serialize, Clone)] pub struct Method { name: String, #[serde(rename = "$value", default)] elems: Vec, } impl Method { /// Return the method name. pub fn name(&self) -> &str { &self.name } /// Return the method arguments. pub fn args(&self) -> Vec<&Arg> { get_vec!(self.elems, MethodElement::Arg) } /// Return the method annotations. pub fn annotations(&self) -> Vec<&Annotation> { get_vec!(self.elems, MethodElement::Annotation) } } #[derive(Debug, Deserialize, Serialize, Clone)] #[serde(rename_all = "lowercase")] enum SignalElement { Arg(Arg), Annotation(Annotation), } /// A signal #[derive(Debug, Deserialize, Serialize, Clone)] pub struct Signal { name: String, #[serde(rename = "$value", default)] elems: Vec, } impl Signal { /// Return the signal name. pub fn name(&self) -> &str { &self.name } /// Return the signal arguments. pub fn args(&self) -> Vec<&Arg> { get_vec!(self.elems, SignalElement::Arg) } /// Return the signal annotations. pub fn annotations(&self) -> Vec<&Annotation> { get_vec!(self.elems, SignalElement::Annotation) } } /// A property #[derive(Debug, Deserialize, Serialize, Clone)] pub struct Property { name: String, r#type: String, access: String, #[serde(rename = "annotation", default)] annotations: Vec, } impl Property { /// Returns the property name. pub fn name(&self) -> &str { &self.name } /// Returns the property type. pub fn ty(&self) -> &str { &self.r#type } /// Returns the property access flags (should be "read", "write" or "readwrite"). pub fn access(&self) -> &str { &self.access } /// Return the associated annotations. pub fn annotations(&self) -> Vec<&Annotation> { self.annotations.iter().collect() } } #[derive(Debug, Deserialize, Serialize, Clone)] #[serde(rename_all = "lowercase")] enum InterfaceElement { Method(Method), Signal(Signal), Property(Property), Annotation(Annotation), } /// An interface #[derive(Debug, Deserialize, Serialize, Clone)] pub struct Interface { name: String, #[serde(rename = "$value", default)] elems: Vec, } impl Interface { /// Returns the interface name. pub fn name(&self) -> &str { &self.name } /// Returns the interface methods. pub fn methods(&self) -> Vec<&Method> { get_vec!(self.elems, InterfaceElement::Method) } /// Returns the interface signals. pub fn signals(&self) -> Vec<&Signal> { get_vec!(self.elems, InterfaceElement::Signal) } /// Returns the interface properties. pub fn properties(&self) -> Vec<&Property> { get_vec!(self.elems, InterfaceElement::Property) } /// Return the associated annotations. pub fn annotations(&self) -> Vec<&Annotation> { get_vec!(self.elems, InterfaceElement::Annotation) } } #[derive(Debug, Deserialize, Serialize, Clone)] #[serde(rename_all = "lowercase")] enum NodeElement { Node(Node), Interface(Interface), } /// An introspection tree node (typically the root of the XML document). #[derive(Debug, Deserialize, Serialize, Clone)] pub struct Node { name: Option, #[serde(rename = "$value", default)] elems: Vec, } impl Node { /// Parse the introspection XML document from reader. pub fn from_reader(reader: R) -> Result { from_reader(reader) } /// Write the XML document to writer. pub fn to_writer(&self, writer: W) -> Result<(), Error> { to_writer(writer, &self) } /// Returns the node name, if any. pub fn name(&self) -> Option<&str> { self.name.as_deref() } /// Returns the children nodes. pub fn nodes(&self) -> Vec<&Node> { get_vec!(self.elems, NodeElement::Node) } /// Returns the interfaces on this node. pub fn interfaces(&self) -> Vec<&Interface> { get_vec!(self.elems, NodeElement::Interface) } } impl std::str::FromStr for Node { type Err = Error; /// Parse the introspection XML document from `s`. fn from_str(s: &str) -> Result { from_str(s) } } #[cfg(test)] mod tests { use std::{error::Error, str::FromStr}; use super::Node; static EXAMPLE: &str = r##" "##; #[test] fn serde() -> Result<(), Box> { let node = Node::from_reader(EXAMPLE.as_bytes())?; assert_eq!(node.interfaces().len(), 1); assert_eq!(node.nodes().len(), 3); let node_str = Node::from_str(EXAMPLE)?; assert_eq!(node_str.interfaces().len(), 1); assert_eq!(node_str.nodes().len(), 3); // TODO: Fails at the moment, this seems fresh & related: // https://github.com/RReverser/serde-xml-rs/pull/129 //let mut writer = Vec::with_capacity(128); //node.to_writer(&mut writer).unwrap(); Ok(()) } }