265 lines
11 KiB
Erlang
265 lines
11 KiB
Erlang
%% This Source Code Form is subject to the terms of the Mozilla Public
|
|
%% License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
%%
|
|
%% Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved.
|
|
%%
|
|
|
|
-define(PERSISTENT_TERM_MAX_PACKET_SIZE_UNAUTHENTICATED, mqtt_max_packet_size_unauthenticated).
|
|
-define(PERSISTENT_TERM_MAX_PACKET_SIZE_AUTHENTICATED, mqtt_max_packet_size_authenticated).
|
|
-define(PERSISTENT_TERM_TOPIC_ALIAS_MAXIMUM, mqtt_topic_alias_maximum).
|
|
|
|
-type protocol_version() :: 3..5.
|
|
|
|
-type property_name() :: atom().
|
|
-type property_value() :: any().
|
|
-type properties() :: #{property_name() := property_value()}.
|
|
-type user_property() :: [{binary(), binary()}].
|
|
|
|
-define(TWO_BYTE_INTEGER_MAX, 16#FFFF).
|
|
%% Packet identifier is a non zero two byte integer.
|
|
-define(MAX_PACKET_ID, ?TWO_BYTE_INTEGER_MAX).
|
|
-type packet_id() :: 1..?MAX_PACKET_ID.
|
|
|
|
%% Defining a single correlation term (sequence number) for the will message is
|
|
%% sufficient because there can be only a single will message per MQTT session.
|
|
%% To prevent clashes with a Packet ID and given Packet IDs must be non-zero, we choose 0.
|
|
-define(WILL_MSG_QOS_1_CORRELATION, 0).
|
|
|
|
%% MQTT 3.1.1 spec:
|
|
%% "This allows applications to send Control Packets of size up to 268,435,455 (256 MB).
|
|
%% The representation of this number on the wire is: 0xFF, 0xFF, 0xFF, 0x7F."
|
|
%% 268,435,455 = 16#FFFFFFF
|
|
-define(VARIABLE_BYTE_INTEGER_MAX, 16#FFFFFFF).
|
|
-define(MAX_PACKET_SIZE, ?VARIABLE_BYTE_INTEGER_MAX).
|
|
-type max_packet_size() :: 1..?MAX_PACKET_SIZE.
|
|
|
|
%% "The Subscription Identifier can have the value of 1 to 268,435,455." [v5 3.8.2.1.2]
|
|
-type subscription_identifier() :: 1..?VARIABLE_BYTE_INTEGER_MAX.
|
|
|
|
-define(UINT_MAX, 16#FFFFFFFF).
|
|
|
|
%% MQTT Control Packet types
|
|
-define(CONNECT, 1).
|
|
-define(CONNACK, 2).
|
|
-define(PUBLISH, 3).
|
|
-define(PUBACK, 4).
|
|
-define(PUBREC, 5).
|
|
-define(PUBREL, 6).
|
|
-define(PUBCOMP, 7).
|
|
-define(SUBSCRIBE, 8).
|
|
-define(SUBACK, 9).
|
|
-define(UNSUBSCRIBE, 10).
|
|
-define(UNSUBACK, 11).
|
|
-define(PINGREQ, 12).
|
|
-define(PINGRESP, 13).
|
|
-define(DISCONNECT, 14).
|
|
-define(AUTH, 15). %% only MQTT v5.0
|
|
%%
|
|
-type packet_type() :: ?CONNECT..?AUTH.
|
|
|
|
%% set in CONNECT variable header
|
|
-define(PROTOCOL_NAMES,
|
|
[{3, <<"MQIsdp">>},
|
|
{4, <<"MQTT">>},
|
|
{5, <<"MQTT">>}]).
|
|
|
|
%% MQTT 5.0 Reason Codes are used across various MQTT Control Packets.
|
|
%%
|
|
%% Reason Codes less than 0x80 indicate successful completion of an operation.
|
|
-define(RC_SUCCESS, 16#00). %% The normal Reason Code for success
|
|
-define(RC_NORMAL_DISCONNECTION, 16#00).
|
|
-define(RC_GRANTED_QOS_0, 16#00).
|
|
-define(RC_GRANTED_QOS_1, 16#01).
|
|
-define(RC_GRANTED_QOS_2, 16#02).
|
|
-define(RC_DISCONNECT_WITH_WILL_MESSAGE, 16#04).
|
|
-define(RC_NO_MATCHING_SUBSCRIBERS, 16#10).
|
|
-define(RC_NO_SUBSCRIPTION_EXISTED, 16#11).
|
|
-define(RC_CONTINUE_AUTHENTICATION, 16#18).
|
|
-define(RC_RE_AUTHENTICATE, 16#19).
|
|
%% Reason Code values of 0x80 or greater indicate failure.
|
|
-define(RC_UNSPECIFIED_ERROR, 16#80).
|
|
-define(RC_MALFORMED_PACKET, 16#81).
|
|
-define(RC_PROTOCOL_ERROR, 16#82).
|
|
-define(RC_IMPLEMENTATION_SPECIFIC_ERROR, 16#83).
|
|
-define(RC_UNSUPPORTED_PROTOCOL_VERSION, 16#84).
|
|
-define(RC_CLIENT_IDENTIFIER_NOT_VALID, 16#85).
|
|
-define(RC_BAD_USER_NAME_OR_PASSWORD, 16#86).
|
|
-define(RC_NOT_AUTHORIZED, 16#87).
|
|
-define(RC_SERVER_UNAVAILABLE, 16#88).
|
|
-define(RC_SERVER_BUSY, 16#89).
|
|
-define(RC_BANNED, 16#8A).
|
|
-define(RC_SERVER_SHUTTING_DOWN, 16#8B).
|
|
-define(RC_BAD_AUTHENTICATION_METHOD, 16#8C).
|
|
-define(RC_KEEP_ALIVE_TIMEOUT, 16#8D).
|
|
-define(RC_SESSION_TAKEN_OVER, 16#8E).
|
|
-define(RC_TOPIC_FILTER_INVALID, 16#8F).
|
|
-define(RC_TOPIC_NAME_INVALID, 16#90).
|
|
-define(RC_PACKET_IDENTIFIER_IN_USE, 16#91).
|
|
-define(RC_PACKET_IDENTIFIER_NOT_FOUND, 16#92).
|
|
-define(RC_RECEIVE_MAXIMUM_EXCEEDED, 16#93).
|
|
-define(RC_TOPIC_ALIAS_INVALID, 16#94).
|
|
-define(RC_PACKET_TOO_LARGE, 16#95).
|
|
-define(RC_MESSAGE_RATE_TOO_HIGH, 16#96).
|
|
-define(RC_QUOTA_EXCEEDED, 16#97).
|
|
-define(RC_ADMINISTRATIVE_ACTION, 16#98).
|
|
-define(RC_PAYLOAD_FORMAT_INVALID, 16#99).
|
|
-define(RC_RETAIN_NOT_SUPPORTED, 16#9A).
|
|
-define(RC_QOS_NOT_SUPPORTED, 16#9B).
|
|
-define(RC_USE_ANOTHER_SERVER, 16#9C).
|
|
-define(RC_SERVER_MOVED, 16#9D).
|
|
-define(RC_SHARED_SUBSCRIPTIONS_NOT_SUPPORTED, 16#9E).
|
|
-define(RC_CONNECTION_RATE_EXCEEDED, 16#9F).
|
|
-define(RC_MAXIMUM_CONNECT_TIME, 16#A0).
|
|
-define(RC_SUBSCRIPTION_IDENTIFIERS_NOT_SUPPORTED, 16#A1).
|
|
-define(RC_WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED, 16#A2).
|
|
%%
|
|
-type reason_code() :: ?RC_SUCCESS..?RC_RE_AUTHENTICATE |
|
|
?RC_UNSPECIFIED_ERROR..?RC_WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED.
|
|
|
|
%% MQTT 3.1.1 Connect return codes in CONNACK variable header
|
|
%%
|
|
%% Connection accepted.
|
|
-define(CONNACK_ACCEPT, 0).
|
|
%% The Server does not support the level of the MQTT protocol requested by the Client.
|
|
-define(CONNACK_UNACCEPTABLE_PROTO_VER, 1).
|
|
%% The Client identifier is correct UTF-8 but not allowed by the Server.
|
|
-define(CONNACK_ID_REJECTED, 2).
|
|
%% The Network Connection has been made but the MQTT service is unavailable.
|
|
-define(CONNACK_SERVER_UNAVAILABLE, 3).
|
|
%% The data in the user name or password is malformed.
|
|
-define(CONNACK_BAD_CREDENTIALS, 4).
|
|
%% The Client is not authorized to connect.
|
|
-define(CONNACK_NOT_AUTHORIZED, 5).
|
|
%%
|
|
-type connect_return_code() :: ?CONNACK_ACCEPT..?CONNACK_NOT_AUTHORIZED.
|
|
|
|
-type connect_code() :: connect_return_code() | %% v3 or v4
|
|
reason_code(). %% v5
|
|
|
|
-define(SUBACK_FAILURE, ?RC_UNSPECIFIED_ERROR).
|
|
|
|
%% Quality of Service levels
|
|
-define(QOS_0, 0). %% at most once
|
|
-define(QOS_1, 1). %% at least once
|
|
-define(QOS_2, 2). %% exactly once
|
|
%%
|
|
-type qos() :: ?QOS_0 | ?QOS_1 | ?QOS_2.
|
|
|
|
-record(mqtt_packet_fixed, {type :: packet_type(),
|
|
dup = false :: boolean(),
|
|
qos = 0 :: qos(),
|
|
retain = false :: boolean()
|
|
}).
|
|
|
|
-record(mqtt_packet, {fixed :: #mqtt_packet_fixed{},
|
|
variable :: option(tuple()),
|
|
payload :: option(iodata())
|
|
}).
|
|
|
|
-type mqtt_packet() :: #mqtt_packet{}.
|
|
|
|
-type client_id() :: binary().
|
|
%% "The label attached to an Application Message which is matched
|
|
%% against the Subscriptions known to the Server." [v5 1.2]
|
|
-type topic() :: binary().
|
|
%% "An expression contained in a Subscription to indicate an interest in one
|
|
%% or more topics. A Topic Filter can include wildcard characters." [v5 1.2]
|
|
-type topic_filter() :: binary().
|
|
|
|
-record(mqtt_packet_connect, {proto_ver :: protocol_version(),
|
|
will_retain :: boolean(),
|
|
will_qos :: qos(),
|
|
will_flag :: boolean(),
|
|
clean_start :: boolean(),
|
|
keep_alive :: non_neg_integer(),
|
|
props :: properties(),
|
|
client_id :: client_id(),
|
|
will_props :: properties(),
|
|
will_topic :: option(topic()),
|
|
will_payload :: option(binary()),
|
|
username :: option(binary()),
|
|
password :: option(binary())
|
|
}).
|
|
|
|
-record(mqtt_packet_connack, {session_present :: boolean(),
|
|
code :: connect_code(),
|
|
props = #{} :: properties()}).
|
|
|
|
-record(mqtt_packet_publish, {topic_name :: topic(),
|
|
%% "The Packet Identifier field is only present in
|
|
%% PUBLISH packets where the QoS level is 1 or 2."
|
|
packet_id :: option(packet_id()),
|
|
props = #{} :: properties()
|
|
}).
|
|
|
|
-record(mqtt_packet_puback, {packet_id :: packet_id(),
|
|
reason_code = ?RC_SUCCESS :: reason_code(),
|
|
props = #{} :: properties()
|
|
}).
|
|
|
|
-record(mqtt_subscription_opts, {qos :: qos(), % maximum QoS
|
|
no_local = false :: boolean(),
|
|
retain_as_published = false :: boolean(),
|
|
retain_handling = 0 :: 0..2,
|
|
id :: option(subscription_identifier())
|
|
}).
|
|
|
|
-record(mqtt_subscription, {topic_filter :: topic_filter(),
|
|
options :: #mqtt_subscription_opts{}
|
|
}).
|
|
|
|
-record(mqtt_packet_subscribe, {packet_id :: packet_id(),
|
|
props :: properties(),
|
|
subscriptions :: [#mqtt_subscription{}, ...]
|
|
}).
|
|
|
|
-record(mqtt_packet_suback, {packet_id :: packet_id(),
|
|
props = #{} :: properties(),
|
|
reason_codes :: [reason_code(), ...]
|
|
}).
|
|
|
|
-record(mqtt_packet_unsubscribe, {packet_id :: packet_id(),
|
|
props :: properties(),
|
|
topic_filters :: [topic_filter(), ...]
|
|
}).
|
|
|
|
-record(mqtt_packet_unsuback, {packet_id :: packet_id(),
|
|
props = #{} :: properties(),
|
|
reason_codes = [] :: [reason_code()]
|
|
}).
|
|
|
|
-record(mqtt_packet_disconnect, {reason_code = ?RC_NORMAL_DISCONNECTION :: reason_code(),
|
|
props = #{} :: properties()
|
|
}).
|
|
|
|
%% old MQTT application message up to 3.12
|
|
-type mqtt_msg_v0() :: {RecordName :: mqtt_msg,
|
|
Retain :: boolean(),
|
|
Qos :: qos(),
|
|
Topic :: topic(),
|
|
Dup :: boolean(),
|
|
Packet_id :: option(packet_id()) | ?WILL_MSG_QOS_1_CORRELATION,
|
|
Payload :: binary()}.
|
|
|
|
%% MQTT application message starting in 3.13
|
|
-record(mqtt_msg, {retain :: boolean(),
|
|
qos :: qos(),
|
|
topic :: option(topic()),
|
|
dup :: boolean(),
|
|
packet_id :: option(packet_id()) | ?WILL_MSG_QOS_1_CORRELATION,
|
|
payload :: iodata(),
|
|
%% PUBLISH or Will properties
|
|
props :: properties(),
|
|
timestamp :: option(integer())
|
|
}).
|
|
|
|
-type mqtt_msg() :: #mqtt_msg{}.
|
|
|
|
%% does not include vhost because vhost is used in the (D)ETS table name
|
|
-record(retained_message, {topic :: topic(),
|
|
mqtt_msg :: mqtt_msg() | mqtt_msg_v0()
|
|
}).
|
|
|
|
-type option(T) :: undefined | T.
|