%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1997-2013. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
%%
-module(gen_tcp_echo_SUITE).
-include_lib("test_server/include/test_server.hrl").
%%-compile(export_all).
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2,
init_per_testcase/2, end_per_testcase/2,
active_echo/1, passive_echo/1, active_once_echo/1,
slow_active_echo/1, slow_passive_echo/1,
limit_active_echo/1, limit_passive_echo/1,
large_limit_active_echo/1, large_limit_passive_echo/1]).
-define(TPKT_VRSN, 3).
-define(LINE_LENGTH, 1023). % (default value of gen_tcp option 'recbuf') - 1
suite() -> [{ct_hooks,[ts_install_cth]}].
all() ->
[active_echo, passive_echo, active_once_echo,
slow_active_echo, slow_passive_echo, limit_active_echo,
limit_passive_echo, large_limit_active_echo,
large_limit_passive_echo].
groups() ->
[].
init_per_suite(Config) ->
Config.
end_per_suite(_Config) ->
ok.
init_per_group(_GroupName, Config) ->
Config.
end_per_group(_GroupName, Config) ->
Config.
init_per_testcase(_Func, Config) ->
Dog = test_server:timetrap(test_server:minutes(5)),
[{watchdog, Dog}|Config].
end_per_testcase(_Func, Config) ->
Dog = ?config(watchdog, Config),
test_server:timetrap_cancel(Dog).
active_echo(doc) ->
["Test sending packets of various sizes and various packet types ",
"to the echo port and receiving them again (socket in active mode)."];
active_echo(suite) -> [];
active_echo(Config) when is_list(Config) ->
?line echo_test([], fun active_echo/4, [{echo, fun echo_server/0}]).
passive_echo(doc) ->
["Test sending packets of various sizes and various packet types ",
"to the echo port and receiving them again (socket in passive mode)."];
passive_echo(suite) -> [];
passive_echo(Config) when is_list(Config) ->
?line echo_test([{active, false}], fun passive_echo/4,
[{echo, fun echo_server/0}]).
active_once_echo(doc) ->
["Test sending packets of various sizes and various packet types ",
"to the echo port and receiving them again (socket in active once mode)."];
active_once_echo(suite) -> [];
active_once_echo(Config) when is_list(Config) ->
?line echo_test([{active, once}], fun active_once_echo/4,
[{echo, fun echo_server/0}]).
slow_active_echo(doc) ->
["Test sending packets of various sizes and various packet types ",
"to the echo port and receiving them again (socket in active mode). ",
"The echo server is a special one that delays between every character."];
slow_active_echo(suite) -> [];
slow_active_echo(Config) when is_list(Config) ->
?line echo_test([], fun active_echo/4,
[slow_echo, {echo, fun slow_echo_server/0}]).
slow_passive_echo(doc) ->
["Test sending packets of various sizes and various packet types ",
"to an echo server and receiving them again (socket in passive mode).",
"The echo server is a special one that delays between every character."];
slow_passive_echo(suite) -> [];
slow_passive_echo(Config) when is_list(Config) ->
?line echo_test([{active, false}], fun passive_echo/4,
[slow_echo, {echo, fun slow_echo_server/0}]).
limit_active_echo(doc) ->
["Test sending packets of various sizes and various packet types ",
"to the echo port and receiving them again (socket in active mode) "
"with packet_size limitation."];
limit_active_echo(suite) -> [];
limit_active_echo(Config) when is_list(Config) ->
?line echo_test([{packet_size, 10}],
fun active_echo/4,
[{packet_size, 10}, {echo, fun echo_server/0}]).
limit_passive_echo(doc) ->
["Test sending packets of various sizes and various packet types ",
"to the echo port and receiving them again (socket in passive mode) ",
"with packet_size limitation."];
limit_passive_echo(suite) -> [];
limit_passive_echo(Config) when is_list(Config) ->
?line echo_test([{packet_size, 10},{active, false}],
fun passive_echo/4,
[{packet_size, 10}, {echo, fun echo_server/0}]).
large_limit_active_echo(doc) ->
["Test sending packets of various sizes and various packet types ",
"to the echo port and receiving them again (socket in active mode) "
"with large packet_size limitation."];
large_limit_active_echo(suite) -> [];
large_limit_active_echo(Config) when is_list(Config) ->
?line echo_test([{packet_size, 10}],
fun active_echo/4,
[{packet_size, (1 bsl 32)-1},
{echo, fun echo_server/0}]).
large_limit_passive_echo(doc) ->
["Test sending packets of various sizes and various packet types ",
"to the echo port and receiving them again (socket in passive mode) ",
"with large packet_size limitation."];
large_limit_passive_echo(suite) -> [];
large_limit_passive_echo(Config) when is_list(Config) ->
?line echo_test([{packet_size, 10},{active, false}],
fun passive_echo/4,
[{packet_size, (1 bsl 32) -1},
{echo, fun echo_server/0}]).
echo_test(SockOpts, EchoFun, Config0) ->
echo_test_1(SockOpts, EchoFun, Config0),
io:format("\nrepeating test with {delay_send,true}"),
echo_test_1([{delay_send,true}|SockOpts], EchoFun, Config0).
echo_test_1(SockOpts, EchoFun, Config0) ->
?line EchoSrvFun = ?config(echo, Config0),
?line {ok, EchoPort} = EchoSrvFun(),
?line Config = [{echo_port, EchoPort}|Config0],
?line echo_packet([{packet, 1}|SockOpts], EchoFun, Config),
?line echo_packet([{packet, 2}|SockOpts], EchoFun, Config),
?line echo_packet([{packet, 4}|SockOpts], EchoFun, Config),
?line echo_packet([{packet, sunrm}|SockOpts], EchoFun, Config),
?line echo_packet([{packet, cdr}|SockOpts], EchoFun,
[{type, {cdr, big}}|Config]),
?line echo_packet([{packet, cdr}|SockOpts], EchoFun,
[{type, {cdr, little}}|Config]),
?line case lists:keymember(packet_size, 1, SockOpts) of
false ->
% This is cheating, we should test that packet_size
% also works for line and http.
echo_packet([{packet, line}|SockOpts], EchoFun, Config),
echo_packet([{packet, http}|SockOpts], EchoFun, Config),
echo_packet([{packet, http_bin}|SockOpts], EchoFun, Config);
true -> ok
end,
?line echo_packet([{packet, tpkt}|SockOpts], EchoFun, Config),
?line ShortTag = [16#E0],
?line LongTag = [16#1F, 16#83, 16#27],
?line echo_packet([{packet, asn1}|SockOpts], EchoFun,
[{type, {asn1, short, ShortTag}}|Config]),
?line echo_packet([{packet, asn1}|SockOpts], EchoFun,
[{type, {asn1, long, ShortTag}}|Config]),
?line echo_packet([{packet, asn1}|SockOpts], EchoFun,
[{type, {asn1, short, LongTag}}|Config]),
?line echo_packet([{packet, asn1}|SockOpts], EchoFun,
[{type, {asn1, long, LongTag}}|Config]),
ok.
echo_packet(SockOpts, EchoFun, Opts) ->
Type = case lists:keysearch(type, 1, Opts) of
{value, {type, T}} ->
T;
_ ->
{value, {packet, T}} = lists:keysearch(packet, 1, SockOpts),
T
end,
%% Connect to the echo server.
EchoPort = ?config(echo_port, Opts),
{ok, Echo} = gen_tcp:connect(localhost, EchoPort, SockOpts),
SlowEcho = lists:member(slow_echo, Opts),
case Type of
http ->
echo_packet_http(Echo, Type, EchoFun);
http_bin ->
echo_packet_http(Echo, Type, EchoFun);
_ ->
echo_packet0(Echo, Type, EchoFun, SlowEcho, Opts)
end.
echo_packet_http(Echo, Type, EchoFun) ->
lists:foreach(fun(Uri)-> P1 = http_request(Uri),
EchoFun(Echo, Type, P1, http_reply(P1, Type))
end,
http_uri_variants()),
P2 = http_response(),
EchoFun(Echo, Type, P2, http_reply(P2, Type)).
echo_packet0(Echo, Type, EchoFun, SlowEcho, Opts) ->
?line PacketSize =
case lists:keysearch(packet_size, 1, Opts) of
{value,{packet_size,Sz}} when Sz < 10 -> Sz;
{value,{packet_size,_}} -> 10;
false -> 0
end,
%% Echo small packets first.
?line echo_packet1(Echo, Type, EchoFun, 0),
?line echo_packet1(Echo, Type, EchoFun, 1),
?line echo_packet1(Echo, Type, EchoFun, 2),
?line echo_packet1(Echo, Type, EchoFun, 3),
?line echo_packet1(Echo, Type, EchoFun, 4),
?line echo_packet1(Echo, Type, EchoFun, 7),
if PacketSize =/= 0 ->
?line echo_packet1(Echo, Type, EchoFun,
{PacketSize-1, PacketSize}),
?line echo_packet1(Echo, Type, EchoFun,
{PacketSize, PacketSize}),
?line echo_packet1(Echo, Type, EchoFun,
{PacketSize+1, PacketSize});
not SlowEcho -> % Go on with bigger packets if not slow echo server.
?line echo_packet1(Echo, Type, EchoFun, 10),
?line echo_packet1(Echo, Type, EchoFun, 13),
?line echo_packet1(Echo, Type, EchoFun, 126),
?line echo_packet1(Echo, Type, EchoFun, 127),
?line echo_packet1(Echo, Type, EchoFun, 128),
?line echo_packet1(Echo, Type, EchoFun, 255),
?line echo_packet1(Echo, Type, EchoFun, 256),
?line echo_packet1(Echo, Type, EchoFun, 1023),
?line echo_packet1(Echo, Type, EchoFun, 3747),
?line echo_packet1(Echo, Type, EchoFun, 32767),
?line echo_packet1(Echo, Type, EchoFun, 32768),
?line echo_packet1(Echo, Type, EchoFun, 65531),
?line echo_packet1(Echo, Type, EchoFun, 65535),
?line echo_packet1(Echo, Type, EchoFun, 65536),
?line echo_packet1(Echo, Type, EchoFun, 70000),
?line echo_packet1(Echo, Type, EchoFun, infinite);
true -> ok
end,
?line gen_tcp:close(Echo),
ok.
echo_packet1(EchoSock, Type, EchoFun, Size) ->
?line case packet(Size, Type) of
false ->
ok;
Packet ->
?line io:format("Type ~p, size ~p, time ~p",
[Type, Size, time()]),
?line
case EchoFun(EchoSock, Type, Packet, [Packet]) of
ok ->
?line
case Size of
{N, Max} when N > Max ->
?line
test_server:fail(
{packet_through, {N, Max}});
_ -> ok
end;
{error, emsgsize} ->
?line
case Size of
{N, Max} when N > Max ->
io:format(" Blocked!");
_ ->
?line
test_server:fail(
{packet_blocked, Size})
end;
Error ->
?line test_server:fail(Error)
end
end.
active_echo(Sock, Type, Packet, PacketEchos) ->
?line ok = gen_tcp:send(Sock, Packet),
active_recv(Sock, Type, PacketEchos).
active_recv(_, _, []) ->
ok;
active_recv(Sock, Type, [PacketEcho|Tail]) ->
Tag = case Type of
http -> http;
http_bin -> http;
_ -> tcp
end,
?line receive Recv->Recv end,
%%io:format("Active received: ~p\n",[Recv]),
?line case Recv of
{Tag, Sock, PacketEcho} ->
active_recv(Sock, Type, Tail);
{Tag, Sock, Bad} ->
?line test_server:fail({wrong_data, Bad, expected, PacketEcho});
{tcp_error, Sock, Reason} ->
{error, Reason};
Other ->
?line test_server:fail({unexpected_message, Other, Tag})
end.
passive_echo(Sock, _Type, Packet, PacketEchos) ->
?line ok = gen_tcp:send(Sock, Packet),
passive_recv(Sock, PacketEchos).
passive_recv(_, []) ->
ok;
passive_recv(Sock, [PacketEcho | Tail]) ->
Recv = gen_tcp:recv(Sock, 0),
%%io:format("Passive received: ~p\n",[Recv]),
?line case Recv of
{ok, PacketEcho} ->
passive_recv(Sock, Tail);
{ok, Bad} ->
io:format("Expected: ~p\nGot: ~p\n",[PacketEcho,Bad]),
?line test_server:fail({wrong_data, Bad});
{error,PacketEcho} ->
passive_recv(Sock, Tail); % expected error
{error, _}=Error ->
Error;
Other ->
?line test_server:fail({unexpected_message, Other})
end.
active_once_echo(Sock, Type, Packet, PacketEchos) ->
?line ok = gen_tcp:send(Sock, Packet),
active_once_recv(Sock, Type, PacketEchos).
active_once_recv(_, _, []) ->
ok;
active_once_recv(Sock, Type, [PacketEcho | Tail]) ->
Tag = case Type of
http -> http;
http_bin -> http;
_ -> tcp
end,
?line receive
{Tag, Sock, PacketEcho} ->
inet:setopts(Sock, [{active, once}]),
active_once_recv(Sock, Type, Tail);
{Tag, Sock, Bad} ->
?line test_server:fail({wrong_data, Bad});
{tcp_error, Sock, Reason} ->
{error, Reason};
Other ->
?line test_server:fail({unexpected_message, Other, expected, {Tag, Sock, PacketEcho}})
end.
%%% Building of random packets.
packet(infinite, {asn1, _, Tag}) ->
Tag++[16#80];
packet(infinite, _) ->
false;
packet({Size, _RecvLimit}, Type) ->
packet(Size, Type);
packet(Size, 1) when Size > 255 ->
false;
packet(Size, 2) when Size > 65535 ->
false;
packet(Size, {asn1, _, Tag}) when Size < 128 ->
Tag++[Size|random_packet(Size)];
packet(Size, {asn1, short, Tag}) when Size < 256 ->
Tag++[16#81, Size|random_packet(Size)];
packet(Size, {asn1, short, Tag}) when Size < 65536 ->
Tag++[16#82|put_int16(Size, big, random_packet(Size))];
packet(Size, {asn1, _, Tag}) ->
Tag++[16#84|put_int32(Size, big, random_packet(Size))];
packet(Size, {cdr, Endian}) ->
[$G, $I, $O, $P, % magic
1, 0, % major minor
if Endian == big -> 0; true -> 1 end, % flags: byte order
0 | % message type
put_int32(Size, Endian, random_packet(Size))];
packet(Size, sunrm) ->
put_int32(Size, big, random_packet(Size));
packet(Size, line) when Size > ?LINE_LENGTH ->
false;
packet(Size, line) ->
random_packet(Size, "\n");
packet(Size, tpkt) ->
HeaderSize = 4,
PacketSize = HeaderSize + Size,
if PacketSize < 65536 ->
Header = [?TPKT_VRSN, 0 | put_int16(PacketSize, big)],
HeaderSize = length(Header), % Just to assert cirkular dependency
Header ++ random_packet(Size);
true ->
false
end;
packet(Size, _Type) ->
random_packet(Size).
random_packet(Size) ->
random_packet(Size, "", random_char()).
random_packet(Size, Tail) ->
random_packet(Size, Tail, random_char()).
random_packet(0, Result, _NextChar) ->
Result;
random_packet(Left, Result, NextChar0) ->
NextChar =
if
NextChar0 >= 126 ->
33;
true ->
NextChar0+1
end,
random_packet(Left-1, [NextChar0|Result], NextChar).
random_char() ->
random_char("abcdefghijklmnopqrstuvxyzABCDEFGHIJKLMNOPQRSTUVXYZ0123456789").
random_char(Chars) ->
lists:nth(uniform(length(Chars)), Chars).
uniform(N) ->
case get(random_seed) of
undefined ->
{X, Y, Z} = time(),
random:seed(X, Y, Z);
_ ->
ok
end,
random:uniform(N).
put_int32(X, big, List) ->
[ (X bsr 24) band 16#ff,
(X bsr 16) band 16#ff,
(X bsr 8) band 16#ff,
(X) band 16#ff | List ];
put_int32(X, little, List) ->
[ (X) band 16#ff,
(X bsr 8) band 16#ff,
(X bsr 16) band 16#ff,
(X bsr 24) band 16#ff | List].
put_int16(X, ByteOrder) ->
put_int16(X, ByteOrder, []).
put_int16(X, big, List) ->
[ (X bsr 8) band 16#ff,
(X) band 16#ff | List ];
put_int16(X, little, List) ->
[ (X) band 16#ff,
(X bsr 8) band 16#ff | List ].
%%% A normal echo server, for systems that don't have one.
echo_server() ->
Self = self(),
?line spawn_link(fun() -> echo_server(Self) end),
?line receive
{echo_port, Port} ->
{ok, Port}
end.
echo_server(ReplyTo) ->
{ok, S} = gen_tcp:listen(0, [{active, false}, binary]),
{ok, {_, Port}} = inet:sockname(S),
ReplyTo ! {echo_port, Port},
echo_server_loop(S).
echo_server_loop(Sock) ->
{ok, E} = gen_tcp:accept(Sock),
Self = self(),
spawn_link(fun() -> echoer(E, Self) end),
echo_server_loop(Sock).
echoer(Sock, Parent) ->
unlink(Parent),
echoer_loop(Sock).
echoer_loop(Sock) ->
case gen_tcp:recv(Sock, 0) of
{ok, Data} ->
ok = gen_tcp:send(Sock, Data),
echoer_loop(Sock);
{error, closed} ->
ok
end.
%%% A "slow" echo server, which will echo data with a short delay
%%% between each character.
slow_echo_server() ->
Self = self(),
?line spawn_link(fun() -> slow_echo_server(Self) end),
?line receive
{echo_port, Port} ->
{ok, Port}
end.
slow_echo_server(ReplyTo) ->
{ok, S} = gen_tcp:listen(0, [{active, false}, {nodelay, true}]),
{ok, {_, Port}} = inet:sockname(S),
ReplyTo ! {echo_port, Port},
slow_echo_server_loop(S).
slow_echo_server_loop(Sock) ->
{ok, E} = gen_tcp:accept(Sock),
spawn_link(fun() -> slow_echoer(E, self()) end),
slow_echo_server_loop(Sock).
slow_echoer(Sock, Parent) ->
unlink(Parent),
slow_echoer_loop(Sock).
slow_echoer_loop(Sock) ->
case gen_tcp:recv(Sock, 0) of
{ok, Data} ->
slow_send(Sock, Data),
slow_echoer_loop(Sock);
{error, closed} ->
ok
end.
slow_send(Sock, [C|Rest]) ->
ok = gen_tcp:send(Sock, [C]),
receive after 1 ->
slow_send(Sock, Rest)
end;
slow_send(_, []) ->
ok.
http_request(Uri) ->
list_to_binary(["POST ", Uri, <<" HTTP/1.1\r\n"
"Connection: close\r\n"
"Host: localhost:8000\r\n"
"User-Agent: perl post\r\n"
"Content-Length: 4\r\n"
"Content-Type: text/xml; charset=utf-8\r\n"
"Other-Field: with some text\r\n"
"Multi-Line: Once upon a time in a land far far away,\r\n"
" there lived a princess imprisoned in the highest tower\r\n"
" of the most haunted castle.\r\n"
"Invalid line without a colon\r\n"
"\r\n">>]).
http_uri_variants() ->
["*",
"http://tools.ietf.org/html/rfcX3986",
"http://otp.ericsson.se:8000/product/internal/",
"https://example.com:8042/over/there?name=ferret#nose",
"ftp://cnn.example.com&story=breaking_news@10.0.0.1/top_story.htm",
"/some/absolute/path",
"something_else", "something_else"].
http_response() ->
<<"HTTP/1.0 404 Object Not Found\r\n"
"Server: inets/4.7.16\r\n"
"Date: Fri, 04 Jul 2008 17:16:22 GMT\r\n"
"Content-Type: text/html\r\n"
"Content-Length: 207\r\n"
"\r\n">>.
http_reply(Bin, Type) ->
{ok, Line, Rest} = erlang:decode_packet(Type,Bin,[]),
HType = case Type of
http -> httph;
http_bin -> httph_bin
end,
Ret = lists:reverse(http_reply(Rest,[Line],HType)),
io:format("HTTP: ~p\n",[Ret]),
Ret.
http_reply(<<>>, Acc, _) ->
Acc;
http_reply(Bin, Acc, HType) ->
{ok, Line, Rest} = erlang:decode_packet(HType,Bin,[]),
http_reply(Rest, [Line | Acc], HType).