7 个版本 (4 个重大更新)

0.5.0 2022年7月19日
0.4.0 2021年7月26日
0.3.0 2021年7月8日
0.2.0 2021年1月13日
0.1.0 2020年10月14日

#9#marshal

Download history 169/week @ 2024-04-12 293/week @ 2024-04-19 384/week @ 2024-04-26 282/week @ 2024-05-03 235/week @ 2024-05-10 460/week @ 2024-05-17 266/week @ 2024-05-24 311/week @ 2024-05-31 237/week @ 2024-06-07 219/week @ 2024-06-14 293/week @ 2024-06-21 149/week @ 2024-06-28 163/week @ 2024-07-05 497/week @ 2024-07-12 402/week @ 2024-07-19 389/week @ 2024-07-26

1,465 每月下载量
15 个包中使用(通过 rustbus

MIT 协议

19KB
418

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包含用于为struct推导(反)序列化特性的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模块中。这是为了保留在可能有必要的情况下,但通常不应使用。

相反,您应该使用序列化和反序列化特性,这些特性为大多数常见类型实现了。想法是将Rust类型尽可能接近dbus类型。对于诸如String和u64等简单类型,处理起来很容易。对于元组结构体,有针对一定大小的实现。之后,您可能需要从该库复制实现并相应地扩展它。如果将来添加了变长泛型,这可能在将来得到处理。

对于结构体,有一个 derive 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 所使用的字节序。注意,从/到本地字节序的解包会更快。默认的字节序是编译目标的本地字节序。

依赖项

~1.5MB
~35K SLoC