Rebus.Message (rebus v0.4.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. The D-Bus message protocol gives the wire layout and the header fields each message type requires. Build outbound messages with new/2 and read a body's signature with signature/1.

Unix file descriptors

The unix_fds struct field holds the descriptors themselves and is separate from the :unix_fds header field, which is only their count. Build an outbound message with fds: [fd, ...], where each fd is a Rebus.UnixFD handle or a raw descriptor number, in any mix; each h value in the body is a zero-based index into that list. Sending neither closes nor transfers a handle, and a raw number stays the caller's to keep open until the send returns and to close afterwards.

A received message holds Rebus.UnixFD handles. On a Rebus.call/3 reply, the calling process owns them. On the message of an inbound call delivered to a Rebus.Service handler, the connection owns them and the handler borrows them until the call completes. 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

A descriptor in unix_fds. A received message holds Rebus.UnixFD handles; one built to be sent may hold handles, raw descriptor numbers, or both.

Functions

Decodes one complete message from a binary.

Encodes a message in the D-Bus wire format, as iodata.

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

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

The most fixed-width scalar array elements one encode or decode may produce.

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

Builds a D-Bus message of type, validating it.

Creates a new D-Bus message, raising on error.

Parses one message from the front of a binary that may hold part of a message, or more than one.

Gets the signature from the message header fields.

Gets the message type as an integer code.

Gets the message type from an integer code.

Checks that a message is well formed.

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: [unix_fd()],
  version: non_neg_integer()
}

D-Bus message structure

unix_fd()

@type unix_fd() :: Rebus.UnixFD.t() | Rebus.UnixFD.raw()

A descriptor in unix_fds. A received message holds Rebus.UnixFD handles; one built to be sent may hold handles, raw descriptor numbers, or both.

Functions

decode(binary)

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

Decodes one complete message from a binary.

Returns {:ok, message}, or {:error, reason} when the binary is not a well-formed D-Bus message. Use parse/1 for a binary that may hold part of a message, or more than one.

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

encode(message, endianness \\ :little)

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

Encodes a message in the D-Bus wire format, as iodata.

endianness is :little by default, or :big.

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

Return values

  • {:ok, iodata} - the encoded frame.
  • {:error, :invalid_body} - the body does not match its signature.
  • {:error, :invalid_header_fields} - a header field value is invalid.
  • {:error, :invalid_message} - the fixed header is invalid, or a required header field is missing.
  • {:error, :message_too_large} - the frame exceeds the D-Bus message or header-fields limit, or an array in it exceeds max_array_size/0.
  • {:error, :resource_limit} - a local structural, nesting or scalar cap was exceeded.

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. max_scalar_elements/0 is a separate, local limit on element counts.

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. A connection can accept less with the :max_message_size option of Rebus.connect/2.

max_scalar_elements()

@spec max_scalar_elements() :: pos_integer()

The most fixed-width scalar array elements one encode or decode may produce.

This is a local safety cap of 1,000,000 elements, not a D-Bus limit. One encode operation shares it across every fixed-width scalar array in the message.

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()}

Builds a D-Bus message of type, validating it.

type is :method_call, :method_return, :error or :signal. The message's serial is set to 1; the connection assigns the real serial as it writes the frame.

Options

  • :path - the object path. Required for method calls and signals.
  • :interface - the interface name. Required for signals.
  • :member - the method or signal name. Required for method calls and signals.
  • :error_name - the D-Bus error name. Required for errors.
  • :reply_serial - the serial of the call being answered. Required for method returns and errors.
  • :destination - the connection to send the message to.
  • :sender - the sending connection's name, normally set by the bus.
  • :body - the body, as a list of values. Defaults to [].
  • :signature - the body's signature. Defaults to one inferred from the body, where :infinity, :negative_infinity and :nan infer d.
  • :fds - Unix file descriptors to send, which h values in the body index: Rebus.UnixFD handles, raw descriptor numbers, or both. Rebus never closes or transfers them. Whether a handle is open is checked only when a connection accepts the message for sending.
  • :flags - a list of :no_reply_expected, :no_auto_start and :allow_interactive_authorization. Defaults to [].
  • :version - the protocol version. Defaults to 1.

Examples

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

Errors

  • :invalid_type - 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 with the signature.
  • :invalid_signature - the signature is not a binary, or 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 the message type requires 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 cap was 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 one message from the front of a binary that may hold part of a message, or more than one.

This is the function to use on a stream. It decodes the first message once the binary holds all of it, and returns nil until then.

Return values

  • {:ok, message, rest} - a complete message, and the bytes after it.
  • nil - the binary does not yet hold a complete message.
  • {:error, reason} - the data is malformed. An invalid endianness, message type or protocol version is rejected as soon as the 12-byte fixed header is available. :message_too_large is returned as soon as the header-fields length shows that the complete message would exceed max_message_size/0, and for a body array whose declared length exceeds max_array_size/0.

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()}

Checks that a message is well formed.

The message type must be valid, the header fields the type requires must be present, every header field must have the right type, and the body signature must be valid. new/2 runs these checks already; this is for a message built or changed by hand.

Examples

iex> message = Rebus.Message.new!(:method_call, path: "/test", member: "Test")
iex> Rebus.Message.validate(message)
:ok