|
|
@@ -182,8 +182,19 @@ parse_remaining_len(
|
|
|
Packet = packet(Header, #mqtt_packet_disconnect{reason_code = ?RC_SUCCESS}),
|
|
|
{ok, Packet, Rest, ?NONE(Options)};
|
|
|
%% Match PINGREQ.
|
|
|
-parse_remaining_len(<<0:8, Rest/binary>>, Header, 1, 0, Options) ->
|
|
|
+parse_remaining_len(
|
|
|
+ <<0:8, Rest/binary>>, Header = #mqtt_packet_header{type = ?PINGREQ}, 1, 0, Options
|
|
|
+) ->
|
|
|
parse_frame(Rest, Header, 0, Options);
|
|
|
+parse_remaining_len(
|
|
|
+ <<0:8, _Rest/binary>>, _Header = #mqtt_packet_header{type = ?PINGRESP}, 1, 0, _Options
|
|
|
+) ->
|
|
|
+ ?PARSE_ERR(#{hint => unexpected_packet, header_type => 'PINGRESP'});
|
|
|
+%% All other types of messages should not have a zero remaining length.
|
|
|
+parse_remaining_len(
|
|
|
+ <<0:8, _Rest/binary>>, Header, 1, 0, _Options
|
|
|
+) ->
|
|
|
+ ?PARSE_ERR(#{hint => zero_remaining_len, header_type => Header#mqtt_packet_header.type});
|
|
|
%% Match PUBACK, PUBREC, PUBREL, PUBCOMP, UNSUBACK...
|
|
|
parse_remaining_len(<<0:1, 2:7, Rest/binary>>, Header, 1, 0, Options) ->
|
|
|
parse_frame(Rest, Header, 2, Options);
|
|
|
@@ -255,20 +266,33 @@ packet(Header, Variable) ->
|
|
|
packet(Header, Variable, Payload) ->
|
|
|
#mqtt_packet{header = Header, variable = Variable, payload = Payload}.
|
|
|
|
|
|
-parse_packet(
|
|
|
- #mqtt_packet_header{type = ?CONNECT},
|
|
|
- FrameBin,
|
|
|
- #{strict_mode := StrictMode}
|
|
|
+parse_connect(FrameBin, StrictMode) ->
|
|
|
+ {ProtoName, Rest} = parse_utf8_string_with_hint(FrameBin, StrictMode, invalid_proto_name),
|
|
|
+ case ProtoName of
|
|
|
+ <<"MQTT">> ->
|
|
|
+ ok;
|
|
|
+ <<"MQIsdp">> ->
|
|
|
+ ok;
|
|
|
+ _ ->
|
|
|
+ %% from spec: the server MAY send disconnect with reason code 0x84
|
|
|
+ %% we chose to close socket because the client is likely not talking MQTT anyway
|
|
|
+ ?PARSE_ERR(#{
|
|
|
+ hint => invalid_proto_name,
|
|
|
+ expected => <<"'MQTT' or 'MQIsdp'">>,
|
|
|
+ received => ProtoName
|
|
|
+ })
|
|
|
+ end,
|
|
|
+ parse_connect2(ProtoName, Rest, StrictMode).
|
|
|
+
|
|
|
+% Note: return malformed if reserved flag is not 0.
|
|
|
+parse_connect2(
|
|
|
+ ProtoName,
|
|
|
+ <<BridgeTag:4, ProtoVer:4, UsernameFlag:1, PasswordFlag:1, WillRetain:1, WillQoS:2, WillFlag:1,
|
|
|
+ CleanStart:1, 0:1, KeepAlive:16/big, Rest2/binary>>,
|
|
|
+ StrictMode
|
|
|
) ->
|
|
|
- {ProtoName, Rest} = parse_utf8_string(FrameBin, StrictMode),
|
|
|
- <<BridgeTag:4, ProtoVer:4, Rest1/binary>> = Rest,
|
|
|
- % Note: Crash when reserved flag doesn't equal to 0, there is no strict
|
|
|
- % compliance with the MQTT5.0.
|
|
|
- <<UsernameFlag:1, PasswordFlag:1, WillRetain:1, WillQoS:2, WillFlag:1, CleanStart:1, 0:1,
|
|
|
- KeepAlive:16/big, Rest2/binary>> = Rest1,
|
|
|
-
|
|
|
{Properties, Rest3} = parse_properties(Rest2, ProtoVer, StrictMode),
|
|
|
- {ClientId, Rest4} = parse_utf8_string(Rest3, StrictMode),
|
|
|
+ {ClientId, Rest4} = parse_utf8_string_with_hint(Rest3, StrictMode, invalid_username),
|
|
|
ConnPacket = #mqtt_packet_connect{
|
|
|
proto_name = ProtoName,
|
|
|
proto_ver = ProtoVer,
|
|
|
@@ -282,26 +306,56 @@ parse_packet(
|
|
|
clientid = ClientId
|
|
|
},
|
|
|
{ConnPacket1, Rest5} = parse_will_message(ConnPacket, Rest4, StrictMode),
|
|
|
- {Username, Rest6} = parse_utf8_string(Rest5, StrictMode, bool(UsernameFlag)),
|
|
|
- {Password, <<>>} = parse_utf8_string(Rest6, StrictMode, bool(PasswordFlag)),
|
|
|
- ConnPacket1#mqtt_packet_connect{username = Username, password = Password};
|
|
|
+ {Username, Rest6} = parse_optional(
|
|
|
+ Rest5,
|
|
|
+ fun(Bin) ->
|
|
|
+ parse_utf8_string_with_hint(Bin, StrictMode, invalid_username)
|
|
|
+ end,
|
|
|
+ bool(UsernameFlag)
|
|
|
+ ),
|
|
|
+ {Password, Rest7} = parse_optional(
|
|
|
+ Rest6,
|
|
|
+ fun(Bin) ->
|
|
|
+ parse_utf8_string_with_hint(Bin, StrictMode, invalid_password)
|
|
|
+ end,
|
|
|
+ bool(PasswordFlag)
|
|
|
+ ),
|
|
|
+ case Rest7 of
|
|
|
+ <<>> ->
|
|
|
+ ConnPacket1#mqtt_packet_connect{username = Username, password = Password};
|
|
|
+ _ ->
|
|
|
+ ?PARSE_ERR(malformed_connect_payload)
|
|
|
+ end;
|
|
|
+parse_connect2(_ProtoName, _, _) ->
|
|
|
+ ?PARSE_ERR(malformed_connect_header).
|
|
|
+
|
|
|
+parse_packet(
|
|
|
+ #mqtt_packet_header{type = ?CONNECT},
|
|
|
+ FrameBin,
|
|
|
+ #{strict_mode := StrictMode}
|
|
|
+) ->
|
|
|
+ parse_connect(FrameBin, StrictMode);
|
|
|
parse_packet(
|
|
|
#mqtt_packet_header{type = ?CONNACK},
|
|
|
<<AckFlags:8, ReasonCode:8, Rest/binary>>,
|
|
|
#{version := Ver, strict_mode := StrictMode}
|
|
|
) ->
|
|
|
- {Properties, <<>>} = parse_properties(Rest, Ver, StrictMode),
|
|
|
- #mqtt_packet_connack{
|
|
|
- ack_flags = AckFlags,
|
|
|
- reason_code = ReasonCode,
|
|
|
- properties = Properties
|
|
|
- };
|
|
|
+ case parse_properties(Rest, Ver, StrictMode) of
|
|
|
+ {Properties, <<>>} ->
|
|
|
+ #mqtt_packet_connack{
|
|
|
+ ack_flags = AckFlags,
|
|
|
+ reason_code = ReasonCode,
|
|
|
+ properties = Properties
|
|
|
+ };
|
|
|
+ _ ->
|
|
|
+ ?PARSE_ERR(malformed_properties)
|
|
|
+ end;
|
|
|
parse_packet(
|
|
|
#mqtt_packet_header{type = ?PUBLISH, qos = QoS},
|
|
|
Bin,
|
|
|
#{strict_mode := StrictMode, version := Ver}
|
|
|
) ->
|
|
|
- {TopicName, Rest} = parse_utf8_string(Bin, StrictMode),
|
|
|
+ {TopicName, Rest} = parse_utf8_string_with_hint(Bin, StrictMode, invalid_topic),
|
|
|
{PacketId, Rest1} =
|
|
|
case QoS of
|
|
|
?QOS_0 -> {undefined, Rest};
|
|
|
@@ -411,7 +465,9 @@ parse_packet(
|
|
|
#{strict_mode := StrictMode, version := ?MQTT_PROTO_V5}
|
|
|
) ->
|
|
|
{Properties, <<>>} = parse_properties(Rest, ?MQTT_PROTO_V5, StrictMode),
|
|
|
- #mqtt_packet_auth{reason_code = ReasonCode, properties = Properties}.
|
|
|
+ #mqtt_packet_auth{reason_code = ReasonCode, properties = Properties};
|
|
|
+parse_packet(_Header, _FrameBin, _Options) ->
|
|
|
+ ?PARSE_ERR(malformed_packet).
|
|
|
|
|
|
parse_will_message(
|
|
|
Packet = #mqtt_packet_connect{
|
|
|
@@ -422,7 +478,7 @@ parse_will_message(
|
|
|
StrictMode
|
|
|
) ->
|
|
|
{Props, Rest} = parse_properties(Bin, Ver, StrictMode),
|
|
|
- {Topic, Rest1} = parse_utf8_string(Rest, StrictMode),
|
|
|
+ {Topic, Rest1} = parse_utf8_string_with_hint(Rest, StrictMode, invalid_topic),
|
|
|
{Payload, Rest2} = parse_binary_data(Rest1),
|
|
|
{
|
|
|
Packet#mqtt_packet_connect{
|
|
|
@@ -437,7 +493,9 @@ parse_will_message(Packet, Bin, _StrictMode) ->
|
|
|
|
|
|
-compile({inline, [parse_packet_id/1]}).
|
|
|
parse_packet_id(<<PacketId:16/big, Rest/binary>>) ->
|
|
|
- {PacketId, Rest}.
|
|
|
+ {PacketId, Rest};
|
|
|
+parse_packet_id(_) ->
|
|
|
+ ?PARSE_ERR(invalid_packet_id).
|
|
|
|
|
|
parse_properties(Bin, Ver, _StrictMode) when Ver =/= ?MQTT_PROTO_V5 ->
|
|
|
{#{}, Bin};
|
|
|
@@ -458,10 +516,10 @@ parse_property(<<16#01, Val, Bin/binary>>, Props, StrictMode) ->
|
|
|
parse_property(<<16#02, Val:32/big, Bin/binary>>, Props, StrictMode) ->
|
|
|
parse_property(Bin, Props#{'Message-Expiry-Interval' => Val}, StrictMode);
|
|
|
parse_property(<<16#03, Bin/binary>>, Props, StrictMode) ->
|
|
|
- {Val, Rest} = parse_utf8_string(Bin, StrictMode),
|
|
|
+ {Val, Rest} = parse_utf8_string_with_hint(Bin, StrictMode, invalid_content_type),
|
|
|
parse_property(Rest, Props#{'Content-Type' => Val}, StrictMode);
|
|
|
parse_property(<<16#08, Bin/binary>>, Props, StrictMode) ->
|
|
|
- {Val, Rest} = parse_utf8_string(Bin, StrictMode),
|
|
|
+ {Val, Rest} = parse_utf8_string_with_hint(Bin, StrictMode, invalid_response_topic),
|
|
|
parse_property(Rest, Props#{'Response-Topic' => Val}, StrictMode);
|
|
|
parse_property(<<16#09, Len:16/big, Val:Len/binary, Bin/binary>>, Props, StrictMode) ->
|
|
|
parse_property(Bin, Props#{'Correlation-Data' => Val}, StrictMode);
|
|
|
@@ -471,12 +529,12 @@ parse_property(<<16#0B, Bin/binary>>, Props, StrictMode) ->
|
|
|
parse_property(<<16#11, Val:32/big, Bin/binary>>, Props, StrictMode) ->
|
|
|
parse_property(Bin, Props#{'Session-Expiry-Interval' => Val}, StrictMode);
|
|
|
parse_property(<<16#12, Bin/binary>>, Props, StrictMode) ->
|
|
|
- {Val, Rest} = parse_utf8_string(Bin, StrictMode),
|
|
|
+ {Val, Rest} = parse_utf8_string_with_hint(Bin, StrictMode, invalid_assigned_client_id),
|
|
|
parse_property(Rest, Props#{'Assigned-Client-Identifier' => Val}, StrictMode);
|
|
|
parse_property(<<16#13, Val:16, Bin/binary>>, Props, StrictMode) ->
|
|
|
parse_property(Bin, Props#{'Server-Keep-Alive' => Val}, StrictMode);
|
|
|
parse_property(<<16#15, Bin/binary>>, Props, StrictMode) ->
|
|
|
- {Val, Rest} = parse_utf8_string(Bin, StrictMode),
|
|
|
+ {Val, Rest} = parse_utf8_string_with_hint(Bin, StrictMode, invalid_authn_method),
|
|
|
parse_property(Rest, Props#{'Authentication-Method' => Val}, StrictMode);
|
|
|
parse_property(<<16#16, Len:16/big, Val:Len/binary, Bin/binary>>, Props, StrictMode) ->
|
|
|
parse_property(Bin, Props#{'Authentication-Data' => Val}, StrictMode);
|
|
|
@@ -487,13 +545,13 @@ parse_property(<<16#18, Val:32, Bin/binary>>, Props, StrictMode) ->
|
|
|
parse_property(<<16#19, Val, Bin/binary>>, Props, StrictMode) ->
|
|
|
parse_property(Bin, Props#{'Request-Response-Information' => Val}, StrictMode);
|
|
|
parse_property(<<16#1A, Bin/binary>>, Props, StrictMode) ->
|
|
|
- {Val, Rest} = parse_utf8_string(Bin, StrictMode),
|
|
|
+ {Val, Rest} = parse_utf8_string_with_hint(Bin, StrictMode, invalid_response_info),
|
|
|
parse_property(Rest, Props#{'Response-Information' => Val}, StrictMode);
|
|
|
parse_property(<<16#1C, Bin/binary>>, Props, StrictMode) ->
|
|
|
- {Val, Rest} = parse_utf8_string(Bin, StrictMode),
|
|
|
+ {Val, Rest} = parse_utf8_string_with_hint(Bin, StrictMode, invalid_server_reference),
|
|
|
parse_property(Rest, Props#{'Server-Reference' => Val}, StrictMode);
|
|
|
parse_property(<<16#1F, Bin/binary>>, Props, StrictMode) ->
|
|
|
- {Val, Rest} = parse_utf8_string(Bin, StrictMode),
|
|
|
+ {Val, Rest} = parse_utf8_string_with_hint(Bin, StrictMode, invalid_reason_string),
|
|
|
parse_property(Rest, Props#{'Reason-String' => Val}, StrictMode);
|
|
|
parse_property(<<16#21, Val:16/big, Bin/binary>>, Props, StrictMode) ->
|
|
|
parse_property(Bin, Props#{'Receive-Maximum' => Val}, StrictMode);
|
|
|
@@ -584,10 +642,18 @@ parse_utf8_pair(Bin, _StrictMode) when
|
|
|
total_bytes => byte_size(Bin)
|
|
|
}).
|
|
|
|
|
|
-parse_utf8_string(Bin, _StrictMode, false) ->
|
|
|
- {undefined, Bin};
|
|
|
-parse_utf8_string(Bin, StrictMode, true) ->
|
|
|
- parse_utf8_string(Bin, StrictMode).
|
|
|
+parse_utf8_string_with_hint(Bin, StrictMode, Hint) ->
|
|
|
+ try
|
|
|
+ parse_utf8_string(Bin, StrictMode)
|
|
|
+ catch
|
|
|
+ throw:{?FRAME_PARSE_ERROR, Reason} when is_map(Reason) ->
|
|
|
+ ?PARSE_ERR(Reason#{hint => Hint})
|
|
|
+ end.
|
|
|
+
|
|
|
+parse_optional(Bin, F, true) ->
|
|
|
+ F(Bin);
|
|
|
+parse_optional(Bin, _F, false) ->
|
|
|
+ {undefined, Bin}.
|
|
|
|
|
|
parse_utf8_string(<<Len:16/big, Str:Len/binary, Rest/binary>>, true) ->
|
|
|
{validate_utf8(Str), Rest};
|
|
|
@@ -604,7 +670,7 @@ parse_utf8_string(<<Len:16/big, Rest/binary>>, _) when
|
|
|
parse_utf8_string(Bin, _) when
|
|
|
2 > byte_size(Bin)
|
|
|
->
|
|
|
- ?PARSE_ERR(malformed_utf8_string_length).
|
|
|
+ ?PARSE_ERR(#{reason => malformed_utf8_string_length}).
|
|
|
|
|
|
parse_binary_data(<<Len:16/big, Data:Len/binary, Rest/binary>>) ->
|
|
|
{Data, Rest};
|