MatterEx.Transport.BTP (matter_ex v0.3.0)

Copy Markdown View Source

BLE Transport Protocol — fragmentation, reassembly, and handshake.

BTP sits between raw BLE characteristic writes/indications and the Matter message layer. It handles:

  • Fragmenting a message into MTU-sized BTP packets
  • Reassembling incoming fragments into a complete message
  • Handshake encode/decode for MTU and window size negotiation
  • Ack packet generation

State is a plain struct threaded through function calls — no GenServer at this layer.

Example

state = BTP.new(mtu: 64)
{packets, state} = BTP.fragment(state, some_large_binary)
# packets is a list of binaries ready to send over BLE

# On the receiving side:
state = BTP.new(mtu: 64)
{:ok, state} = BTP.receive_segment(state, packet_1)
{:complete, message, state} = BTP.receive_segment(state, packet_2)

Summary

Functions

Emit a standalone ACK packet if one is pending.

Decode a handshake packet.

Encode an ack-only packet.

Fragment a message into BTP packets.

Encode a BTP handshake request.

Encode a BTP handshake response.

Create a new BTP state.

Process one incoming BTP segment.

Types

t()

@type t() :: %MatterEx.Transport.BTP{
  ack_pending: boolean(),
  mtu: pos_integer(),
  rx_buffer: [binary()],
  rx_message_length: non_neg_integer() | nil,
  rx_seq: 0..255 | nil,
  tx_seq: 0..255,
  window_size: pos_integer()
}

Functions

ack_if_pending(state)

@spec ack_if_pending(t()) :: {:ok, binary() | nil, t()}

Emit a standalone ACK packet if one is pending.

BTP ACK packets carry both the received sequence being acknowledged and their own transmit sequence number, so this updates tx_seq when an ACK is sent.

decode_handshake(binary)

@spec decode_handshake(binary()) ::
  {:request, map()} | {:response, map()} | {:error, atom()}

Decode a handshake packet.

Returns {:request, params} or {:response, params} or {:error, reason}.

encode_ack(ack_num)

@spec encode_ack(non_neg_integer()) :: binary()

Encode an ack-only packet.

fragment(state, message)

@spec fragment(t(), binary()) :: {[binary()], t()}

Fragment a message into BTP packets.

Returns {packets, new_state} where packets is a list of binaries. The first packet has the Start flag and includes the total message length. Middle packets use Continue. The last packet has the End flag. Each packet gets an incrementing sequence number (wrapping at 255).

handshake_request(opts \\ [])

@spec handshake_request(keyword()) :: binary()

Encode a BTP handshake request.

Options:

  • :versions — 4-byte supported versions binary (default <<4, 0, 0, 0>>, version 4)
  • :mtu — proposed BTP fragment size (default 244)
  • :window_size — proposed window size (default 6)

handshake_response(selected_version, opts \\ [])

@spec handshake_response(
  non_neg_integer(),
  keyword()
) :: binary()

Encode a BTP handshake response.

  • selected_version — chosen BTP version (integer)

Options:

  • :mtu — selected BTP fragment size (default 244)
  • :window_size — selected window size (default 6)

new(opts \\ [])

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

Create a new BTP state.

Options:

  • :mtu — negotiated MTU (default 247)
  • :window_size — max unacked segments (default 6)

receive_segment(state, segment)

@spec receive_segment(t(), binary()) ::
  {:ok, t()}
  | {:complete, binary(), t()}
  | {:ack_only, non_neg_integer(), t()}
  | {:error, atom()}

Process one incoming BTP segment.

Returns:

  • {:ok, new_state} — fragment buffered, message not yet complete
  • {:complete, message, new_state} — full message reassembled
  • {:ack_only, ack_num, new_state} — received an ack-only packet
  • {:error, reason} — sequence error or invalid packet