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
@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
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 a handshake packet.
Returns {:request, params} or {:response, params} or {:error, reason}.
@spec encode_ack(non_neg_integer()) :: binary()
Encode an ack-only packet.
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).
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)
@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)
Create a new BTP state.
Options:
:mtu— negotiated MTU (default 247):window_size— max unacked segments (default 6)
@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