28个版本 (17个重大更改)

0.19.3 2023年8月29日
0.19.2 2023年1月25日
0.19.1 2022年12月4日
0.19.0 2022年11月25日
0.3.2 2020年3月2日

#52Unix API

Download history 273/week @ 2024-04-20 404/week @ 2024-04-27 245/week @ 2024-05-04 245/week @ 2024-05-11 472/week @ 2024-05-18 201/week @ 2024-05-25 352/week @ 2024-06-01 189/week @ 2024-06-08 191/week @ 2024-06-15 306/week @ 2024-06-22 142/week @ 2024-06-29 231/week @ 2024-07-06 501/week @ 2024-07-13 370/week @ 2024-07-20 488/week @ 2024-07-27 314/week @ 2024-08-03

1,730 每月下载量
用于 14 个crate(6个直接使用)

MIT 许可证

395KB
10K SLoC

Rustbus

Actions Status

Rustbus实现了针对本地Unix套接字的dbus规范。它不是一个总线实现,而是一个库,允许客户端通过dbus守护进程进行通信。

这是仅通过阅读https://dbus.freedesktop.org/doc/dbus-specification.html中的规范创建的。在实现规范时,我做出了一些错误的假设,这主要是我个人的错误。似乎文档足够编写一个可工作的实现,而不必查看其他代码。

这提供了什么?

此库提供了通过dbus总线发送和接收消息的手段。这意味着:信号、调用和(错误)回复。它还提供了一些标准消息以方便使用。还有一个MessageBuilder,可以帮助您方便地构建自己的消息。

技术上,Dbus可以在任何传输上工作,但目前仅支持Unix流套接字。对其他传输的支持应该相当简单,但它们需要实现一些其他认证机制。

传输文件描述符是可行的。当前的消息限制为每条消息10个,但由于dbus-daemon的限制更为严格,这应该没问题。

本项目状态

有一些针对正确性的测试,并且dbus-daemon似乎通常接受此库发送的所有消息。

与libdbus的互操作性尚未彻底测试,但dbus-monitor工具正确显示了'examples/sig.rs'示例中发送的信号,该示例使用了几乎所有可能出现的不同类型。

反序列化已进行模糊测试,并且至今未在任何输入上引发恐慌。如果您想帮助模糊测试,只需使用以下命令:cargo +nightly fuzz run fuzz_unmarshal

API仍在开发中,预计会有破坏性更改。

在哪里是什么?

  • rustbus是包含总线连接和(反)序列化代码的核心crate。如果您想编写应用程序,只需要这个。
  • rustbus_derive包含用于派生(Un-)Marshal特质的procmacros。这些宏由rustbus重新导出,因此您无需担心这一点。
  • rustbus_derive_test仅用于验证派生是否正确执行。procmacro crates显然不能包含自己的测试。
  • example_keywallet存在
    • 作为更复杂的示例,展示了rustbus的使用
    • 是测试新想法的场所,以验证它对实际开发的影响

快速入门

use rustbus::{connection::{Timeout, ll_conn::force_finish_on_error}, standard_messages, MessageBuilder, MessageType, RpcConn};

fn main() {
    // Connect to the session bus. This also takes care of the mandatory hello messages
    let mut rpc_con = RpcConn::session_conn(Timeout::Infinite).unwrap();

    // Create a new call to a method on a remote object
    let mut call = MessageBuilder::new()
        .call("evaluateScript")
        .with_interface("org.kde.PlasmaShell")
        .on("/PlasmaShell")
        .at("org.kde.plasmashell")
        .build();

    // Send the message and remeber the id, so we can retrieve the answer
    let id = rpc_con
        .send_message(&mut call)
        .expect("Wanna send message :(")
        .write_all()
        .map_err(force_finish_on_error)
        .expect("Wanna send message :(");

    // Retrieve the answer to this message
    let message = rpc_con
        .wait_response(id, Timeout::Infinite)
        .expect("Get failed");
}

连接类型

  • 低级别连接是构建更抽象包装的基础。您可能不希望在特殊情况下之外使用它。
  • RpcConn是为客户端在总线上的服务上调用方法而设计的(如快速入门所示)
  • DispatchConn是为需要将调用分派给许多处理器的服务而设计的。

由于不同的用例有不同的约束,您可能需要围绕低级别连接编写自己的包装器。如果您复制现有的并修改以满足您的需求,这应该不会太困难。如果您有一个对其他人有帮助的问题,我当然会考虑将其添加到这个库中。

低级别连接示例

use rustbus::{connection::Timeout, get_session_bus_path, DuplexConn, MessageBuilder};
fn main() -> Result<(), rustbus::connection::Error> {
   // To get a connection going you need to connect to a bus. You will likely use either the session or the system bus.
   let session_path = get_session_bus_path()?;
   let mut con: DuplexConn = DuplexConn::connect_to_bus(session_path, true)?;
   // Dont forget to send the **mandatory** hello message. send_hello wraps the call and parses the response for convenience.
   let _unique_name: String = con.send_hello(Timeout::Infinite)?;

   // Next you will probably want to create a new message to send out to the world
   let mut sig = MessageBuilder::new()
       .signal("io.killing.spark", "TestSignal", r#"/io/killing/spark"#)
       .build();

   // To put parameters into that message you use the sig.body.push_param functions. These accept anything that can be marshalled into a dbus parameter
   // You can derive or manually implement that trait for your own types if you need that.
   sig.body.push_param("My cool new Signal!").unwrap();

   // Now send you signal to all that want to hear it!
   con.send.send_message(&sig)?.write_all().unwrap();

   // To receive messages sent to you you can call the various functions on the RecvConn. The simplest is this:
   let message = con.recv.get_next_message(Timeout::Infinite)?;

   // Now you can inspect the message.dynheader for all the metadata on the message
   println!("The messages dynamic header: {:?}", message.dynheader);

   // After inspecting that dynheader you should know which content the message should contain
   let cool_string = message.body.parser().get::<&str>().unwrap();
   println!("Received a cool string: {}", cool_string);
   Ok(())
}

参数和序列化和反序列化

这个库最初是试图理解dbus是如何工作的。因此,我将类型尽可能地与枚举进行建模,这仍然在params模块中。这保留了下来,以防在需要的地方可能是必要的,但它们通常不应该被使用。

相反,您应该使用Marshal和Unmarshal traits,这些traits为大多数常见类型实现了。想法是将rust类型尽可能地映射到dbus类型。对于像String和u64这样的简单类型,处理起来很简单。对于元组结构体,有上限大小的实现。在那之后,您需要从该库复制实现并相应地扩展它。如果Rust添加了变通泛型,这可能在将来得到解决。

对于结构体,有一个派生proc-macro,可以为您派生必要的特实现。如果您需要,请查看rustbus_derive。

对于枚举,也有一个proc-macro可以为您派生必要的特实现。有两个遗留宏:dbus_variant_sig!dbus_variant_var!。它们实际上做的是同样的事情,但遗留宏向我们的枚举添加了一个CatchAll,以帮助处理意外的类型,其中proc-macros会由于错误而取消反序列化。

特质的文档提供了更多关于如何实现它们的详细信息,如果需要的话。

examples/user_defined_types.rs中有一个所有这些的示例。对于结构体的派生,还有examples/deriving.rs中的示例。

文件描述符

Dbus可以为您发送文件描述符。Rustbus支持这一点。在wire模块中有一个特殊的包装器类型。此类型尝试以合理的方式处理发送和接收文件描述符的陷阱。如果您在API或希望扩展API方面遇到任何问题,请提出问题。

字节序

Dbus 支持大端和小端字节序,rustbus 也同样支持。在创建 MessageBuilder 时,您可以指定消息应该如何打包。消息可以在任何字节序中接收,并且会透明地解包成您 CPU 使用的字节序。请注意,从/到本地字节序的解包将更快。默认的字节序是编译目标的本地字节序。

依赖项

~3MB
~70K SLoC