aboutsummaryrefslogtreecommitdiffstats
path: root/lib/kernel/test/gen_tcp_echo_SUITE.erl
diff options
context:
space:
mode:
authorErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
committerErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
commit84adefa331c4159d432d22840663c38f155cd4c1 (patch)
treebff9a9c66adda4df2106dfd0e5c053ab182a12bd /lib/kernel/test/gen_tcp_echo_SUITE.erl
downloadotp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz
otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2
otp-84adefa331c4159d432d22840663c38f155cd4c1.zip
The R13B03 release.OTP_R13B03
Diffstat (limited to 'lib/kernel/test/gen_tcp_echo_SUITE.erl')
-rw-r--r--lib/kernel/test/gen_tcp_echo_SUITE.erl585
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).
+
+
+
+