diff options
Diffstat (limited to 'lib/kernel/test/gen_tcp_echo_SUITE.erl')
-rw-r--r-- | lib/kernel/test/gen_tcp_echo_SUITE.erl | 585 |
1 files changed, 585 insertions, 0 deletions
diff --git a/lib/kernel/test/gen_tcp_echo_SUITE.erl b/lib/kernel/test/gen_tcp_echo_SUITE.erl new file mode 100644 index 0000000000..a2e09877af --- /dev/null +++ b/lib/kernel/test/gen_tcp_echo_SUITE.erl @@ -0,0 +1,585 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2009. 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("test_server.hrl"). + +%%-compile(export_all). + +-export([all/1, init_per_testcase/2, fin_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 + +all(suite) -> + [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]. + +init_per_testcase(_Func, Config) -> + Dog = test_server:timetrap(test_server:minutes(5)), + [{watchdog, Dog}|Config]. +fin_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 -> + ?line echo_packet([{packet, line}|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]), + + ?line echo_packet([{packet, http}|SockOpts], EchoFun, Config), + ?line echo_packet([{packet, http_bin}|SockOpts], EchoFun, Config), + ok. + +echo_packet(SockOpts, EchoFun, Opts) -> + ?line 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. + ?line EchoPort = ?config(echo_port, Opts), + ?line {ok, Echo} = gen_tcp:connect(localhost, EchoPort, SockOpts), + + ?line SlowEcho = + case os:type() of + vxworks -> true; + _ -> lists:member(slow_echo, Opts) + end, + + 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&[email protected]/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). + + + + |