Odysseus uses the DBus IPC/RPC protocol to add a progress bar to it’s dock icon (on the Pantheon or the late Unity desktops), issue notifications, and possibly open links (though that can be done through commandline arguments).
This protocol is mediated by a server daemon locally to your computer or account, though there have been efforts to create a simpler transport mechanism in the Linux kernel.
This will be discussed as implemented by GIO/GLib.
The first thing GIO does when sending a DBus “message” is to check it’s in a good state and serialized the given method call to a standardized binary format.
The in-memory representation of that call, in my case, is constructed by code the Vala compiler generates for me. And it mostly serves to read/write itself into it’s own binary array implementation.
It holds GVariant objects for it’s main data which it reencodes.
GVariant is a GLib class which stores a tree of binary data, as opposed to the array that can be read/written. It can be converted between numerous types and is used elsewhere (e.g. HTTP & AppStream caches).
From there it determines a serial number for tracking replies (tracking the latest per-thread), corrects the endianness, locks the message, and tells it’s “worker” object to send it.
That worker then integrates into the mainloop to queue up the work until the program isn’t otherwise busy, and it writes the messages out to the underlying transport whenever it’s ready.
Meanwhile when this “worker” is constructed it starts asynchronously/cooperatively reading messages from that underlying transport. Once it’s fully read the header it’ll see how much more it needs to read, and after that it is decoded into a message and handed to the calling GDBusConnection object.
That object then looks up the appropriate method via a selection of hashtables and on idle looks up the appropriate via GObject and calls it.
There’s more interesting stuff to cover about DBus, but that’s enough for tonight.
Vala (syntactic sugar)
Yesterday I described how GIO implements DBus and mentioned that Vala generates code to hide most of that API from me, so I’m slowly reading how that works now.
But some of this is a runtime abstraction instead: all these objects subclass a “DBusProxy” class mostly in order to store the “DBusConnection” on which to send the method calls and some routing data. Which it copies, alongside the GVariant-encoded arguments, over into the method call objects it constructs.
More generally, this compilation pass looks through any interfaces/classes you define and checks them for a DBus “annotation” before generating implementations for all it’s methods and properties which in turn call the code I described last night.
It also rewrites some method calls to that DBusConnection class in order to pass additional arguments describing the passed types.
There’s a similar pass for mapping between received messages to an interface. And a superclass between them.
DBus (reference implementation, routing daemon/server)
Last night I described how GIO sends and recieves DBus RPC messages. Tonight I’ll describe how the reference implementation routes these messages to the correct program (application, daemon, etc).
I checked with Apt, this is the implementation I have running on my laptop.
The main construct in this daemon is known as the “Bus Context”, possibly enhanced with auditing, SELinux, and Apparmor (the latter two are Linux security modules). And wraps it’s own DBus client implementation.
That Context uses a “Registry” (a hashmap and lists) to determine where to route messages, reads in configuration, all wrapper a “Bus Connections” object which in turn coordinates between the multiple open channels and expires them.
To dispatch signals it has an array of hashmaps, and it may use a simple “containers” implementation to improve sandboxing. And once everything is initialized the registry is prepopulated with the read configuration.
The DBus server is configured whilst reading the startup configuration, and when a client connects, it adds it to the Connections with timeouts and in turn the Dispather.
To handle any messages it receives the dispatcher:
- Blocks any “monitors” from sending messages.
- Removes invalid data.
- Maybe outputs debugging info.
- Opens a “transaction” on the current context.
- Stores the sender channel.
- Looks up the destination and dispatches according to:
- the hardcoded “Driver” RPC object.
- the “matchmaker” for signals
- the registry (and it’s hashmap). If that object isn’t running, it may autostart it.
- Performs security checks before sending it out.