Current section
Files
Jump to
Current section
Files
src/macula_protocol_encoder.erl
%%%-------------------------------------------------------------------
%%% @doc
%%% Protocol message encoder for Macula mesh.
%%% Encodes message maps to binary wire format.
%%%
%%% Frame Format (8-byte header + payload):
%%% - Version (1 byte): Protocol version (currently 0x01)
%%% - Type (1 byte): Message type ID
%%% - Flags (1 byte): Reserved for future use (0x00)
%%% - Reserved (1 byte): Must be 0x00
%%% - Payload Length (4 bytes): Big-endian uint32
%%% - Payload (N bytes): MessagePack-encoded message data
%%% @end
%%%-------------------------------------------------------------------
-module(macula_protocol_encoder).
-export([encode/2]).
%%%===================================================================
%%% Constants
%%%===================================================================
-define(PROTOCOL_VERSION, 1).
-define(FLAGS_NONE, 0).
-define(RESERVED, 0).
%%%===================================================================
%%% API Functions
%%%===================================================================
%% @doc Encode a message to binary format.
%% Returns a binary with 8-byte header + MessagePack payload.
%% @end
-spec encode(macula_protocol_types:message_type(), map()) -> binary().
encode(Type, Msg) when is_atom(Type), is_map(Msg) ->
%% Validate message has required fields
validate_message(Type, Msg),
%% Get message type ID
TypeId = macula_protocol_types:message_type_id(Type),
%% Encode payload with MessagePack
Payload = msgpack:pack(Msg, [{map_format, map}]),
%% Build frame: 8-byte header + payload
PayloadLen = byte_size(Payload),
<<?PROTOCOL_VERSION:8,
TypeId:8,
?FLAGS_NONE:8,
?RESERVED:8,
PayloadLen:32/big-unsigned,
Payload/binary>>.
%%%===================================================================
%%% Internal Functions
%%%===================================================================
%% @doc Validate message contains required fields for its type.
%% Crashes with badarg if validation fails.
%% @end
-spec validate_message(atom(), map()) -> ok.
validate_message(connect, Msg) ->
#{version := _, node_id := _, realm_id := _, capabilities := _} = Msg,
ok;
validate_message(disconnect, Msg) ->
#{reason := _, message := _} = Msg,
ok;
validate_message(ping, Msg) ->
#{timestamp := _} = Msg,
ok;
validate_message(pong, Msg) ->
#{timestamp := _, server_time := _} = Msg,
ok;
validate_message(publish, Msg) ->
#{topic := _, payload := _, qos := _, retain := _, message_id := _} = Msg,
ok;
validate_message(subscribe, Msg) ->
#{topics := _, qos := _} = Msg,
ok;
validate_message(unsubscribe, Msg) ->
#{topics := _} = Msg,
ok;
validate_message(_Type, _Msg) ->
%% For message types not yet validated, allow anything
ok.