# `Bluez.Rebus.Message`
[🔗](https://github.com/bbangert/bluez/blob/v0.1.0/lib/bluez/rebus/message.ex#L5)

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

# `flag`

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

Message flags

# `header_field`

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

Header field keys

# `message_type`

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

Message type

# `t`

```elixir
@type t() :: %Bluez.Rebus.Message{
  body: [term()],
  body_length: non_neg_integer(),
  flags: [flag()],
  header_fields: %{optional(header_field()) =&gt; term()},
  serial: non_neg_integer(),
  type: message_type(),
  version: non_neg_integer()
}
```

D-Bus message structure

# `decode`

```elixir
@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`

```elixir
@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`

```elixir
@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!`

```elixir
@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`

```elixir
@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`

```elixir
@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`

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

Gets the message type as an integer code.

# `type_from_code`

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

Gets the message type from an integer code.

# `validate`

```elixir
@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"}

---

*Consult [api-reference.md](api-reference.md) for complete listing*
