D-Bus message protocol implementation.
This module implements the D-Bus message format as specified in the D-Bus specification. A message consists of a header and a body, where the header contains metadata about the message and the body contains the actual data being transmitted.
Message Structure
A D-Bus message has the following structure:
- Header: Fixed signature "yyyyuua(yv)" containing endianness, type, flags, version, body length, serial, and header fields
- Body: Variable content based on the message signature
Message Types
:method_call- Method call message:method_return- Method reply with returned data:error- Error reply:signal- Signal emission
Header Fields
:path- Object path (required for METHOD_CALL and SIGNAL):interface- Interface name (optional for METHOD_CALL, required for SIGNAL):member- Method or signal name (required for METHOD_CALL and SIGNAL):error_name- Error name (required for ERROR):reply_serial- Serial of message being replied to (required for ERROR and METHOD_RETURN):destination- Target connection name (optional):sender- Sending connection name (optional, usually set by message bus):signature- Signature of message body (optional, defaults to empty, automatically added to header_fields when body is present):unix_fds- Number of Unix file descriptors (optional)
Note: The signature is stored in header_fields[:signature] and can be accessed using Bluez.Rebus.Message.signature/1.
Message Flags
:no_reply_expected- Don't expect a reply:no_auto_start- Don't auto-start destination service:allow_interactive_authorization- Allow interactive authorization
Examples
# Create a method call message
iex> Bluez.Rebus.Message.new(:method_call,
...> path: "/com/example/Object",
...> interface: "com.example.Interface",
...> member: "Method",
...> destination: "com.example.Service",
...> body: [42, "hello"],
...> signature: "is"
...> )
# Create a signal message
iex> Bluez.Rebus.Message.new(:signal,
...> path: "/com/example/Object",
...> interface: "com.example.Interface",
...> member: "SignalName",
...> body: ["value"],
...> signature: "s"
...> )
# Create an error reply
iex> Bluez.Rebus.Message.new(:error,
...> error_name: "com.example.Error.Failed",
...> reply_serial: 123,
...> body: ["Error message"],
...> signature: "s"
...> )
Summary
Functions
Decodes a binary message.
Encodes a message to iodata format.
Creates a new D-Bus message.
Creates a new D-Bus message, raising on error.
Parses a complete D-Bus message from a binary if sufficient data is available.
Gets the signature from the message header fields.
Gets the message type as an integer code.
Gets the message type from an integer code.
Validates that a message is well-formed according to D-Bus rules.
Types
@type flag() :: :no_reply_expected | :no_auto_start | :allow_interactive_authorization
Message flags
@type header_field() ::
:path
| :interface
| :member
| :error_name
| :reply_serial
| :destination
| :sender
| :signature
| :unix_fds
Header field keys
@type message_type() :: :method_call | :method_return | :error | :signal
Message type
@type t() :: %Bluez.Rebus.Message{ body: [term()], body_length: non_neg_integer(), flags: [flag()], header_fields: %{optional(header_field()) => term()}, serial: non_neg_integer(), type: message_type(), version: non_neg_integer() }
D-Bus message structure
Functions
Decodes a binary message.
Parses a D-Bus message from binary format according to the wire format specification.
Parameters
binary- The binary data to decode
Examples
iex> message = Bluez.Rebus.Message.new!(:signal, path: "/", interface: "test", member: "Test")
iex> {:ok, encoded} = Bluez.Rebus.Message.encode(message)
iex> {:ok, decoded} = Bluez.Rebus.Message.decode(encoded)
iex> decoded.type
:signalReturns
{:ok, message} on success, {:error, reason} on failure.
Encodes a message to iodata format.
Returns the message encoded according to the D-Bus wire format specification.
The endianness can be specified as :little or :big (default: :little).
Parameters
message- The message to encodeendianness- Byte order (:littleor:big, default::little)
Examples
iex> message = Bluez.Rebus.Message.new!(:signal, path: "/", interface: "test", member: "Test")
iex> {:ok, iodata} = Bluez.Rebus.Message.encode(message)
iex> is_binary(IO.iodata_to_binary(iodata))
trueReturns
{:ok, iodata} on success, {:error, reason} on failure.
@spec new( message_type(), keyword() ) :: {:ok, t()} | {:error, String.t()}
Creates a new D-Bus message.
Parameters
type- The message type (:method_call,:method_return,:error,:signal)opts- Keyword list of options::flags- List of message flags (default:[]):version- Protocol version (default:1):body- Message body as list of values (default:[]):signature- Message body signature (default: auto-generated from body)- Header fields like
:path,:interface,:member, etc.
Note
The serial number is always initialized to 0. The transport layer that dispatches the message will assign the actual serial number.
Examples
iex> Bluez.Rebus.Message.new(:method_call,
...> path: "/com/example/Object",
...> member: "TestMethod"
...> )
%Bluez.Rebus.Message{type: :method_call, ...}Errors
Returns {:error, reason} if:
- Invalid message type
- Missing required header fields
- Invalid header field types
- Invalid signature
@spec new!( message_type(), keyword() ) :: t()
Creates a new D-Bus message, raising on error.
Same as new/2 but raises ArgumentError instead of returning {:error, reason}.
Parses a complete D-Bus message from a binary if sufficient data is available.
This function checks if the provided binary contains enough data to parse a complete
D-Bus message (both header and body). If it does, it extracts exactly the right
amount of data and passes it to decode/1. If the binary is too small, returns nil.
This is useful for streaming scenarios where you receive partial data and need to determine when you have a complete message.
Parameters
binary- The binary data that may contain a D-Bus message
Returns
{:ok, message, remaining_data}- If a complete message was successfully parsed{:error, reason}- If the binary contains sufficient data but parsing failednil- If the binary does not contain sufficient data for a complete message
Examples
# Insufficient data
iex> Bluez.Rebus.Message.parse(<<1, 2, 3>>)
nil
# Complete message data
iex> {:ok, message} = Bluez.Rebus.Message.new(:signal, path: "/", interface: "test", member: "Test")
iex> {:ok, encoded} = Bluez.Rebus.Message.encode(message)
iex> binary = IO.iodata_to_binary(encoded)
iex> Bluez.Rebus.Message.parse(binary)
{:ok, %Bluez.Rebus.Message{type: :signal, ...}, <<>>}
# Message with extra data
iex> extra_data = <<1, 2, 3, 4>>
iex> binary_with_extra = binary <> extra_data
iex> Bluez.Rebus.Message.parse(binary_with_extra)
{:ok, %Bluez.Rebus.Message{type: :signal, ...}, <<1, 2, 3, 4>>}
Gets the signature from the message header fields.
Returns the signature string if present, or an empty string if not.
Examples
iex> message = Bluez.Rebus.Message.new!(:signal, path: "/", interface: "test", member: "Test", body: [42], signature: "i")
iex> Bluez.Rebus.Message.signature(message)
"i"
iex> message = Bluez.Rebus.Message.new!(:signal, path: "/", interface: "test", member: "Test")
iex> Bluez.Rebus.Message.signature(message)
""
@spec type_code(message_type()) :: non_neg_integer()
Gets the message type as an integer code.
@spec type_from_code(non_neg_integer()) :: {:ok, message_type()} | {:error, :invalid_message_type}
Gets the message type from an integer code.
Validates that a message is well-formed according to D-Bus rules.
Checks that:
- Message type is valid
- Required header fields are present for the message type
- Header field types are correct
- Message signature is valid
Examples
iex> message = Bluez.Rebus.Message.new!(:method_call, path: "/test", member: "Test")
iex> Bluez.Rebus.Message.validate(message)
:ok
iex> invalid = %Bluez.Rebus.Message{type: :method_call, header_fields: %{}}
iex> Bluez.Rebus.Message.validate(invalid)
{:error, "Missing required field: path"}