Skip to content

Commit 667b36e

Browse files
STOMP: more test suite improvements
1 parent 4d58acb commit 667b36e

3 files changed

Lines changed: 110 additions & 5 deletions

File tree

deps/rabbitmq_stomp/src/rabbit_stomp_frame.erl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
-export([stream_offset_header/1, stream_filter_header/1]).
1919
-export([serialize/1, serialize/2]).
2020

21+
%% Only used by tests. Production code uses `initial_state/1` with an explicitly provided config.
2122
initial_state() -> {none, ?DEFAULT_STOMP_PARSER_CONFIG}.
2223
initial_state(Config) -> {none, Config}.
2324

@@ -453,7 +454,7 @@ integer_header(F, Key) ->
453454
case header(F, Key) of
454455
{ok, Str} ->
455456
try {ok, binary_to_integer(string:trim(Str))}
456-
catch error:badarg -> not_found
457+
catch _:_ -> not_found
457458
end;
458459
not_found -> not_found
459460
end.

deps/rabbitmq_stomp/test/connections_SUITE.erl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
all() ->
1919
[
2020
messages_not_dropped_on_disconnect,
21-
direct_client_connections_are_not_leaked,
21+
connections_not_leaked_on_parse_error,
2222
stats_are_not_leaked,
2323
stats,
2424
heartbeat,
@@ -84,7 +84,7 @@ rpc_count_connections(Config, ConnSpec) ->
8484
rabbit_ct_broker_helpers:rpc(Config, 0,
8585
ranch_server, count_connections, [ConnSpec]).
8686

87-
direct_client_connections_are_not_leaked(Config) ->
87+
connections_not_leaked_on_parse_error(Config) ->
8888
StompPort = get_stomp_port(Config),
8989
N = count_connections(Config),
9090
lists:foreach(fun (_) ->

deps/rabbitmq_stomp/test/prop_frame_SUITE.erl

Lines changed: 106 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@ groups() ->
2929
prop_duplicates_do_not_exhaust_limit,
3030
prop_negative_content_length_rejected,
3131
prop_valid_content_length_round_trips,
32-
prop_non_numeric_content_length_does_not_crash
32+
prop_non_numeric_content_length_does_not_crash,
33+
prop_round_trip,
34+
prop_incremental_parse,
35+
prop_max_body_length_enforced
3336
]}].
3437

3538
%% Any frame with up to LIMIT unique header names parses successfully.
@@ -108,6 +111,84 @@ prop_non_numeric_content_length_does_not_crash(_Config) ->
108111
end)
109112
end, [], 1000).
110113

114+
%% Serialize then parse preserves command, headers, and body.
115+
%% Headers with escape-triggering characters (colon, backslash, LF, CR)
116+
%% exercise both the fast and slow parser paths.
117+
prop_round_trip(_Config) ->
118+
run_proper(
119+
fun() ->
120+
?FORALL({RawPairs, Body},
121+
{resize(5, list({stomp_hdr_name(), stomp_hdr_value()})),
122+
resize(200, binary())},
123+
begin
124+
Hdrs = maps:from_list(
125+
[{<<"destination">>, <<"/queue/t">>} |
126+
[{K, V} || {K, V} <- RawPairs,
127+
K =/= <<"content-length">>,
128+
K =/= <<"destination">>]]),
129+
Frame = #stomp_frame{command = 'SEND',
130+
headers = Hdrs,
131+
body_iolist_rev = Body},
132+
Bin = iolist_to_binary(rabbit_stomp_frame:serialize(Frame)),
133+
{ok, Parsed, _} = parse(Bin),
134+
Parsed#stomp_frame.command =:= 'SEND' andalso
135+
lists:all(
136+
fun({K, V}) ->
137+
maps:get(K, Parsed#stomp_frame.headers, undefined) =:= V
138+
end, maps:to_list(Hdrs)) andalso
139+
Body =:= body_to_binary(Parsed)
140+
end)
141+
end, [], 500).
142+
143+
%% Splitting a valid frame at any byte boundary and parsing in two
144+
%% chunks must produce the same frame as parsing in one call.
145+
prop_incremental_parse(_Config) ->
146+
run_proper(
147+
fun() ->
148+
?FORALL({Body, N},
149+
{resize(200, binary()), non_neg_integer()},
150+
begin
151+
Bin = iolist_to_binary(
152+
rabbit_stomp_frame:serialize(
153+
#stomp_frame{command = 'SEND',
154+
headers = #{<<"destination">> => <<"/queue/t">>},
155+
body_iolist_rev = Body})),
156+
Pos = N rem (byte_size(Bin) + 1),
157+
<<C1:Pos/binary, C2/binary>> = Bin,
158+
{ok, Full, _} = parse(Bin),
159+
Chunked = case parse(C1) of
160+
{ok, F, _} -> F;
161+
{more, St} ->
162+
{ok, F2, _} = rabbit_stomp_frame:parse(C2, St),
163+
F2
164+
end,
165+
Full#stomp_frame.command =:= Chunked#stomp_frame.command andalso
166+
Full#stomp_frame.headers =:= Chunked#stomp_frame.headers andalso
167+
body_to_binary(Full) =:= body_to_binary(Chunked)
168+
end)
169+
end, [], 1000).
170+
171+
%% A SEND frame exceeding max_body_length is always rejected.
172+
prop_max_body_length_enforced(_Config) ->
173+
run_proper(
174+
fun() ->
175+
?FORALL({MaxLen, BodySize},
176+
{range(1, 500), range(0, 1000)},
177+
begin
178+
Body = binary:copy(<<"x">>, BodySize),
179+
Bin = iolist_to_binary(
180+
rabbit_stomp_frame:serialize(
181+
#stomp_frame{command = 'SEND',
182+
headers = #{<<"destination">> => <<"/queue/t">>},
183+
body_iolist_rev = Body})),
184+
Config = #stomp_parser_config{max_body_length = MaxLen},
185+
case rabbit_stomp_frame:parse(Bin, rabbit_stomp_frame:initial_state(Config)) of
186+
{ok, _, _} -> BodySize =< MaxLen;
187+
{error, _} -> BodySize > MaxLen
188+
end
189+
end)
190+
end, [], 1000).
191+
111192
%%-------------------------------------------------------------------
112193

113194
unique_headers(N) ->
@@ -122,13 +203,36 @@ send_frame(HdrName, HdrValue, Body) ->
122203
HdrName, ":", HdrValue, "\n\n",
123204
Body, "\0"]).
124205

125-
%% Generator for binaries that are not valid integer strings.
206+
%% Produces binaries that are not valid integer strings.
126207
non_numeric_bin() ->
127208
?SUCHTHAT(Bin,
128209
?LET(Chars, list(range(0, 255)),
129210
list_to_binary(
130211
[C || C <- Chars, C =/= $\n, C =/= $\r, C =/= $:, C =/= $\\, C =/= 0])),
131212
not is_integer(catch binary_to_integer(string:trim(Bin)))).
132213

214+
%% Produces non-empty, no NUL header names. Biased towards escaped chars.
215+
stomp_hdr_name() ->
216+
?SUCHTHAT(Bin,
217+
?LET(Chars, resize(15, non_empty(list(stomp_char()))),
218+
list_to_binary(Chars)),
219+
Bin =/= <<"content-length">> andalso
220+
Bin =/= <<"destination">>).
221+
222+
%% Produces no-NUL header values. Biased towards escaped chars.
223+
stomp_hdr_value() ->
224+
?LET(Chars, resize(20, list(stomp_char())),
225+
list_to_binary(Chars)).
226+
227+
%% Biased towards characters that trigger the escape slow path.
228+
stomp_char() ->
229+
frequency([{3, $:}, {3, $\\}, {3, $\n}, {3, $\r},
230+
{88, range(32, 126)}]).
231+
232+
body_to_binary(#stomp_frame{body_iolist_rev = Rev}) when is_list(Rev) ->
233+
iolist_to_binary(lists:reverse(Rev));
234+
body_to_binary(#stomp_frame{body_iolist_rev = Bin}) when is_binary(Bin) ->
235+
Bin.
236+
133237
parse(Bin) ->
134238
rabbit_stomp_frame:parse(Bin, rabbit_stomp_frame:initial_state()).

0 commit comments

Comments
 (0)