Rebus.Message (rebus v0.3.0)

View Source

Constructs, validates, encodes, and decodes D-Bus messages.

A message has a fixed header, typed header fields, and a body described by a D-Bus signature. See the D-Bus message protocol for the wire layout and required header fields. Use new/2 for outbound messages and signature/1 to read the body signature.

Unix file descriptors

The unix_fds struct field is separate from the :unix_fds header count. Construct outbound messages with fds: [fd, ...]; h body values are their zero-based wire indexes. Inbound descriptors appear in message.unix_fds only on a Rebus.call/3 reply, and the calling process then owns them. See Unix file descriptor passing.

Examples

# A method call
{:ok, message} =
  Rebus.Message.new(:method_call,
    path: "/com/example/Object",
    interface: "com.example.Interface",
    member: "Method",
    destination: "com.example.Service",
    body: [42, "hello"],
    signature: "is"
  )

# A signal
{:ok, message} =
  Rebus.Message.new(:signal,
    path: "/com/example/Object",
    interface: "com.example.Interface",
    member: "SignalName",
    body: ["value"],
    signature: "s"
  )

# An error reply
{:ok, message} =
  Rebus.Message.new(:error,
    error_name: "com.example.Error.Failed",
    reply_serial: 123,
    body: ["Error message"],
    signature: "s"
  )

Summary

Types

A reason why new/2 or validate/1 rejected a message.

A reason why encode/2 could not produce a valid D-Bus frame.

Message flags

Header field keys

Message type

t()

D-Bus message structure

Functions

Decodes a binary message.

Encodes a message to iodata format.

The largest D-Bus array payload accepted or emitted, in bytes.

The largest complete D-Bus message accepted from the wire, in bytes.

The maximum number of fixed-width scalar array elements per encode or decode.

The maximum number of Unix file descriptors accepted in one message.

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

construction_error()

@type construction_error() ::
  :invalid_body
  | :invalid_flags
  | :invalid_header_fields
  | :invalid_signature
  | :invalid_type
  | :invalid_unix_fds
  | :invalid_version
  | :message_too_large
  | :resource_limit
  | :unix_fd_limit
  | {:invalid_header_field, header_field()}
  | {:missing_header_field, header_field()}
  | {:unknown_header_field, term()}

A reason why new/2 or validate/1 rejected a message.

encoding_error()

@type encoding_error() ::
  :invalid_body
  | :invalid_header_fields
  | :invalid_message
  | :invalid_unix_fds
  | :message_too_large
  | :resource_limit
  | :unix_fd_limit

A reason why encode/2 could not produce a valid D-Bus frame.

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() :: %Rebus.Message{
  body: [term()],
  body_length: non_neg_integer(),
  encoded_body: {[term()], binary(), binary()} | nil,
  flags: [flag()],
  header_fields: %{optional(header_field()) => term()},
  serial: non_neg_integer(),
  type: message_type(),
  unix_fds: [Rebus.UnixFD.t()],
  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 = Rebus.Message.new!(:signal, path: "/", interface: "org.example.Test", member: "Test")
iex> {:ok, encoded} = Rebus.Message.encode(message)
iex> {:ok, decoded} = Rebus.Message.decode(IO.iodata_to_binary(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, encoding_error()}

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 = Rebus.Message.new!(:signal, path: "/", interface: "org.example.Test", member: "Test")
iex> {:ok, iodata} = Rebus.Message.encode(message)
iex> is_binary(IO.iodata_to_binary(iodata))
true

Returns

{:ok, iodata} on success. Returns {:error, :invalid_body} when the message body does not match its signature, {:error, :invalid_header_fields} for invalid header values, or {:error, :invalid_message} for an invalid fixed header or missing required fields. Returns {:error, :message_too_large} when the encoded frame exceeds the D-Bus message or header-fields limits, or an encoded array exceeds max_array_size/0, or {:error, :resource_limit} when a local structural, nesting, or scalar cap is exhausted.

max_array_size()

@spec max_array_size() :: pos_integer()

The largest D-Bus array payload accepted or emitted, in bytes.

This is the D-Bus protocol limit of 2^26 bytes. It is distinct from the local scalar materialization cap exposed by max_scalar_elements/0.

max_message_size()

@spec max_message_size() :: pos_integer()

The largest complete D-Bus message accepted from the wire, in bytes.

This is the D-Bus protocol limit of 2^27 bytes and includes the fixed header, header fields, alignment padding, and body.

max_scalar_elements()

@spec max_scalar_elements() :: pos_integer()

The maximum number of fixed-width scalar array elements per encode or decode.

This local safety cap is 1,000,000 elements. Encoding shares it cumulatively across every fixed-width scalar array in one encode operation; it is not a D-Bus wire-format limit.

max_unix_fds()

@spec max_unix_fds() :: pos_integer()

The maximum number of Unix file descriptors accepted in one message.

This local bound applies to the D-Bus header count and the ancillary-data control buffer. It is deliberately lower than operating-system limits.

new(type, opts \\ [])

@spec new(
  message_type(),
  keyword()
) :: {:ok, t()} | {:error, construction_error()}

Creates a new D-Bus message.

Parameters

  • type - The message type (:method_call, :method_return, :error, :signal)
  • opts - Keyword list of options:
    • :flags - A list of flags (default: []): :no_reply_expected suppresses a reply, :no_auto_start prevents service activation, and :allow_interactive_authorization permits interactive authorization
    • :version - Protocol version (default: 1)
    • :body - Message body as list of values (default: [])
    • :signature - Message body signature (default: auto-generated from body; :infinity, :negative_infinity, and :nan infer d)
    • :fds - Borrowed Unix file descriptors. Each h value in the body is an index into this list. Rebus never closes outbound descriptors.
    • :path - Object path; required for method calls and signals
    • :interface - Interface name; required for signals
    • :member - Method or signal name; required for method calls and signals
    • :error_name - D-Bus error name; required for error replies
    • :reply_serial - Request serial; required for method returns and errors
    • :destination - Optional target connection name
    • :sender - Optional sending connection name, normally supplied by a bus

Note

The serial number is initialized to 1. The transport layer that dispatches the message assigns its own serial number before writing the frame.

Examples

iex> {:ok, message} = Rebus.Message.new(:method_call,
...>   path: "/com/example/Object",
...>   member: "TestMethod"
...> )
iex> message.type
:method_call

Errors

Returns {:error, reason} where reason is one of:

  • :invalid_type - the message type is not a D-Bus message type
  • :invalid_flags - the flags are not a list, or include an unknown flag
  • :invalid_version - the protocol version is unsupported
  • :invalid_body - the body is not a list, or its values cannot be encoded by the signature
  • :invalid_signature - the signature is not a binary, or is not a valid D-Bus type expression
  • {:invalid_header_field, field} - the value given for that header field is not valid
  • {:missing_header_field, field} - a header field required for the message type was not given
  • :invalid_unix_fds - the descriptors do not match the body
  • :unix_fd_limit - the message exceeds the Unix file descriptor limit
  • :message_too_large - the encoded message, or an array within it, exceeds the D-Bus size limit
  • :resource_limit - a local structural, nesting, or scalar materialization cap is exceeded

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. Invalid endianness, message type, and protocol version are rejected as soon as the 12-byte fixed header is available. :message_too_large is returned as soon as the header-fields length is available and the declared complete message would exceed max_message_size/0, and for a body array whose declared length exceeds max_array_size/0.
  • nil - If the binary does not contain sufficient data for a complete message

Examples

# Insufficient data
iex> Rebus.Message.parse(<<1, 2, 3>>)
nil

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 = Rebus.Message.new!(:signal, path: "/", interface: "org.example.Test", member: "Test", body: [42], signature: "i")
iex> Rebus.Message.signature(message)
"i"

iex> message = Rebus.Message.new!(:signal, path: "/", interface: "org.example.Test", member: "Test")
iex> 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, construction_error()}

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 = Rebus.Message.new!(:method_call, path: "/test", member: "Test")
iex> Rebus.Message.validate(message)
:ok