Bluez.Rebus.Message (bluez v0.1.0)

Copy Markdown View Source

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

Types

Message flags

Header field keys

Message type

t()

D-Bus message structure

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

flag()

@type flag() :: :no_reply_expected | :no_auto_start | :allow_interactive_authorization

Message flags

header_field()

@type header_field() ::
  :path
  | :interface
  | :member
  | :error_name
  | :reply_serial
  | :destination
  | :sender
  | :signature
  | :unix_fds

Header field keys

message_type()

@type message_type() :: :method_call | :method_return | :error | :signal

Message type

t()

@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

decode(binary)

@spec decode(binary()) :: {:ok, t()} | {:error, any()}

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
:signal

Returns

{:ok, message} on success, {:error, reason} on failure.

encode(message, endianness \\ :little)

@spec encode(t(), :little | :big) :: {:ok, iodata()} | {:error, String.t()}

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 encode
  • endianness - Byte order (:little or :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))
true

Returns

{:ok, iodata} on success, {:error, reason} on failure.

new(type, opts \\ [])

@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

new!(type, opts \\ [])

@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}.

parse(binary)

@spec parse(binary()) :: {:ok, t(), binary()} | {:error, any()} | nil

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 failed
  • nil - 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>>}

signature(message)

@spec signature(t()) :: String.t()

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)
""

type_code(type)

@spec type_code(message_type()) :: non_neg_integer()

Gets the message type as an integer code.

type_from_code(code)

@spec type_from_code(non_neg_integer()) ::
  {:ok, message_type()} | {:error, :invalid_message_type}

Gets the message type from an integer code.

validate(message)

@spec validate(t()) :: :ok | {:error, String.t()}

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"}