230 lines
6.7 KiB
Erlang
230 lines
6.7 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.
|
|
|
|
-module(packet_prop_SUITE).
|
|
-compile([export_all, nowarn_export_all]).
|
|
|
|
-include_lib("common_test/include/ct.hrl").
|
|
-include_lib("proper/include/proper.hrl").
|
|
-include_lib("eunit/include/eunit.hrl").
|
|
-include("rabbit_mqtt_packet.hrl").
|
|
|
|
-import(rabbit_ct_proper_helpers, [run_proper/3]).
|
|
|
|
all() ->
|
|
[
|
|
{group, v3},
|
|
{group, v4},
|
|
{group, v5}
|
|
].
|
|
|
|
groups() ->
|
|
[
|
|
{v3, [parallel, shuffle], test_cases()},
|
|
{v4, [parallel, shuffle], test_cases()},
|
|
{v5, [parallel, shuffle], test_cases() ++ [prop_publish_properties,
|
|
prop_puback_properties,
|
|
prop_disconnect]}
|
|
].
|
|
|
|
test_cases() ->
|
|
[
|
|
prop_publish,
|
|
prop_puback
|
|
].
|
|
|
|
init_per_suite(Config) ->
|
|
ok = persistent_term:put(?PERSISTENT_TERM_MAX_PACKET_SIZE_AUTHENTICATED, ?MAX_PACKET_SIZE),
|
|
Config.
|
|
|
|
end_per_suite(_) ->
|
|
ok.
|
|
|
|
init_per_group(Group, Config) ->
|
|
Vsn = case Group of
|
|
v3 -> 3;
|
|
v4 -> 4;
|
|
v5 -> 5
|
|
end,
|
|
[{mqtt_vsn, Vsn} | Config].
|
|
|
|
end_per_group(_, Config) ->
|
|
Config.
|
|
|
|
%%%%%%%%%%%%%%%%%%
|
|
%%% Properties %%%
|
|
%%%%%%%%%%%%%%%%%%
|
|
|
|
prop_publish(Config) ->
|
|
run_proper(fun publish_packet/0, Config).
|
|
|
|
prop_publish_properties(Config) ->
|
|
run_proper(fun publish_with_properties_packet/0, Config).
|
|
|
|
prop_puback(Config) ->
|
|
run_proper(fun puback_packet/0, Config).
|
|
|
|
prop_puback_properties(Config) ->
|
|
run_proper(fun puback_with_properties_packet/0, Config).
|
|
|
|
prop_disconnect(Config) ->
|
|
run_proper(fun disconnect_packet/0, Config).
|
|
|
|
%%%%%%%%%%%%%%%
|
|
%%% Helpers %%%
|
|
%%%%%%%%%%%%%%%
|
|
|
|
run_proper(Generator, Config) ->
|
|
run_proper(fun() -> ?FORALL(Packet,
|
|
Generator(),
|
|
symmetric(Packet, Config))
|
|
end, [], 100).
|
|
|
|
symmetric(Packet, Config) ->
|
|
Vsn = ?config(mqtt_vsn, Config),
|
|
Binary = iolist_to_binary(rabbit_mqtt_packet:serialise(Packet, Vsn)),
|
|
equals({ok, Packet, <<>>, Vsn},
|
|
rabbit_mqtt_packet:parse(Binary, Vsn)).
|
|
|
|
%%%%%%%%%%%%%%%%%%
|
|
%%% Generators %%%
|
|
%%%%%%%%%%%%%%%%%%
|
|
|
|
publish_packet() ->
|
|
?LET(Qos, qos(),
|
|
#mqtt_packet{
|
|
fixed = #mqtt_packet_fixed{
|
|
type = ?PUBLISH,
|
|
dup = boolean(),
|
|
qos = Qos,
|
|
retain = boolean()},
|
|
variable = #mqtt_packet_publish{
|
|
packet_id = packet_id(Qos),
|
|
topic_name = mqtt_utf8_string()},
|
|
payload = binary()}).
|
|
|
|
publish_with_properties_packet() ->
|
|
?LET(Packet = #mqtt_packet{variable = Publish},
|
|
publish_packet(),
|
|
Packet#mqtt_packet{variable = Publish#mqtt_packet_publish{props = publish_properties()}}).
|
|
|
|
puback_packet() ->
|
|
#mqtt_packet{
|
|
fixed = #mqtt_packet_fixed{type = ?PUBACK},
|
|
variable = #mqtt_packet_puback{packet_id = packet_id()}
|
|
}.
|
|
|
|
puback_with_properties_packet() ->
|
|
?LET(Packet = #mqtt_packet{variable = Puback},
|
|
puback_packet(),
|
|
Packet#mqtt_packet{variable = Puback#mqtt_packet_puback{reason_code = reason_code(),
|
|
props = puback_properties()}}).
|
|
|
|
disconnect_packet() ->
|
|
#mqtt_packet{
|
|
fixed = #mqtt_packet_fixed{type = ?DISCONNECT},
|
|
variable = #mqtt_packet_disconnect{
|
|
reason_code = reason_code(),
|
|
props = disconnect_properties()}}.
|
|
|
|
publish_properties() ->
|
|
?LET(L,
|
|
list(elements([{'Payload-Format-Indicator', bit()},
|
|
{'Message-Expiry-Interval', four_byte_integer()},
|
|
{'Topic-Alias', two_byte_integer()},
|
|
{'Response-Topic', mqtt_utf8_string()},
|
|
{'Correlation-Data', binary_data()},
|
|
user_property(),
|
|
{'Content-Type', mqtt_utf8_string()}])),
|
|
maps:from_list(L)).
|
|
|
|
puback_properties() ->
|
|
?LET(L,
|
|
list(elements([{'Reason-String', mqtt_utf8_string()},
|
|
user_property()
|
|
])),
|
|
maps:from_list(L)).
|
|
|
|
disconnect_properties() ->
|
|
?LET(L,
|
|
list(elements([{'Session-Expiry-Interval', four_byte_integer()},
|
|
{'Reason-String', mqtt_utf8_string()},
|
|
user_property()
|
|
])),
|
|
maps:from_list(L)).
|
|
|
|
user_property() ->
|
|
{'User-Property',
|
|
non_empty(list(frequency(
|
|
[{5, utf8_string_pair()},
|
|
%% "The same name is allowed to appear more than once." [v5 3.3.2.3.7]
|
|
{1, {<<"same name">>, mqtt_utf8_string()}},
|
|
{1, {<<"same name">>, <<"same value">>}}
|
|
])))}.
|
|
|
|
qos() ->
|
|
range(0, 2).
|
|
|
|
packet_id() ->
|
|
non_zero_two_byte_integer().
|
|
|
|
%% "The Packet Identifier field is only present in PUBLISH packets
|
|
%% where the QoS level is 1 or 2." [v5 3.3.2.2]
|
|
packet_id(0) ->
|
|
undefined;
|
|
packet_id(Qos) when Qos =:= 1;
|
|
Qos =:= 2 ->
|
|
packet_id().
|
|
|
|
two_byte_integer() ->
|
|
integer(0, 16#ffff).
|
|
|
|
non_zero_two_byte_integer() ->
|
|
integer(1, 16#ffff).
|
|
|
|
four_byte_integer() ->
|
|
integer(0, 16#ffffffff).
|
|
|
|
%% v5 1.5.5
|
|
variable_byte_integer() ->
|
|
integer(0, 268_435_455).
|
|
|
|
non_zero_variable_byte_integer() ->
|
|
integer(1, 268_435_455).
|
|
|
|
%% "The length of Binary Data is limited to the range of 0 to 65,535 Bytes." [v5 1.5.6]
|
|
binary_data() ->
|
|
binary_up_to(16#ffff).
|
|
|
|
binary_up_to(N) ->
|
|
?LET(X, integer(0, N), binary(X)).
|
|
|
|
%% v5 1.5.7
|
|
utf8_string_pair() ->
|
|
{mqtt_utf8_string(), mqtt_utf8_string()}.
|
|
|
|
%% "Unless stated otherwise all UTF-8 encoded strings can have any length
|
|
%% in the range 0 to 65,535 bytes." v5 1.5.4
|
|
mqtt_utf8_string() ->
|
|
%% Defining an upper size other than 'inf' is too slow because the
|
|
%% test ?SIZE is not taken into account anymore.
|
|
MaxCodePointSize = 4,
|
|
MaxCodePoints = 16#ffff div MaxCodePointSize,
|
|
?LET(Bin, utf8(inf, MaxCodePointSize),
|
|
begin
|
|
L0 = unicode:characters_to_list(Bin, utf8),
|
|
L = lists:sublist(L0, MaxCodePoints),
|
|
unicode:characters_to_binary(L, utf8, utf8)
|
|
end).
|
|
|
|
%% "A Reason Code is a one byte unsigned value" [v5 2.4]
|
|
reason_code() ->
|
|
%% Choose "Success" more often because the serialiser will omit some bytes.
|
|
oneof([_Success = 0, byte()]).
|
|
|
|
bit() ->
|
|
oneof([0, 1]).
|