MatterEx.Protocol.MRP (matter_ex v0.3.1)

Copy Markdown View Source

Message Reliability Protocol — retransmission state and backoff.

Pure struct with no GenServer. Timer scheduling is the caller's responsibility (use Process.send_after in a GenServer).

Backoff formula (Matter spec section 4.11.8)

timeout = base_interval * 1.1 * 1.6^attempt * (1 + jitter)

where base_interval is 300ms (active) or 500ms (idle). Max transmissions = 5 (1 original + 4 retries).

Summary

Functions

Timeout in ms before sending a standalone ACK.

Compute retransmission timeout in milliseconds.

Return the exchange id associated with a pending message counter.

Maximum number of transmissions (initial + retries).

Create new MRP state.

Record that an ACK was received for an exchange.

Handle a retransmission timer firing.

Check if there is a pending retransmission for an exchange.

Record an outgoing reliable message. The caller should schedule the first retransmission timer using backoff_ms/3 with attempt 0.

Move a pending retransmission to a new message counter after retransmit.

Types

pending()

@type pending() :: %{
  message: binary(),
  attempt: non_neg_integer(),
  exchange_id: non_neg_integer()
}

t()

@type t() :: %MatterEx.Protocol.MRP{
  mode: :active | :idle,
  pending: %{required(non_neg_integer()) => pending()}
}

Functions

ack_timeout_ms()

@spec ack_timeout_ms() :: non_neg_integer()

Timeout in ms before sending a standalone ACK.

backoff_ms(mrp, attempt, opts \\ [])

@spec backoff_ms(t(), non_neg_integer(), keyword()) :: non_neg_integer()

Compute retransmission timeout in milliseconds.

Pass deterministic: true to remove jitter (for testing).

exchange_id(state, message_counter)

@spec exchange_id(t(), non_neg_integer()) :: non_neg_integer() | nil

Return the exchange id associated with a pending message counter.

max_transmissions()

@spec max_transmissions() :: non_neg_integer()

Maximum number of transmissions (initial + retries).

new(opts \\ [])

@spec new(keyword()) :: t()

Create new MRP state.

on_ack(state, message_counter)

@spec on_ack(t(), non_neg_integer()) :: {:ok, t()} | {:error, :not_found}

Record that an ACK was received for an exchange.

on_timeout(state, message_counter, attempt)

@spec on_timeout(t(), non_neg_integer(), non_neg_integer()) ::
  {:retransmit, binary(), t()} | {:give_up, t()} | {:already_acked, t()}

Handle a retransmission timer firing.

Returns:

  • {:retransmit, message, new_state} — resend the message
  • {:give_up, new_state} — max retransmissions reached
  • {:already_acked, new_state} — exchange already acknowledged

pending?(state, message_counter)

@spec pending?(t(), non_neg_integer()) :: boolean()

Check if there is a pending retransmission for an exchange.

record_send(state, message_counter, message, exchange_id \\ nil)

@spec record_send(t(), non_neg_integer(), binary(), non_neg_integer() | nil) :: t()

Record an outgoing reliable message. The caller should schedule the first retransmission timer using backoff_ms/3 with attempt 0.

rekey(state, old_message_counter, new_message_counter)

@spec rekey(t(), non_neg_integer(), non_neg_integer()) :: t()

Move a pending retransmission to a new message counter after retransmit.