aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorolgeni <olgeni@FreeBSD.org>2015-05-08 04:34:30 +0800
committerolgeni <olgeni@FreeBSD.org>2015-05-08 04:34:30 +0800
commit1884a385818ca10fdf5a279ffe2b9c5ea46ed5f5 (patch)
treeb8e420f7abb9857550d224bf4693cfa78b4b8c82
parentcf8587700191e43cd36d3c7c59f35c592ebd998f (diff)
downloadfreebsd-ports-gnome-1884a385818ca10fdf5a279ffe2b9c5ea46ed5f5.tar.gz
freebsd-ports-gnome-1884a385818ca10fdf5a279ffe2b9c5ea46ed5f5.tar.zst
freebsd-ports-gnome-1884a385818ca10fdf5a279ffe2b9c5ea46ed5f5.zip
Upgrade all Erlang 17 ports to 17.5.3.
-rw-r--r--lang/erlang-java/Makefile5
-rw-r--r--lang/erlang-runtime17/Makefile23
-rw-r--r--lang/erlang-runtime17/files/patch-otp-17.5.33738
-rw-r--r--lang/erlang-wx/Makefile5
-rw-r--r--lang/erlang/Makefile23
-rw-r--r--lang/erlang/files/patch-otp-17.5.33738
6 files changed, 7522 insertions, 10 deletions
diff --git a/lang/erlang-java/Makefile b/lang/erlang-java/Makefile
index dac934c4c27f..a3591087ed47 100644
--- a/lang/erlang-java/Makefile
+++ b/lang/erlang-java/Makefile
@@ -1,7 +1,7 @@
# $FreeBSD$
PORTNAME= erlang
-PORTVERSION= 17.5.2
+PORTVERSION= 17.5.3
CATEGORIES= lang parallel java
MASTER_SITES= http://www.erlang.org/download/:erlangorg \
http://erlang.stacken.kth.se/download/:erlangorg \
@@ -25,8 +25,7 @@ OPTIONS_DEFINE= DOCS
ERL_RELEASE= 17.5
-USES= gmake
-USE_AUTOTOOLS= autoconf:env
+USES= autoreconf gmake
GNU_CONFIGURE= yes
LDFLAGS+= -L${LOCALBASE}/lib
diff --git a/lang/erlang-runtime17/Makefile b/lang/erlang-runtime17/Makefile
index 2d81c390859c..c568752830ae 100644
--- a/lang/erlang-runtime17/Makefile
+++ b/lang/erlang-runtime17/Makefile
@@ -2,8 +2,7 @@
# $FreeBSD$
PORTNAME= erlang
-PORTVERSION= 17.5.2
-PORTREVISION= 2
+PORTVERSION= 17.5.3
CATEGORIES= lang parallel java
MASTER_SITES= http://www.erlang.org/download/:erlangorg \
http://erlang.stacken.kth.se/download/:erlangorg \
@@ -186,6 +185,10 @@ post-install:
${TAR} --unlink -xzpf ${DISTDIR}/${DIST_SUBDIR}/${ERLANG_DOCS} \
-C ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}
+ ${MV} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/erts-6.4/* \
+ ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/erts-6.4.1
+ ${RMDIR} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/erts-6.4
+
${MV} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/inets-5.10.6/* \
${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/inets-5.10.7
${RMDIR} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/inets-5.10.6
@@ -194,6 +197,22 @@ post-install:
${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/ssh-3.2.2
${RMDIR} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/ssh-3.2
+ ${MV} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/common_test-1.10/* \
+ ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/common_test-1.10.1
+ ${RMDIR} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/common_test-1.10
+
+ ${MV} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/diameter-1.9/* \
+ ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/diameter-1.9.1
+ ${RMDIR} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/diameter-1.9
+
+ ${MV} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/snmp-5.1.1/* \
+ ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/snmp-5.1.2
+ ${RMDIR} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/snmp-5.1.1
+
+ ${MV} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/test_server-3.8/* \
+ ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/test_server-3.8.1
+ ${RMDIR} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/test_server-3.8
+
${INSTALL_DATA} ${WRKSRC}/lib/dialyzer/doc/*.txt \
${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/dialyzer-*/doc/
.endif
diff --git a/lang/erlang-runtime17/files/patch-otp-17.5.3 b/lang/erlang-runtime17/files/patch-otp-17.5.3
new file mode 100644
index 000000000000..f9ed64007fd2
--- /dev/null
+++ b/lang/erlang-runtime17/files/patch-otp-17.5.3
@@ -0,0 +1,3738 @@
+diff --git OTP_VERSION OTP_VERSION
+index 808ab16..f32d20d 100644
+--- OTP_VERSION
++++ OTP_VERSION
+@@ -1 +1 @@
+-17.5.2
++17.5.3
+diff --git erts/doc/src/notes.xml erts/doc/src/notes.xml
+index a2b4ae4..35e6e55 100644
+--- erts/doc/src/notes.xml
++++ erts/doc/src/notes.xml
+@@ -30,6 +30,22 @@
+ </header>
+ <p>This document describes the changes made to the ERTS application.</p>
+
++<section><title>Erts 6.4.1</title>
++
++ <section><title>Fixed Bugs and Malfunctions</title>
++ <list>
++ <item>
++ <p>
++ The VTS mode in Common Test has been modified to use a
++ private version of the Webtool application (ct_webtool).</p>
++ <p>
++ Own Id: OTP-12704 Aux Id: OTP-10922 </p>
++ </item>
++ </list>
++ </section>
++
++</section>
++
+ <section><title>Erts 6.4</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+diff --git erts/etc/common/ct_run.c erts/etc/common/ct_run.c
+index bb59b93..9e67b94 100644
+--- erts/etc/common/ct_run.c
++++ erts/etc/common/ct_run.c
+@@ -239,7 +239,7 @@ int main(int argc, char** argv)
+ */
+
+ if (ct_mode == VTS_MODE) {
+- PUSH4("-s", "webtool", "script_start", "vts");
++ PUSH4("-s", "ct_webtool", "script_start", "vts");
+ if (browser[0] != '\0') PUSH(browser);
+ PUSH3("-s", "ct_run", "script_start");
+ }
+diff --git erts/vsn.mk erts/vsn.mk
+index abc9c0b..9e5aa99 100644
+--- erts/vsn.mk
++++ erts/vsn.mk
+@@ -17,7 +17,7 @@
+ # %CopyrightEnd%
+ #
+
+-VSN = 6.4
++VSN = 6.4.1
+
+ # Port number 4365 in 4.2
+ # Port number 4366 in 4.3
+diff --git lib/common_test/doc/src/notes.xml lib/common_test/doc/src/notes.xml
+index 822ebf1..472e3b7 100644
+--- lib/common_test/doc/src/notes.xml
++++ lib/common_test/doc/src/notes.xml
+@@ -32,6 +32,66 @@
+ <file>notes.xml</file>
+ </header>
+
++<section><title>Common_Test 1.10.1</title>
++
++ <section><title>Fixed Bugs and Malfunctions</title>
++ <list>
++ <item>
++ <p>
++ A fault in the Common Test logger process, that caused
++ the application to crash when running on a long name
++ node, has been corrected.</p>
++ <p>
++ Own Id: OTP-12643</p>
++ </item>
++ <item>
++ <p>
++ A 'wait_for_prompt' option in ct_telnet:expect/3 has been
++ introduced which forces the function to not return until
++ a prompt string has been received, even if other expect
++ patterns have already been found.</p>
++ <p>
++ Own Id: OTP-12688 Aux Id: seq12818 </p>
++ </item>
++ <item>
++ <p>
++ If the last expression in a test case causes a timetrap
++ timeout, the stack trace is ignored and not printed to
++ the test case log file. This happens because the
++ {Suite,TestCase,Line} info is not available in the stack
++ trace in this scenario, due to tail call elimination.
++ Common Test has been modified to handle this situation by
++ inserting a {Suite,TestCase,last_expr} tuple in the
++ correct place and printing the stack trace as expected.</p>
++ <p>
++ Own Id: OTP-12697 Aux Id: seq12848 </p>
++ </item>
++ <item>
++ <p>
++ Fixed a buffer problem in ct_netconfc which could cause
++ that some messages where buffered forever.</p>
++ <p>
++ Own Id: OTP-12698 Aux Id: seq12844 </p>
++ </item>
++ <item>
++ <p>
++ The VTS mode in Common Test has been modified to use a
++ private version of the Webtool application (ct_webtool).</p>
++ <p>
++ Own Id: OTP-12704 Aux Id: OTP-10922 </p>
++ </item>
++ <item>
++ <p>
++ Add possibility to add user capabilities in
++ <c>ct_netconfc:hello/3</c>.</p>
++ <p>
++ Own Id: OTP-12707 Aux Id: seq12846 </p>
++ </item>
++ </list>
++ </section>
++
++</section>
++
+ <section><title>Common_Test 1.10</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+diff --git lib/common_test/src/Makefile lib/common_test/src/Makefile
+index 8d74546..449cba6 100644
+--- lib/common_test/src/Makefile
++++ lib/common_test/src/Makefile
+@@ -62,6 +62,8 @@ MODULES= \
+ ct_telnet_client \
+ ct_make \
+ vts \
++ ct_webtool \
++ ct_webtool_sup \
+ unix_telnet \
+ ct_config \
+ ct_config_plain \
+diff --git lib/common_test/src/ct_logs.erl lib/common_test/src/ct_logs.erl
+index dc118ed..fa55a97 100644
+--- lib/common_test/src/ct_logs.erl
++++ lib/common_test/src/ct_logs.erl
+@@ -1908,13 +1908,14 @@ sort_all_runs(Dirs) ->
+ sort_ct_runs(Dirs) ->
+ %% Directory naming: <Prefix>.NodeName.Date_Time[/...]
+ %% Sort on Date_Time string: "YYYY-MM-DD_HH.MM.SS"
+- lists:sort(fun(Dir1,Dir2) ->
+- [_Prefix,_Node1,DateHH1,MM1,SS1] =
+- string:tokens(filename:dirname(Dir1),[$.]),
+- [_Prefix,_Node2,DateHH2,MM2,SS2] =
+- string:tokens(filename:dirname(Dir2),[$.]),
+- {DateHH1,MM1,SS1} =< {DateHH2,MM2,SS2}
+- end, Dirs).
++ lists:sort(
++ fun(Dir1,Dir2) ->
++ [SS1,MM1,DateHH1 | _] =
++ lists:reverse(string:tokens(filename:dirname(Dir1),[$.])),
++ [SS2,MM2,DateHH2 | _] =
++ lists:reverse(string:tokens(filename:dirname(Dir2),[$.])),
++ {DateHH1,MM1,SS1} =< {DateHH2,MM2,SS2}
++ end, Dirs).
+
+ dir_diff_all_runs(Dirs, LogCache) ->
+ case LogCache#log_cache.all_runs of
+diff --git lib/common_test/src/ct_netconfc.erl lib/common_test/src/ct_netconfc.erl
+index 85fb1ea..80ffb51 100644
+--- lib/common_test/src/ct_netconfc.erl
++++ lib/common_test/src/ct_netconfc.erl
+@@ -172,6 +172,7 @@
+ only_open/2,
+ hello/1,
+ hello/2,
++ hello/3,
+ close_session/1,
+ close_session/2,
+ kill_session/2,
+@@ -456,23 +457,35 @@ only_open(KeyOrName, ExtraOpts) ->
+
+ %%----------------------------------------------------------------------
+ %% @spec hello(Client) -> Result
+-%% @equiv hello(Client, infinity)
++%% @equiv hello(Client, [], infinity)
+ hello(Client) ->
+- hello(Client,?DEFAULT_TIMEOUT).
++ hello(Client,[],?DEFAULT_TIMEOUT).
+
+ %%----------------------------------------------------------------------
+ -spec hello(Client,Timeout) -> Result when
+ Client :: handle(),
+ Timeout :: timeout(),
+ Result :: ok | {error,error_reason()}.
+-%% @doc Exchange `hello' messages with the server.
+-%%
+-%% Sends a `hello' message to the server and waits for the return.
+-%%
+-%% @end
+-%%----------------------------------------------------------------------
++%% @spec hello(Client, Timeout) -> Result
++%% @equiv hello(Client, [], Timeout)
+ hello(Client,Timeout) ->
+- call(Client, {hello, Timeout}).
++ hello(Client,[],Timeout).
++
++%%----------------------------------------------------------------------
++-spec hello(Client,Options,Timeout) -> Result when
++ Client :: handle(),
++ Options :: [{capability, [string()]}],
++ Timeout :: timeout(),
++ Result :: ok | {error,error_reason()}.
++%% @doc Exchange `hello' messages with the server.
++%%
++%% Adds optional capabilities and sends a `hello' message to the
++%% server and waits for the return.
++%% @end
++%%----------------------------------------------------------------------
++hello(Client,Options,Timeout) ->
++ call(Client, {hello, Options, Timeout}).
++
+
+ %%----------------------------------------------------------------------
+ %% @spec get_session_id(Client) -> Result
+@@ -1040,9 +1053,9 @@ terminate(_, #state{connection=Connection}) ->
+ ok.
+
+ %% @private
+-handle_msg({hello,Timeout}, From,
++handle_msg({hello, Options, Timeout}, From,
+ #state{connection=Connection,hello_status=HelloStatus} = State) ->
+- case do_send(Connection, client_hello()) of
++ case do_send(Connection, client_hello(Options)) of
+ ok ->
+ case HelloStatus of
+ undefined ->
+@@ -1118,7 +1131,9 @@ handle_msg({Ref,timeout},#state{pending=Pending} = State) ->
+ close_session -> stop;
+ _ -> noreply
+ end,
+- {R,State#state{pending=Pending1}}.
++ %% Halfhearted try to get in correct state, this matches
++ %% the implementation before this patch
++ {R,State#state{pending=Pending1, buff= <<>>}}.
+
+ %% @private
+ %% Called by ct_util_server to close registered connections before terminate.
+@@ -1222,10 +1237,14 @@ set_request_timer(T) ->
+
+
+ %%%-----------------------------------------------------------------
+-client_hello() ->
++client_hello(Options) when is_list(Options) ->
++ UserCaps = [{capability, UserCap} ||
++ {capability, UserCap} <- Options,
++ is_list(hd(UserCap))],
+ {hello, ?NETCONF_NAMESPACE_ATTR,
+ [{capabilities,
+- [{capability,[?NETCONF_BASE_CAP++?NETCONF_BASE_CAP_VSN]}]}]}.
++ [{capability,[?NETCONF_BASE_CAP++?NETCONF_BASE_CAP_VSN]}|
++ UserCaps]}]}.
+
+ %%%-----------------------------------------------------------------
+
+@@ -1308,72 +1327,54 @@ to_xml_doc(Simple) ->
+
+ %%%-----------------------------------------------------------------
+ %%% Parse and handle received XML data
+-handle_data(NewData,#state{connection=Connection,buff=Buff} = State) ->
++handle_data(NewData,#state{connection=Connection,buff=Buff0} = State0) ->
+ log(Connection,recv,NewData),
+- Data = <<Buff/binary,NewData/binary>>,
+- case xmerl_sax_parser:stream(<<>>,
+- [{continuation_fun,fun sax_cont/1},
+- {continuation_state,{Data,Connection,false}},
+- {event_fun,fun sax_event/3},
+- {event_state,[]}]) of
+- {ok, Simple, Rest} ->
+- decode(Simple,State#state{buff=Rest});
+- {fatal_error,_Loc,Reason,_EndTags,_EventState} ->
+- ?error(Connection#connection.name,[{parse_error,Reason},
+- {buffer,Buff},
+- {new_data,NewData}]),
+- case Reason of
+- {could_not_fetch_data,Msg} ->
+- handle_msg(Msg,State#state{buff = <<>>});
+- _Other ->
+- Pending1 =
+- case State#state.pending of
+- [] ->
+- [];
+- Pending ->
+- %% Assuming the first request gets the
+- %% first answer
+- P=#pending{tref=TRef,caller=Caller} =
+- lists:last(Pending),
+- _ = timer:cancel(TRef),
+- Reason1 = {failed_to_parse_received_data,Reason},
+- ct_gen_conn:return(Caller,{error,Reason1}),
+- lists:delete(P,Pending)
+- end,
+- {noreply,State#state{pending=Pending1,buff = <<>>}}
+- end
+- end.
+-
+-%%%-----------------------------------------------------------------
+-%%% Parsing of XML data
+-%% Contiuation function for the sax parser
+-sax_cont(done) ->
+- {<<>>,done};
+-sax_cont({Data,Connection,false}) ->
++ Data = append_wo_initial_nl(Buff0,NewData),
+ case binary:split(Data,[?END_TAG],[]) of
+- [All] ->
+- %% No end tag found. Remove what could be a part
+- %% of an end tag from the data and save for next
+- %% iteration
+- SafeSize = size(All)-5,
+- <<New:SafeSize/binary,Save:5/binary>> = All,
+- {New,{Save,Connection,true}};
+- [_Msg,_Rest]=Msgs ->
+- %% We have at least one full message. Any excess data will
+- %% be returned from xmerl_sax_parser:stream/2 in the Rest
+- %% parameter.
+- {list_to_binary(Msgs),done}
+- end;
+-sax_cont({Data,Connection,true}) ->
+- case ssh_receive_data() of
+- {ok,Bin} ->
+- log(Connection,recv,Bin),
+- sax_cont({<<Data/binary,Bin/binary>>,Connection,false});
+- {error,Reason} ->
+- throw({could_not_fetch_data,Reason})
++ [_NoEndTagFound] ->
++ {noreply, State0#state{buff=Data}};
++ [FirstMsg,Buff1] ->
++ SaxArgs = [{event_fun,fun sax_event/3}, {event_state,[]}],
++ case xmerl_sax_parser:stream(FirstMsg, SaxArgs) of
++ {ok, Simple, _Thrash} ->
++ case decode(Simple, State0#state{buff=Buff1}) of
++ {noreply, #state{buff=Buff} = State} when Buff =/= <<>> ->
++ %% Recurse if we have more data in buffer
++ handle_data(<<>>, State);
++ Other ->
++ Other
++ end;
++ {fatal_error,_Loc,Reason,_EndTags,_EventState} ->
++ ?error(Connection#connection.name,
++ [{parse_error,Reason},
++ {buffer, Buff0},
++ {new_data,NewData}]),
++ handle_error(Reason, State0#state{buff= <<>>})
++ end
+ end.
+
++%% xml does not accept a leading nl and some netconf server add a nl after
++%% each ?END_TAG, ignore them
++append_wo_initial_nl(<<>>,NewData) -> NewData;
++append_wo_initial_nl(<<"\n", Data/binary>>, NewData) ->
++ append_wo_initial_nl(Data, NewData);
++append_wo_initial_nl(Data, NewData) ->
++ <<Data/binary, NewData/binary>>.
+
++handle_error(Reason, State) ->
++ Pending1 = case State#state.pending of
++ [] -> [];
++ Pending ->
++ %% Assuming the first request gets the
++ %% first answer
++ P=#pending{tref=TRef,caller=Caller} =
++ lists:last(Pending),
++ _ = timer:cancel(TRef),
++ Reason1 = {failed_to_parse_received_data,Reason},
++ ct_gen_conn:return(Caller,{error,Reason1}),
++ lists:delete(P,Pending)
++ end,
++ {noreply, State#state{pending=Pending1}}.
+
+ %% Event function for the sax parser. It builds a simple XML structure.
+ %% Care is taken to keep namespace attributes and prefixes as in the original XML.
+@@ -1836,16 +1837,6 @@ get_tag([]) ->
+
+ %%%-----------------------------------------------------------------
+ %%% SSH stuff
+-ssh_receive_data() ->
+- receive
+- {ssh_cm, CM, {data, Ch, _Type, Data}} ->
+- ssh_connection:adjust_window(CM,Ch,size(Data)),
+- {ok, Data};
+- {ssh_cm, _CM, {Closed, _Ch}} = X when Closed == closed; Closed == eof ->
+- {error,X};
+- {_Ref,timeout} = X ->
+- {error,X}
+- end.
+
+ ssh_open(#options{host=Host,timeout=Timeout,port=Port,ssh=SshOpts,name=Name}) ->
+ case ssh:connect(Host, Port,
+diff --git lib/common_test/src/ct_run.erl lib/common_test/src/ct_run.erl
+index 4a12481..be547b4 100644
+--- lib/common_test/src/ct_run.erl
++++ lib/common_test/src/ct_run.erl
+@@ -225,18 +225,24 @@ finish(Tracing, ExitStatus, Args) ->
+ if ExitStatus == interactive_mode ->
+ interactive_mode;
+ true ->
+- %% it's possible to tell CT to finish execution with a call
+- %% to a different function than the normal halt/1 BIF
+- %% (meant to be used mainly for reading the CT exit status)
+- case get_start_opt(halt_with,
+- fun([HaltMod,HaltFunc]) ->
+- {list_to_atom(HaltMod),
+- list_to_atom(HaltFunc)} end,
+- Args) of
+- undefined ->
+- halt(ExitStatus);
+- {M,F} ->
+- apply(M, F, [ExitStatus])
++ case get_start_opt(vts, true, Args) of
++ true ->
++ %% VTS mode, don't halt the node
++ ok;
++ _ ->
++ %% it's possible to tell CT to finish execution with a call
++ %% to a different function than the normal halt/1 BIF
++ %% (meant to be used mainly for reading the CT exit status)
++ case get_start_opt(halt_with,
++ fun([HaltMod,HaltFunc]) ->
++ {list_to_atom(HaltMod),
++ list_to_atom(HaltFunc)} end,
++ Args) of
++ undefined ->
++ halt(ExitStatus);
++ {M,F} ->
++ apply(M, F, [ExitStatus])
++ end
+ end
+ end.
+
+@@ -244,7 +250,7 @@ script_start1(Parent, Args) ->
+ %% read general start flags
+ Label = get_start_opt(label, fun([Lbl]) -> Lbl end, Args),
+ Profile = get_start_opt(profile, fun([Prof]) -> Prof end, Args),
+- Vts = get_start_opt(vts, true, Args),
++ Vts = get_start_opt(vts, true, undefined, Args),
+ Shell = get_start_opt(shell, true, Args),
+ Cover = get_start_opt(cover, fun([CoverFile]) -> ?abs(CoverFile) end, Args),
+ CoverStop = get_start_opt(cover_stop,
+@@ -330,8 +336,8 @@ script_start1(Parent, Args) ->
+ Stylesheet = get_start_opt(stylesheet,
+ fun([SS]) -> ?abs(SS) end, Args),
+ %% basic_html - used by ct_logs
+- BasicHtml = case proplists:get_value(basic_html, Args) of
+- undefined ->
++ BasicHtml = case {Vts,proplists:get_value(basic_html, Args)} of
++ {undefined,undefined} ->
+ application:set_env(common_test, basic_html, false),
+ undefined;
+ _ ->
+@@ -364,9 +370,10 @@ script_start1(Parent, Args) ->
+ scale_timetraps = ScaleTT,
+ create_priv_dir = CreatePrivDir,
+ starter = script},
+-
++
+ %% check if log files should be refreshed or go on to run tests...
+ Result = run_or_refresh(Opts, Args),
++
+ %% send final results to starting process waiting in script_start/0
+ Parent ! {self(), Result}.
+
+@@ -757,21 +764,6 @@ script_start4(Opts = #opts{tests = Tests}, Args) ->
+ %%% @doc Print usage information for <code>ct_run</code>.
+ script_usage() ->
+ io:format("\n\nUsage:\n\n"),
+- io:format("Run tests in web based GUI:\n\n"
+- "\tct_run -vts [-browser Browser]"
+- "\n\t[-config ConfigFile1 ConfigFile2 .. ConfigFileN]"
+- "\n\t[-decrypt_key Key] | [-decrypt_file KeyFile]"
+- "\n\t[-dir TestDir1 TestDir2 .. TestDirN] |"
+- "\n\t[-suite Suite [-case Case]]"
+- "\n\t[-logopts LogOpt1 LogOpt2 .. LogOptN]"
+- "\n\t[-verbosity GenVLvl | [CategoryVLvl1 .. CategoryVLvlN]]"
+- "\n\t[-include InclDir1 InclDir2 .. InclDirN]"
+- "\n\t[-no_auto_compile]"
+- "\n\t[-abort_if_missing_suites]"
+- "\n\t[-multiply_timetraps N]"
+- "\n\t[-scale_timetraps]"
+- "\n\t[-create_priv_dir auto_per_run | auto_per_tc | manual_per_tc]"
+- "\n\t[-basic_html]\n\n"),
+ io:format("Run tests from command line:\n\n"
+ "\tct_run [-dir TestDir1 TestDir2 .. TestDirN] |"
+ "\n\t[[-dir TestDir] -suite Suite1 Suite2 .. SuiteN"
+@@ -831,7 +823,22 @@ script_usage() ->
+ io:format("Run CT in interactive mode:\n\n"
+ "\tct_run -shell"
+ "\n\t[-config ConfigFile1 ConfigFile2 .. ConfigFileN]"
+- "\n\t[-decrypt_key Key] | [-decrypt_file KeyFile]\n\n").
++ "\n\t[-decrypt_key Key] | [-decrypt_file KeyFile]\n\n"),
++ io:format("Run tests in web based GUI:\n\n"
++ "\tct_run -vts [-browser Browser]"
++ "\n\t[-config ConfigFile1 ConfigFile2 .. ConfigFileN]"
++ "\n\t[-decrypt_key Key] | [-decrypt_file KeyFile]"
++ "\n\t[-dir TestDir1 TestDir2 .. TestDirN] |"
++ "\n\t[-suite Suite [-case Case]]"
++ "\n\t[-logopts LogOpt1 LogOpt2 .. LogOptN]"
++ "\n\t[-verbosity GenVLvl | [CategoryVLvl1 .. CategoryVLvlN]]"
++ "\n\t[-include InclDir1 InclDir2 .. InclDirN]"
++ "\n\t[-no_auto_compile]"
++ "\n\t[-abort_if_missing_suites]"
++ "\n\t[-multiply_timetraps N]"
++ "\n\t[-scale_timetraps]"
++ "\n\t[-create_priv_dir auto_per_run | auto_per_tc | manual_per_tc]"
++ "\n\t[-basic_html]\n\n").
+
+ %%%-----------------------------------------------------------------
+ %%% @hidden
+diff --git lib/common_test/src/ct_telnet.erl lib/common_test/src/ct_telnet.erl
+index d906a26..844f537 100644
+--- lib/common_test/src/ct_telnet.erl
++++ lib/common_test/src/ct_telnet.erl
+@@ -486,7 +486,8 @@ expect(Connection,Patterns) ->
+ %%% Opts = [Opt]
+ %%% Opt = {idle_timeout,IdleTimeout} | {total_timeout,TotalTimeout} |
+ %%% repeat | {repeat,N} | sequence | {halt,HaltPatterns} |
+-%%% ignore_prompt | no_prompt_check
++%%% ignore_prompt | no_prompt_check | wait_for_prompt |
++%%% {wait_for_prompt,Prompt}
+ %%% IdleTimeout = infinity | integer()
+ %%% TotalTimeout = infinity | integer()
+ %%% N = integer()
+@@ -499,9 +500,9 @@ expect(Connection,Patterns) ->
+ %%%
+ %%% @doc Get data from telnet and wait for the expected pattern.
+ %%%
+-%%% <p><code>Pattern</code> can be a POSIX regular expression. If more
+-%%% than one pattern is given, the function returns when the first
+-%%% match is found.</p>
++%%% <p><code>Pattern</code> can be a POSIX regular expression. The function
++%%% returns as soon as a pattern has been successfully matched (at least one,
++%%% in the case of multiple patterns).</p>
+ %%%
+ %%% <p><code>RxMatch</code> is a list of matched strings. It looks
+ %%% like this: <code>[FullMatch, SubMatch1, SubMatch2, ...]</code>
+@@ -524,10 +525,13 @@ expect(Connection,Patterns) ->
+ %%% milliseconds, <code>{error,timeout}</code> is returned. The default
+ %%% value is <code>infinity</code> (i.e. no time limit).</p>
+ %%%
+-%%% <p>The function will always return when a prompt is found, unless
+-%%% any of the <code>ignore_prompt</code> or
+-%%% <code>no_prompt_check</code> options are used, in which case it
+-%%% will return when a match is found or after a timeout.</p>
++%%% <p>The function will return when a prompt is received, even if no
++%%% pattern has yet been matched. In this event,
++%%% <code>{error,{prompt,Prompt}}</code> is returned.
++%%% However, this behaviour may be modified with the
++%%% <code>ignore_prompt</code> or <code>no_prompt_check</code> option, which
++%%% tells <code>expect</code> to return only when a match is found or after a
++%%% timeout.</p>
+ %%%
+ %%% <p>If the <code>ignore_prompt</code> option is used,
+ %%% <code>ct_telnet</code> will ignore any prompt found. This option
+@@ -541,6 +545,13 @@ expect(Connection,Patterns) ->
+ %%% is useful if, for instance, the <code>Pattern</code> itself
+ %%% matches the prompt.</p>
+ %%%
++%%% <p>The <code>wait_for_prompt</code> option forces <code>ct_telnet</code>
++%%% to wait until the prompt string has been received before returning
++%%% (even if a pattern has already been matched). This is equal to calling:
++%%% <code>expect(Conn, Patterns++[{prompt,Prompt}], [sequence|Opts])</code>.
++%%% Note that <code>idle_timeout</code> and <code>total_timeout</code>
++%%% may abort the operation of waiting for prompt.</p>
++%%%
+ %%% <p>The <code>repeat</code> option indicates that the pattern(s)
+ %%% shall be matched multiple times. If <code>N</code> is given, the
+ %%% pattern(s) will be matched <code>N</code> times, and the function
+@@ -653,18 +664,21 @@ handle_msg({cmd,Cmd,Opts},State) ->
+ start_gen_log(heading(cmd,State#state.name)),
+ log(State,cmd,"Cmd: ~p",[Cmd]),
+
++ %% whatever is in the buffer from previous operations
++ %% will be ignored as we go ahead with this telnet cmd
++
+ debug_cont_gen_log("Throwing Buffer:",[]),
+ debug_log_lines(State#state.buffer),
+
+ case {State#state.type,State#state.prompt} of
+- {ts,_} ->
++ {ts,_} ->
+ silent_teln_expect(State#state.name,
+ State#state.teln_pid,
+ State#state.buffer,
+ prompt,
+ State#state.prx,
+ [{idle_timeout,2000}]);
+- {ip,false} ->
++ {ip,false} ->
+ silent_teln_expect(State#state.name,
+ State#state.teln_pid,
+ State#state.buffer,
+@@ -1029,10 +1043,12 @@ teln_expect(Name,Pid,Data,Pattern0,Prx,Opts) ->
+ end,
+
+ PromptCheck = get_prompt_check(Opts),
+- Seq = get_seq(Opts),
+- Pattern = convert_pattern(Pattern0,Seq),
+
+- {IdleTimeout,TotalTimeout} = get_timeouts(Opts),
++ {WaitForPrompt,Pattern1,Opts1} = wait_for_prompt(Pattern0,Opts),
++
++ Seq = get_seq(Opts1),
++ Pattern2 = convert_pattern(Pattern1,Seq),
++ {IdleTimeout,TotalTimeout} = get_timeouts(Opts1),
+
+ EO = #eo{teln_pid=Pid,
+ prx=Prx,
+@@ -1042,9 +1058,16 @@ teln_expect(Name,Pid,Data,Pattern0,Prx,Opts) ->
+ haltpatterns=HaltPatterns,
+ prompt_check=PromptCheck},
+
+- case get_repeat(Opts) of
++ case get_repeat(Opts1) of
+ false ->
+- case teln_expect1(Name,Pid,Data,Pattern,[],EO) of
++ case teln_expect1(Name,Pid,Data,Pattern2,[],EO) of
++ {ok,Matched,Rest} when WaitForPrompt ->
++ case lists:reverse(Matched) of
++ [{prompt,_},Matched1] ->
++ {ok,Matched1,Rest};
++ [{prompt,_}|Matched1] ->
++ {ok,lists:reverse(Matched1),Rest}
++ end;
+ {ok,Matched,Rest} ->
+ {ok,Matched,Rest};
+ {halt,Why,Rest} ->
+@@ -1054,7 +1077,7 @@ teln_expect(Name,Pid,Data,Pattern0,Prx,Opts) ->
+ end;
+ N ->
+ EO1 = EO#eo{repeat=N},
+- repeat_expect(Name,Pid,Data,Pattern,[],EO1)
++ repeat_expect(Name,Pid,Data,Pattern2,[],EO1)
+ end.
+
+ convert_pattern(Pattern,Seq)
+@@ -1118,6 +1141,40 @@ get_ignore_prompt(Opts) ->
+ get_prompt_check(Opts) ->
+ not lists:member(no_prompt_check,Opts).
+
++wait_for_prompt(Pattern, Opts) ->
++ case lists:member(wait_for_prompt, Opts) of
++ true ->
++ wait_for_prompt1(prompt, Pattern,
++ lists:delete(wait_for_prompt,Opts));
++ false ->
++ case proplists:get_value(wait_for_prompt, Opts) of
++ undefined ->
++ {false,Pattern,Opts};
++ PromptStr ->
++ wait_for_prompt1({prompt,PromptStr}, Pattern,
++ proplists:delete(wait_for_prompt,Opts))
++ end
++ end.
++
++wait_for_prompt1(Prompt, [Ch|_] = Pattern, Opts) when is_integer(Ch) ->
++ wait_for_prompt2(Prompt, [Pattern], Opts);
++wait_for_prompt1(Prompt, Pattern, Opts) when is_list(Pattern) ->
++ wait_for_prompt2(Prompt, Pattern, Opts);
++wait_for_prompt1(Prompt, Pattern, Opts) ->
++ wait_for_prompt2(Prompt, [Pattern], Opts).
++
++wait_for_prompt2(Prompt, Pattern, Opts) ->
++ Pattern1 = case lists:reverse(Pattern) of
++ [prompt|_] -> Pattern;
++ [{prompt,_}|_] -> Pattern;
++ _ -> Pattern ++ [Prompt]
++ end,
++ Opts1 = case lists:member(sequence, Opts) of
++ true -> Opts;
++ false -> [sequence|Opts]
++ end,
++ {true,Pattern1,Opts1}.
++
+ %% Repeat either single or sequence. All match results are accumulated
+ %% and returned when a halt condition is fulllfilled.
+ repeat_expect(_Name,_Pid,Rest,_Pattern,Acc,#eo{repeat=0}) ->
+@@ -1210,7 +1267,7 @@ get_data1(Pid) ->
+ %% 1) Single expect.
+ %% First the whole data chunk is searched for a prompt (to avoid doing
+ %% a regexp match for the prompt at each line).
+-%% If we are searching for anyting else, the datachunk is split into
++%% If we are searching for anything else, the datachunk is split into
+ %% lines and each line is matched against each pattern.
+
+ %% one_expect: split data chunk at prompts
+@@ -1227,7 +1284,7 @@ one_expect(Name,Pid,Data,Pattern,EO) ->
+ log(name_or_pid(Name,Pid),"PROMPT: ~ts",[PromptType]),
+ {match,{prompt,PromptType},Rest};
+ [{prompt,_OtherPromptType}] ->
+- %% Only searching for one specific prompt, not thisone
++ %% Only searching for one specific prompt, not this one
+ log_lines(Name,Pid,UptoPrompt),
+ {nomatch,Rest};
+ _ ->
+diff --git lib/common_test/src/ct_webtool.erl lib/common_test/src/ct_webtool.erl
+new file mode 100644
+index 0000000..b67a7c2
+--- /dev/null
++++ lib/common_test/src/ct_webtool.erl
+@@ -0,0 +1,1207 @@
++%%
++%% %CopyrightBegin%
++%%
++%% Copyright Ericsson AB 2001-2010. 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(ct_webtool).
++-behaviour(gen_server).
++
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++%% %%
++%% The general idea is: %%
++%% %%
++%% %%
++%% 1. Scan through the path for *.tool files and find all the web %%
++%% based tools. Query each tool for configuration data. %%
++%% 2. Add Alias for Erlscript and html for each tool to %%
++%% the webserver configuration data. %%
++%% 3. Start the webserver. %%
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++
++%% API functions
++-export([start/0, start/2, stop/0]).
++
++%% Starting Webtool from a shell script
++-export([script_start/0, script_start/1]).
++
++%% Web api
++-export([started_tools/2, toolbar/2, start_tools/2, stop_tools/2]).
++
++%% API against other tools
++-export([is_localhost/0]).
++
++%% Debug export s
++-export([get_tools1/1]).
++-export([debug/1, stop_debug/0, debug_app/1]).
++
++%% gen_server callbacks
++-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
++ terminate/2, code_change/3]).
++
++-include_lib("kernel/include/file.hrl").
++-include_lib("stdlib/include/ms_transform.hrl").
++
++-record(state,{priv_dir,app_data,supvis,web_data,started=[]}).
++
++-define(MAX_NUMBER_OF_WEBTOOLS,256).
++-define(DEFAULT_PORT,8888).% must be >1024 or the user must be root on unix
++-define(DEFAULT_ADDR,{127,0,0,1}).
++
++-define(WEBTOOL_ALIAS,{ct_webtool,[{alias,{erl_alias,"/ct_webtool",[ct_webtool]}}]}).
++-define(HEADER,"Pragma:no-cache\r\n Content-type: text/html\r\n\r\n").
++-define(HTML_HEADER,"<HTML>\r\n<HEAD>\r\n<TITLE>WebTool</TITLE>\r\n</HEAD>\r\n<BODY BGCOLOR=\"#FFFFFF\">\r\n").
++-define(HTML_HEADER_RELOAD,"<HTML>\r\n<HEAD>\r\n<TITLE>WebTool
++ </TITLE>\r\n</HEAD>\r\n
++ <BODY BGCOLOR=\"#FFFFFF\" onLoad=reloadCompiledList()>\r\n").
++
++-define(HTML_END,"</BODY></HTML>").
++
++-define(SEND_URL_TIMEOUT,5000).
++
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++%% %%
++%% For debugging only. %%
++%% %%
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++%% Start tracing with
++%% debug(Functions).
++%% Functions = local | global | FunctionList
++%% FunctionList = [Function]
++%% Function = {FunctionName,Arity} | FunctionName |
++%% {Module, FunctionName, Arity} | {Module,FunctionName}
++debug(F) ->
++ ttb:tracer(all,[{file,"webtool.trc"}]), % tracing all nodes
++ ttb:p(all,[call,timestamp]),
++ MS = [{'_',[],[{return_trace},{message,{caller}}]}],
++ tp(F,MS),
++ ttb:ctp(?MODULE,stop_debug), % don't want tracing of the stop_debug func
++ ok.
++tp(local,MS) -> % all functions
++ ttb:tpl(?MODULE,MS);
++tp(global,MS) -> % all exported functions
++ ttb:tp(?MODULE,MS);
++tp([{M,F,A}|T],MS) -> % Other module
++ ttb:tpl(M,F,A,MS),
++ tp(T,MS);
++tp([{M,F}|T],MS) when is_atom(F) -> % Other module
++ ttb:tpl(M,F,MS),
++ tp(T,MS);
++tp([{F,A}|T],MS) -> % function/arity
++ ttb:tpl(?MODULE,F,A,MS),
++ tp(T,MS);
++tp([F|T],MS) -> % function
++ ttb:tpl(?MODULE,F,MS),
++ tp(T,MS);
++tp([],_MS) ->
++ ok.
++stop_debug() ->
++ ttb:stop([format]).
++
++debug_app(Mod) ->
++ ttb:tracer(all,[{file,"webtool_app.trc"},{handler,{fun out/4,true}}]),
++ ttb:p(all,[call,timestamp]),
++ MS = [{'_',[],[{return_trace},{message,{caller}}]}],
++ ttb:tp(Mod,MS),
++ ok.
++
++out(_,{trace_ts,Pid,call,MFA={M,F,A},{W,_,_},TS},_,S)
++ when W==webtool;W==mod_esi->
++ io:format("~w: (~p)~ncall ~s~n", [TS,Pid,ffunc(MFA)]),
++ [{M,F,length(A)}|S];
++out(_,{trace_ts,Pid,return_from,MFA,R,TS},_,[MFA|S]) ->
++ io:format("~w: (~p)~nreturned from ~s -> ~p~n", [TS,Pid,ffunc(MFA),R]),
++ S;
++out(_,_,_,_) ->
++ ok.
++
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++%% %%
++%% Functions called via script. %%
++%% %%
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++script_start() ->
++ usage(),
++ halt().
++script_start([App]) ->
++ DefaultBrowser =
++ case os:type() of
++ {win32,_} -> iexplore;
++ _ -> firefox
++ end,
++ script_start([App,DefaultBrowser]);
++script_start([App,Browser]) ->
++ io:format("Starting webtool...\n"),
++ start(),
++ AvailableApps = get_applications(),
++ {OSType,_} = os:type(),
++ case lists:keysearch(App,1,AvailableApps) of
++ {value,{App,StartPage}} ->
++ io:format("Starting ~w...\n",[App]),
++ start_tools([],"app=" ++ atom_to_list(App)),
++ PortStr = integer_to_list(get_port()),
++ Url = case StartPage of
++ "/" ++ Page ->
++ "http://localhost:" ++ PortStr ++ "/" ++ Page;
++ _ ->
++ "http://localhost:" ++ PortStr ++ "/" ++ StartPage
++ end,
++ case Browser of
++ none ->
++ ok;
++ iexplore when OSType == win32->
++ io:format("Starting internet explorer...\n"),
++ {ok,R} = win32reg:open(""),
++ Key="\\local_machine\\SOFTWARE\\Microsoft\\IE Setup\\Setup",
++ win32reg:change_key(R,Key),
++ {ok,Val} = win32reg:value(R,"Path"),
++ IExplore=filename:join(win32reg:expand(Val),"iexplore.exe"),
++ os:cmd("\"" ++ IExplore ++ "\" " ++ Url);
++ _ when OSType == win32 ->
++ io:format("Starting ~w...\n",[Browser]),
++ os:cmd("\"" ++ atom_to_list(Browser) ++ "\" " ++ Url);
++ B when B==firefox; B==mozilla ->
++ io:format("Sending URL to ~w...",[Browser]),
++ BStr = atom_to_list(Browser),
++ SendCmd = BStr ++ " -raise -remote \'openUrl(" ++
++ Url ++ ")\'",
++ Port = open_port({spawn,SendCmd},[exit_status]),
++ receive
++ {Port,{exit_status,0}} ->
++ io:format("done\n"),
++ ok;
++ {Port,{exit_status,_Error}} ->
++ io:format(" not running, starting ~w...\n",
++ [Browser]),
++ os:cmd(BStr ++ " " ++ Url),
++ ok
++ after ?SEND_URL_TIMEOUT ->
++ io:format(" failed, starting ~w...\n",[Browser]),
++ erlang:port_close(Port),
++ os:cmd(BStr ++ " " ++ Url)
++ end;
++ _ ->
++ io:format("Starting ~w...\n",[Browser]),
++ os:cmd(atom_to_list(Browser) ++ " " ++ Url)
++ end,
++ ok;
++ false ->
++ stop(),
++ io:format("\n{error,{unknown_app,~p}}\n",[App]),
++ halt()
++ end.
++
++usage() ->
++ io:format("Starting webtool...\n"),
++ start(),
++ Apps = lists:map(fun({A,_}) -> A end,get_applications()),
++ io:format(
++ "\nUsage: start_webtool application [ browser ]\n"
++ "\nAvailable applications are: ~p\n"
++ "Default browser is \'iexplore\' (Internet Explorer) on Windows "
++ "or else \'firefox\'\n",
++ [Apps]),
++ stop().
++
++
++get_applications() ->
++ gen_server:call(ct_web_tool,get_applications).
++
++get_port() ->
++ gen_server:call(ct_web_tool,get_port).
++
++
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++%% %%
++%% Api functions to the genserver. %%
++%% %%
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++%----------------------------------------------------------------------
++%
++%----------------------------------------------------------------------
++
++start()->
++ start(standard_path,standard_data).
++
++start(Path,standard_data)->
++ case get_standard_data() of
++ {error,Reason} ->
++ {error,Reason};
++ Data ->
++ start(Path,Data)
++ end;
++
++start(standard_path,Data)->
++ Path=get_path(),
++ start(Path,Data);
++
++start(Path,Port) when is_integer(Port)->
++ Data = get_standard_data(Port),
++ start(Path,Data);
++
++start(Path,Data0)->
++ Data = Data0 ++ rest_of_standard_data(),
++ gen_server:start({local,ct_web_tool},ct_webtool,{Path,Data},[]).
++
++stop()->
++ gen_server:call(ct_web_tool,stoppit).
++
++%----------------------------------------------------------------------
++%Web Api functions called by the web
++%----------------------------------------------------------------------
++started_tools(Env,Input)->
++ gen_server:call(ct_web_tool,{started_tools,Env,Input}).
++
++toolbar(Env,Input)->
++ gen_server:call(ct_web_tool,{toolbar,Env,Input}).
++
++start_tools(Env,Input)->
++ gen_server:call(ct_web_tool,{start_tools,Env,Input}).
++
++stop_tools(Env,Input)->
++ gen_server:call(ct_web_tool,{stop_tools,Env,Input}).
++%----------------------------------------------------------------------
++%Support API for other tools
++%----------------------------------------------------------------------
++
++is_localhost()->
++ gen_server:call(ct_web_tool,is_localhost).
++
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++%% %%
++%%The gen_server callback functions that builds the webbpages %%
++%% %%
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++handle_call(get_applications,_,State)->
++ MS = ets:fun2ms(fun({Tool,{web_data,{_,Start}}}) -> {Tool,Start} end),
++ Tools = ets:select(State#state.app_data,MS),
++ {reply,Tools,State};
++
++handle_call(get_port,_,State)->
++ {value,{port,Port}}=lists:keysearch(port,1,State#state.web_data),
++ {reply,Port,State};
++
++handle_call({started_tools,_Env,_Input},_,State)->
++ {reply,started_tools_page(State),State};
++
++handle_call({toolbar,_Env,_Input},_,State)->
++ {reply,toolbar(),State};
++
++handle_call({start_tools,Env,Input},_,State)->
++ {NewState,Page}=start_tools_page(Env,Input,State),
++ {reply,Page,NewState};
++
++handle_call({stop_tools,Env,Input},_,State)->
++ {NewState,Page}=stop_tools_page(Env,Input,State),
++ {reply,Page,NewState};
++
++handle_call(stoppit,_From,Data)->
++ {stop,normal,ok,Data};
++
++handle_call(is_localhost,_From,Data)->
++ Result=case proplists:get_value(bind_address, Data#state.web_data) of
++ ?DEFAULT_ADDR ->
++ true;
++ _IpNumber ->
++ false
++ end,
++ {reply,Result,Data}.
++
++
++handle_info(_Message,State)->
++ {noreply,State}.
++
++handle_cast(_Request,State)->
++ {noreply,State}.
++
++code_change(_,State,_)->
++ {ok,State}.
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++%
++% The other functions needed by the gen_server behaviour
++%
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++%----------------------------------------------------------------------
++% Start the gen_server
++%----------------------------------------------------------------------
++init({Path,Config})->
++ case filelib:is_dir(Path) of
++ true ->
++ {ok, Table} = get_tool_files_data(),
++ insert_app(?WEBTOOL_ALIAS, Table),
++ case ct_webtool_sup:start_link() of
++ {ok, Pid} ->
++ case start_webserver(Table, Path, Config) of
++ {ok, _} ->
++ print_url(Config),
++ {ok,#state{priv_dir=Path,
++ app_data=Table,
++ supvis=Pid,
++ web_data=Config}};
++ {error, Error} ->
++ {stop, {error, Error}}
++ end;
++ Error ->
++ {stop,Error}
++ end;
++ false ->
++ {stop, {error, error_dir}}
++ end.
++
++terminate(_Reason,Data)->
++ %%shut down the webbserver
++ shutdown_server(Data),
++ %%Shutdown the different tools that are started with application:start
++ shutdown_apps(Data),
++ %%Shutdown the supervisor and its children will die
++ shutdown_supervisor(Data),
++ ok.
++
++print_url(ConfigData)->
++ Server=proplists:get_value(server_name,ConfigData,"undefined"),
++ Port=proplists:get_value(port,ConfigData,"undefined"),
++ {A,B,C,D}=proplists:get_value(bind_address,ConfigData,"undefined"),
++ io:format("WebTool is available at http://~s:~w/~n",[Server,Port]),
++ io:format("Or http://~w.~w.~w.~w:~w/~n",[A,B,C,D,Port]).
++
++
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++%
++% begin build the pages
++%
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++
++%----------------------------------------------------------------------
++%The page that shows the started tools
++%----------------------------------------------------------------------
++started_tools_page(State)->
++ [?HEADER,?HTML_HEADER,started_tools(State),?HTML_END].
++
++toolbar()->
++ [?HEADER,?HTML_HEADER,toolbar_page(),?HTML_END].
++
++
++start_tools_page(_Env,Input,State)->
++ %%io:format("~n======= ~n ~p ~n============~n",[Input]),
++ case get_tools(Input) of
++ {tools,Tools}->
++ %%io:format("~n======= ~n ~p ~n============~n",[Tools]),
++ {ok,NewState}=handle_apps(Tools,State,start),
++ {NewState,[?HEADER,?HTML_HEADER_RELOAD,reload_started_apps(),
++ show_unstarted_apps(NewState),?HTML_END]};
++ _ ->
++ {State,[?HEADER,?HTML_HEADER,show_unstarted_apps(State),?HTML_END]}
++ end.
++
++stop_tools_page(_Env,Input,State)->
++ case get_tools(Input) of
++ {tools,Tools}->
++ {ok,NewState}=handle_apps(Tools,State,stop),
++ {NewState,[?HEADER,?HTML_HEADER_RELOAD,reload_started_apps(),
++ show_started_apps(NewState),?HTML_END]};
++ _ ->
++ {State,[?HEADER,?HTML_HEADER,show_started_apps(State),?HTML_END]}
++ end.
++
++
++
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++%%
++%% Functions that start and config the webserver
++%% 1. Collect the config data
++%% 2. Start webserver
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++
++%----------------------------------------------------------------------
++% Start the webserver
++%----------------------------------------------------------------------
++start_webserver(Data,Path,Config)->
++ case get_conf_data(Data,Path,Config) of
++ {ok,Conf_data}->
++ %%io:format("Conf_data: ~p~n",[Conf_data]),
++ start_server(Conf_data);
++ {error,Error} ->
++ {error,{error_server_conf_file,Error}}
++ end.
++
++start_server(Conf_data)->
++ case inets:start(httpd, Conf_data, stand_alone) of
++ {ok,Pid}->
++ {ok,Pid};
++ Error->
++ {error,{server_error,Error}}
++ end.
++
++%----------------------------------------------------------------------
++% Create config data for the webserver
++%----------------------------------------------------------------------
++get_conf_data(Data,Path,Config)->
++ Aliases=get_aliases(Data),
++ ServerRoot = filename:join([Path,"root"]),
++ MimeTypesFile = filename:join([ServerRoot,"conf","mime.types"]),
++ case httpd_conf:load_mime_types(MimeTypesFile) of
++ {ok,MimeTypes} ->
++ Config1 = Config ++ Aliases,
++ Config2 = [{server_root,ServerRoot},
++ {document_root,filename:join([Path,"root/doc"])},
++ {mime_types,MimeTypes} |
++ Config1],
++ {ok,Config2};
++ Error ->
++ Error
++ end.
++
++%----------------------------------------------------------------------
++% Control the path for *.tools files
++%----------------------------------------------------------------------
++get_tool_files_data()->
++ Tools=get_tools1(code:get_path()),
++ %%io:format("Data : ~p ~n",[Tools]),
++ get_file_content(Tools).
++
++%----------------------------------------------------------------------
++%Control that the data in the file really is erlang terms
++%----------------------------------------------------------------------
++get_file_content(Tools)->
++ Get_data=fun({tool,ToolData}) ->
++ %%io:format("Data : ~p ~n",[ToolData]),
++ case proplists:get_value(config_func,ToolData) of
++ {M,F,A}->
++ case catch apply(M,F,A) of
++ {'EXIT',_} ->
++ bad_data;
++ Data when is_tuple(Data) ->
++ Data;
++ _->
++ bad_data
++ end;
++ _ ->
++ bad_data
++ end
++ end,
++ insert_file_content([X ||X<-lists:map(Get_data,Tools),X/=bad_data]).
++
++%----------------------------------------------------------------------
++%Insert the data from the file in to the ets:table
++%----------------------------------------------------------------------
++insert_file_content(Content)->
++ Table=ets:new(app_data,[bag]),
++ lists:foreach(fun(X)->
++ insert_app(X,Table)
++ end,Content),
++ {ok,Table}.
++
++%----------------------------------------------------------------------
++%Control that we got a a tuple of a atom and a list if so add the
++%elements in the list to the ets:table
++%----------------------------------------------------------------------
++insert_app({Name,Key_val_list},Table) when is_list(Key_val_list),is_atom(Name)->
++ %%io:format("ToolData: ~p: ~p~n",[Name,Key_val_list]),
++ lists:foreach(
++ fun({alias,{erl_alias,Alias,Mods}}) ->
++ Key_val = {erl_script_alias,{Alias,Mods}},
++ %%io:format("Insert: ~p~n",[Key_val]),
++ ets:insert(Table,{Name,Key_val});
++ (Key_val_pair)->
++ %%io:format("Insert: ~p~n",[Key_val_pair]),
++ ets:insert(Table,{Name,Key_val_pair})
++ end,
++ Key_val_list);
++
++insert_app(_,_)->
++ ok.
++
++%----------------------------------------------------------------------
++% Select all the alias in the database
++%----------------------------------------------------------------------
++get_aliases(Data)->
++ MS = ets:fun2ms(fun({_,{erl_script_alias,Alias}}) ->
++ {erl_script_alias,Alias};
++ ({_,{alias,Alias}}) ->
++ {alias,Alias}
++ end),
++ ets:select(Data,MS).
++
++
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++%% %%
++%% Helper functions %%
++%% %%
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++get_standard_data(Port)->
++ [
++ {port,Port},
++ {bind_address,?DEFAULT_ADDR},
++ {server_name,"localhost"}
++ ].
++
++get_standard_data()->
++ case get_free_port(?DEFAULT_PORT,?MAX_NUMBER_OF_WEBTOOLS) of
++ {error,Reason} -> {error,Reason};
++ Port ->
++ [
++ {port,Port},
++ {bind_address,?DEFAULT_ADDR},
++ {server_name,"localhost"}
++ ]
++ end.
++
++get_free_port(_Port,0) ->
++ {error,no_free_port_found};
++get_free_port(Port,N) ->
++ case gen_tcp:connect("localhost",Port,[]) of
++ {error, _Reason} ->
++ Port;
++ {ok,Sock} ->
++ gen_tcp:close(Sock),
++ get_free_port(Port+1,N-1)
++ end.
++
++rest_of_standard_data() ->
++ [
++ %% Do not allow the server to be crashed by malformed http-request
++ {max_header_siz,1024},
++ {max_header_action,reply414},
++ %% Go on a straight ip-socket
++ {com_type,ip_comm},
++ %% Do not change the order of these module names!!
++ {modules,[mod_alias,
++ mod_auth,
++ mod_esi,
++ mod_actions,
++ mod_cgi,
++ mod_include,
++ mod_dir,
++ mod_get,
++ mod_head,
++ mod_log,
++ mod_disk_log]},
++ {directory_index,["index.html"]},
++ {default_type,"text/plain"}
++ ].
++
++
++get_path()->
++ code:priv_dir(webtool).
++
++
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++% These functions is used to shutdown the webserver
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++
++%----------------------------------------------------------------------
++% Shut down the webbserver
++%----------------------------------------------------------------------
++shutdown_server(State)->
++ {Addr,Port} = get_addr_and_port(State#state.web_data),
++ inets:stop(httpd,{Addr,Port}).
++
++get_addr_and_port(Config) ->
++ Addr = proplists:get_value(bind_address,Config,?DEFAULT_ADDR),
++ Port = proplists:get_value(port,Config,?DEFAULT_PORT),
++ {Addr,Port}.
++
++%----------------------------------------------------------------------
++% Select all apps in the table and close them
++%----------------------------------------------------------------------
++shutdown_apps(State)->
++ Data=State#state.app_data,
++ MS = ets:fun2ms(fun({_,{start,HowToStart}}) -> HowToStart end),
++ lists:foreach(fun(Start_app)->
++ stop_app(Start_app)
++ end,
++ ets:select(Data,MS)).
++
++%----------------------------------------------------------------------
++%Shuts down the supervisor that supervises tools that is not
++%Designed as applications
++%----------------------------------------------------------------------
++shutdown_supervisor(State)->
++ %io:format("~n==================~n"),
++ ct_webtool_sup:stop(State#state.supvis).
++ %io:format("~n==================~n").
++
++%----------------------------------------------------------------------
++%close the individual apps.
++%----------------------------------------------------------------------
++stop_app({child,_Real_name})->
++ ok;
++
++stop_app({app,Real_name})->
++ application:stop(Real_name);
++
++stop_app({func,_Start,Stop})->
++ case Stop of
++ {M,F,A} ->
++ catch apply(M,F,A);
++ _NoStop ->
++ ok
++ end.
++
++
++
++
++
++
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++%%
++%% These functions creates the webpage where the user can select if
++%% to start apps or to stop apps
++%%
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++
++toolbar_page()->
++ "<TABLE>
++ <TR>
++ <TD>
++ <B>Select Action</B>
++ </TD>
++ </TR>
++ <TR>
++ <TD>
++ <A HREF=\"./start_tools\" TARGET=right> Start Tools</A>
++ </TD>
++ </TR>
++ <TR>
++ <TD>
++ <A HREF=\"./stop_tools\" TARGET=right> Stop Tools</A>
++ </TD>
++ </TR>
++ </TABLE>".
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++%%
++%% These functions creates the webbpage that shows the started apps
++%%
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++
++%----------------------------------------------------------------------
++% started_tools(State)->String (html table)
++% State is a record of type state
++%----------------------------------------------------------------------
++started_tools(State)->
++ Names=get_started_apps(State#state.app_data,State#state.started),
++ "<TABLE BORDER=1 WIDTH=100%>
++ "++ make_rows(Names,[],0) ++"
++ </TABLE>".
++%----------------------------------------------------------------------
++%get_started_apps(Data,Started)-> [{web_name,link}]
++%selects the started apps from the ets table of apps.
++%----------------------------------------------------------------------
++
++get_started_apps(Data,Started)->
++ SelectData=fun({Name,Link}) ->
++ {Name,Link}
++ end,
++ MS = lists:map(fun(A) -> {{A,{web_data,'$1'}},[],['$1']} end,Started),
++
++ [{"WebTool","/tool_management.html"} |
++ [SelectData(X) || X <- ets:select(Data,MS)]].
++
++%----------------------------------------------------------------------
++% make_rows(List,Result,Fields)-> String (The rows of a htmltable
++% List a list of tupler discibed above
++% Result an accumulator for the result
++% Field, counter that counts the number of cols in each row.
++%----------------------------------------------------------------------
++make_rows([],Result,Fields)->
++ Result ++ fill_out(Fields);
++make_rows([Data|Paths],Result,Field)when Field==0->
++ make_rows(Paths,Result ++ "<TR>" ++ make_field(Data),Field+1);
++
++make_rows([Path|Paths],Result,Field)when Field==4->
++ make_rows(Paths,Result ++ make_field(Path) ++ "</TR>",0);
++
++make_rows([Path|Paths],Result,Field)->
++ make_rows(Paths,Result ++ make_field(Path),Field+1).
++
++%----------------------------------------------------------------------
++% make_fields(Path)-> String that is a field i a html table
++% Path is a name url tuple {Name,url}
++%----------------------------------------------------------------------
++make_field(Path)->
++ "<TD WIDTH=20%>" ++ get_name(Path) ++ "</TD>".
++
++
++%----------------------------------------------------------------------
++%get_name({Nae,Url})->String that represents a <A> tag in html.
++%----------------------------------------------------------------------
++get_name({Name,Url})->
++ "<A HREF=\"" ++ Url ++ "\" TARGET=app_frame>" ++ Name ++ "</A>".
++
++
++%----------------------------------------------------------------------
++% fill_out(Nr)-> String, that represent Nr fields in a html-table.
++%----------------------------------------------------------------------
++fill_out(Nr)when Nr==0->
++ [];
++fill_out(Nr)when Nr==4->
++ "<TD WIDTH=\"20%\" >&nbsp</TD></TR>";
++
++fill_out(Nr)->
++ "<TD WIDTH=\"20%\">&nbsp</TD>" ++ fill_out(Nr+1).
++
++
++
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++%%
++%%These functions starts applicatons and builds the page showing tools
++%%to start
++%%
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++%----------------------------------------------------------------------
++%Controls whether the user selected a tool to start
++%----------------------------------------------------------------------
++get_tools(Input)->
++ case httpd:parse_query(Input) of
++ []->
++ no_tools;
++ Tools->
++ FormatData=fun({_Name,Data}) -> list_to_atom(Data) end,
++ SelectData=
++ fun({Name,_Data}) -> string:equal(Name,"app") end,
++ {tools,[FormatData(X)||X<-Tools,SelectData(X)]}
++ end.
++
++%----------------------------------------------------------------------
++% Selects the data to start the applications the user has ordered
++% starting of
++%----------------------------------------------------------------------
++handle_apps([],State,_Cmd)->
++ {ok,State};
++
++handle_apps([Tool|Tools],State,Cmd)->
++ case ets:match_object(State#state.app_data,{Tool,{start,'_'}}) of
++ []->
++ Started = case Cmd of
++ start ->
++ [Tool|State#state.started];
++ stop ->
++ lists:delete(Tool,State#state.started)
++ end,
++ {ok,#state{priv_dir=State#state.priv_dir,
++ app_data=State#state.app_data,
++ supvis=State#state.supvis,
++ web_data=State#state.web_data,
++ started=Started}};
++ ToStart ->
++ case handle_apps2(ToStart,State,Cmd) of
++ {ok,NewState}->
++ handle_apps(Tools,NewState,Cmd);
++ _->
++ handle_apps(Tools,State,Cmd)
++ end
++ end.
++
++%----------------------------------------------------------------------
++%execute every start or stop data about a tool.
++%----------------------------------------------------------------------
++handle_apps2([{Name,Start_data}],State,Cmd)->
++ case handle_app({Name,Start_data},State#state.app_data,State#state.supvis,Cmd) of
++ ok->
++ Started = case Cmd of
++ start ->
++ [Name|State#state.started];
++ stop ->
++
++ lists:delete(Name,State#state.started)
++ end,
++ {ok,#state{priv_dir=State#state.priv_dir,
++ app_data=State#state.app_data,
++ supvis=State#state.supvis,
++ web_data=State#state.web_data,
++ started=Started}};
++ _->
++ error
++ end;
++
++handle_apps2([{Name,Start_data}|Rest],State,Cmd)->
++ case handle_app({Name,Start_data},State#state.app_data,State#state.supvis,Cmd)of
++ ok->
++ handle_apps2(Rest,State,Cmd);
++ _->
++ error
++ end.
++
++
++%----------------------------------------------------------------------
++% Handle start and stop of applications
++%----------------------------------------------------------------------
++
++handle_app({Name,{start,{func,Start,Stop}}},Data,_Pid,Cmd)->
++ Action = case Cmd of
++ start ->
++ Start;
++ _ ->
++ Stop
++ end,
++ case Action of
++ {M,F,A} ->
++ case catch apply(M,F,A) of
++ {'EXIT',_} = Exit->
++ %%! Here the tool disappears from the webtool interface!!
++ io:format("\n=======ERROR (webtool, line ~w) =======\n"
++ "Could not start application \'~p\'\n\n"
++ "~w:~w(~s) ->\n"
++ "~p\n\n",
++ [?LINE,Name,M,F,format_args(A),Exit]),
++ ets:delete(Data,Name);
++ _OK->
++ ok
++ end;
++ _NoStart ->
++ ok
++ end;
++
++
++handle_app({Name,{start,{child,ChildSpec}}},Data,Pid,Cmd)->
++ case Cmd of
++ start ->
++ case catch supervisor:start_child(Pid,ChildSpec) of
++ {ok,_}->
++ ok;
++ {ok,_,_}->
++ ok;
++ {error,Reason}->
++ %%! Here the tool disappears from the webtool interface!!
++ io:format("\n=======ERROR (webtool, line ~w) =======\n"
++ "Could not start application \'~p\'\n\n"
++ "supervisor:start_child(~p,~p) ->\n"
++ "~p\n\n",
++ [?LINE,Name,Pid,ChildSpec,{error,Reason}]),
++ ets:delete(Data,Name);
++ Error ->
++ %%! Here the tool disappears from the webtool interface!!
++ io:format("\n=======ERROR (webtool, line ~w) =======\n"
++ "Could not start application \'~p\'\n\n"
++ "supervisor:start_child(~p,~p) ->\n"
++ "~p\n\n",
++ [?LINE,Name,Pid,ChildSpec,Error]),
++ ets:delete(Data,Name)
++ end;
++ stop ->
++ case catch supervisor:terminate_child(websup,element(1,ChildSpec)) of
++ ok ->
++ supervisor:delete_child(websup,element(1,ChildSpec));
++ _ ->
++ error
++ end
++ end;
++
++
++
++handle_app({Name,{start,{app,Real_name}}},Data,_Pid,Cmd)->
++ case Cmd of
++ start ->
++ case application:start(Real_name,temporary) of
++ ok->
++ io:write(Name),
++ ok;
++ {error,{already_started,_}}->
++ %% Remove it from the database so we dont start
++ %% anything already started
++ ets:match_delete(Data,{Name,{start,{app,Real_name}}}),
++ ok;
++ {error,_Reason}=Error->
++ %%! Here the tool disappears from the webtool interface!!
++ io:format("\n=======ERROR (webtool, line ~w) =======\n"
++ "Could not start application \'~p\'\n\n"
++ "application:start(~p,~p) ->\n"
++ "~p\n\n",
++ [?LINE,Name,Real_name,temporary,Error]),
++ ets:delete(Data,Name)
++ end;
++
++ stop ->
++ application:stop(Real_name)
++ end;
++
++%----------------------------------------------------------------------
++% If the data is incorrect delete the app
++%----------------------------------------------------------------------
++handle_app({Name,Incorrect},Data,_Pid,Cmd)->
++ %%! Here the tool disappears from the webtool interface!!
++ io:format("\n=======ERROR (webtool, line ~w) =======\n"
++ "Could not ~w application \'~p\'\n\n"
++ "Incorrect data: ~p\n\n",
++ [?LINE,Cmd,Name,Incorrect]),
++ ets:delete(Data,Name).
++
++
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++%% %%
++%% this functions creates the page that shows the unstarted tools %%
++%% %%
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++
++reload_started_apps()->
++ "<script>
++ function reloadCompiledList()
++ {
++ parent.parent.top1.document.location.href=\"/webtool/webtool/started_tools\";
++ }
++ </script>".
++
++show_unstarted_apps(State)->
++ "<TABLE HEIGHT=100% WIDTH=100% BORDER=0>
++ <TR HEIGHT=80%><TD ALIGN=\"center\" VALIGN=\"middle\">
++ <FORM NAME=\"stop_apps\" ACTION=\"/webtool/webtool/start_tools\" >
++ <TABLE BORDER=1 WIDTH=60%>
++ <TR BGCOLOR=\"#8899AA\">
++ <TD ALIGN=CENTER COLSPAN=2><FONT SIZE=4>Available Tools<FONT></TD>
++ </TR>
++ <TR>
++ <TD WIDTH=50%>
++ <TABLE BORDER=0>
++ "++ list_available_apps(State)++"
++ <TR><TD COLSPAN=2>&nbsp;</TD></TR>
++ <TR>
++ <TD COLSPAN=2 ALIGN=\"center\">
++ <INPUT TYPE=submit VALUE=\"Start\">
++ </TD>
++ </TR>
++ </TABLE>
++ </TD>
++ <TD>
++ To Start a Tool:
++ <UL>
++ <LI>Select the
++ checkbox for each tool to
++ start.</LI>
++ <LI>Click on the
++ button marked <EM>Start</EM>.</LI></UL>
++ </TD>
++ </TR>
++ </TABLE>
++ </FORM>
++ </TD></TR>
++ <TR><TD>&nbsp;</TD></TR>
++ </TABLE>".
++
++
++
++list_available_apps(State)->
++ MS = ets:fun2ms(fun({Tool,{web_data,{Name,_}}}) -> {Tool,Name} end),
++ Unstarted_apps=
++ lists:filter(
++ fun({Tool,_})->
++ false==lists:member(Tool,State#state.started)
++ end,
++ ets:select(State#state.app_data,MS)),
++ case Unstarted_apps of
++ []->
++ "<TR><TD>All tools are started</TD></TR>";
++ _->
++ list_apps(Unstarted_apps)
++ end.
++
++
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++%% %%
++%% these functions creates the page that shows the started apps %%
++%% the user can select to shutdown %%
++%% %%
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++show_started_apps(State)->
++ "<TABLE HEIGHT=100% WIDTH=100% BORDER=0>
++ <TR HEIGHT=80%><TD ALIGN=\"center\" VALIGN=\"middle\">
++ <FORM NAME=\"stop_apps\" ACTION=\"/webtool/webtool/stop_tools\" >
++ <TABLE BORDER=1 WIDTH=60%>
++ <TR BGCOLOR=\"#8899AA\">
++ <TD ALIGN=CENTER COLSPAN=2><FONT SIZE=4>Started Tools<FONT></TD>
++ </TR>
++ <TR>
++ <TD WIDTH=50%>
++ <TABLE BORDER=0>
++ "++ list_started_apps(State)++"
++ <TR><TD COLSPAN=2>&nbsp;</TD></TR>
++ <TR>
++ <TD COLSPAN=2 ALIGN=\"center\">
++ <INPUT TYPE=submit VALUE=\"Stop\">
++ </TD>
++ </TR>
++ </TABLE>
++ </TD>
++ <TD>
++ Stop a Tool:
++ <UL>
++ <LI>Select the
++ checkbox for each tool to
++ stop.</LI>
++ <LI>Click on the
++ button marked <EM>Stop</EM>.</LI></UL>
++ </TD>
++ </TR>
++ </TABLE>
++ </FORM>
++ </TD></TR>
++ <TR><TD>&nbsp;</TD></TR>
++ </TABLE>".
++
++list_started_apps(State)->
++ MS = lists:map(fun(A) -> {{A,{web_data,{'$1','_'}}},[],[{{A,'$1'}}]} end,
++ State#state.started),
++ Started_apps= ets:select(State#state.app_data,MS),
++ case Started_apps of
++ []->
++ "<TR><TD>No tool is started yet.</TD></TR>";
++ _->
++ list_apps(Started_apps)
++ end.
++
++
++list_apps(Apps) ->
++ lists:map(fun({Tool,Name})->
++ "<TR><TD>
++ <INPUT TYPE=\"checkbox\" NAME=\"app\" VALUE=\""
++ ++ atom_to_list(Tool) ++ "\">
++ " ++ Name ++ "
++ </TD></TR>"
++ end,
++ Apps).
++
++
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++%% %%
++%% Collecting the data from the *.tool files %%
++%% %%
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++%----------------------------------------
++% get_tools(Dirs) => [{M,F,A},{M,F,A}...{M,F,A}]
++% Dirs - [string()] Directory names
++% Calls get_tools2/2 recursively for a number of directories
++% to retireve the configuration data for the web based tools.
++%----------------------------------------
++get_tools1(Dirs)->
++ get_tools1(Dirs,[]).
++
++get_tools1([Dir|Rest],Data) when is_list(Dir) ->
++ Tools=case filename:basename(Dir) of
++ %% Dir is an 'ebin' directory, check in '../priv' as well
++ "ebin" ->
++ [get_tools2(filename:join(filename:dirname(Dir),"priv")) |
++ get_tools2(Dir)];
++ _ ->
++ get_tools2(Dir)
++ end,
++ get_tools1(Rest,[Tools|Data]);
++
++get_tools1([],Data) ->
++ lists:flatten(Data).
++
++%----------------------------------------
++% get_tools2(Directory) => DataList
++% DataList : [WebTuple]|[]
++% WebTuple: {tool,[{web,M,F,A}]}
++%
++%----------------------------------------
++get_tools2(Dir)->
++ get_tools2(tool_files(Dir),[]).
++
++get_tools2([ToolFile|Rest],Data) ->
++ case get_tools3(ToolFile) of
++ {tool,WebData} ->
++ get_tools2(Rest,[{tool,WebData}|Data]);
++ {error,_Reason} ->
++ get_tools2(Rest,Data);
++ nodata ->
++ get_tools2(Rest,Data)
++ end;
++
++get_tools2([],Data) ->
++ Data.
++
++%----------------------------------------
++% get_tools3(ToolFile) => {ok,Tool}|{error,Reason}|nodata
++% Tool: {tool,[KeyValTuple]}
++% ToolFile - string() A .tool file
++% Now we have the file get the data and sort it out
++%----------------------------------------
++get_tools3(ToolFile) ->
++ case file:consult(ToolFile) of
++ {error,open} ->
++ {error,nofile};
++ {error,read} ->
++ {error,format};
++ {ok,[{version,"1.2"},ToolInfo]} when is_list(ToolInfo)->
++ webdata(ToolInfo);
++ {ok,[{version,_Vsn},_Info]} ->
++ {error,old_version};
++ {ok,_Other} ->
++ {error,format}
++ end.
++
++
++%----------------------------------------------------------------------
++% webdata(TupleList)-> ToolTuple| nodata
++% ToolTuple: {tool,[{config_func,{M,F,A}}]}
++%
++% There are a little unneccesary work in this format but it is extendable
++%----------------------------------------------------------------------
++webdata(TupleList)->
++ case proplists:get_value(config_func,TupleList,nodata) of
++ {M,F,A} ->
++ {tool,[{config_func,{M,F,A}}]};
++ _ ->
++ nodata
++ end.
++
++
++%=============================================================================
++% Functions for getting *.tool configuration files
++%=============================================================================
++
++%----------------------------------------
++% tool_files(Dir) => ToolFiles
++% Dir - string() Directory name
++% ToolFiles - [string()]
++% Return the list of all files in Dir ending with .tool (appended to Dir)
++%----------------------------------------
++tool_files(Dir) ->
++ case file:list_dir(Dir) of
++ {ok,Files} ->
++ filter_tool_files(Dir,Files);
++ {error,_Reason} ->
++ []
++ end.
++
++%----------------------------------------
++% filter_tool_files(Dir,Files) => ToolFiles
++% Dir - string() Directory name
++% Files, ToolFiles - [string()] File names
++% Filters out the files in Files ending with .tool and append them to Dir
++%----------------------------------------
++filter_tool_files(_Dir,[]) ->
++ [];
++filter_tool_files(Dir,[File|Rest]) ->
++ case filename:extension(File) of
++ ".tool" ->
++ [filename:join(Dir,File)|filter_tool_files(Dir,Rest)];
++ _ ->
++ filter_tool_files(Dir,Rest)
++ end.
++
++
++%%%-----------------------------------------------------------------
++%%% format functions
++ffunc({M,F,A}) when is_list(A) ->
++ io_lib:format("~w:~w(~s)\n",[M,F,format_args(A)]);
++ffunc({M,F,A}) when is_integer(A) ->
++ io_lib:format("~w:~w/~w\n",[M,F,A]).
++
++format_args([]) ->
++ "";
++format_args(Args) ->
++ Str = lists:append(["~p"|lists:duplicate(length(Args)-1,",~p")]),
++ io_lib:format(Str,Args).
+diff --git lib/common_test/src/ct_webtool_sup.erl lib/common_test/src/ct_webtool_sup.erl
+new file mode 100644
+index 0000000..1d612a2
+--- /dev/null
++++ lib/common_test/src/ct_webtool_sup.erl
+@@ -0,0 +1,74 @@
++%%
++%% %CopyrightBegin%
++%%
++%% Copyright Ericsson AB 2001-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(ct_webtool_sup).
++
++-behaviour(supervisor).
++
++%% External exports
++-export([start_link/0,stop/1]).
++
++%% supervisor callbacks
++-export([init/1]).
++
++%%%----------------------------------------------------------------------
++%%% API
++%%%----------------------------------------------------------------------
++start_link() ->
++ supervisor:start_link({local,ct_websup},ct_webtool_sup, []).
++
++stop(Pid)->
++ exit(Pid,normal).
++%%%----------------------------------------------------------------------
++%%% Callback functions from supervisor
++%%%----------------------------------------------------------------------
++
++%%----------------------------------------------------------------------
++%% Func: init/1
++%% Returns: {ok, {SupFlags, [ChildSpec]}} |
++%% ignore |
++%% {error, Reason}
++%%----------------------------------------------------------------------
++init(_StartArgs) ->
++ %%Child1 =
++ %%Child2 ={webcover_backend,{webcover_backend,start_link,[]},permanent,2000,worker,[webcover_backend]},
++ %%{ok,{{simple_one_for_one,5,10},[Child1]}}.
++ {ok,{{one_for_one,100,10},[]}}.
++
++%%%----------------------------------------------------------------------
++%%% Internal functions
++%%%----------------------------------------------------------------------
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
+diff --git lib/common_test/src/vts.erl lib/common_test/src/vts.erl
+index b340c6f..ab13e7d 100644
+--- lib/common_test/src/vts.erl
++++ lib/common_test/src/vts.erl
+@@ -63,21 +63,21 @@
+ %%%-----------------------------------------------------------------
+ %%% User API
+ start() ->
+- webtool:start(),
+- webtool:start_tools([],"app=vts").
++ ct_webtool:start(),
++ ct_webtool:start_tools([],"app=vts").
+
+ init_data(ConfigFiles,EvHandlers,LogDir,LogOpts,Tests) ->
+ call({init_data,ConfigFiles,EvHandlers,LogDir,LogOpts,Tests}).
+
+ stop() ->
+- webtool:stop_tools([],"app=vts"),
+- webtool:stop().
++ ct_webtool:stop_tools([],"app=vts"),
++ ct_webtool:stop().
+
+ report(What,Data) ->
+ call({report,What,Data}).
+
+ %%%-----------------------------------------------------------------
+-%%% Return config data used by webtool
++%%% Return config data used by ct_webtool
+ config_data() ->
+ {ok,LogDir} =
+ case lists:keysearch(logdir,1,init:get_arguments()) of
+diff --git lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl
+index a145d85..d01211b 100644
+--- lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl
++++ lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl
+@@ -164,7 +164,7 @@ hello_from_server_first(Config) ->
+ {ok,Client} = ct_netconfc:only_open(?DEFAULT_SSH_OPTS(DataDir)),
+ ct:sleep(500),
+ ?NS:expect(hello),
+- ?ok = ct_netconfc:hello(Client),
++ ?ok = ct_netconfc:hello(Client, [{capability, ["urn:com:ericsson:ebase:1.1.0"]}], infinity),
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client),
+ ok.
+@@ -490,13 +490,16 @@ action(Config) ->
+ Data = [{myactionreturn,[{xmlns,"myns"}],["value"]}],
+ %% test either to receive {data,Data} or {ok,Data},
+ %% both need to be handled
+- {Reply,RetVal} = case element(3, now()) rem 2 of
+- 0 -> {{data,Data},{ok,Data}};
+- 1 -> {{ok,Data},ok}
+- end,
+- ct:log("Client will receive {~w,Data}", [element(1,Reply)]),
+- ?NS:expect_reply(action,Reply),
+- RetVal = ct_netconfc:action(Client,{myaction,[{xmlns,"myns"}],[]}),
++ ct:log("Client will receive {~w,~p}", [data,Data]),
++ ct:log("Expecting ~p", [{ok, Data}]),
++ ?NS:expect_reply(action,{data, Data}),
++ {ok, Data} = ct_netconfc:action(Client,{myaction,[{xmlns,"myns"}],[]}),
++
++ ct:log("Client will receive {~w,~p}", [ok,Data]),
++ ct:log("Expecting ~p", [ok]),
++ ?NS:expect_reply(action,{ok, Data}),
++ ok = ct_netconfc:action(Client,{myaction,[{xmlns,"myns"}],[]}),
++
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client),
+ ok.
+diff --git lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_own_server_SUITE.erl lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_own_server_SUITE.erl
+index 1d3f591..9dc9095 100644
+--- lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_own_server_SUITE.erl
++++ lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_own_server_SUITE.erl
+@@ -40,6 +40,7 @@ all() ->
+ expect,
+ expect_repeat,
+ expect_sequence,
++ expect_wait_until_prompt,
+ expect_error_prompt,
+ expect_error_timeout1,
+ expect_error_timeout2,
+@@ -81,6 +82,8 @@ end_per_group(_GroupName, Config) ->
+ expect(_) ->
+ {ok, Handle} = ct_telnet:open(telnet_server_conn1),
+ ok = ct_telnet:send(Handle, "echo ayt"),
++ {ok,["ayt"]} = ct_telnet:expect(Handle, "ayt"),
++ ok = ct_telnet:send(Handle, "echo ayt"),
+ {ok,["ayt"]} = ct_telnet:expect(Handle, ["ayt"]),
+ ok = ct_telnet:close(Handle),
+ ok.
+@@ -103,6 +106,21 @@ expect_sequence(_) ->
+ ok = ct_telnet:close(Handle),
+ ok.
+
++%% Check that expect can wait for delayed prompt
++expect_wait_until_prompt(_) ->
++ {ok, Handle} = ct_telnet:open(telnet_server_conn1),
++ Timeouts = [{idle_timeout,5000},{total_timeout,7000}],
++
++ ok = ct_telnet:send(Handle, "echo_delayed_prompt 3000 xxx"),
++ {ok,["xxx"]} =
++ ct_telnet:expect(Handle, "xxx",
++ [wait_for_prompt|Timeouts]),
++ ok = ct_telnet:send(Handle, "echo_delayed_prompt 3000 yyy zzz"),
++ {ok,[["yyy"],["zzz"]]} =
++ ct_telnet:expect(Handle, ["yyy","zzz"],
++ [{wait_for_prompt,"> "}|Timeouts]),
++ ok.
++
+ %% Check that expect returns when a prompt is found, even if pattern
+ %% is not matched.
+ expect_error_prompt(_) ->
+diff --git lib/common_test/test/telnet_server.erl lib/common_test/test/telnet_server.erl
+index 11959c3..2db5a9b 100644
+--- lib/common_test/test/telnet_server.erl
++++ lib/common_test/test/telnet_server.erl
+@@ -242,6 +242,12 @@ do_handle_data("echo_loop " ++ Data,State) ->
+ ReturnData = string:join(Lines,"\n"),
+ send_loop(list_to_integer(TStr),ReturnData,State),
+ {ok,State};
++do_handle_data("echo_delayed_prompt "++Data,State) ->
++ [MsStr|EchoData] = string:tokens(Data, " "),
++ send(string:join(EchoData,"\n"),State),
++ ct:sleep(list_to_integer(MsStr)),
++ send("\r\n> ",State),
++ {ok,State};
+ do_handle_data("disconnect_after " ++WaitStr,State) ->
+ Wait = list_to_integer(string:strip(WaitStr,right,$\n)),
+ dbg("Server will close connection in ~w ms...", [Wait]),
+diff --git lib/common_test/vsn.mk lib/common_test/vsn.mk
+index d654a8a..e2d9217 100644
+--- lib/common_test/vsn.mk
++++ lib/common_test/vsn.mk
+@@ -1 +1 @@
+-COMMON_TEST_VSN = 1.10
++COMMON_TEST_VSN = 1.10.1
+diff --git lib/diameter/doc/src/diameter.xml lib/diameter/doc/src/diameter.xml
+index 6e41b01..ea175a5 100644
+--- lib/diameter/doc/src/diameter.xml
++++ lib/diameter/doc/src/diameter.xml
+@@ -1820,7 +1820,8 @@ The information presented here is as in the <c>connect</c> case except
+ that the client connections are grouped under an <c>accept</c> tuple.</p>
+
+ <p>
+-Whether or not the &transport_opt; <c>pool_size</c> affects the format
++Whether or not the &transport_opt; <c>pool_size</c> has been
++configured affects the format
+ of the listing in the case of a connecting transport, since a value
+ greater than 1 implies multiple transport processes for the same
+ <c>&transport_ref;</c>, as in the listening case.
+diff --git lib/diameter/doc/src/notes.xml lib/diameter/doc/src/notes.xml
+index 479fab2..6931788 100644
+--- lib/diameter/doc/src/notes.xml
++++ lib/diameter/doc/src/notes.xml
+@@ -42,6 +42,47 @@ first.</p>
+
+ <!-- ===================================================================== -->
+
++<section><title>diameter 1.9.1</title>
++
++ <section><title>Known Bugs and Problems</title>
++ <list>
++ <item>
++ <p>
++ Don't leave extra bit in decoded AVP data.</p>
++ <p>
++ OTP-12074 in OTP 17.3 missed one case: a length error on
++ a trailing AVP unknown to the dictionary in question.</p>
++ <p>
++ Own Id: OTP-12642</p>
++ </item>
++ <item>
++ <p>
++ Don't confuse Result-Code and Experimental-Result</p>
++ <p>
++ The errors field of a decoded diameter_packet record was
++ populated with a Result-Code AVP when an
++ Experimental-Result containing a 3xxx Result-Code was
++ received in an answer not setting the E-bit. The correct
++ AVP is now extracted from the incoming message.</p>
++ <p>
++ Own Id: OTP-12654 Aux Id: seq12851 </p>
++ </item>
++ <item>
++ <p>
++ Don't count on unknown Application Id.</p>
++ <p>
++ OTP-11721 in OTP 17.1 missed the case of an Application
++ Id not agreeing with that of the dictionary in question,
++ causing counters to be accumulated on keys containing the
++ unknown id.</p>
++ <p>
++ Own Id: OTP-12701</p>
++ </item>
++ </list>
++ </section>
++
++</section>
++
+ <section><title>diameter 1.9</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+diff --git lib/diameter/include/diameter_gen.hrl lib/diameter/include/diameter_gen.hrl
+index 0eef218..e8ffe7f 100644
+--- lib/diameter/include/diameter_gen.hrl
++++ lib/diameter/include/diameter_gen.hrl
+@@ -445,7 +445,7 @@ reset(_, _) ->
+ %% undecoded. Note that the type field is 'undefined' in this case.
+
+ decode_AVP(Name, Avp, {Avps, Acc}) ->
+- {[Avp | Avps], pack_AVP(Name, Avp, Acc)}.
++ {[trim(Avp) | Avps], pack_AVP(Name, Avp, Acc)}.
+
+ %% rc/1
+
+diff --git lib/diameter/src/base/diameter_codec.erl lib/diameter/src/base/diameter_codec.erl
+index 15a4c5e..bf2fe8e 100644
+--- lib/diameter/src/base/diameter_codec.erl
++++ lib/diameter/src/base/diameter_codec.erl
+@@ -640,8 +640,12 @@ split_data(Bin, Len) ->
+ %% payload if this is a request. Do this (in cases that we
+ %% know the type) by inducing a decode failure and letting
+ %% the dictionary's decode (in diameter_gen) deal with it.
+- %% Here we don't know type. If the type isn't known, then
+- %% the decode just strips the extra bit.
++ %%
++ %% Note that the extra bit can only occur in the trailing
++ %% AVP of a message or Grouped AVP, since a faulty AVP
++ %% Length is otherwise indistinguishable from a correct
++ %% one here, since we don't know the types of the AVPs
++ %% being extracted.
+ {<<0:1, Bin/binary>>, <<>>}
+ end.
+
+@@ -690,8 +694,8 @@ pack_avp(#diameter_avp{code = undefined, data = B})
+ Len = size(<<H:5/binary, _:24, T/binary>> = <<B/binary, 0:Pad>>),
+ <<H/binary, Len:24, T/binary>>;
+
+-%% ... from a dictionary compiled against old code in diameter_gen ...
+ %% ... when ignoring errors in Failed-AVP ...
++%% ... during a relay encode ...
+ pack_avp(#diameter_avp{data = <<0:1, B/binary>>} = A) ->
+ pack_avp(A#diameter_avp{data = B});
+
+diff --git lib/diameter/src/base/diameter_traffic.erl lib/diameter/src/base/diameter_traffic.erl
+index 538ebee..ffd2c0a 100644
+--- lib/diameter/src/base/diameter_traffic.erl
++++ lib/diameter/src/base/diameter_traffic.erl
+@@ -980,8 +980,8 @@ answer_message(OH, OR, RC, Dict0, #diameter_packet{avps = Avps,
+ session_id(Code, Vid, Dict0, Avps)
+ when is_list(Avps) ->
+ try
+- {value, #diameter_avp{data = D}} = find_avp(Code, Vid, Avps),
+- [{'Session-Id', [Dict0:avp(decode, D, 'Session-Id')]}]
++ #diameter_avp{data = Bin} = find_avp(Code, Vid, Avps),
++ [{'Session-Id', [Dict0:avp(decode, Bin, 'Session-Id')]}]
+ catch
+ error: _ ->
+ []
+@@ -998,26 +998,17 @@ failed_avp(_, [] = No) ->
+
+ %% find_avp/3
+
+-find_avp(Code, Vid, Avps)
+- when is_integer(Code), (undefined == Vid orelse is_integer(Vid)) ->
+- find(fun(A) -> is_avp(Code, Vid, A) end, Avps).
++%% Grouped ...
++find_avp(Code, VId, [[#diameter_avp{code = Code, vendor_id = VId} | _] = As
++ | _]) ->
++ As;
+
+-%% The final argument here could be a list of AVP's, depending on the case,
+-%% but we're only searching at the top level.
+-is_avp(Code, Vid, #diameter_avp{code = Code, vendor_id = Vid}) ->
+- true;
+-is_avp(_, _, _) ->
+- false.
++%% ... or not.
++find_avp(Code, VId, [#diameter_avp{code = Code, vendor_id = VId} = A | _]) ->
++ A;
+
+-find(_, []) ->
+- false;
+-find(Pred, [H|T]) ->
+- case Pred(H) of
+- true ->
+- {value, H};
+- false ->
+- find(Pred, T)
+- end.
++find_avp(Code, VId, [_ | Avps]) ->
++ find_avp(Code, VId, Avps).
+
+ %% 7. Error Handling
+ %%
+@@ -1086,7 +1077,6 @@ incr_result(_, #diameter_packet{msg = undefined = No}, _, _) ->
+ incr_result(Dir, Pkt, TPid, {Dict, AppDict, Dict0}) ->
+ #diameter_packet{header = #diameter_header{is_error = E}
+ = Hdr,
+- msg = Msg,
+ errors = Es}
+ = Pkt,
+
+@@ -1096,13 +1086,13 @@ incr_result(Dir, Pkt, TPid, {Dict, AppDict, Dict0}) ->
+ recv /= Dir orelse [] == Es orelse incr_error(Dir, Id, TPid, AppDict),
+
+ %% Exit on a missing result code.
+- T = rc_counter(Dict, Msg),
++ T = rc_counter(Dict, Dir, Pkt),
+ T == false andalso ?LOGX(no_result_code, {Dict, Dir, Hdr}),
+- {Ctr, RC} = T,
++ {Ctr, RC, Avp} = T,
+
+ %% Or on an inappropriate value.
+ is_result(RC, E, Dict0)
+- orelse ?LOGX(invalid_error_bit, {Dict, Dir, Hdr, RC}),
++ orelse ?LOGX(invalid_error_bit, {Dict, Dir, Hdr, Avp}),
+
+ incr(TPid, {Id, Dir, Ctr}),
+ Ctr.
+@@ -1116,19 +1106,15 @@ msg_id(#diameter_packet{header = H}, Dict) ->
+ %% there are 2^32 (application ids) * 2^24 (command codes) = 2^56
+ %% pairs for an attacker to choose from.
+ msg_id(Hdr, Dict) ->
+- {_ApplId, Code, R} = Id = diameter_codec:msg_id(Hdr),
+- case Dict:msg_name(Code, 0 == R) of
+- '' ->
+- unknown(Dict:id(), R);
+- _ ->
+- Id
++ {Aid, Code, R} = Id = diameter_codec:msg_id(Hdr),
++ if Aid == ?APP_ID_RELAY ->
++ {relay, R};
++ true ->
++ choose(Aid /= Dict:id() orelse '' == Dict:msg_name(Code, 0 == R),
++ unknown,
++ Id)
+ end.
+
+-unknown(?APP_ID_RELAY, R) ->
+- {relay, R};
+-unknown(_, _) ->
+- unknown.
+-
+ %% No E-bit: can't be 3xxx.
+ is_result(RC, false, _Dict0) ->
+ RC < 3000 orelse 4000 =< RC;
+@@ -1148,7 +1134,7 @@ is_result(RC, true, _) ->
+ incr(TPid, Counter) ->
+ diameter_stats:incr(Counter, TPid, 1).
+
+-%% rc_counter/2
++%% rc_counter/3
+
+ %% RFC 3588, 7.6:
+ %%
+@@ -1156,39 +1142,45 @@ incr(TPid, Counter) ->
+ %% applications MUST include either one Result-Code AVP or one
+ %% Experimental-Result AVP.
+
++rc_counter(Dict, recv, #diameter_packet{header = H, avps = As}) ->
++ rc_counter(Dict, [H|As]);
++
++rc_counter(Dict, _, #diameter_packet{msg = Msg}) ->
++ rc_counter(Dict, Msg).
++
+ rc_counter(Dict, Msg) ->
+- rcc(Dict, Msg, int(get_avp_value(Dict, 'Result-Code', Msg))).
++ rcc(get_result(Dict, Msg)).
+
+-rcc(Dict, Msg, undefined) ->
+- rcc(get_avp_value(Dict, 'Experimental-Result', Msg));
+-
+-rcc(_, _, N)
++rcc(#diameter_avp{name = 'Result-Code' = Name, value = N} = A)
+ when is_integer(N) ->
+- {{'Result-Code', N}, N}.
++ {{Name, N}, N, A};
+
+-%% Outgoing answers may be in any of the forms messages can be sent
+-%% in. Incoming messages will be records. We're assuming here that the
+-%% arity of the result code AVP's is 0 or 1.
++rcc(#diameter_avp{name = 'Result-Code' = Name, value = [N|_]} = A)
++ when is_integer(N) ->
++ {{Name, N}, N, A};
+
+-rcc([{_,_,N} = T | _])
++rcc(#diameter_avp{name = 'Experimental-Result', value = {_,_,N} = T} = A)
+ when is_integer(N) ->
+- {T,N};
+-rcc({_,_,N} = T)
++ {T, N, A};
++
++rcc(#diameter_avp{name = 'Experimental-Result', value = [{_,_,N} = T|_]} = A)
+ when is_integer(N) ->
+- {T,N};
++ {T, N, A};
++
+ rcc(_) ->
+ false.
+
+-%% Extract the first good looking integer. There's no guarantee
+-%% that what we're looking for has arity 1.
+-int([N|_])
+- when is_integer(N) ->
+- N;
+-int(N)
+- when is_integer(N) ->
+- N;
+-int(_) ->
+- undefined.
++%% get_result/2
++
++get_result(Dict, Msg) ->
++ try
++ [throw(A) || N <- ['Result-Code', 'Experimental-Result'],
++ #diameter_avp{} = A <- [get_avp(Dict, N, Msg)]]
++ of
++ [] -> false
++ catch
++ #diameter_avp{} = A -> A
++ end.
+
+ x(T) ->
+ exit(T).
+@@ -1528,10 +1520,10 @@ handle_A(Pkt, SvcName, Dict, Dict0, App, #request{transport = TPid} = Req) ->
+ %% a missing AVP. If both are optional in the dictionary
+ %% then this isn't a decode error: just continue on.
+ answer(Pkt, SvcName, App, Req);
+- exit: {invalid_error_bit, {_, _, _, RC}} ->
++ exit: {invalid_error_bit, {_, _, _, Avp}} ->
+ #diameter_packet{errors = Es}
+ = Pkt,
+- E = {5004, #diameter_avp{name = 'Result-Code', value = RC}},
++ E = {5004, Avp},
+ answer(Pkt#diameter_packet{errors = [E|Es]}, SvcName, App, Req)
+ end.
+
+@@ -1868,7 +1860,7 @@ str([]) ->
+ str(T) ->
+ T.
+
+-%% get_avp_value/3
++%% get_avp/3
+ %%
+ %% Find an AVP in a message of one of three forms:
+ %%
+@@ -1885,47 +1877,71 @@ str(T) ->
+ %% look for are in the common dictionary. This is required since the
+ %% relay dictionary doesn't inherit the common dictionary (which maybe
+ %% it should).
+-get_avp_value(?RELAY, Name, Msg) ->
+- get_avp_value(?BASE, Name, Msg);
++get_avp(?RELAY, Name, Msg) ->
++ get_avp(?BASE, Name, Msg);
+
+-%% Message sent as a header/avps list, probably a relay case but not
+-%% necessarily.
+-get_avp_value(Dict, Name, [#diameter_header{} | Avps]) ->
++%% Message as a header/avps list.
++get_avp(Dict, Name, [#diameter_header{} | Avps]) ->
+ try
+ {Code, _, VId} = Dict:avp_header(Name),
+- [A|_] = lists:dropwhile(fun(#diameter_avp{code = C, vendor_id = V}) ->
+- C /= Code orelse V /= VId
+- end,
+- Avps),
+- avp_decode(Dict, Name, A)
++ find_avp(Code, VId, Avps)
++ of
++ A ->
++ avp_decode(Dict, Name, ungroup(A))
+ catch
+ error: _ ->
+ undefined
+ end;
+
+ %% Outgoing message as a name/values list.
+-get_avp_value(_, Name, [_MsgName | Avps]) ->
++get_avp(_, Name, [_MsgName | Avps]) ->
+ case lists:keyfind(Name, 1, Avps) of
+ {_, V} ->
+- V;
++ #diameter_avp{name = Name, value = V};
+ _ ->
+ undefined
+ end;
+
+ %% Message is typically a record but not necessarily.
+-get_avp_value(Dict, Name, Rec) ->
++get_avp(Dict, Name, Rec) ->
+ try
+- Dict:'#get-'(Name, Rec)
++ #diameter_avp{name = Name, value = Dict:'#get-'(Name, Rec)}
+ catch
+ error:_ ->
+ undefined
+ end.
+
++%% get_avp_value/3
++
++get_avp_value(Dict, Name, Msg) ->
++ case get_avp(Dict, Name, Msg) of
++ #diameter_avp{value = V} ->
++ V;
++ undefined = No ->
++ No
++ end.
++
++%% ungroup/1
++
++ungroup([Avp|_]) ->
++ Avp;
++ungroup(Avp) ->
++ Avp.
++
++%% avp_decode/3
++
+ avp_decode(Dict, Name, #diameter_avp{value = undefined,
+- data = Bin}) ->
+- Dict:avp(decode, Bin, Name);
+-avp_decode(_, _, #diameter_avp{value = V}) ->
+- V.
++ data = Bin}
++ = Avp) ->
++ try Dict:avp(decode, Bin, Name) of
++ V ->
++ Avp#diameter_avp{value = V}
++ catch
++ error:_ ->
++ Avp
++ end;
++avp_decode(_, _, #diameter_avp{} = Avp) ->
++ Avp.
+
+ cb(#diameter_app{module = [_|_] = M}, F, A) ->
+ eval(M, F, A);
+diff --git lib/diameter/src/diameter.appup.src lib/diameter/src/diameter.appup.src
+index a54eb24..0ef0fd3 100644
+--- lib/diameter/src/diameter.appup.src
++++ lib/diameter/src/diameter.appup.src
+@@ -35,32 +35,10 @@
+ {"1.4.3", [{restart_application, diameter}]}, %% R16B02
+ {"1.4.4", [{restart_application, diameter}]},
+ {"1.5", [{restart_application, diameter}]}, %% R16B03
+- {"1.6", [{load_module, diameter_lib}, %% 17.0
+- {load_module, diameter_traffic},
+- {load_module, diameter_watchdog},
+- {load_module, diameter_peer_fsm},
+- {load_module, diameter_service},
+- {load_module, diameter_gen_base_rfc6733},
+- {load_module, diameter_gen_acct_rfc6733},
+- {load_module, diameter_gen_base_rfc3588},
+- {load_module, diameter_gen_base_accounting},
+- {load_module, diameter_gen_relay},
+- {load_module, diameter_codec},
+- {load_module, diameter_sctp}]},
+- {"1.7", [{load_module, diameter_service}, %% 17.1
+- {load_module, diameter_codec},
+- {load_module, diameter_gen_base_rfc6733},
+- {load_module, diameter_gen_acct_rfc6733},
+- {load_module, diameter_gen_base_rfc3588},
+- {load_module, diameter_gen_base_accounting},
+- {load_module, diameter_gen_relay},
+- {load_module, diameter_traffic},
+- {load_module, diameter_peer_fsm}]},
+- {"1.7.1", [{load_module, diameter_traffic}, %% 17.3
+- {load_module, diameter_watchdog},
+- {load_module, diameter_peer_fsm},
+- {load_module, diameter_service}]},
+- {"1.8", [{load_module, diameter_lib}, %% 17.4
++ {"1.6", [{restart_application, diameter}]}, %% 17.0
++ {"1.7", [{restart_application, diameter}]}, %% 17.[12]
++ {<<"^1\\.(7\\.1|8)$">>, %% 17.[34]
++ [{load_module, diameter_lib},
+ {load_module, diameter_peer},
+ {load_module, diameter_reg},
+ {load_module, diameter_session},
+@@ -84,7 +62,14 @@
+ {load_module, diameter_gen_relay},
+ {update, diameter_transport_sup, supervisor},
+ {update, diameter_service_sup, supervisor},
+- {update, diameter_sup, supervisor}]}
++ {update, diameter_sup, supervisor}]},
++ {"1.9", [{load_module, diameter_codec}, %% 17.5
++ {load_module, diameter_traffic},
++ {load_module, diameter_gen_base_rfc6733},
++ {load_module, diameter_gen_acct_rfc6733},
++ {load_module, diameter_gen_base_rfc3588},
++ {load_module, diameter_gen_base_accounting},
++ {load_module, diameter_gen_relay}]}
+ ],
+ [
+ {"0.9", [{restart_application, diameter}]},
+@@ -102,55 +87,40 @@
+ {"1.4.3", [{restart_application, diameter}]},
+ {"1.4.4", [{restart_application, diameter}]},
+ {"1.5", [{restart_application, diameter}]},
+- {"1.6", [{load_module, diameter_sctp},
+- {load_module, diameter_codec},
++ {"1.6", [{restart_application, diameter}]},
++ {"1.7", [{restart_application, diameter}]},
++ {<<"^1\\.(7\\.1|8)$">>,
++ [{update, diameter_sup, supervisor},
++ {update, diameter_service_sup, supervisor},
++ {update, diameter_transport_sup, supervisor},
+ {load_module, diameter_gen_relay},
+ {load_module, diameter_gen_base_accounting},
+ {load_module, diameter_gen_base_rfc3588},
+ {load_module, diameter_gen_acct_rfc6733},
+ {load_module, diameter_gen_base_rfc6733},
+- {load_module, diameter_service},
+- {load_module, diameter_peer_fsm},
++ {load_module, diameter},
++ {load_module, diameter_config},
++ {load_module, diameter_sctp},
++ {load_module, diameter_tcp},
+ {load_module, diameter_watchdog},
++ {load_module, diameter_peer_fsm},
++ {load_module, diameter_service},
+ {load_module, diameter_traffic},
++ {load_module, diameter_types},
++ {load_module, diameter_codec},
++ {load_module, diameter_capx},
++ {load_module, diameter_sync},
++ {load_module, diameter_stats},
++ {load_module, diameter_session},
++ {load_module, diameter_reg},
++ {load_module, diameter_peer},
+ {load_module, diameter_lib}]},
+- {"1.7", [{load_module, diameter_peer_fsm},
+- {load_module, diameter_traffic},
+- {load_module, diameter_gen_relay},
+- {load_module, diameter_gen_base_accounting},
+- {load_module, diameter_gen_base_rfc3588},
+- {load_module, diameter_gen_acct_rfc6733},
+- {load_module, diameter_gen_base_rfc6733},
+- {load_module, diameter_codec},
+- {load_module, diameter_service}]},
+- {"1.7.1", [{load_module, diameter_service},
+- {load_module, diameter_peer_fsm},
+- {load_module, diameter_watchdog},
+- {load_module, diameter_traffic}]},
+- {"1.8", [{update, diameter_sup, supervisor},
+- {update, diameter_service_sup, supervisor},
+- {update, diameter_transport_sup, supervisor},
+- {load_module, diameter_gen_relay},
++ {"1.9", [{load_module, diameter_gen_relay},
+ {load_module, diameter_gen_base_accounting},
+ {load_module, diameter_gen_base_rfc3588},
+ {load_module, diameter_gen_acct_rfc6733},
+ {load_module, diameter_gen_base_rfc6733},
+- {load_module, diameter},
+- {load_module, diameter_config},
+- {load_module, diameter_sctp},
+- {load_module, diameter_tcp},
+- {load_module, diameter_watchdog},
+- {load_module, diameter_peer_fsm},
+- {load_module, diameter_service},
+ {load_module, diameter_traffic},
+- {load_module, diameter_types},
+- {load_module, diameter_codec},
+- {load_module, diameter_capx},
+- {load_module, diameter_sync},
+- {load_module, diameter_stats},
+- {load_module, diameter_session},
+- {load_module, diameter_reg},
+- {load_module, diameter_peer},
+- {load_module, diameter_lib}]}
++ {load_module, diameter_codec}]}
+ ]
+ }.
+diff --git lib/diameter/test/diameter_3xxx_SUITE.erl lib/diameter/test/diameter_3xxx_SUITE.erl
+index 071b1a1..44fc3a6 100644
+--- lib/diameter/test/diameter_3xxx_SUITE.erl
++++ lib/diameter/test/diameter_3xxx_SUITE.erl
+@@ -47,6 +47,7 @@
+ send_double_error/1,
+ send_3xxx/1,
+ send_5xxx/1,
++ counters/1,
+ stop/1]).
+
+ %% diameter callbacks
+@@ -111,7 +112,7 @@ all() ->
+
+ groups() ->
+ Tc = tc(),
+- [{?util:name([E,D]), [], [start] ++ Tc ++ [stop]}
++ [{?util:name([E,D]), [], [start] ++ Tc ++ [counters, stop]}
+ || E <- ?ERRORS, D <- ?RFCS].
+
+ init_per_suite(Config) ->
+@@ -169,6 +170,203 @@ stop(_Config) ->
+ ok = diameter:stop_service(?SERVER),
+ ok = diameter:stop_service(?CLIENT).
+
++%% counters/1
++%%
++%% Check that counters are as expected.
++
++counters(Config) ->
++ Group = proplists:get_value(group, Config),
++ [_Errors, _Rfc] = G = ?util:name(Group),
++ [] = ?util:run([[fun counters/3, K, S, G]
++ || K <- [statistics, transport, connections],
++ S <- [?CLIENT, ?SERVER]]).
++
++counters(Key, Svc, Group) ->
++ counters(Key, Svc, Group, [_|_] = diameter:service_info(Svc, Key)).
++
++counters(statistics, Svc, [Errors, Rfc], L) ->
++ [{P, Stats}] = L,
++ true = is_pid(P),
++ stats(Svc, Errors, Rfc, lists:sort(Stats));
++
++counters(_, _, _, _) ->
++ todo.
++
++stats(?CLIENT, E, rfc3588, L)
++ when E == answer;
++ E == answer_3xxx ->
++ [{{unknown,recv},2},
++ {{{0,257,0},recv},1},
++ {{{0,257,1},send},1},
++ {{{0,275,0},recv},6},
++ {{{0,275,1},send},10},
++ {{unknown,recv,{'Result-Code',3001}},1},
++ {{unknown,recv,{'Result-Code',3007}},1},
++ {{{0,257,0},recv,{'Result-Code',2001}},1},
++ {{{0,275,0},recv,{'Result-Code',2001}},1},
++ {{{0,275,0},recv,{'Result-Code',3008}},2},
++ {{{0,275,0},recv,{'Result-Code',3999}},1},
++ {{{0,275,0},recv,{'Result-Code',5002}},1},
++ {{{0,275,0},recv,{'Result-Code',5005}},1}]
++ = L;
++
++stats(?SERVER, E, rfc3588, L)
++ when E == answer;
++ E == answer_3xxx ->
++ [{{unknown,recv},1},
++ {{unknown,send},2},
++ {{{0,257,0},send},1},
++ {{{0,257,1},recv},1},
++ {{{0,275,0},send},6},
++ {{{0,275,1},recv},8},
++ {{unknown,recv,error},1},
++ {{unknown,send,{'Result-Code',3001}},1},
++ {{unknown,send,{'Result-Code',3007}},1},
++ {{{0,257,0},send,{'Result-Code',2001}},1},
++ {{{0,275,0},send,{'Result-Code',2001}},1},
++ {{{0,275,0},send,{'Result-Code',3008}},2},
++ {{{0,275,0},send,{'Result-Code',3999}},1},
++ {{{0,275,0},send,{'Result-Code',5002}},1},
++ {{{0,275,0},send,{'Result-Code',5005}},1},
++ {{{0,275,1},recv,error},5}]
++ = L;
++
++stats(?CLIENT, answer, rfc6733, L) ->
++ [{{unknown,recv},2},
++ {{{0,257,0},recv},1},
++ {{{0,257,1},send},1},
++ {{{0,275,0},recv},8},
++ {{{0,275,1},send},10},
++ {{unknown,recv,{'Result-Code',3001}},1},
++ {{unknown,recv,{'Result-Code',3007}},1},
++ {{{0,257,0},recv,{'Result-Code',2001}},1},
++ {{{0,275,0},recv,{'Result-Code',3008}},2},
++ {{{0,275,0},recv,{'Result-Code',3999}},1},
++ {{{0,275,0},recv,{'Result-Code',5002}},1},
++ {{{0,275,0},recv,{'Result-Code',5005}},3},
++ {{{0,275,0},recv,{'Result-Code',5999}},1}]
++ = L;
++
++stats(?SERVER, answer, rfc6733, L) ->
++ [{{unknown,recv},1},
++ {{unknown,send},2},
++ {{{0,257,0},send},1},
++ {{{0,257,1},recv},1},
++ {{{0,275,0},send},8},
++ {{{0,275,1},recv},8},
++ {{unknown,recv,error},1},
++ {{unknown,send,{'Result-Code',3001}},1},
++ {{unknown,send,{'Result-Code',3007}},1},
++ {{{0,257,0},send,{'Result-Code',2001}},1},
++ {{{0,275,0},send,{'Result-Code',3008}},2},
++ {{{0,275,0},send,{'Result-Code',3999}},1},
++ {{{0,275,0},send,{'Result-Code',5002}},1},
++ {{{0,275,0},send,{'Result-Code',5005}},3},
++ {{{0,275,0},send,{'Result-Code',5999}},1},
++ {{{0,275,1},recv,error},5}]
++ = L;
++
++stats(?CLIENT, answer_3xxx, rfc6733, L) ->
++ [{{unknown,recv},2},
++ {{{0,257,0},recv},1},
++ {{{0,257,1},send},1},
++ {{{0,275,0},recv},8},
++ {{{0,275,1},send},10},
++ {{unknown,recv,{'Result-Code',3001}},1},
++ {{unknown,recv,{'Result-Code',3007}},1},
++ {{{0,257,0},recv,{'Result-Code',2001}},1},
++ {{{0,275,0},recv,{'Result-Code',2001}},1},
++ {{{0,275,0},recv,{'Result-Code',3008}},2},
++ {{{0,275,0},recv,{'Result-Code',3999}},1},
++ {{{0,275,0},recv,{'Result-Code',5002}},1},
++ {{{0,275,0},recv,{'Result-Code',5005}},2},
++ {{{0,275,0},recv,{'Result-Code',5999}},1}]
++ = L;
++
++stats(?SERVER, answer_3xxx, rfc6733, L) ->
++ [{{unknown,recv},1},
++ {{unknown,send},2},
++ {{{0,257,0},send},1},
++ {{{0,257,1},recv},1},
++ {{{0,275,0},send},8},
++ {{{0,275,1},recv},8},
++ {{unknown,recv,error},1},
++ {{unknown,send,{'Result-Code',3001}},1},
++ {{unknown,send,{'Result-Code',3007}},1},
++ {{{0,257,0},send,{'Result-Code',2001}},1},
++ {{{0,275,0},send,{'Result-Code',2001}},1},
++ {{{0,275,0},send,{'Result-Code',3008}},2},
++ {{{0,275,0},send,{'Result-Code',3999}},1},
++ {{{0,275,0},send,{'Result-Code',5002}},1},
++ {{{0,275,0},send,{'Result-Code',5005}},2},
++ {{{0,275,0},send,{'Result-Code',5999}},1},
++ {{{0,275,1},recv,error},5}]
++ = L;
++
++stats(?CLIENT, callback, rfc3588, L) ->
++ [{{unknown,recv},1},
++ {{{0,257,0},recv},1},
++ {{{0,257,1},send},1},
++ {{{0,275,0},recv},6},
++ {{{0,275,1},send},10},
++ {{unknown,recv,{'Result-Code',3007}},1},
++ {{{0,257,0},recv,{'Result-Code',2001}},1},
++ {{{0,275,0},recv,{'Result-Code',2001}},2},
++ {{{0,275,0},recv,{'Result-Code',3999}},1},
++ {{{0,275,0},recv,{'Result-Code',5002}},1},
++ {{{0,275,0},recv,{'Result-Code',5005}},2}]
++ = L;
++
++stats(?SERVER, callback, rfc3588, L) ->
++ [{{unknown,recv},1},
++ {{unknown,send},1},
++ {{{0,257,0},send},1},
++ {{{0,257,1},recv},1},
++ {{{0,275,0},send},6},
++ {{{0,275,1},recv},8},
++ {{unknown,recv,error},1},
++ {{unknown,send,{'Result-Code',3007}},1},
++ {{{0,257,0},send,{'Result-Code',2001}},1},
++ {{{0,275,0},send,{'Result-Code',2001}},2},
++ {{{0,275,0},send,{'Result-Code',3999}},1},
++ {{{0,275,0},send,{'Result-Code',5002}},1},
++ {{{0,275,0},send,{'Result-Code',5005}},2},
++ {{{0,275,1},recv,error},5}]
++ = L;
++
++stats(?CLIENT, callback, rfc6733, L) ->
++ [{{unknown,recv},1},
++ {{{0,257,0},recv},1},
++ {{{0,257,1},send},1},
++ {{{0,275,0},recv},8},
++ {{{0,275,1},send},10},
++ {{unknown,recv,{'Result-Code',3007}},1},
++ {{{0,257,0},recv,{'Result-Code',2001}},1},
++ {{{0,275,0},recv,{'Result-Code',2001}},2},
++ {{{0,275,0},recv,{'Result-Code',3999}},1},
++ {{{0,275,0},recv,{'Result-Code',5002}},1},
++ {{{0,275,0},recv,{'Result-Code',5005}},3},
++ {{{0,275,0},recv,{'Result-Code',5999}},1}]
++ = L;
++
++stats(?SERVER, callback, rfc6733, L) ->
++ [{{unknown,recv},1},
++ {{unknown,send},1},
++ {{{0,257,0},send},1},
++ {{{0,257,1},recv},1},
++ {{{0,275,0},send},8},
++ {{{0,275,1},recv},8},
++ {{unknown,recv,error},1},
++ {{unknown,send,{'Result-Code',3007}},1},
++ {{{0,257,0},send,{'Result-Code',2001}},1},
++ {{{0,275,0},send,{'Result-Code',2001}},2},
++ {{{0,275,0},send,{'Result-Code',3999}},1},
++ {{{0,275,0},send,{'Result-Code',5002}},1},
++ {{{0,275,0},send,{'Result-Code',5005}},3},
++ {{{0,275,0},send,{'Result-Code',5999}},1},
++ {{{0,275,1},recv,error},5}]
++ = L.
++
+ %% send_unknown_application/1
+ %%
+ %% Send an unknown application that a callback (which shouldn't take
+diff --git lib/diameter/test/diameter_app_SUITE.erl lib/diameter/test/diameter_app_SUITE.erl
+index 6975e83..84f8a66 100644
+--- lib/diameter/test/diameter_app_SUITE.erl
++++ lib/diameter/test/diameter_app_SUITE.erl
+@@ -249,11 +249,10 @@ release() ->
+ end.
+
+ unversion(App) ->
+- T = lists:dropwhile(fun is_vsn_ch/1, lists:reverse(App)),
+- lists:reverse(case T of [$-|TT] -> TT; _ -> T end).
+-
+-is_vsn_ch(C) ->
+- $0 =< C andalso C =< $9 orelse $. == C.
++ {Name, [$-|Vsn]} = lists:splitwith(fun(C) -> C /= $- end, App),
++ true = is_app(Name), %% assert
++ Vsn = vsn_str(Vsn), %%
++ Name.
+
+ app('$M_EXPR') -> %% could be anything but assume it's ok
+ "erts";
+@@ -322,11 +321,11 @@ acc_rel(Dir, Rel, {Vsn, _}, Acc) ->
+
+ %% Write a rel file and return its name.
+ write_rel(Dir, [Erts | Apps], Vsn) ->
+- true = is_vsn(Vsn),
+- Name = "diameter_test_" ++ Vsn,
++ VS = vsn_str(Vsn),
++ Name = "diameter_test_" ++ VS,
+ ok = write_file(filename:join([Dir, Name ++ ".rel"]),
+ {release,
+- {"diameter " ++ Vsn ++ " test release", Vsn},
++ {"diameter " ++ VS ++ " test release", VS},
+ Erts,
+ Apps}),
+ Name.
+@@ -341,10 +340,34 @@ fetch(Key, List) ->
+ write_file(Path, T) ->
+ file:write_file(Path, io_lib:format("~p.", [T])).
+
+-%% Is a version string of the expected form? Return the argument
+-%% itself for 'false' for a useful badmatch.
++%% Is a version string of the expected form?
+ is_vsn(V) ->
+- is_list(V)
+- andalso length(V) == string:span(V, "0123456789.")
+- andalso V == string:join(string:tokens(V, [$.]), ".") %% no ".."
+- orelse {error, V}.
++ V = vsn_str(V),
++ true.
++
++%% Turn a from/to version in appup to a version string after ensuring
++%% that it's valid version number of regexp. In the regexp case, the
++%% regexp itself becomes the version string since there's no
++%% requirement that a version in appup be anything but a string. The
++%% restrictions placed on string-valued version numbers (that they be
++%% '.'-separated integers) are our own.
++
++vsn_str(S)
++ when is_list(S) ->
++ {_, match} = {S, match(S, "^(0|[1-9][0-9]*)(\\.(0|[1-9][0-9]*))*$")},
++ {_, nomatch} = {S, match(S, "\\.0\\.0$")},
++ S;
++
++vsn_str(B)
++ when is_binary(B) ->
++ {ok, _} = re:compile(B),
++ binary_to_list(B).
++
++match(S, RE) ->
++ re:run(S, RE, [{capture, none}]).
++
++%% Is an application name of the expected form?
++is_app(S)
++ when is_list(S) ->
++ {_, match} = {S, match(S, "^([a-z]([a-z_]*|[a-zA-Z]*))$")},
++ true.
+diff --git lib/diameter/test/diameter_traffic_SUITE.erl lib/diameter/test/diameter_traffic_SUITE.erl
+index 7dd9f39..7ff6ba7 100644
+--- lib/diameter/test/diameter_traffic_SUITE.erl
++++ lib/diameter/test/diameter_traffic_SUITE.erl
+@@ -41,6 +41,7 @@
+ send_eval/1,
+ send_bad_answer/1,
+ send_protocol_error/1,
++ send_experimental_result/1,
+ send_arbitrary/1,
+ send_unknown/1,
+ send_unknown_short/1,
+@@ -301,6 +302,7 @@ tc() ->
+ send_eval,
+ send_bad_answer,
+ send_protocol_error,
++ send_experimental_result,
+ send_arbitrary,
+ send_unknown,
+ send_unknown_short,
+@@ -443,7 +445,7 @@ send_ok(Config) ->
+ Req = ['ACR', {'Accounting-Record-Type', ?EVENT_RECORD},
+ {'Accounting-Record-Number', 1}],
+
+- ['ACA', _SessionId, {'Result-Code', ?SUCCESS} | _]
++ ['ACA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _]
+ = call(Config, Req).
+
+ %% Send an accounting ACR that the server answers badly to.
+@@ -459,7 +461,7 @@ send_eval(Config) ->
+ Req = ['ACR', {'Accounting-Record-Type', ?EVENT_RECORD},
+ {'Accounting-Record-Number', 3}],
+
+- ['ACA', _SessionId, {'Result-Code', ?SUCCESS} | _]
++ ['ACA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _]
+ = call(Config, Req).
+
+ %% Send an accounting ACR that the server tries to answer with an
+@@ -480,12 +482,20 @@ send_protocol_error(Config) ->
+ ?answer_message(?TOO_BUSY)
+ = call(Config, Req).
+
++%% Send a 3xxx Experimental-Result in an answer not setting the E-bit
++%% and missing a Result-Code.
++send_experimental_result(Config) ->
++ Req = ['ACR', {'Accounting-Record-Type', ?EVENT_RECORD},
++ {'Accounting-Record-Number', 5}],
++ ['ACA', {'Session-Id', _} | _]
++ = call(Config, Req).
++
+ %% Send an ASR with an arbitrary non-mandatory AVP and expect success
+ %% and the same AVP in the reply.
+ send_arbitrary(Config) ->
+ Req = ['ASR', {'AVP', [#diameter_avp{name = 'Product-Name',
+ value = "XXX"}]}],
+- ['ASA', _SessionId, {'Result-Code', ?SUCCESS} | Avps]
++ ['ASA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | Avps]
+ = call(Config, Req),
+ {'AVP', [#diameter_avp{name = 'Product-Name',
+ value = V}]}
+@@ -497,7 +507,7 @@ send_unknown(Config) ->
+ Req = ['ASR', {'AVP', [#diameter_avp{code = 999,
+ is_mandatory = false,
+ data = <<17>>}]}],
+- ['ASA', _SessionId, {'Result-Code', ?SUCCESS} | Avps]
++ ['ASA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | Avps]
+ = call(Config, Req),
+ {'AVP', [#diameter_avp{code = 999,
+ is_mandatory = false,
+@@ -513,7 +523,7 @@ send_unknown_short(Config, M, RC) ->
+ Req = ['ASR', {'AVP', [#diameter_avp{code = 999,
+ is_mandatory = M,
+ data = <<17>>}]}],
+- ['ASA', _SessionId, {'Result-Code', RC} | Avps]
++ ['ASA', {'Session-Id', _}, {'Result-Code', RC} | Avps]
+ = call(Config, Req),
+ [#'diameter_base_Failed-AVP'{'AVP' = As}]
+ = proplists:get_value('Failed-AVP', Avps),
+@@ -527,7 +537,7 @@ send_unknown_mandatory(Config) ->
+ Req = ['ASR', {'AVP', [#diameter_avp{code = 999,
+ is_mandatory = true,
+ data = <<17>>}]}],
+- ['ASA', _SessionId, {'Result-Code', ?AVP_UNSUPPORTED} | Avps]
++ ['ASA', {'Session-Id', _}, {'Result-Code', ?AVP_UNSUPPORTED} | Avps]
+ = call(Config, Req),
+ [#'diameter_base_Failed-AVP'{'AVP' = As}]
+ = proplists:get_value('Failed-AVP', Avps),
+@@ -547,7 +557,7 @@ send_unexpected_mandatory_decode(Config) ->
+ Req = ['ASR', {'AVP', [#diameter_avp{code = 27, %% Session-Timeout
+ is_mandatory = true,
+ data = <<12:32>>}]}],
+- ['ASA', _SessionId, {'Result-Code', ?AVP_UNSUPPORTED} | Avps]
++ ['ASA', {'Session-Id', _}, {'Result-Code', ?AVP_UNSUPPORTED} | Avps]
+ = call(Config, Req),
+ [#'diameter_base_Failed-AVP'{'AVP' = As}]
+ = proplists:get_value('Failed-AVP', Avps),
+@@ -583,7 +593,7 @@ send_error_bit(Config) ->
+ %% Send a bad version and check that we get 5011.
+ send_unsupported_version(Config) ->
+ Req = ['STR', {'Termination-Cause', ?LOGOUT}],
+- ['STA', _SessionId, {'Result-Code', ?UNSUPPORTED_VERSION} | _]
++ ['STA', {'Session-Id', _}, {'Result-Code', ?UNSUPPORTED_VERSION} | _]
+ = call(Config, Req).
+
+ %% Send a request containing an AVP length > data size.
+@@ -603,14 +613,14 @@ send_zero_avp_length(Config) ->
+ send_invalid_avp_length(Config) ->
+ Req = ['STR', {'Termination-Cause', ?LOGOUT}],
+
+- ['STA', _SessionId,
++ ['STA', {'Session-Id', _},
+ {'Result-Code', ?INVALID_AVP_LENGTH},
+- _OriginHost,
+- _OriginRealm,
+- _UserName,
+- _Class,
+- _ErrorMessage,
+- _ErrorReportingHost,
++ {'Origin-Host', _},
++ {'Origin-Realm', _},
++ {'User-Name', _},
++ {'Class', _},
++ {'Error-Message', _},
++ {'Error-Reporting-Host', _},
+ {'Failed-AVP', [#'diameter_base_Failed-AVP'{'AVP' = [_]}]}
+ | _]
+ = call(Config, Req).
+@@ -628,14 +638,14 @@ send_invalid_reject(Config) ->
+ send_unexpected_mandatory(Config) ->
+ Req = ['STR', {'Termination-Cause', ?LOGOUT}],
+
+- ['STA', _SessionId, {'Result-Code', ?AVP_UNSUPPORTED} | _]
++ ['STA', {'Session-Id', _}, {'Result-Code', ?AVP_UNSUPPORTED} | _]
+ = call(Config, Req).
+
+ %% Send something long that will be fragmented by TCP.
+ send_long(Config) ->
+ Req = ['STR', {'Termination-Cause', ?LOGOUT},
+ {'User-Name', [lists:duplicate(1 bsl 20, $X)]}],
+- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _]
++ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _]
+ = call(Config, Req).
+
+ %% Send something longer than the configure incoming_maxlen.
+@@ -677,7 +687,7 @@ send_any_2(Config) ->
+ send_all_1(Config) ->
+ Req = ['STR', {'Termination-Cause', ?LOGOUT}],
+ Realm = lists:foldr(fun(C,A) -> [C,A] end, [], ?REALM),
+- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _]
++ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _]
+ = call(Config, Req, [{filter, {all, [{host, any},
+ {realm, Realm}]}}]).
+ send_all_2(Config) ->
+@@ -697,9 +707,8 @@ send_timeout(Config) ->
+ %% received the Session-Id.
+ send_error(Config) ->
+ Req = ['RAR', {'Re-Auth-Request-Type', ?AUTHORIZE_AUTHENTICATE}],
+- ?answer_message(SId, ?TOO_BUSY)
+- = call(Config, Req),
+- true = undefined /= SId.
++ ?answer_message([_], ?TOO_BUSY)
++ = call(Config, Req).
+
+ %% Send a request with the detached option and receive it as a message
+ %% from handle_answer instead.
+@@ -708,7 +717,7 @@ send_detach(Config) ->
+ Ref = make_ref(),
+ ok = call(Config, Req, [{extra, [{self(), Ref}]}, detach]),
+ Ans = receive {Ref, T} -> T end,
+- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _]
++ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _]
+ = Ans.
+
+ %% Send a request which can't be encoded and expect {error, encode}.
+@@ -721,11 +730,11 @@ send_destination_1(Config) ->
+ = group(Config),
+ Req = ['STR', {'Termination-Cause', ?LOGOUT},
+ {'Destination-Host', [?HOST(SN, ?REALM)]}],
+- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _]
++ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _]
+ = call(Config, Req, [{filter, {all, [host, realm]}}]).
+ send_destination_2(Config) ->
+ Req = ['STR', {'Termination-Cause', ?LOGOUT}],
+- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _]
++ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _]
+ = call(Config, Req, [{filter, {all, [host, realm]}}]).
+
+ %% Send with filtering on and expect failure when specifying an
+@@ -789,7 +798,7 @@ send_bad_filter(Config, F) ->
+ %% Specify multiple filter options and expect them be conjunctive.
+ send_multiple_filters_1(Config) ->
+ Fun = fun(#diameter_caps{}) -> true end,
+- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _]
++ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _]
+ = send_multiple_filters(Config, [host, {eval, Fun}]).
+ send_multiple_filters_2(Config) ->
+ E = {erlang, is_tuple, []},
+@@ -800,7 +809,7 @@ send_multiple_filters_3(Config) ->
+ E2 = {erlang, is_tuple, []},
+ E3 = {erlang, is_record, [diameter_caps]},
+ E4 = [{erlang, is_record, []}, diameter_caps],
+- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _]
++ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _]
+ = send_multiple_filters(Config, [{eval, E} || E <- [E1,E2,E3,E4]]).
+
+ send_multiple_filters(Config, Fs) ->
+@@ -811,7 +820,7 @@ send_multiple_filters(Config, Fs) ->
+ %% only the return value from the prepare_request callback being
+ %% significant.
+ send_anything(Config) ->
+- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _]
++ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _]
+ = call(Config, anything).
+
+ %% ===========================================================================
+@@ -1144,6 +1153,13 @@ answer(Pkt, Req, _Peer, Name, #group{client_dict0 = Dict0}) ->
+ [R | Vs] = Dict:'#get-'(answer(Ans, Es, Name)),
+ [Dict:rec2msg(R) | Vs].
+
++%% Missing Result-Codec and inapproriate Experimental-Result-Code.
++answer(Rec, Es, send_experimental_result) ->
++ [{5004, #diameter_avp{name = 'Experimental-Result'}},
++ {5005, #diameter_avp{name = 'Result-Code'}}]
++ = Es,
++ Rec;
++
+ %% An inappropriate E-bit results in a decode error ...
+ answer(Rec, Es, send_bad_answer) ->
+ [{5004, #diameter_avp{name = 'Result-Code'}} | _] = Es,
+@@ -1175,7 +1191,9 @@ handle_error(Reason, _Req, [$C|_], _Peer, _, _Time) ->
+ %% Note that diameter will set Result-Code and Failed-AVPs if
+ %% #diameter_packet.errors is non-null.
+
+-handle_request(#diameter_packet{header = H, msg = M}, _, {_Ref, Caps}) ->
++handle_request(#diameter_packet{header = H, msg = M, avps = As},
++ _,
++ {_Ref, Caps}) ->
+ #diameter_header{end_to_end_id = EI,
+ hop_by_hop_id = HI}
+ = H,
+@@ -1183,10 +1201,12 @@ handle_request(#diameter_packet{header = H, msg = M}, _, {_Ref, Caps}) ->
+ V = EI bsr B, %% assert
+ V = HI bsr B, %%
+ #diameter_caps{origin_state_id = {_,[Id]}} = Caps,
+- answer(origin(Id), request(M, Caps)).
++ answer(origin(Id), request(M, [H|As], Caps)).
+
+ answer(T, {Tag, Action, Post}) ->
+ {Tag, answer(T, Action), Post};
++answer(_, {reply, [#diameter_header{} | _]} = T) ->
++ T;
+ answer({A,C}, {reply, Ans}) ->
+ answer(C, {reply, msg(Ans, A, diameter_gen_base_rfc3588)});
+ answer(pkt, {reply, Ans})
+@@ -1195,6 +1215,41 @@ answer(pkt, {reply, Ans})
+ answer(_, T) ->
+ T.
+
++%% request/3
++
++%% send_experimental_result
++request(#diameter_base_accounting_ACR{'Accounting-Record-Number' = 5},
++ [Hdr | Avps],
++ #diameter_caps{origin_host = {OH, _},
++ origin_realm = {OR, _}}) ->
++ [H,R|T] = [A || N <- ['Origin-Host',
++ 'Origin-Realm',
++ 'Session-Id',
++ 'Accounting-Record-Type',
++ 'Accounting-Record-Number'],
++ #diameter_avp{} = A
++ <- [lists:keyfind(N, #diameter_avp.name, Avps)]],
++ Ans = [Hdr#diameter_header{is_request = false},
++ H#diameter_avp{data = OH},
++ R#diameter_avp{data = OR},
++ #diameter_avp{name = 'Experimental-Result',
++ code = 297,
++ need_encryption = false,
++ data = [#diameter_avp{data = {?DIAMETER_DICT_COMMON,
++ 'Vendor-Id',
++ 123}},
++ #diameter_avp{data
++ = {?DIAMETER_DICT_COMMON,
++ 'Experimental-Result-Code',
++ 3987}}]}
++ | T],
++ {reply, Ans};
++
++request(Msg, _Avps, Caps) ->
++ request(Msg, Caps).
++
++%% request/2
++
+ %% send_nok
+ request(#diameter_base_accounting_ACR{'Accounting-Record-Number' = 0},
+ _) ->
+diff --git lib/diameter/vsn.mk lib/diameter/vsn.mk
+index c00bac2..db7f72c 100644
+--- lib/diameter/vsn.mk
++++ lib/diameter/vsn.mk
+@@ -16,5 +16,5 @@
+ # %CopyrightEnd%
+
+ APPLICATION = diameter
+-DIAMETER_VSN = 1.9
++DIAMETER_VSN = 1.9.1
+ APP_VSN = $(APPLICATION)-$(DIAMETER_VSN)$(PRE_VSN)
+diff --git lib/snmp/doc/src/notes.xml lib/snmp/doc/src/notes.xml
+index fd307ef..52022f5 100644
+--- lib/snmp/doc/src/notes.xml
++++ lib/snmp/doc/src/notes.xml
+@@ -33,7 +33,40 @@
+ </header>
+
+
+- <section>
++ <section><title>SNMP 5.1.2</title>
++
++ <section><title>Fixed Bugs and Malfunctions</title>
++ <list>
++ <item>
++ <p>
++ A bug in the SNMP Agent has been corrected; when opening
++ a port using the command line argument -snmpa_fd the Port
++ should be 0 when calling gen_udp:open.</p>
++ <p>
++ A bug in the SNMP manager has been corrected; it should
++ not look at the -snmp_fd command line argument, but
++ instead at -snmpm_fd.</p>
++ <p>
++ Own Id: OTP-12669 Aux Id: seq12841 </p>
++ </item>
++ </list>
++ </section>
++
++
++ <section><title>Improvements and New Features</title>
++ <list>
++ <item>
++ <p>
++ Improved cryptocraphic capability.</p>
++ <p>
++ Own Id: OTP-12452</p>
++ </item>
++ </list>
++ </section>
++
++</section>
++
++<section>
+ <title>SNMP Development Toolkit 5.1.1</title>
+ <p>Version 5.1.1 supports code replacement in runtime from/to
+ version 5.1. </p>
+diff --git lib/snmp/src/agent/snmpa_net_if.erl lib/snmp/src/agent/snmpa_net_if.erl
+index 840d56d..57d63ba 100644
+--- lib/snmp/src/agent/snmpa_net_if.erl
++++ lib/snmp/src/agent/snmpa_net_if.erl
+@@ -1,7 +1,7 @@
+ %%
+ %% %CopyrightBegin%
+ %%
+-%% Copyright Ericsson AB 2004-2014. All Rights Reserved.
++%% Copyright Ericsson AB 2004-2015. 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
+@@ -297,14 +297,14 @@ socket_open(snmpUDPDomain = Domain, [IpPort | Opts]) ->
+ Fd = list_to_integer(FdStr),
+ ?vdebug("socket_open(~p, [~p | ~p]) Fd: ~p",
+ [Domain, IpPort, Opts, Fd]),
+- gen_udp_open(IpPort, [{fd, Fd} | Opts]);
++ gen_udp_open(0, [{fd, Fd} | Opts]);
+ error ->
+ case init:get_argument(snmpa_fd) of
+ {ok, [[FdStr]]} ->
+ Fd = list_to_integer(FdStr),
+ ?vdebug("socket_open(~p, [~p | ~p]) Fd: ~p",
+ [Domain, IpPort, Opts, Fd]),
+- gen_udp_open(IpPort, [{fd, Fd} | Opts]);
++ gen_udp_open(0, [{fd, Fd} | Opts]);
+ error ->
+ ?vdebug("socket_open(~p, [~p | ~p])",
+ [Domain, IpPort, Opts]),
+diff --git lib/snmp/src/app/snmp.appup.src lib/snmp/src/app/snmp.appup.src
+index e7e54f5..081163b 100644
+--- lib/snmp/src/app/snmp.appup.src
++++ lib/snmp/src/app/snmp.appup.src
+@@ -1,7 +1,7 @@
+ %%
+ %% %CopyrightBegin%
+ %%
+-%% Copyright Ericsson AB 1999-2014. All Rights Reserved.
++%% Copyright Ericsson AB 1999-2015. 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
+@@ -28,6 +28,7 @@
+ %% {update, snmpa_local_db, soft, soft_purge, soft_purge, []}
+ %% {add_module, snmpm_net_if_mt}
+ [
++ {"5.1.1", [{restart_application, snmp}]},
+ {"5.1", [ % Only compiler changes
+ ]},
+ {"5.0", [{restart_application, snmp}]},
+@@ -46,6 +47,7 @@
+ %% {remove, {snmpm_net_if_mt, soft_purge, soft_purge}}
+
+ [
++ {"5.1.1", [{restart_application, snmp}]},
+ {"5.1", [ % Only compiler changes
+ ]},
+ {"5.0", [{restart_application, snmp}]},
+diff --git lib/snmp/src/manager/snmpm_net_if.erl lib/snmp/src/manager/snmpm_net_if.erl
+index b4cc165..0e1c51c 100644
+--- lib/snmp/src/manager/snmpm_net_if.erl
++++ lib/snmp/src/manager/snmpm_net_if.erl
+@@ -1,7 +1,7 @@
+ %%
+ %% %CopyrightBegin%
+ %%
+-%% Copyright Ericsson AB 2004-2014. All Rights Reserved.
++%% Copyright Ericsson AB 2004-2015. 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
+@@ -330,7 +330,7 @@ socket_params(Domain, {IpAddr, IpPort} = Addr, BindTo, CommonSocketOpts) ->
+ end,
+ case Family of
+ inet ->
+- case init:get_argument(snmp_fd) of
++ case init:get_argument(snmpm_fd) of
+ {ok, [[FdStr]]} ->
+ Fd = list_to_integer(FdStr),
+ case BindTo of
+diff --git lib/snmp/src/manager/snmpm_server.erl lib/snmp/src/manager/snmpm_server.erl
+index a75122d..8fc3359 100644
+--- lib/snmp/src/manager/snmpm_server.erl
++++ lib/snmp/src/manager/snmpm_server.erl
+@@ -1,7 +1,7 @@
+ %%
+ %% %CopyrightBegin%
+ %%
+-%% Copyright Ericsson AB 2004-2014. All Rights Reserved.
++%% Copyright Ericsson AB 2004-2015. 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
+@@ -2116,7 +2116,8 @@ do_handle_agent(DefUserId, DefMod,
+ ok;
+
+ InvalidResult ->
+- CallbackArgs = [Domain, Addr, Type, SnmpInfo, DefData],
++ CallbackArgs =
++ [Domain_or_Ip, Addr_or_Port, Type, SnmpInfo, DefData],
+ handle_invalid_result(handle_agent, CallbackArgs, InvalidResult)
+
+ catch
+@@ -2212,7 +2213,8 @@ do_handle_agent(DefUserId, DefMod,
+ end;
+
+ T:E ->
+- CallbackArgs = [Domain, Addr, Type, SnmpInfo, DefData],
++ CallbackArgs =
++ [Domain_or_Ip, Addr_or_Port, Type, SnmpInfo, DefData],
+ handle_invalid_result(handle_agent, CallbackArgs, T, E)
+
+ end.
+diff --git lib/snmp/vsn.mk lib/snmp/vsn.mk
+index 345cc79..67adf0a 100644
+--- lib/snmp/vsn.mk
++++ lib/snmp/vsn.mk
+@@ -2,7 +2,7 @@
+
+ # %CopyrightBegin%
+ #
+-# Copyright Ericsson AB 1997-2014. All Rights Reserved.
++# Copyright Ericsson AB 1997-2015. 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
+@@ -18,6 +18,6 @@
+ # %CopyrightEnd%
+
+ APPLICATION = snmp
+-SNMP_VSN = 5.1.1
++SNMP_VSN = 5.1.2
+ PRE_VSN =
+ APP_VSN = "$(APPLICATION)-$(SNMP_VSN)$(PRE_VSN)"
+diff --git lib/test_server/doc/src/notes.xml lib/test_server/doc/src/notes.xml
+index f21c32a..e996d2b 100644
+--- lib/test_server/doc/src/notes.xml
++++ lib/test_server/doc/src/notes.xml
+@@ -32,6 +32,28 @@
+ <file>notes.xml</file>
+ </header>
+
++<section><title>Test_Server 3.8.1</title>
++
++ <section><title>Fixed Bugs and Malfunctions</title>
++ <list>
++ <item>
++ <p>
++ If the last expression in a test case causes a timetrap
++ timeout, the stack trace is ignored and not printed to
++ the test case log file. This happens because the
++ {Suite,TestCase,Line} info is not available in the stack
++ trace in this scenario, due to tail call elimination.
++ Common Test has been modified to handle this situation by
++ inserting a {Suite,TestCase,last_expr} tuple in the
++ correct place and printing the stack trace as expected.</p>
++ <p>
++ Own Id: OTP-12697 Aux Id: seq12848 </p>
++ </item>
++ </list>
++ </section>
++
++</section>
++
+ <section><title>Test_Server 3.8</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+diff --git lib/test_server/src/erl2html2.erl lib/test_server/src/erl2html2.erl
+index 7cfaa2c..50dbbb8 100644
+--- lib/test_server/src/erl2html2.erl
++++ lib/test_server/src/erl2html2.erl
+@@ -117,9 +117,10 @@ parse_preprocessed_file(Epp,File,InCorrectFile) ->
+ parse_preprocessed_file(Epp,File,true);
+ {attribute,_,file,{_OtherFile,_}} ->
+ parse_preprocessed_file(Epp,File,false);
+- {function,L,F,A,[_|C]} when InCorrectFile ->
+- Clauses = [{clause,CL} || {clause,CL,_,_,_} <- C],
+- [{atom_to_list(F),A,L} | Clauses] ++
++ {function,L,F,A,Cs} when InCorrectFile ->
++ {CLs,LastCL} = find_clause_lines(Cs, []),
++ %% tl(CLs) cause we know the start line already
++ [{atom_to_list(F),A,L,LastCL} | tl(CLs)] ++
+ parse_preprocessed_file(Epp,File,true);
+ _ ->
+ parse_preprocessed_file(Epp,File,InCorrectFile)
+@@ -146,9 +147,10 @@ parse_non_preprocessed_file(Epp, File, Location) ->
+ case epp_dodger:parse_form(Epp, Location) of
+ {ok,Tree,Location1} ->
+ try erl_syntax:revert(Tree) of
+- {function,L,F,A,[_|C]} ->
+- Clauses = [{clause,CL} || {clause,CL,_,_,_} <- C],
+- [{atom_to_list(F),A,L} | Clauses] ++
++ {function,L,F,A,Cs} ->
++ {CLs,LastCL} = find_clause_lines(Cs, []),
++ %% tl(CLs) cause we know the start line already
++ [{atom_to_list(F),A,L,LastCL} | tl(CLs)] ++
+ parse_non_preprocessed_file(Epp, File, Location1);
+ _ ->
+ parse_non_preprocessed_file(Epp, File, Location1)
+@@ -162,22 +164,48 @@ parse_non_preprocessed_file(Epp, File, Location) ->
+ end.
+
+ %%%-----------------------------------------------------------------
++%%% Find the line number of the last expression in the function
++find_clause_lines([{clause,CL,_Params,_Op,Exprs}], CLs) -> % last clause
++ try tuple_to_list(lists:last(Exprs)) of
++ [_Type,ExprLine | _] ->
++ {lists:reverse([{clause,CL}|CLs]), ExprLine};
++ _ ->
++ {lists:reverse([{clause,CL}|CLs]), CL}
++ catch
++ _:_ ->
++ {lists:reverse([{clause,CL}|CLs]), CL}
++ end;
++
++find_clause_lines([{clause,CL,_Params,_Op,_Exprs} | Cs], CLs) ->
++ find_clause_lines(Cs, [{clause,CL}|CLs]).
++
++%%%-----------------------------------------------------------------
+ %%% Add a link target for each line and one for each function definition.
+-build_html(SFd,DFd,Encoding,Functions) ->
+- build_html(SFd,DFd,Encoding,file:read_line(SFd),1,Functions,false).
++build_html(SFd,DFd,Encoding,FuncsAndCs) ->
++ build_html(SFd,DFd,Encoding,file:read_line(SFd),1,FuncsAndCs,
++ false,undefined).
+
+-build_html(SFd,DFd,Encoding,{ok,Str},L,[{F,A,L}|Functions],_IsFuncDef) ->
++%% function start line found
++build_html(SFd,DFd,Enc,{ok,Str},L0,[{F,A,L0,LastL}|FuncsAndCs],
++ _IsFuncDef,_FAndLastL) ->
+ FALink = test_server_ctrl:uri_encode(F++"-"++integer_to_list(A),utf8),
+- file:write(DFd,["<a name=\"",to_raw_list(FALink,Encoding),"\"/>"]),
+- build_html(SFd,DFd,Encoding,{ok,Str},L,Functions,true);
+-build_html(SFd,DFd,Encoding,{ok,Str},L,[{clause,L}|Functions],_IsFuncDef) ->
+- build_html(SFd,DFd,Encoding,{ok,Str},L,Functions,true);
+-build_html(SFd,DFd,Encoding,{ok,Str},L,Functions,IsFuncDef) ->
++ file:write(DFd,["<a name=\"",to_raw_list(FALink,Enc),"\"/>"]),
++ build_html(SFd,DFd,Enc,{ok,Str},L0,FuncsAndCs,true,{F,LastL});
++%% line of last expression in function found
++build_html(SFd,DFd,Enc,{ok,Str},LastL,FuncsAndCs,_IsFuncDef,{F,LastL}) ->
++ LastLineLink = test_server_ctrl:uri_encode(F++"-last_expr",utf8),
++ file:write(DFd,["<a name=\"",
++ to_raw_list(LastLineLink,Enc),"\"/>"]),
++ build_html(SFd,DFd,Enc,{ok,Str},LastL,FuncsAndCs,true,undefined);
++build_html(SFd,DFd,Enc,{ok,Str},L,[{clause,L}|FuncsAndCs],
++ _IsFuncDef,FAndLastL) ->
++ build_html(SFd,DFd,Enc,{ok,Str},L,FuncsAndCs,true,FAndLastL);
++build_html(SFd,DFd,Enc,{ok,Str},L,FuncsAndCs,IsFuncDef,FAndLastL) ->
+ LStr = line_number(L),
+ Str1 = line(Str,IsFuncDef),
+ file:write(DFd,[LStr,Str1]),
+- build_html(SFd,DFd,Encoding,file:read_line(SFd),L+1,Functions,false);
+-build_html(_SFd,_DFd,_Encoding,eof,L,_Functions,_IsFuncDef) ->
++ build_html(SFd,DFd,Enc,file:read_line(SFd),L+1,FuncsAndCs,false,FAndLastL);
++build_html(_SFd,_DFd,_Enc,eof,L,_FuncsAndCs,_IsFuncDef,_FAndLastL) ->
+ L.
+
+ line_number(L) ->
+diff --git lib/test_server/src/test_server.erl lib/test_server/src/test_server.erl
+index 8d91778..1c33525 100644
+--- lib/test_server/src/test_server.erl
++++ lib/test_server/src/test_server.erl
+@@ -1355,12 +1355,30 @@ get_loc(Pid) ->
+ Stk = [rewrite_loc_item(Loc) || Loc <- Stk0],
+ case get(test_server_loc) of
+ [{Suite,Case}] ->
+- %% location info unknown, check if {Suite,Case,Line}
+- %% is available in stacktrace. and if so, use stacktrace
+- %% instead of current test_server_loc
++ %% Location info unknown, check if {Suite,Case,Line}
++ %% is available in stacktrace and if so, use stacktrace
++ %% instead of current test_server_loc.
++ %% If location is the last expression in a test case
++ %% function, the info is not available due to tail call
++ %% elimination. We need to check if the test case has been
++ %% called by ts_tc/3 and, if so, insert the test case info
++ %% at that position.
+ case [match || {S,C,_L} <- Stk, S == Suite, C == Case] of
+- [match|_] -> put(test_server_loc, Stk);
+- _ -> ok
++ [match|_] ->
++ put(test_server_loc, Stk);
++ _ ->
++ {PreTC,PostTC} =
++ lists:splitwith(fun({test_server,ts_tc,_}) ->
++ false;
++ (_) ->
++ true
++ end, Stk),
++ if PostTC == [] ->
++ ok;
++ true ->
++ put(test_server_loc,
++ PreTC++[{Suite,Case,last_expr} | PostTC])
++ end
+ end;
+ _ ->
+ put(test_server_loc, Stk)
+@@ -1422,7 +1440,10 @@ lookup_config(Key,Config) ->
+ undefined
+ end.
+
+-%% timer:tc/3
++%%
++%% IMPORTANT: get_loc/1 uses the name of this function when analysing
++%% stack traces. If the name changes, get_loc/1 must be updated!
++%%
+ ts_tc(M, F, A) ->
+ Before = erlang:now(),
+ Result = try
+diff --git lib/test_server/src/test_server_sup.erl lib/test_server/src/test_server_sup.erl
+index 96e369a..15a6fdd 100644
+--- lib/test_server/src/test_server_sup.erl
++++ lib/test_server/src/test_server_sup.erl
+@@ -61,33 +61,37 @@ timetrap(Timeout0, ReportTVal, Scale, Pid) ->
+ TruncTO = trunc(Timeout),
+ receive
+ after TruncTO ->
+- case is_process_alive(Pid) of
+- true ->
+- TimeToReport = if Timeout0 == ReportTVal -> TruncTO;
+- true -> ReportTVal end,
+- MFLs = test_server:get_loc(Pid),
+- Mon = erlang:monitor(process, Pid),
+- Trap = {timetrap_timeout,TimeToReport,MFLs},
+- exit(Pid, Trap),
+- receive
+- {'DOWN', Mon, process, Pid, _} ->
+- ok
+- after 10000 ->
+- %% Pid is probably trapping exits, hit it harder...
+- catch error_logger:warning_msg(
+- "Testcase process ~w not "
+- "responding to timetrap "
+- "timeout:~n"
+- " ~p.~n"
+- "Killing testcase...~n",
+- [Pid, Trap]),
+- exit(Pid, kill)
+- end;
+- false ->
++ kill_the_process(Pid, Timeout0, TruncTO, ReportTVal)
++ end.
++
++kill_the_process(Pid, Timeout0, TruncTO, ReportTVal) ->
++ case is_process_alive(Pid) of
++ true ->
++ TimeToReport = if Timeout0 == ReportTVal -> TruncTO;
++ true -> ReportTVal end,
++ MFLs = test_server:get_loc(Pid),
++ Mon = erlang:monitor(process, Pid),
++ Trap = {timetrap_timeout,TimeToReport,MFLs},
++ exit(Pid, Trap),
++ receive
++ {'DOWN', Mon, process, Pid, _} ->
+ ok
+- end
++ after 10000 ->
++ %% Pid is probably trapping exits, hit it harder...
++ catch error_logger:warning_msg(
++ "Testcase process ~w not "
++ "responding to timetrap "
++ "timeout:~n"
++ " ~p.~n"
++ "Killing testcase...~n",
++ [Pid, Trap]),
++ exit(Pid, kill)
++ end;
++ false ->
++ ok
+ end.
+
++
+ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %% timetrap_cancel(Handle) -> ok
+ %% Handle = term()
+@@ -812,10 +816,19 @@ format_loc1({Mod,Func,Line}) ->
+ case {lists:member(no_src, get(test_server_logopts)),
+ lists:reverse(ModStr)} of
+ {false,[$E,$T,$I,$U,$S,$_|_]} ->
+- io_lib:format("{~w,~w,<a href=\"~ts~ts#~w\">~w</a>}",
++ Link = if is_integer(Line) ->
++ integer_to_list(Line);
++ Line == last_expr ->
++ list_to_atom(atom_to_list(Func)++"-last_expr");
++ is_atom(Line) ->
++ atom_to_list(Line);
++ true ->
++ Line
++ end,
++ io_lib:format("{~w,~w,<a href=\"~ts~ts#~s\">~w</a>}",
+ [Mod,Func,
+ test_server_ctrl:uri_encode(downcase(ModStr)),
+- ?src_listing_ext,Line,Line]);
++ ?src_listing_ext,Link,Line]);
+ _ ->
+ io_lib:format("{~w,~w,~w}",[Mod,Func,Line])
+ end.
+diff --git lib/test_server/vsn.mk lib/test_server/vsn.mk
+index 77225b4..2a2ed2b 100644
+--- lib/test_server/vsn.mk
++++ lib/test_server/vsn.mk
+@@ -1 +1 @@
+-TEST_SERVER_VSN = 3.8
++TEST_SERVER_VSN = 3.8.1
+diff --git otp_versions.table otp_versions.table
+index 4bf6cb9..12790c8 100644
+--- otp_versions.table
++++ otp_versions.table
+@@ -1,3 +1,4 @@
++OTP-17.5.3 : common_test-1.10.1 diameter-1.9.1 erts-6.4.1 snmp-5.1.2 test_server-3.8.1 # asn1-3.0.4 compiler-5.0.4 cosEvent-2.1.15 cosEventDomain-1.1.14 cosFileTransfer-1.1.16 cosNotification-1.1.21 cosProperty-1.1.17 cosTime-1.1.14 cosTransactions-1.2.14 crypto-3.5 debugger-4.0.3 dialyzer-2.7.4 edoc-0.7.16 eldap-1.1.1 erl_docgen-0.3.7 erl_interface-3.7.20 et-1.5 eunit-2.2.9 gs-1.5.16 hipe-3.11.3 ic-4.3.6 inets-5.10.7 jinterface-1.5.12 kernel-3.2 megaco-3.17.3 mnesia-4.12.5 observer-2.0.4 odbc-2.10.22 orber-3.7.1 os_mon-2.3.1 ose-1.0.2 otp_mibs-1.0.10 parsetools-2.0.12 percept-0.8.10 public_key-0.23 reltool-0.6.6 runtime_tools-1.8.16 sasl-2.4.1 ssh-3.2.2 ssl-6.0 stdlib-2.4 syntax_tools-1.6.18 tools-2.7.2 typer-0.9.8 webtool-0.8.10 wx-1.3.3 xmerl-1.3.7 :
+ OTP-17.5.2 : inets-5.10.7 ssh-3.2.2 # asn1-3.0.4 common_test-1.10 compiler-5.0.4 cosEvent-2.1.15 cosEventDomain-1.1.14 cosFileTransfer-1.1.16 cosNotification-1.1.21 cosProperty-1.1.17 cosTime-1.1.14 cosTransactions-1.2.14 crypto-3.5 debugger-4.0.3 dialyzer-2.7.4 diameter-1.9 edoc-0.7.16 eldap-1.1.1 erl_docgen-0.3.7 erl_interface-3.7.20 erts-6.4 et-1.5 eunit-2.2.9 gs-1.5.16 hipe-3.11.3 ic-4.3.6 jinterface-1.5.12 kernel-3.2 megaco-3.17.3 mnesia-4.12.5 observer-2.0.4 odbc-2.10.22 orber-3.7.1 os_mon-2.3.1 ose-1.0.2 otp_mibs-1.0.10 parsetools-2.0.12 percept-0.8.10 public_key-0.23 reltool-0.6.6 runtime_tools-1.8.16 sasl-2.4.1 snmp-5.1.1 ssl-6.0 stdlib-2.4 syntax_tools-1.6.18 test_server-3.8 tools-2.7.2 typer-0.9.8 webtool-0.8.10 wx-1.3.3 xmerl-1.3.7 :
+ OTP-17.5.1 : ssh-3.2.1 # asn1-3.0.4 common_test-1.10 compiler-5.0.4 cosEvent-2.1.15 cosEventDomain-1.1.14 cosFileTransfer-1.1.16 cosNotification-1.1.21 cosProperty-1.1.17 cosTime-1.1.14 cosTransactions-1.2.14 crypto-3.5 debugger-4.0.3 dialyzer-2.7.4 diameter-1.9 edoc-0.7.16 eldap-1.1.1 erl_docgen-0.3.7 erl_interface-3.7.20 erts-6.4 et-1.5 eunit-2.2.9 gs-1.5.16 hipe-3.11.3 ic-4.3.6 inets-5.10.6 jinterface-1.5.12 kernel-3.2 megaco-3.17.3 mnesia-4.12.5 observer-2.0.4 odbc-2.10.22 orber-3.7.1 os_mon-2.3.1 ose-1.0.2 otp_mibs-1.0.10 parsetools-2.0.12 percept-0.8.10 public_key-0.23 reltool-0.6.6 runtime_tools-1.8.16 sasl-2.4.1 snmp-5.1.1 ssl-6.0 stdlib-2.4 syntax_tools-1.6.18 test_server-3.8 tools-2.7.2 typer-0.9.8 webtool-0.8.10 wx-1.3.3 xmerl-1.3.7 :
+ OTP-17.5 : asn1-3.0.4 common_test-1.10 compiler-5.0.4 crypto-3.5 debugger-4.0.3 dialyzer-2.7.4 diameter-1.9 eldap-1.1.1 erts-6.4 hipe-3.11.3 inets-5.10.6 kernel-3.2 mnesia-4.12.5 observer-2.0.4 os_mon-2.3.1 public_key-0.23 runtime_tools-1.8.16 ssh-3.2 ssl-6.0 stdlib-2.4 syntax_tools-1.6.18 test_server-3.8 tools-2.7.2 wx-1.3.3 # cosEvent-2.1.15 cosEventDomain-1.1.14 cosFileTransfer-1.1.16 cosNotification-1.1.21 cosProperty-1.1.17 cosTime-1.1.14 cosTransactions-1.2.14 edoc-0.7.16 erl_docgen-0.3.7 erl_interface-3.7.20 et-1.5 eunit-2.2.9 gs-1.5.16 ic-4.3.6 jinterface-1.5.12 megaco-3.17.3 odbc-2.10.22 orber-3.7.1 ose-1.0.2 otp_mibs-1.0.10 parsetools-2.0.12 percept-0.8.10 reltool-0.6.6 sasl-2.4.1 snmp-5.1.1 typer-0.9.8 webtool-0.8.10 xmerl-1.3.7 :
diff --git a/lang/erlang-wx/Makefile b/lang/erlang-wx/Makefile
index 5938abdbeb78..d1612bae34b3 100644
--- a/lang/erlang-wx/Makefile
+++ b/lang/erlang-wx/Makefile
@@ -1,7 +1,7 @@
# $FreeBSD$
PORTNAME= erlang
-PORTVERSION= 17.5.2
+PORTVERSION= 17.5.3
CATEGORIES= lang parallel
MASTER_SITES= http://www.erlang.org/download/:erlangorg \
http://erlang.stacken.kth.se/download/:erlangorg \
@@ -26,8 +26,7 @@ OPTIONS_DEFINE= DOCS
ERL_RELEASE= 17.5
-USES= gmake
-USE_AUTOTOOLS= autoconf:env
+USES= autoreconf gmake
GNU_CONFIGURE= yes
LDFLAGS+= -L${LOCALBASE}/lib
diff --git a/lang/erlang/Makefile b/lang/erlang/Makefile
index 42df0d01fb6a..97869da4c6e6 100644
--- a/lang/erlang/Makefile
+++ b/lang/erlang/Makefile
@@ -2,8 +2,7 @@
# $FreeBSD$
PORTNAME= erlang
-PORTVERSION= 17.5.2
-PORTREVISION= 1
+PORTVERSION= 17.5.3
PORTEPOCH= 3
CATEGORIES= lang parallel java
MASTER_SITES= http://www.erlang.org/download/:erlangorg \
@@ -207,6 +206,10 @@ post-install:
${TAR} --unlink -xzpf ${DISTDIR}/${DIST_SUBDIR}/${ERLANG_DOCS} \
-C ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}
+ ${MV} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/erts-6.4/* \
+ ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/erts-6.4.1
+ ${RMDIR} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/erts-6.4
+
${MV} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/inets-5.10.6/* \
${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/inets-5.10.7
${RMDIR} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/inets-5.10.6
@@ -215,6 +218,22 @@ post-install:
${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/ssh-3.2.2
${RMDIR} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/ssh-3.2
+ ${MV} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/common_test-1.10/* \
+ ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/common_test-1.10.1
+ ${RMDIR} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/common_test-1.10
+
+ ${MV} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/diameter-1.9/* \
+ ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/diameter-1.9.1
+ ${RMDIR} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/diameter-1.9
+
+ ${MV} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/snmp-5.1.1/* \
+ ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/snmp-5.1.2
+ ${RMDIR} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/snmp-5.1.1
+
+ ${MV} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/test_server-3.8/* \
+ ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/test_server-3.8.1
+ ${RMDIR} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/test_server-3.8
+
${INSTALL_DATA} ${WRKSRC}/lib/dialyzer/doc/*.txt \
${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/dialyzer-*/doc/
@${MKDIR} ${STAGEDIR}${DOCSDIR}
diff --git a/lang/erlang/files/patch-otp-17.5.3 b/lang/erlang/files/patch-otp-17.5.3
new file mode 100644
index 000000000000..f9ed64007fd2
--- /dev/null
+++ b/lang/erlang/files/patch-otp-17.5.3
@@ -0,0 +1,3738 @@
+diff --git OTP_VERSION OTP_VERSION
+index 808ab16..f32d20d 100644
+--- OTP_VERSION
++++ OTP_VERSION
+@@ -1 +1 @@
+-17.5.2
++17.5.3
+diff --git erts/doc/src/notes.xml erts/doc/src/notes.xml
+index a2b4ae4..35e6e55 100644
+--- erts/doc/src/notes.xml
++++ erts/doc/src/notes.xml
+@@ -30,6 +30,22 @@
+ </header>
+ <p>This document describes the changes made to the ERTS application.</p>
+
++<section><title>Erts 6.4.1</title>
++
++ <section><title>Fixed Bugs and Malfunctions</title>
++ <list>
++ <item>
++ <p>
++ The VTS mode in Common Test has been modified to use a
++ private version of the Webtool application (ct_webtool).</p>
++ <p>
++ Own Id: OTP-12704 Aux Id: OTP-10922 </p>
++ </item>
++ </list>
++ </section>
++
++</section>
++
+ <section><title>Erts 6.4</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+diff --git erts/etc/common/ct_run.c erts/etc/common/ct_run.c
+index bb59b93..9e67b94 100644
+--- erts/etc/common/ct_run.c
++++ erts/etc/common/ct_run.c
+@@ -239,7 +239,7 @@ int main(int argc, char** argv)
+ */
+
+ if (ct_mode == VTS_MODE) {
+- PUSH4("-s", "webtool", "script_start", "vts");
++ PUSH4("-s", "ct_webtool", "script_start", "vts");
+ if (browser[0] != '\0') PUSH(browser);
+ PUSH3("-s", "ct_run", "script_start");
+ }
+diff --git erts/vsn.mk erts/vsn.mk
+index abc9c0b..9e5aa99 100644
+--- erts/vsn.mk
++++ erts/vsn.mk
+@@ -17,7 +17,7 @@
+ # %CopyrightEnd%
+ #
+
+-VSN = 6.4
++VSN = 6.4.1
+
+ # Port number 4365 in 4.2
+ # Port number 4366 in 4.3
+diff --git lib/common_test/doc/src/notes.xml lib/common_test/doc/src/notes.xml
+index 822ebf1..472e3b7 100644
+--- lib/common_test/doc/src/notes.xml
++++ lib/common_test/doc/src/notes.xml
+@@ -32,6 +32,66 @@
+ <file>notes.xml</file>
+ </header>
+
++<section><title>Common_Test 1.10.1</title>
++
++ <section><title>Fixed Bugs and Malfunctions</title>
++ <list>
++ <item>
++ <p>
++ A fault in the Common Test logger process, that caused
++ the application to crash when running on a long name
++ node, has been corrected.</p>
++ <p>
++ Own Id: OTP-12643</p>
++ </item>
++ <item>
++ <p>
++ A 'wait_for_prompt' option in ct_telnet:expect/3 has been
++ introduced which forces the function to not return until
++ a prompt string has been received, even if other expect
++ patterns have already been found.</p>
++ <p>
++ Own Id: OTP-12688 Aux Id: seq12818 </p>
++ </item>
++ <item>
++ <p>
++ If the last expression in a test case causes a timetrap
++ timeout, the stack trace is ignored and not printed to
++ the test case log file. This happens because the
++ {Suite,TestCase,Line} info is not available in the stack
++ trace in this scenario, due to tail call elimination.
++ Common Test has been modified to handle this situation by
++ inserting a {Suite,TestCase,last_expr} tuple in the
++ correct place and printing the stack trace as expected.</p>
++ <p>
++ Own Id: OTP-12697 Aux Id: seq12848 </p>
++ </item>
++ <item>
++ <p>
++ Fixed a buffer problem in ct_netconfc which could cause
++ that some messages where buffered forever.</p>
++ <p>
++ Own Id: OTP-12698 Aux Id: seq12844 </p>
++ </item>
++ <item>
++ <p>
++ The VTS mode in Common Test has been modified to use a
++ private version of the Webtool application (ct_webtool).</p>
++ <p>
++ Own Id: OTP-12704 Aux Id: OTP-10922 </p>
++ </item>
++ <item>
++ <p>
++ Add possibility to add user capabilities in
++ <c>ct_netconfc:hello/3</c>.</p>
++ <p>
++ Own Id: OTP-12707 Aux Id: seq12846 </p>
++ </item>
++ </list>
++ </section>
++
++</section>
++
+ <section><title>Common_Test 1.10</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+diff --git lib/common_test/src/Makefile lib/common_test/src/Makefile
+index 8d74546..449cba6 100644
+--- lib/common_test/src/Makefile
++++ lib/common_test/src/Makefile
+@@ -62,6 +62,8 @@ MODULES= \
+ ct_telnet_client \
+ ct_make \
+ vts \
++ ct_webtool \
++ ct_webtool_sup \
+ unix_telnet \
+ ct_config \
+ ct_config_plain \
+diff --git lib/common_test/src/ct_logs.erl lib/common_test/src/ct_logs.erl
+index dc118ed..fa55a97 100644
+--- lib/common_test/src/ct_logs.erl
++++ lib/common_test/src/ct_logs.erl
+@@ -1908,13 +1908,14 @@ sort_all_runs(Dirs) ->
+ sort_ct_runs(Dirs) ->
+ %% Directory naming: <Prefix>.NodeName.Date_Time[/...]
+ %% Sort on Date_Time string: "YYYY-MM-DD_HH.MM.SS"
+- lists:sort(fun(Dir1,Dir2) ->
+- [_Prefix,_Node1,DateHH1,MM1,SS1] =
+- string:tokens(filename:dirname(Dir1),[$.]),
+- [_Prefix,_Node2,DateHH2,MM2,SS2] =
+- string:tokens(filename:dirname(Dir2),[$.]),
+- {DateHH1,MM1,SS1} =< {DateHH2,MM2,SS2}
+- end, Dirs).
++ lists:sort(
++ fun(Dir1,Dir2) ->
++ [SS1,MM1,DateHH1 | _] =
++ lists:reverse(string:tokens(filename:dirname(Dir1),[$.])),
++ [SS2,MM2,DateHH2 | _] =
++ lists:reverse(string:tokens(filename:dirname(Dir2),[$.])),
++ {DateHH1,MM1,SS1} =< {DateHH2,MM2,SS2}
++ end, Dirs).
+
+ dir_diff_all_runs(Dirs, LogCache) ->
+ case LogCache#log_cache.all_runs of
+diff --git lib/common_test/src/ct_netconfc.erl lib/common_test/src/ct_netconfc.erl
+index 85fb1ea..80ffb51 100644
+--- lib/common_test/src/ct_netconfc.erl
++++ lib/common_test/src/ct_netconfc.erl
+@@ -172,6 +172,7 @@
+ only_open/2,
+ hello/1,
+ hello/2,
++ hello/3,
+ close_session/1,
+ close_session/2,
+ kill_session/2,
+@@ -456,23 +457,35 @@ only_open(KeyOrName, ExtraOpts) ->
+
+ %%----------------------------------------------------------------------
+ %% @spec hello(Client) -> Result
+-%% @equiv hello(Client, infinity)
++%% @equiv hello(Client, [], infinity)
+ hello(Client) ->
+- hello(Client,?DEFAULT_TIMEOUT).
++ hello(Client,[],?DEFAULT_TIMEOUT).
+
+ %%----------------------------------------------------------------------
+ -spec hello(Client,Timeout) -> Result when
+ Client :: handle(),
+ Timeout :: timeout(),
+ Result :: ok | {error,error_reason()}.
+-%% @doc Exchange `hello' messages with the server.
+-%%
+-%% Sends a `hello' message to the server and waits for the return.
+-%%
+-%% @end
+-%%----------------------------------------------------------------------
++%% @spec hello(Client, Timeout) -> Result
++%% @equiv hello(Client, [], Timeout)
+ hello(Client,Timeout) ->
+- call(Client, {hello, Timeout}).
++ hello(Client,[],Timeout).
++
++%%----------------------------------------------------------------------
++-spec hello(Client,Options,Timeout) -> Result when
++ Client :: handle(),
++ Options :: [{capability, [string()]}],
++ Timeout :: timeout(),
++ Result :: ok | {error,error_reason()}.
++%% @doc Exchange `hello' messages with the server.
++%%
++%% Adds optional capabilities and sends a `hello' message to the
++%% server and waits for the return.
++%% @end
++%%----------------------------------------------------------------------
++hello(Client,Options,Timeout) ->
++ call(Client, {hello, Options, Timeout}).
++
+
+ %%----------------------------------------------------------------------
+ %% @spec get_session_id(Client) -> Result
+@@ -1040,9 +1053,9 @@ terminate(_, #state{connection=Connection}) ->
+ ok.
+
+ %% @private
+-handle_msg({hello,Timeout}, From,
++handle_msg({hello, Options, Timeout}, From,
+ #state{connection=Connection,hello_status=HelloStatus} = State) ->
+- case do_send(Connection, client_hello()) of
++ case do_send(Connection, client_hello(Options)) of
+ ok ->
+ case HelloStatus of
+ undefined ->
+@@ -1118,7 +1131,9 @@ handle_msg({Ref,timeout},#state{pending=Pending} = State) ->
+ close_session -> stop;
+ _ -> noreply
+ end,
+- {R,State#state{pending=Pending1}}.
++ %% Halfhearted try to get in correct state, this matches
++ %% the implementation before this patch
++ {R,State#state{pending=Pending1, buff= <<>>}}.
+
+ %% @private
+ %% Called by ct_util_server to close registered connections before terminate.
+@@ -1222,10 +1237,14 @@ set_request_timer(T) ->
+
+
+ %%%-----------------------------------------------------------------
+-client_hello() ->
++client_hello(Options) when is_list(Options) ->
++ UserCaps = [{capability, UserCap} ||
++ {capability, UserCap} <- Options,
++ is_list(hd(UserCap))],
+ {hello, ?NETCONF_NAMESPACE_ATTR,
+ [{capabilities,
+- [{capability,[?NETCONF_BASE_CAP++?NETCONF_BASE_CAP_VSN]}]}]}.
++ [{capability,[?NETCONF_BASE_CAP++?NETCONF_BASE_CAP_VSN]}|
++ UserCaps]}]}.
+
+ %%%-----------------------------------------------------------------
+
+@@ -1308,72 +1327,54 @@ to_xml_doc(Simple) ->
+
+ %%%-----------------------------------------------------------------
+ %%% Parse and handle received XML data
+-handle_data(NewData,#state{connection=Connection,buff=Buff} = State) ->
++handle_data(NewData,#state{connection=Connection,buff=Buff0} = State0) ->
+ log(Connection,recv,NewData),
+- Data = <<Buff/binary,NewData/binary>>,
+- case xmerl_sax_parser:stream(<<>>,
+- [{continuation_fun,fun sax_cont/1},
+- {continuation_state,{Data,Connection,false}},
+- {event_fun,fun sax_event/3},
+- {event_state,[]}]) of
+- {ok, Simple, Rest} ->
+- decode(Simple,State#state{buff=Rest});
+- {fatal_error,_Loc,Reason,_EndTags,_EventState} ->
+- ?error(Connection#connection.name,[{parse_error,Reason},
+- {buffer,Buff},
+- {new_data,NewData}]),
+- case Reason of
+- {could_not_fetch_data,Msg} ->
+- handle_msg(Msg,State#state{buff = <<>>});
+- _Other ->
+- Pending1 =
+- case State#state.pending of
+- [] ->
+- [];
+- Pending ->
+- %% Assuming the first request gets the
+- %% first answer
+- P=#pending{tref=TRef,caller=Caller} =
+- lists:last(Pending),
+- _ = timer:cancel(TRef),
+- Reason1 = {failed_to_parse_received_data,Reason},
+- ct_gen_conn:return(Caller,{error,Reason1}),
+- lists:delete(P,Pending)
+- end,
+- {noreply,State#state{pending=Pending1,buff = <<>>}}
+- end
+- end.
+-
+-%%%-----------------------------------------------------------------
+-%%% Parsing of XML data
+-%% Contiuation function for the sax parser
+-sax_cont(done) ->
+- {<<>>,done};
+-sax_cont({Data,Connection,false}) ->
++ Data = append_wo_initial_nl(Buff0,NewData),
+ case binary:split(Data,[?END_TAG],[]) of
+- [All] ->
+- %% No end tag found. Remove what could be a part
+- %% of an end tag from the data and save for next
+- %% iteration
+- SafeSize = size(All)-5,
+- <<New:SafeSize/binary,Save:5/binary>> = All,
+- {New,{Save,Connection,true}};
+- [_Msg,_Rest]=Msgs ->
+- %% We have at least one full message. Any excess data will
+- %% be returned from xmerl_sax_parser:stream/2 in the Rest
+- %% parameter.
+- {list_to_binary(Msgs),done}
+- end;
+-sax_cont({Data,Connection,true}) ->
+- case ssh_receive_data() of
+- {ok,Bin} ->
+- log(Connection,recv,Bin),
+- sax_cont({<<Data/binary,Bin/binary>>,Connection,false});
+- {error,Reason} ->
+- throw({could_not_fetch_data,Reason})
++ [_NoEndTagFound] ->
++ {noreply, State0#state{buff=Data}};
++ [FirstMsg,Buff1] ->
++ SaxArgs = [{event_fun,fun sax_event/3}, {event_state,[]}],
++ case xmerl_sax_parser:stream(FirstMsg, SaxArgs) of
++ {ok, Simple, _Thrash} ->
++ case decode(Simple, State0#state{buff=Buff1}) of
++ {noreply, #state{buff=Buff} = State} when Buff =/= <<>> ->
++ %% Recurse if we have more data in buffer
++ handle_data(<<>>, State);
++ Other ->
++ Other
++ end;
++ {fatal_error,_Loc,Reason,_EndTags,_EventState} ->
++ ?error(Connection#connection.name,
++ [{parse_error,Reason},
++ {buffer, Buff0},
++ {new_data,NewData}]),
++ handle_error(Reason, State0#state{buff= <<>>})
++ end
+ end.
+
++%% xml does not accept a leading nl and some netconf server add a nl after
++%% each ?END_TAG, ignore them
++append_wo_initial_nl(<<>>,NewData) -> NewData;
++append_wo_initial_nl(<<"\n", Data/binary>>, NewData) ->
++ append_wo_initial_nl(Data, NewData);
++append_wo_initial_nl(Data, NewData) ->
++ <<Data/binary, NewData/binary>>.
+
++handle_error(Reason, State) ->
++ Pending1 = case State#state.pending of
++ [] -> [];
++ Pending ->
++ %% Assuming the first request gets the
++ %% first answer
++ P=#pending{tref=TRef,caller=Caller} =
++ lists:last(Pending),
++ _ = timer:cancel(TRef),
++ Reason1 = {failed_to_parse_received_data,Reason},
++ ct_gen_conn:return(Caller,{error,Reason1}),
++ lists:delete(P,Pending)
++ end,
++ {noreply, State#state{pending=Pending1}}.
+
+ %% Event function for the sax parser. It builds a simple XML structure.
+ %% Care is taken to keep namespace attributes and prefixes as in the original XML.
+@@ -1836,16 +1837,6 @@ get_tag([]) ->
+
+ %%%-----------------------------------------------------------------
+ %%% SSH stuff
+-ssh_receive_data() ->
+- receive
+- {ssh_cm, CM, {data, Ch, _Type, Data}} ->
+- ssh_connection:adjust_window(CM,Ch,size(Data)),
+- {ok, Data};
+- {ssh_cm, _CM, {Closed, _Ch}} = X when Closed == closed; Closed == eof ->
+- {error,X};
+- {_Ref,timeout} = X ->
+- {error,X}
+- end.
+
+ ssh_open(#options{host=Host,timeout=Timeout,port=Port,ssh=SshOpts,name=Name}) ->
+ case ssh:connect(Host, Port,
+diff --git lib/common_test/src/ct_run.erl lib/common_test/src/ct_run.erl
+index 4a12481..be547b4 100644
+--- lib/common_test/src/ct_run.erl
++++ lib/common_test/src/ct_run.erl
+@@ -225,18 +225,24 @@ finish(Tracing, ExitStatus, Args) ->
+ if ExitStatus == interactive_mode ->
+ interactive_mode;
+ true ->
+- %% it's possible to tell CT to finish execution with a call
+- %% to a different function than the normal halt/1 BIF
+- %% (meant to be used mainly for reading the CT exit status)
+- case get_start_opt(halt_with,
+- fun([HaltMod,HaltFunc]) ->
+- {list_to_atom(HaltMod),
+- list_to_atom(HaltFunc)} end,
+- Args) of
+- undefined ->
+- halt(ExitStatus);
+- {M,F} ->
+- apply(M, F, [ExitStatus])
++ case get_start_opt(vts, true, Args) of
++ true ->
++ %% VTS mode, don't halt the node
++ ok;
++ _ ->
++ %% it's possible to tell CT to finish execution with a call
++ %% to a different function than the normal halt/1 BIF
++ %% (meant to be used mainly for reading the CT exit status)
++ case get_start_opt(halt_with,
++ fun([HaltMod,HaltFunc]) ->
++ {list_to_atom(HaltMod),
++ list_to_atom(HaltFunc)} end,
++ Args) of
++ undefined ->
++ halt(ExitStatus);
++ {M,F} ->
++ apply(M, F, [ExitStatus])
++ end
+ end
+ end.
+
+@@ -244,7 +250,7 @@ script_start1(Parent, Args) ->
+ %% read general start flags
+ Label = get_start_opt(label, fun([Lbl]) -> Lbl end, Args),
+ Profile = get_start_opt(profile, fun([Prof]) -> Prof end, Args),
+- Vts = get_start_opt(vts, true, Args),
++ Vts = get_start_opt(vts, true, undefined, Args),
+ Shell = get_start_opt(shell, true, Args),
+ Cover = get_start_opt(cover, fun([CoverFile]) -> ?abs(CoverFile) end, Args),
+ CoverStop = get_start_opt(cover_stop,
+@@ -330,8 +336,8 @@ script_start1(Parent, Args) ->
+ Stylesheet = get_start_opt(stylesheet,
+ fun([SS]) -> ?abs(SS) end, Args),
+ %% basic_html - used by ct_logs
+- BasicHtml = case proplists:get_value(basic_html, Args) of
+- undefined ->
++ BasicHtml = case {Vts,proplists:get_value(basic_html, Args)} of
++ {undefined,undefined} ->
+ application:set_env(common_test, basic_html, false),
+ undefined;
+ _ ->
+@@ -364,9 +370,10 @@ script_start1(Parent, Args) ->
+ scale_timetraps = ScaleTT,
+ create_priv_dir = CreatePrivDir,
+ starter = script},
+-
++
+ %% check if log files should be refreshed or go on to run tests...
+ Result = run_or_refresh(Opts, Args),
++
+ %% send final results to starting process waiting in script_start/0
+ Parent ! {self(), Result}.
+
+@@ -757,21 +764,6 @@ script_start4(Opts = #opts{tests = Tests}, Args) ->
+ %%% @doc Print usage information for <code>ct_run</code>.
+ script_usage() ->
+ io:format("\n\nUsage:\n\n"),
+- io:format("Run tests in web based GUI:\n\n"
+- "\tct_run -vts [-browser Browser]"
+- "\n\t[-config ConfigFile1 ConfigFile2 .. ConfigFileN]"
+- "\n\t[-decrypt_key Key] | [-decrypt_file KeyFile]"
+- "\n\t[-dir TestDir1 TestDir2 .. TestDirN] |"
+- "\n\t[-suite Suite [-case Case]]"
+- "\n\t[-logopts LogOpt1 LogOpt2 .. LogOptN]"
+- "\n\t[-verbosity GenVLvl | [CategoryVLvl1 .. CategoryVLvlN]]"
+- "\n\t[-include InclDir1 InclDir2 .. InclDirN]"
+- "\n\t[-no_auto_compile]"
+- "\n\t[-abort_if_missing_suites]"
+- "\n\t[-multiply_timetraps N]"
+- "\n\t[-scale_timetraps]"
+- "\n\t[-create_priv_dir auto_per_run | auto_per_tc | manual_per_tc]"
+- "\n\t[-basic_html]\n\n"),
+ io:format("Run tests from command line:\n\n"
+ "\tct_run [-dir TestDir1 TestDir2 .. TestDirN] |"
+ "\n\t[[-dir TestDir] -suite Suite1 Suite2 .. SuiteN"
+@@ -831,7 +823,22 @@ script_usage() ->
+ io:format("Run CT in interactive mode:\n\n"
+ "\tct_run -shell"
+ "\n\t[-config ConfigFile1 ConfigFile2 .. ConfigFileN]"
+- "\n\t[-decrypt_key Key] | [-decrypt_file KeyFile]\n\n").
++ "\n\t[-decrypt_key Key] | [-decrypt_file KeyFile]\n\n"),
++ io:format("Run tests in web based GUI:\n\n"
++ "\tct_run -vts [-browser Browser]"
++ "\n\t[-config ConfigFile1 ConfigFile2 .. ConfigFileN]"
++ "\n\t[-decrypt_key Key] | [-decrypt_file KeyFile]"
++ "\n\t[-dir TestDir1 TestDir2 .. TestDirN] |"
++ "\n\t[-suite Suite [-case Case]]"
++ "\n\t[-logopts LogOpt1 LogOpt2 .. LogOptN]"
++ "\n\t[-verbosity GenVLvl | [CategoryVLvl1 .. CategoryVLvlN]]"
++ "\n\t[-include InclDir1 InclDir2 .. InclDirN]"
++ "\n\t[-no_auto_compile]"
++ "\n\t[-abort_if_missing_suites]"
++ "\n\t[-multiply_timetraps N]"
++ "\n\t[-scale_timetraps]"
++ "\n\t[-create_priv_dir auto_per_run | auto_per_tc | manual_per_tc]"
++ "\n\t[-basic_html]\n\n").
+
+ %%%-----------------------------------------------------------------
+ %%% @hidden
+diff --git lib/common_test/src/ct_telnet.erl lib/common_test/src/ct_telnet.erl
+index d906a26..844f537 100644
+--- lib/common_test/src/ct_telnet.erl
++++ lib/common_test/src/ct_telnet.erl
+@@ -486,7 +486,8 @@ expect(Connection,Patterns) ->
+ %%% Opts = [Opt]
+ %%% Opt = {idle_timeout,IdleTimeout} | {total_timeout,TotalTimeout} |
+ %%% repeat | {repeat,N} | sequence | {halt,HaltPatterns} |
+-%%% ignore_prompt | no_prompt_check
++%%% ignore_prompt | no_prompt_check | wait_for_prompt |
++%%% {wait_for_prompt,Prompt}
+ %%% IdleTimeout = infinity | integer()
+ %%% TotalTimeout = infinity | integer()
+ %%% N = integer()
+@@ -499,9 +500,9 @@ expect(Connection,Patterns) ->
+ %%%
+ %%% @doc Get data from telnet and wait for the expected pattern.
+ %%%
+-%%% <p><code>Pattern</code> can be a POSIX regular expression. If more
+-%%% than one pattern is given, the function returns when the first
+-%%% match is found.</p>
++%%% <p><code>Pattern</code> can be a POSIX regular expression. The function
++%%% returns as soon as a pattern has been successfully matched (at least one,
++%%% in the case of multiple patterns).</p>
+ %%%
+ %%% <p><code>RxMatch</code> is a list of matched strings. It looks
+ %%% like this: <code>[FullMatch, SubMatch1, SubMatch2, ...]</code>
+@@ -524,10 +525,13 @@ expect(Connection,Patterns) ->
+ %%% milliseconds, <code>{error,timeout}</code> is returned. The default
+ %%% value is <code>infinity</code> (i.e. no time limit).</p>
+ %%%
+-%%% <p>The function will always return when a prompt is found, unless
+-%%% any of the <code>ignore_prompt</code> or
+-%%% <code>no_prompt_check</code> options are used, in which case it
+-%%% will return when a match is found or after a timeout.</p>
++%%% <p>The function will return when a prompt is received, even if no
++%%% pattern has yet been matched. In this event,
++%%% <code>{error,{prompt,Prompt}}</code> is returned.
++%%% However, this behaviour may be modified with the
++%%% <code>ignore_prompt</code> or <code>no_prompt_check</code> option, which
++%%% tells <code>expect</code> to return only when a match is found or after a
++%%% timeout.</p>
+ %%%
+ %%% <p>If the <code>ignore_prompt</code> option is used,
+ %%% <code>ct_telnet</code> will ignore any prompt found. This option
+@@ -541,6 +545,13 @@ expect(Connection,Patterns) ->
+ %%% is useful if, for instance, the <code>Pattern</code> itself
+ %%% matches the prompt.</p>
+ %%%
++%%% <p>The <code>wait_for_prompt</code> option forces <code>ct_telnet</code>
++%%% to wait until the prompt string has been received before returning
++%%% (even if a pattern has already been matched). This is equal to calling:
++%%% <code>expect(Conn, Patterns++[{prompt,Prompt}], [sequence|Opts])</code>.
++%%% Note that <code>idle_timeout</code> and <code>total_timeout</code>
++%%% may abort the operation of waiting for prompt.</p>
++%%%
+ %%% <p>The <code>repeat</code> option indicates that the pattern(s)
+ %%% shall be matched multiple times. If <code>N</code> is given, the
+ %%% pattern(s) will be matched <code>N</code> times, and the function
+@@ -653,18 +664,21 @@ handle_msg({cmd,Cmd,Opts},State) ->
+ start_gen_log(heading(cmd,State#state.name)),
+ log(State,cmd,"Cmd: ~p",[Cmd]),
+
++ %% whatever is in the buffer from previous operations
++ %% will be ignored as we go ahead with this telnet cmd
++
+ debug_cont_gen_log("Throwing Buffer:",[]),
+ debug_log_lines(State#state.buffer),
+
+ case {State#state.type,State#state.prompt} of
+- {ts,_} ->
++ {ts,_} ->
+ silent_teln_expect(State#state.name,
+ State#state.teln_pid,
+ State#state.buffer,
+ prompt,
+ State#state.prx,
+ [{idle_timeout,2000}]);
+- {ip,false} ->
++ {ip,false} ->
+ silent_teln_expect(State#state.name,
+ State#state.teln_pid,
+ State#state.buffer,
+@@ -1029,10 +1043,12 @@ teln_expect(Name,Pid,Data,Pattern0,Prx,Opts) ->
+ end,
+
+ PromptCheck = get_prompt_check(Opts),
+- Seq = get_seq(Opts),
+- Pattern = convert_pattern(Pattern0,Seq),
+
+- {IdleTimeout,TotalTimeout} = get_timeouts(Opts),
++ {WaitForPrompt,Pattern1,Opts1} = wait_for_prompt(Pattern0,Opts),
++
++ Seq = get_seq(Opts1),
++ Pattern2 = convert_pattern(Pattern1,Seq),
++ {IdleTimeout,TotalTimeout} = get_timeouts(Opts1),
+
+ EO = #eo{teln_pid=Pid,
+ prx=Prx,
+@@ -1042,9 +1058,16 @@ teln_expect(Name,Pid,Data,Pattern0,Prx,Opts) ->
+ haltpatterns=HaltPatterns,
+ prompt_check=PromptCheck},
+
+- case get_repeat(Opts) of
++ case get_repeat(Opts1) of
+ false ->
+- case teln_expect1(Name,Pid,Data,Pattern,[],EO) of
++ case teln_expect1(Name,Pid,Data,Pattern2,[],EO) of
++ {ok,Matched,Rest} when WaitForPrompt ->
++ case lists:reverse(Matched) of
++ [{prompt,_},Matched1] ->
++ {ok,Matched1,Rest};
++ [{prompt,_}|Matched1] ->
++ {ok,lists:reverse(Matched1),Rest}
++ end;
+ {ok,Matched,Rest} ->
+ {ok,Matched,Rest};
+ {halt,Why,Rest} ->
+@@ -1054,7 +1077,7 @@ teln_expect(Name,Pid,Data,Pattern0,Prx,Opts) ->
+ end;
+ N ->
+ EO1 = EO#eo{repeat=N},
+- repeat_expect(Name,Pid,Data,Pattern,[],EO1)
++ repeat_expect(Name,Pid,Data,Pattern2,[],EO1)
+ end.
+
+ convert_pattern(Pattern,Seq)
+@@ -1118,6 +1141,40 @@ get_ignore_prompt(Opts) ->
+ get_prompt_check(Opts) ->
+ not lists:member(no_prompt_check,Opts).
+
++wait_for_prompt(Pattern, Opts) ->
++ case lists:member(wait_for_prompt, Opts) of
++ true ->
++ wait_for_prompt1(prompt, Pattern,
++ lists:delete(wait_for_prompt,Opts));
++ false ->
++ case proplists:get_value(wait_for_prompt, Opts) of
++ undefined ->
++ {false,Pattern,Opts};
++ PromptStr ->
++ wait_for_prompt1({prompt,PromptStr}, Pattern,
++ proplists:delete(wait_for_prompt,Opts))
++ end
++ end.
++
++wait_for_prompt1(Prompt, [Ch|_] = Pattern, Opts) when is_integer(Ch) ->
++ wait_for_prompt2(Prompt, [Pattern], Opts);
++wait_for_prompt1(Prompt, Pattern, Opts) when is_list(Pattern) ->
++ wait_for_prompt2(Prompt, Pattern, Opts);
++wait_for_prompt1(Prompt, Pattern, Opts) ->
++ wait_for_prompt2(Prompt, [Pattern], Opts).
++
++wait_for_prompt2(Prompt, Pattern, Opts) ->
++ Pattern1 = case lists:reverse(Pattern) of
++ [prompt|_] -> Pattern;
++ [{prompt,_}|_] -> Pattern;
++ _ -> Pattern ++ [Prompt]
++ end,
++ Opts1 = case lists:member(sequence, Opts) of
++ true -> Opts;
++ false -> [sequence|Opts]
++ end,
++ {true,Pattern1,Opts1}.
++
+ %% Repeat either single or sequence. All match results are accumulated
+ %% and returned when a halt condition is fulllfilled.
+ repeat_expect(_Name,_Pid,Rest,_Pattern,Acc,#eo{repeat=0}) ->
+@@ -1210,7 +1267,7 @@ get_data1(Pid) ->
+ %% 1) Single expect.
+ %% First the whole data chunk is searched for a prompt (to avoid doing
+ %% a regexp match for the prompt at each line).
+-%% If we are searching for anyting else, the datachunk is split into
++%% If we are searching for anything else, the datachunk is split into
+ %% lines and each line is matched against each pattern.
+
+ %% one_expect: split data chunk at prompts
+@@ -1227,7 +1284,7 @@ one_expect(Name,Pid,Data,Pattern,EO) ->
+ log(name_or_pid(Name,Pid),"PROMPT: ~ts",[PromptType]),
+ {match,{prompt,PromptType},Rest};
+ [{prompt,_OtherPromptType}] ->
+- %% Only searching for one specific prompt, not thisone
++ %% Only searching for one specific prompt, not this one
+ log_lines(Name,Pid,UptoPrompt),
+ {nomatch,Rest};
+ _ ->
+diff --git lib/common_test/src/ct_webtool.erl lib/common_test/src/ct_webtool.erl
+new file mode 100644
+index 0000000..b67a7c2
+--- /dev/null
++++ lib/common_test/src/ct_webtool.erl
+@@ -0,0 +1,1207 @@
++%%
++%% %CopyrightBegin%
++%%
++%% Copyright Ericsson AB 2001-2010. 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(ct_webtool).
++-behaviour(gen_server).
++
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++%% %%
++%% The general idea is: %%
++%% %%
++%% %%
++%% 1. Scan through the path for *.tool files and find all the web %%
++%% based tools. Query each tool for configuration data. %%
++%% 2. Add Alias for Erlscript and html for each tool to %%
++%% the webserver configuration data. %%
++%% 3. Start the webserver. %%
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++
++%% API functions
++-export([start/0, start/2, stop/0]).
++
++%% Starting Webtool from a shell script
++-export([script_start/0, script_start/1]).
++
++%% Web api
++-export([started_tools/2, toolbar/2, start_tools/2, stop_tools/2]).
++
++%% API against other tools
++-export([is_localhost/0]).
++
++%% Debug export s
++-export([get_tools1/1]).
++-export([debug/1, stop_debug/0, debug_app/1]).
++
++%% gen_server callbacks
++-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
++ terminate/2, code_change/3]).
++
++-include_lib("kernel/include/file.hrl").
++-include_lib("stdlib/include/ms_transform.hrl").
++
++-record(state,{priv_dir,app_data,supvis,web_data,started=[]}).
++
++-define(MAX_NUMBER_OF_WEBTOOLS,256).
++-define(DEFAULT_PORT,8888).% must be >1024 or the user must be root on unix
++-define(DEFAULT_ADDR,{127,0,0,1}).
++
++-define(WEBTOOL_ALIAS,{ct_webtool,[{alias,{erl_alias,"/ct_webtool",[ct_webtool]}}]}).
++-define(HEADER,"Pragma:no-cache\r\n Content-type: text/html\r\n\r\n").
++-define(HTML_HEADER,"<HTML>\r\n<HEAD>\r\n<TITLE>WebTool</TITLE>\r\n</HEAD>\r\n<BODY BGCOLOR=\"#FFFFFF\">\r\n").
++-define(HTML_HEADER_RELOAD,"<HTML>\r\n<HEAD>\r\n<TITLE>WebTool
++ </TITLE>\r\n</HEAD>\r\n
++ <BODY BGCOLOR=\"#FFFFFF\" onLoad=reloadCompiledList()>\r\n").
++
++-define(HTML_END,"</BODY></HTML>").
++
++-define(SEND_URL_TIMEOUT,5000).
++
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++%% %%
++%% For debugging only. %%
++%% %%
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++%% Start tracing with
++%% debug(Functions).
++%% Functions = local | global | FunctionList
++%% FunctionList = [Function]
++%% Function = {FunctionName,Arity} | FunctionName |
++%% {Module, FunctionName, Arity} | {Module,FunctionName}
++debug(F) ->
++ ttb:tracer(all,[{file,"webtool.trc"}]), % tracing all nodes
++ ttb:p(all,[call,timestamp]),
++ MS = [{'_',[],[{return_trace},{message,{caller}}]}],
++ tp(F,MS),
++ ttb:ctp(?MODULE,stop_debug), % don't want tracing of the stop_debug func
++ ok.
++tp(local,MS) -> % all functions
++ ttb:tpl(?MODULE,MS);
++tp(global,MS) -> % all exported functions
++ ttb:tp(?MODULE,MS);
++tp([{M,F,A}|T],MS) -> % Other module
++ ttb:tpl(M,F,A,MS),
++ tp(T,MS);
++tp([{M,F}|T],MS) when is_atom(F) -> % Other module
++ ttb:tpl(M,F,MS),
++ tp(T,MS);
++tp([{F,A}|T],MS) -> % function/arity
++ ttb:tpl(?MODULE,F,A,MS),
++ tp(T,MS);
++tp([F|T],MS) -> % function
++ ttb:tpl(?MODULE,F,MS),
++ tp(T,MS);
++tp([],_MS) ->
++ ok.
++stop_debug() ->
++ ttb:stop([format]).
++
++debug_app(Mod) ->
++ ttb:tracer(all,[{file,"webtool_app.trc"},{handler,{fun out/4,true}}]),
++ ttb:p(all,[call,timestamp]),
++ MS = [{'_',[],[{return_trace},{message,{caller}}]}],
++ ttb:tp(Mod,MS),
++ ok.
++
++out(_,{trace_ts,Pid,call,MFA={M,F,A},{W,_,_},TS},_,S)
++ when W==webtool;W==mod_esi->
++ io:format("~w: (~p)~ncall ~s~n", [TS,Pid,ffunc(MFA)]),
++ [{M,F,length(A)}|S];
++out(_,{trace_ts,Pid,return_from,MFA,R,TS},_,[MFA|S]) ->
++ io:format("~w: (~p)~nreturned from ~s -> ~p~n", [TS,Pid,ffunc(MFA),R]),
++ S;
++out(_,_,_,_) ->
++ ok.
++
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++%% %%
++%% Functions called via script. %%
++%% %%
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++script_start() ->
++ usage(),
++ halt().
++script_start([App]) ->
++ DefaultBrowser =
++ case os:type() of
++ {win32,_} -> iexplore;
++ _ -> firefox
++ end,
++ script_start([App,DefaultBrowser]);
++script_start([App,Browser]) ->
++ io:format("Starting webtool...\n"),
++ start(),
++ AvailableApps = get_applications(),
++ {OSType,_} = os:type(),
++ case lists:keysearch(App,1,AvailableApps) of
++ {value,{App,StartPage}} ->
++ io:format("Starting ~w...\n",[App]),
++ start_tools([],"app=" ++ atom_to_list(App)),
++ PortStr = integer_to_list(get_port()),
++ Url = case StartPage of
++ "/" ++ Page ->
++ "http://localhost:" ++ PortStr ++ "/" ++ Page;
++ _ ->
++ "http://localhost:" ++ PortStr ++ "/" ++ StartPage
++ end,
++ case Browser of
++ none ->
++ ok;
++ iexplore when OSType == win32->
++ io:format("Starting internet explorer...\n"),
++ {ok,R} = win32reg:open(""),
++ Key="\\local_machine\\SOFTWARE\\Microsoft\\IE Setup\\Setup",
++ win32reg:change_key(R,Key),
++ {ok,Val} = win32reg:value(R,"Path"),
++ IExplore=filename:join(win32reg:expand(Val),"iexplore.exe"),
++ os:cmd("\"" ++ IExplore ++ "\" " ++ Url);
++ _ when OSType == win32 ->
++ io:format("Starting ~w...\n",[Browser]),
++ os:cmd("\"" ++ atom_to_list(Browser) ++ "\" " ++ Url);
++ B when B==firefox; B==mozilla ->
++ io:format("Sending URL to ~w...",[Browser]),
++ BStr = atom_to_list(Browser),
++ SendCmd = BStr ++ " -raise -remote \'openUrl(" ++
++ Url ++ ")\'",
++ Port = open_port({spawn,SendCmd},[exit_status]),
++ receive
++ {Port,{exit_status,0}} ->
++ io:format("done\n"),
++ ok;
++ {Port,{exit_status,_Error}} ->
++ io:format(" not running, starting ~w...\n",
++ [Browser]),
++ os:cmd(BStr ++ " " ++ Url),
++ ok
++ after ?SEND_URL_TIMEOUT ->
++ io:format(" failed, starting ~w...\n",[Browser]),
++ erlang:port_close(Port),
++ os:cmd(BStr ++ " " ++ Url)
++ end;
++ _ ->
++ io:format("Starting ~w...\n",[Browser]),
++ os:cmd(atom_to_list(Browser) ++ " " ++ Url)
++ end,
++ ok;
++ false ->
++ stop(),
++ io:format("\n{error,{unknown_app,~p}}\n",[App]),
++ halt()
++ end.
++
++usage() ->
++ io:format("Starting webtool...\n"),
++ start(),
++ Apps = lists:map(fun({A,_}) -> A end,get_applications()),
++ io:format(
++ "\nUsage: start_webtool application [ browser ]\n"
++ "\nAvailable applications are: ~p\n"
++ "Default browser is \'iexplore\' (Internet Explorer) on Windows "
++ "or else \'firefox\'\n",
++ [Apps]),
++ stop().
++
++
++get_applications() ->
++ gen_server:call(ct_web_tool,get_applications).
++
++get_port() ->
++ gen_server:call(ct_web_tool,get_port).
++
++
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++%% %%
++%% Api functions to the genserver. %%
++%% %%
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++%----------------------------------------------------------------------
++%
++%----------------------------------------------------------------------
++
++start()->
++ start(standard_path,standard_data).
++
++start(Path,standard_data)->
++ case get_standard_data() of
++ {error,Reason} ->
++ {error,Reason};
++ Data ->
++ start(Path,Data)
++ end;
++
++start(standard_path,Data)->
++ Path=get_path(),
++ start(Path,Data);
++
++start(Path,Port) when is_integer(Port)->
++ Data = get_standard_data(Port),
++ start(Path,Data);
++
++start(Path,Data0)->
++ Data = Data0 ++ rest_of_standard_data(),
++ gen_server:start({local,ct_web_tool},ct_webtool,{Path,Data},[]).
++
++stop()->
++ gen_server:call(ct_web_tool,stoppit).
++
++%----------------------------------------------------------------------
++%Web Api functions called by the web
++%----------------------------------------------------------------------
++started_tools(Env,Input)->
++ gen_server:call(ct_web_tool,{started_tools,Env,Input}).
++
++toolbar(Env,Input)->
++ gen_server:call(ct_web_tool,{toolbar,Env,Input}).
++
++start_tools(Env,Input)->
++ gen_server:call(ct_web_tool,{start_tools,Env,Input}).
++
++stop_tools(Env,Input)->
++ gen_server:call(ct_web_tool,{stop_tools,Env,Input}).
++%----------------------------------------------------------------------
++%Support API for other tools
++%----------------------------------------------------------------------
++
++is_localhost()->
++ gen_server:call(ct_web_tool,is_localhost).
++
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++%% %%
++%%The gen_server callback functions that builds the webbpages %%
++%% %%
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++handle_call(get_applications,_,State)->
++ MS = ets:fun2ms(fun({Tool,{web_data,{_,Start}}}) -> {Tool,Start} end),
++ Tools = ets:select(State#state.app_data,MS),
++ {reply,Tools,State};
++
++handle_call(get_port,_,State)->
++ {value,{port,Port}}=lists:keysearch(port,1,State#state.web_data),
++ {reply,Port,State};
++
++handle_call({started_tools,_Env,_Input},_,State)->
++ {reply,started_tools_page(State),State};
++
++handle_call({toolbar,_Env,_Input},_,State)->
++ {reply,toolbar(),State};
++
++handle_call({start_tools,Env,Input},_,State)->
++ {NewState,Page}=start_tools_page(Env,Input,State),
++ {reply,Page,NewState};
++
++handle_call({stop_tools,Env,Input},_,State)->
++ {NewState,Page}=stop_tools_page(Env,Input,State),
++ {reply,Page,NewState};
++
++handle_call(stoppit,_From,Data)->
++ {stop,normal,ok,Data};
++
++handle_call(is_localhost,_From,Data)->
++ Result=case proplists:get_value(bind_address, Data#state.web_data) of
++ ?DEFAULT_ADDR ->
++ true;
++ _IpNumber ->
++ false
++ end,
++ {reply,Result,Data}.
++
++
++handle_info(_Message,State)->
++ {noreply,State}.
++
++handle_cast(_Request,State)->
++ {noreply,State}.
++
++code_change(_,State,_)->
++ {ok,State}.
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++%
++% The other functions needed by the gen_server behaviour
++%
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++%----------------------------------------------------------------------
++% Start the gen_server
++%----------------------------------------------------------------------
++init({Path,Config})->
++ case filelib:is_dir(Path) of
++ true ->
++ {ok, Table} = get_tool_files_data(),
++ insert_app(?WEBTOOL_ALIAS, Table),
++ case ct_webtool_sup:start_link() of
++ {ok, Pid} ->
++ case start_webserver(Table, Path, Config) of
++ {ok, _} ->
++ print_url(Config),
++ {ok,#state{priv_dir=Path,
++ app_data=Table,
++ supvis=Pid,
++ web_data=Config}};
++ {error, Error} ->
++ {stop, {error, Error}}
++ end;
++ Error ->
++ {stop,Error}
++ end;
++ false ->
++ {stop, {error, error_dir}}
++ end.
++
++terminate(_Reason,Data)->
++ %%shut down the webbserver
++ shutdown_server(Data),
++ %%Shutdown the different tools that are started with application:start
++ shutdown_apps(Data),
++ %%Shutdown the supervisor and its children will die
++ shutdown_supervisor(Data),
++ ok.
++
++print_url(ConfigData)->
++ Server=proplists:get_value(server_name,ConfigData,"undefined"),
++ Port=proplists:get_value(port,ConfigData,"undefined"),
++ {A,B,C,D}=proplists:get_value(bind_address,ConfigData,"undefined"),
++ io:format("WebTool is available at http://~s:~w/~n",[Server,Port]),
++ io:format("Or http://~w.~w.~w.~w:~w/~n",[A,B,C,D,Port]).
++
++
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++%
++% begin build the pages
++%
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++
++%----------------------------------------------------------------------
++%The page that shows the started tools
++%----------------------------------------------------------------------
++started_tools_page(State)->
++ [?HEADER,?HTML_HEADER,started_tools(State),?HTML_END].
++
++toolbar()->
++ [?HEADER,?HTML_HEADER,toolbar_page(),?HTML_END].
++
++
++start_tools_page(_Env,Input,State)->
++ %%io:format("~n======= ~n ~p ~n============~n",[Input]),
++ case get_tools(Input) of
++ {tools,Tools}->
++ %%io:format("~n======= ~n ~p ~n============~n",[Tools]),
++ {ok,NewState}=handle_apps(Tools,State,start),
++ {NewState,[?HEADER,?HTML_HEADER_RELOAD,reload_started_apps(),
++ show_unstarted_apps(NewState),?HTML_END]};
++ _ ->
++ {State,[?HEADER,?HTML_HEADER,show_unstarted_apps(State),?HTML_END]}
++ end.
++
++stop_tools_page(_Env,Input,State)->
++ case get_tools(Input) of
++ {tools,Tools}->
++ {ok,NewState}=handle_apps(Tools,State,stop),
++ {NewState,[?HEADER,?HTML_HEADER_RELOAD,reload_started_apps(),
++ show_started_apps(NewState),?HTML_END]};
++ _ ->
++ {State,[?HEADER,?HTML_HEADER,show_started_apps(State),?HTML_END]}
++ end.
++
++
++
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++%%
++%% Functions that start and config the webserver
++%% 1. Collect the config data
++%% 2. Start webserver
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++
++%----------------------------------------------------------------------
++% Start the webserver
++%----------------------------------------------------------------------
++start_webserver(Data,Path,Config)->
++ case get_conf_data(Data,Path,Config) of
++ {ok,Conf_data}->
++ %%io:format("Conf_data: ~p~n",[Conf_data]),
++ start_server(Conf_data);
++ {error,Error} ->
++ {error,{error_server_conf_file,Error}}
++ end.
++
++start_server(Conf_data)->
++ case inets:start(httpd, Conf_data, stand_alone) of
++ {ok,Pid}->
++ {ok,Pid};
++ Error->
++ {error,{server_error,Error}}
++ end.
++
++%----------------------------------------------------------------------
++% Create config data for the webserver
++%----------------------------------------------------------------------
++get_conf_data(Data,Path,Config)->
++ Aliases=get_aliases(Data),
++ ServerRoot = filename:join([Path,"root"]),
++ MimeTypesFile = filename:join([ServerRoot,"conf","mime.types"]),
++ case httpd_conf:load_mime_types(MimeTypesFile) of
++ {ok,MimeTypes} ->
++ Config1 = Config ++ Aliases,
++ Config2 = [{server_root,ServerRoot},
++ {document_root,filename:join([Path,"root/doc"])},
++ {mime_types,MimeTypes} |
++ Config1],
++ {ok,Config2};
++ Error ->
++ Error
++ end.
++
++%----------------------------------------------------------------------
++% Control the path for *.tools files
++%----------------------------------------------------------------------
++get_tool_files_data()->
++ Tools=get_tools1(code:get_path()),
++ %%io:format("Data : ~p ~n",[Tools]),
++ get_file_content(Tools).
++
++%----------------------------------------------------------------------
++%Control that the data in the file really is erlang terms
++%----------------------------------------------------------------------
++get_file_content(Tools)->
++ Get_data=fun({tool,ToolData}) ->
++ %%io:format("Data : ~p ~n",[ToolData]),
++ case proplists:get_value(config_func,ToolData) of
++ {M,F,A}->
++ case catch apply(M,F,A) of
++ {'EXIT',_} ->
++ bad_data;
++ Data when is_tuple(Data) ->
++ Data;
++ _->
++ bad_data
++ end;
++ _ ->
++ bad_data
++ end
++ end,
++ insert_file_content([X ||X<-lists:map(Get_data,Tools),X/=bad_data]).
++
++%----------------------------------------------------------------------
++%Insert the data from the file in to the ets:table
++%----------------------------------------------------------------------
++insert_file_content(Content)->
++ Table=ets:new(app_data,[bag]),
++ lists:foreach(fun(X)->
++ insert_app(X,Table)
++ end,Content),
++ {ok,Table}.
++
++%----------------------------------------------------------------------
++%Control that we got a a tuple of a atom and a list if so add the
++%elements in the list to the ets:table
++%----------------------------------------------------------------------
++insert_app({Name,Key_val_list},Table) when is_list(Key_val_list),is_atom(Name)->
++ %%io:format("ToolData: ~p: ~p~n",[Name,Key_val_list]),
++ lists:foreach(
++ fun({alias,{erl_alias,Alias,Mods}}) ->
++ Key_val = {erl_script_alias,{Alias,Mods}},
++ %%io:format("Insert: ~p~n",[Key_val]),
++ ets:insert(Table,{Name,Key_val});
++ (Key_val_pair)->
++ %%io:format("Insert: ~p~n",[Key_val_pair]),
++ ets:insert(Table,{Name,Key_val_pair})
++ end,
++ Key_val_list);
++
++insert_app(_,_)->
++ ok.
++
++%----------------------------------------------------------------------
++% Select all the alias in the database
++%----------------------------------------------------------------------
++get_aliases(Data)->
++ MS = ets:fun2ms(fun({_,{erl_script_alias,Alias}}) ->
++ {erl_script_alias,Alias};
++ ({_,{alias,Alias}}) ->
++ {alias,Alias}
++ end),
++ ets:select(Data,MS).
++
++
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++%% %%
++%% Helper functions %%
++%% %%
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++get_standard_data(Port)->
++ [
++ {port,Port},
++ {bind_address,?DEFAULT_ADDR},
++ {server_name,"localhost"}
++ ].
++
++get_standard_data()->
++ case get_free_port(?DEFAULT_PORT,?MAX_NUMBER_OF_WEBTOOLS) of
++ {error,Reason} -> {error,Reason};
++ Port ->
++ [
++ {port,Port},
++ {bind_address,?DEFAULT_ADDR},
++ {server_name,"localhost"}
++ ]
++ end.
++
++get_free_port(_Port,0) ->
++ {error,no_free_port_found};
++get_free_port(Port,N) ->
++ case gen_tcp:connect("localhost",Port,[]) of
++ {error, _Reason} ->
++ Port;
++ {ok,Sock} ->
++ gen_tcp:close(Sock),
++ get_free_port(Port+1,N-1)
++ end.
++
++rest_of_standard_data() ->
++ [
++ %% Do not allow the server to be crashed by malformed http-request
++ {max_header_siz,1024},
++ {max_header_action,reply414},
++ %% Go on a straight ip-socket
++ {com_type,ip_comm},
++ %% Do not change the order of these module names!!
++ {modules,[mod_alias,
++ mod_auth,
++ mod_esi,
++ mod_actions,
++ mod_cgi,
++ mod_include,
++ mod_dir,
++ mod_get,
++ mod_head,
++ mod_log,
++ mod_disk_log]},
++ {directory_index,["index.html"]},
++ {default_type,"text/plain"}
++ ].
++
++
++get_path()->
++ code:priv_dir(webtool).
++
++
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++% These functions is used to shutdown the webserver
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++
++%----------------------------------------------------------------------
++% Shut down the webbserver
++%----------------------------------------------------------------------
++shutdown_server(State)->
++ {Addr,Port} = get_addr_and_port(State#state.web_data),
++ inets:stop(httpd,{Addr,Port}).
++
++get_addr_and_port(Config) ->
++ Addr = proplists:get_value(bind_address,Config,?DEFAULT_ADDR),
++ Port = proplists:get_value(port,Config,?DEFAULT_PORT),
++ {Addr,Port}.
++
++%----------------------------------------------------------------------
++% Select all apps in the table and close them
++%----------------------------------------------------------------------
++shutdown_apps(State)->
++ Data=State#state.app_data,
++ MS = ets:fun2ms(fun({_,{start,HowToStart}}) -> HowToStart end),
++ lists:foreach(fun(Start_app)->
++ stop_app(Start_app)
++ end,
++ ets:select(Data,MS)).
++
++%----------------------------------------------------------------------
++%Shuts down the supervisor that supervises tools that is not
++%Designed as applications
++%----------------------------------------------------------------------
++shutdown_supervisor(State)->
++ %io:format("~n==================~n"),
++ ct_webtool_sup:stop(State#state.supvis).
++ %io:format("~n==================~n").
++
++%----------------------------------------------------------------------
++%close the individual apps.
++%----------------------------------------------------------------------
++stop_app({child,_Real_name})->
++ ok;
++
++stop_app({app,Real_name})->
++ application:stop(Real_name);
++
++stop_app({func,_Start,Stop})->
++ case Stop of
++ {M,F,A} ->
++ catch apply(M,F,A);
++ _NoStop ->
++ ok
++ end.
++
++
++
++
++
++
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++%%
++%% These functions creates the webpage where the user can select if
++%% to start apps or to stop apps
++%%
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++
++toolbar_page()->
++ "<TABLE>
++ <TR>
++ <TD>
++ <B>Select Action</B>
++ </TD>
++ </TR>
++ <TR>
++ <TD>
++ <A HREF=\"./start_tools\" TARGET=right> Start Tools</A>
++ </TD>
++ </TR>
++ <TR>
++ <TD>
++ <A HREF=\"./stop_tools\" TARGET=right> Stop Tools</A>
++ </TD>
++ </TR>
++ </TABLE>".
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++%%
++%% These functions creates the webbpage that shows the started apps
++%%
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++
++%----------------------------------------------------------------------
++% started_tools(State)->String (html table)
++% State is a record of type state
++%----------------------------------------------------------------------
++started_tools(State)->
++ Names=get_started_apps(State#state.app_data,State#state.started),
++ "<TABLE BORDER=1 WIDTH=100%>
++ "++ make_rows(Names,[],0) ++"
++ </TABLE>".
++%----------------------------------------------------------------------
++%get_started_apps(Data,Started)-> [{web_name,link}]
++%selects the started apps from the ets table of apps.
++%----------------------------------------------------------------------
++
++get_started_apps(Data,Started)->
++ SelectData=fun({Name,Link}) ->
++ {Name,Link}
++ end,
++ MS = lists:map(fun(A) -> {{A,{web_data,'$1'}},[],['$1']} end,Started),
++
++ [{"WebTool","/tool_management.html"} |
++ [SelectData(X) || X <- ets:select(Data,MS)]].
++
++%----------------------------------------------------------------------
++% make_rows(List,Result,Fields)-> String (The rows of a htmltable
++% List a list of tupler discibed above
++% Result an accumulator for the result
++% Field, counter that counts the number of cols in each row.
++%----------------------------------------------------------------------
++make_rows([],Result,Fields)->
++ Result ++ fill_out(Fields);
++make_rows([Data|Paths],Result,Field)when Field==0->
++ make_rows(Paths,Result ++ "<TR>" ++ make_field(Data),Field+1);
++
++make_rows([Path|Paths],Result,Field)when Field==4->
++ make_rows(Paths,Result ++ make_field(Path) ++ "</TR>",0);
++
++make_rows([Path|Paths],Result,Field)->
++ make_rows(Paths,Result ++ make_field(Path),Field+1).
++
++%----------------------------------------------------------------------
++% make_fields(Path)-> String that is a field i a html table
++% Path is a name url tuple {Name,url}
++%----------------------------------------------------------------------
++make_field(Path)->
++ "<TD WIDTH=20%>" ++ get_name(Path) ++ "</TD>".
++
++
++%----------------------------------------------------------------------
++%get_name({Nae,Url})->String that represents a <A> tag in html.
++%----------------------------------------------------------------------
++get_name({Name,Url})->
++ "<A HREF=\"" ++ Url ++ "\" TARGET=app_frame>" ++ Name ++ "</A>".
++
++
++%----------------------------------------------------------------------
++% fill_out(Nr)-> String, that represent Nr fields in a html-table.
++%----------------------------------------------------------------------
++fill_out(Nr)when Nr==0->
++ [];
++fill_out(Nr)when Nr==4->
++ "<TD WIDTH=\"20%\" >&nbsp</TD></TR>";
++
++fill_out(Nr)->
++ "<TD WIDTH=\"20%\">&nbsp</TD>" ++ fill_out(Nr+1).
++
++
++
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++%%
++%%These functions starts applicatons and builds the page showing tools
++%%to start
++%%
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++%----------------------------------------------------------------------
++%Controls whether the user selected a tool to start
++%----------------------------------------------------------------------
++get_tools(Input)->
++ case httpd:parse_query(Input) of
++ []->
++ no_tools;
++ Tools->
++ FormatData=fun({_Name,Data}) -> list_to_atom(Data) end,
++ SelectData=
++ fun({Name,_Data}) -> string:equal(Name,"app") end,
++ {tools,[FormatData(X)||X<-Tools,SelectData(X)]}
++ end.
++
++%----------------------------------------------------------------------
++% Selects the data to start the applications the user has ordered
++% starting of
++%----------------------------------------------------------------------
++handle_apps([],State,_Cmd)->
++ {ok,State};
++
++handle_apps([Tool|Tools],State,Cmd)->
++ case ets:match_object(State#state.app_data,{Tool,{start,'_'}}) of
++ []->
++ Started = case Cmd of
++ start ->
++ [Tool|State#state.started];
++ stop ->
++ lists:delete(Tool,State#state.started)
++ end,
++ {ok,#state{priv_dir=State#state.priv_dir,
++ app_data=State#state.app_data,
++ supvis=State#state.supvis,
++ web_data=State#state.web_data,
++ started=Started}};
++ ToStart ->
++ case handle_apps2(ToStart,State,Cmd) of
++ {ok,NewState}->
++ handle_apps(Tools,NewState,Cmd);
++ _->
++ handle_apps(Tools,State,Cmd)
++ end
++ end.
++
++%----------------------------------------------------------------------
++%execute every start or stop data about a tool.
++%----------------------------------------------------------------------
++handle_apps2([{Name,Start_data}],State,Cmd)->
++ case handle_app({Name,Start_data},State#state.app_data,State#state.supvis,Cmd) of
++ ok->
++ Started = case Cmd of
++ start ->
++ [Name|State#state.started];
++ stop ->
++
++ lists:delete(Name,State#state.started)
++ end,
++ {ok,#state{priv_dir=State#state.priv_dir,
++ app_data=State#state.app_data,
++ supvis=State#state.supvis,
++ web_data=State#state.web_data,
++ started=Started}};
++ _->
++ error
++ end;
++
++handle_apps2([{Name,Start_data}|Rest],State,Cmd)->
++ case handle_app({Name,Start_data},State#state.app_data,State#state.supvis,Cmd)of
++ ok->
++ handle_apps2(Rest,State,Cmd);
++ _->
++ error
++ end.
++
++
++%----------------------------------------------------------------------
++% Handle start and stop of applications
++%----------------------------------------------------------------------
++
++handle_app({Name,{start,{func,Start,Stop}}},Data,_Pid,Cmd)->
++ Action = case Cmd of
++ start ->
++ Start;
++ _ ->
++ Stop
++ end,
++ case Action of
++ {M,F,A} ->
++ case catch apply(M,F,A) of
++ {'EXIT',_} = Exit->
++ %%! Here the tool disappears from the webtool interface!!
++ io:format("\n=======ERROR (webtool, line ~w) =======\n"
++ "Could not start application \'~p\'\n\n"
++ "~w:~w(~s) ->\n"
++ "~p\n\n",
++ [?LINE,Name,M,F,format_args(A),Exit]),
++ ets:delete(Data,Name);
++ _OK->
++ ok
++ end;
++ _NoStart ->
++ ok
++ end;
++
++
++handle_app({Name,{start,{child,ChildSpec}}},Data,Pid,Cmd)->
++ case Cmd of
++ start ->
++ case catch supervisor:start_child(Pid,ChildSpec) of
++ {ok,_}->
++ ok;
++ {ok,_,_}->
++ ok;
++ {error,Reason}->
++ %%! Here the tool disappears from the webtool interface!!
++ io:format("\n=======ERROR (webtool, line ~w) =======\n"
++ "Could not start application \'~p\'\n\n"
++ "supervisor:start_child(~p,~p) ->\n"
++ "~p\n\n",
++ [?LINE,Name,Pid,ChildSpec,{error,Reason}]),
++ ets:delete(Data,Name);
++ Error ->
++ %%! Here the tool disappears from the webtool interface!!
++ io:format("\n=======ERROR (webtool, line ~w) =======\n"
++ "Could not start application \'~p\'\n\n"
++ "supervisor:start_child(~p,~p) ->\n"
++ "~p\n\n",
++ [?LINE,Name,Pid,ChildSpec,Error]),
++ ets:delete(Data,Name)
++ end;
++ stop ->
++ case catch supervisor:terminate_child(websup,element(1,ChildSpec)) of
++ ok ->
++ supervisor:delete_child(websup,element(1,ChildSpec));
++ _ ->
++ error
++ end
++ end;
++
++
++
++handle_app({Name,{start,{app,Real_name}}},Data,_Pid,Cmd)->
++ case Cmd of
++ start ->
++ case application:start(Real_name,temporary) of
++ ok->
++ io:write(Name),
++ ok;
++ {error,{already_started,_}}->
++ %% Remove it from the database so we dont start
++ %% anything already started
++ ets:match_delete(Data,{Name,{start,{app,Real_name}}}),
++ ok;
++ {error,_Reason}=Error->
++ %%! Here the tool disappears from the webtool interface!!
++ io:format("\n=======ERROR (webtool, line ~w) =======\n"
++ "Could not start application \'~p\'\n\n"
++ "application:start(~p,~p) ->\n"
++ "~p\n\n",
++ [?LINE,Name,Real_name,temporary,Error]),
++ ets:delete(Data,Name)
++ end;
++
++ stop ->
++ application:stop(Real_name)
++ end;
++
++%----------------------------------------------------------------------
++% If the data is incorrect delete the app
++%----------------------------------------------------------------------
++handle_app({Name,Incorrect},Data,_Pid,Cmd)->
++ %%! Here the tool disappears from the webtool interface!!
++ io:format("\n=======ERROR (webtool, line ~w) =======\n"
++ "Could not ~w application \'~p\'\n\n"
++ "Incorrect data: ~p\n\n",
++ [?LINE,Cmd,Name,Incorrect]),
++ ets:delete(Data,Name).
++
++
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++%% %%
++%% this functions creates the page that shows the unstarted tools %%
++%% %%
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++
++reload_started_apps()->
++ "<script>
++ function reloadCompiledList()
++ {
++ parent.parent.top1.document.location.href=\"/webtool/webtool/started_tools\";
++ }
++ </script>".
++
++show_unstarted_apps(State)->
++ "<TABLE HEIGHT=100% WIDTH=100% BORDER=0>
++ <TR HEIGHT=80%><TD ALIGN=\"center\" VALIGN=\"middle\">
++ <FORM NAME=\"stop_apps\" ACTION=\"/webtool/webtool/start_tools\" >
++ <TABLE BORDER=1 WIDTH=60%>
++ <TR BGCOLOR=\"#8899AA\">
++ <TD ALIGN=CENTER COLSPAN=2><FONT SIZE=4>Available Tools<FONT></TD>
++ </TR>
++ <TR>
++ <TD WIDTH=50%>
++ <TABLE BORDER=0>
++ "++ list_available_apps(State)++"
++ <TR><TD COLSPAN=2>&nbsp;</TD></TR>
++ <TR>
++ <TD COLSPAN=2 ALIGN=\"center\">
++ <INPUT TYPE=submit VALUE=\"Start\">
++ </TD>
++ </TR>
++ </TABLE>
++ </TD>
++ <TD>
++ To Start a Tool:
++ <UL>
++ <LI>Select the
++ checkbox for each tool to
++ start.</LI>
++ <LI>Click on the
++ button marked <EM>Start</EM>.</LI></UL>
++ </TD>
++ </TR>
++ </TABLE>
++ </FORM>
++ </TD></TR>
++ <TR><TD>&nbsp;</TD></TR>
++ </TABLE>".
++
++
++
++list_available_apps(State)->
++ MS = ets:fun2ms(fun({Tool,{web_data,{Name,_}}}) -> {Tool,Name} end),
++ Unstarted_apps=
++ lists:filter(
++ fun({Tool,_})->
++ false==lists:member(Tool,State#state.started)
++ end,
++ ets:select(State#state.app_data,MS)),
++ case Unstarted_apps of
++ []->
++ "<TR><TD>All tools are started</TD></TR>";
++ _->
++ list_apps(Unstarted_apps)
++ end.
++
++
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++%% %%
++%% these functions creates the page that shows the started apps %%
++%% the user can select to shutdown %%
++%% %%
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++show_started_apps(State)->
++ "<TABLE HEIGHT=100% WIDTH=100% BORDER=0>
++ <TR HEIGHT=80%><TD ALIGN=\"center\" VALIGN=\"middle\">
++ <FORM NAME=\"stop_apps\" ACTION=\"/webtool/webtool/stop_tools\" >
++ <TABLE BORDER=1 WIDTH=60%>
++ <TR BGCOLOR=\"#8899AA\">
++ <TD ALIGN=CENTER COLSPAN=2><FONT SIZE=4>Started Tools<FONT></TD>
++ </TR>
++ <TR>
++ <TD WIDTH=50%>
++ <TABLE BORDER=0>
++ "++ list_started_apps(State)++"
++ <TR><TD COLSPAN=2>&nbsp;</TD></TR>
++ <TR>
++ <TD COLSPAN=2 ALIGN=\"center\">
++ <INPUT TYPE=submit VALUE=\"Stop\">
++ </TD>
++ </TR>
++ </TABLE>
++ </TD>
++ <TD>
++ Stop a Tool:
++ <UL>
++ <LI>Select the
++ checkbox for each tool to
++ stop.</LI>
++ <LI>Click on the
++ button marked <EM>Stop</EM>.</LI></UL>
++ </TD>
++ </TR>
++ </TABLE>
++ </FORM>
++ </TD></TR>
++ <TR><TD>&nbsp;</TD></TR>
++ </TABLE>".
++
++list_started_apps(State)->
++ MS = lists:map(fun(A) -> {{A,{web_data,{'$1','_'}}},[],[{{A,'$1'}}]} end,
++ State#state.started),
++ Started_apps= ets:select(State#state.app_data,MS),
++ case Started_apps of
++ []->
++ "<TR><TD>No tool is started yet.</TD></TR>";
++ _->
++ list_apps(Started_apps)
++ end.
++
++
++list_apps(Apps) ->
++ lists:map(fun({Tool,Name})->
++ "<TR><TD>
++ <INPUT TYPE=\"checkbox\" NAME=\"app\" VALUE=\""
++ ++ atom_to_list(Tool) ++ "\">
++ " ++ Name ++ "
++ </TD></TR>"
++ end,
++ Apps).
++
++
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++%% %%
++%% Collecting the data from the *.tool files %%
++%% %%
++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
++%----------------------------------------
++% get_tools(Dirs) => [{M,F,A},{M,F,A}...{M,F,A}]
++% Dirs - [string()] Directory names
++% Calls get_tools2/2 recursively for a number of directories
++% to retireve the configuration data for the web based tools.
++%----------------------------------------
++get_tools1(Dirs)->
++ get_tools1(Dirs,[]).
++
++get_tools1([Dir|Rest],Data) when is_list(Dir) ->
++ Tools=case filename:basename(Dir) of
++ %% Dir is an 'ebin' directory, check in '../priv' as well
++ "ebin" ->
++ [get_tools2(filename:join(filename:dirname(Dir),"priv")) |
++ get_tools2(Dir)];
++ _ ->
++ get_tools2(Dir)
++ end,
++ get_tools1(Rest,[Tools|Data]);
++
++get_tools1([],Data) ->
++ lists:flatten(Data).
++
++%----------------------------------------
++% get_tools2(Directory) => DataList
++% DataList : [WebTuple]|[]
++% WebTuple: {tool,[{web,M,F,A}]}
++%
++%----------------------------------------
++get_tools2(Dir)->
++ get_tools2(tool_files(Dir),[]).
++
++get_tools2([ToolFile|Rest],Data) ->
++ case get_tools3(ToolFile) of
++ {tool,WebData} ->
++ get_tools2(Rest,[{tool,WebData}|Data]);
++ {error,_Reason} ->
++ get_tools2(Rest,Data);
++ nodata ->
++ get_tools2(Rest,Data)
++ end;
++
++get_tools2([],Data) ->
++ Data.
++
++%----------------------------------------
++% get_tools3(ToolFile) => {ok,Tool}|{error,Reason}|nodata
++% Tool: {tool,[KeyValTuple]}
++% ToolFile - string() A .tool file
++% Now we have the file get the data and sort it out
++%----------------------------------------
++get_tools3(ToolFile) ->
++ case file:consult(ToolFile) of
++ {error,open} ->
++ {error,nofile};
++ {error,read} ->
++ {error,format};
++ {ok,[{version,"1.2"},ToolInfo]} when is_list(ToolInfo)->
++ webdata(ToolInfo);
++ {ok,[{version,_Vsn},_Info]} ->
++ {error,old_version};
++ {ok,_Other} ->
++ {error,format}
++ end.
++
++
++%----------------------------------------------------------------------
++% webdata(TupleList)-> ToolTuple| nodata
++% ToolTuple: {tool,[{config_func,{M,F,A}}]}
++%
++% There are a little unneccesary work in this format but it is extendable
++%----------------------------------------------------------------------
++webdata(TupleList)->
++ case proplists:get_value(config_func,TupleList,nodata) of
++ {M,F,A} ->
++ {tool,[{config_func,{M,F,A}}]};
++ _ ->
++ nodata
++ end.
++
++
++%=============================================================================
++% Functions for getting *.tool configuration files
++%=============================================================================
++
++%----------------------------------------
++% tool_files(Dir) => ToolFiles
++% Dir - string() Directory name
++% ToolFiles - [string()]
++% Return the list of all files in Dir ending with .tool (appended to Dir)
++%----------------------------------------
++tool_files(Dir) ->
++ case file:list_dir(Dir) of
++ {ok,Files} ->
++ filter_tool_files(Dir,Files);
++ {error,_Reason} ->
++ []
++ end.
++
++%----------------------------------------
++% filter_tool_files(Dir,Files) => ToolFiles
++% Dir - string() Directory name
++% Files, ToolFiles - [string()] File names
++% Filters out the files in Files ending with .tool and append them to Dir
++%----------------------------------------
++filter_tool_files(_Dir,[]) ->
++ [];
++filter_tool_files(Dir,[File|Rest]) ->
++ case filename:extension(File) of
++ ".tool" ->
++ [filename:join(Dir,File)|filter_tool_files(Dir,Rest)];
++ _ ->
++ filter_tool_files(Dir,Rest)
++ end.
++
++
++%%%-----------------------------------------------------------------
++%%% format functions
++ffunc({M,F,A}) when is_list(A) ->
++ io_lib:format("~w:~w(~s)\n",[M,F,format_args(A)]);
++ffunc({M,F,A}) when is_integer(A) ->
++ io_lib:format("~w:~w/~w\n",[M,F,A]).
++
++format_args([]) ->
++ "";
++format_args(Args) ->
++ Str = lists:append(["~p"|lists:duplicate(length(Args)-1,",~p")]),
++ io_lib:format(Str,Args).
+diff --git lib/common_test/src/ct_webtool_sup.erl lib/common_test/src/ct_webtool_sup.erl
+new file mode 100644
+index 0000000..1d612a2
+--- /dev/null
++++ lib/common_test/src/ct_webtool_sup.erl
+@@ -0,0 +1,74 @@
++%%
++%% %CopyrightBegin%
++%%
++%% Copyright Ericsson AB 2001-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(ct_webtool_sup).
++
++-behaviour(supervisor).
++
++%% External exports
++-export([start_link/0,stop/1]).
++
++%% supervisor callbacks
++-export([init/1]).
++
++%%%----------------------------------------------------------------------
++%%% API
++%%%----------------------------------------------------------------------
++start_link() ->
++ supervisor:start_link({local,ct_websup},ct_webtool_sup, []).
++
++stop(Pid)->
++ exit(Pid,normal).
++%%%----------------------------------------------------------------------
++%%% Callback functions from supervisor
++%%%----------------------------------------------------------------------
++
++%%----------------------------------------------------------------------
++%% Func: init/1
++%% Returns: {ok, {SupFlags, [ChildSpec]}} |
++%% ignore |
++%% {error, Reason}
++%%----------------------------------------------------------------------
++init(_StartArgs) ->
++ %%Child1 =
++ %%Child2 ={webcover_backend,{webcover_backend,start_link,[]},permanent,2000,worker,[webcover_backend]},
++ %%{ok,{{simple_one_for_one,5,10},[Child1]}}.
++ {ok,{{one_for_one,100,10},[]}}.
++
++%%%----------------------------------------------------------------------
++%%% Internal functions
++%%%----------------------------------------------------------------------
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
++
+diff --git lib/common_test/src/vts.erl lib/common_test/src/vts.erl
+index b340c6f..ab13e7d 100644
+--- lib/common_test/src/vts.erl
++++ lib/common_test/src/vts.erl
+@@ -63,21 +63,21 @@
+ %%%-----------------------------------------------------------------
+ %%% User API
+ start() ->
+- webtool:start(),
+- webtool:start_tools([],"app=vts").
++ ct_webtool:start(),
++ ct_webtool:start_tools([],"app=vts").
+
+ init_data(ConfigFiles,EvHandlers,LogDir,LogOpts,Tests) ->
+ call({init_data,ConfigFiles,EvHandlers,LogDir,LogOpts,Tests}).
+
+ stop() ->
+- webtool:stop_tools([],"app=vts"),
+- webtool:stop().
++ ct_webtool:stop_tools([],"app=vts"),
++ ct_webtool:stop().
+
+ report(What,Data) ->
+ call({report,What,Data}).
+
+ %%%-----------------------------------------------------------------
+-%%% Return config data used by webtool
++%%% Return config data used by ct_webtool
+ config_data() ->
+ {ok,LogDir} =
+ case lists:keysearch(logdir,1,init:get_arguments()) of
+diff --git lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl
+index a145d85..d01211b 100644
+--- lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl
++++ lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl
+@@ -164,7 +164,7 @@ hello_from_server_first(Config) ->
+ {ok,Client} = ct_netconfc:only_open(?DEFAULT_SSH_OPTS(DataDir)),
+ ct:sleep(500),
+ ?NS:expect(hello),
+- ?ok = ct_netconfc:hello(Client),
++ ?ok = ct_netconfc:hello(Client, [{capability, ["urn:com:ericsson:ebase:1.1.0"]}], infinity),
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client),
+ ok.
+@@ -490,13 +490,16 @@ action(Config) ->
+ Data = [{myactionreturn,[{xmlns,"myns"}],["value"]}],
+ %% test either to receive {data,Data} or {ok,Data},
+ %% both need to be handled
+- {Reply,RetVal} = case element(3, now()) rem 2 of
+- 0 -> {{data,Data},{ok,Data}};
+- 1 -> {{ok,Data},ok}
+- end,
+- ct:log("Client will receive {~w,Data}", [element(1,Reply)]),
+- ?NS:expect_reply(action,Reply),
+- RetVal = ct_netconfc:action(Client,{myaction,[{xmlns,"myns"}],[]}),
++ ct:log("Client will receive {~w,~p}", [data,Data]),
++ ct:log("Expecting ~p", [{ok, Data}]),
++ ?NS:expect_reply(action,{data, Data}),
++ {ok, Data} = ct_netconfc:action(Client,{myaction,[{xmlns,"myns"}],[]}),
++
++ ct:log("Client will receive {~w,~p}", [ok,Data]),
++ ct:log("Expecting ~p", [ok]),
++ ?NS:expect_reply(action,{ok, Data}),
++ ok = ct_netconfc:action(Client,{myaction,[{xmlns,"myns"}],[]}),
++
+ ?NS:expect_do_reply('close-session',close,ok),
+ ?ok = ct_netconfc:close_session(Client),
+ ok.
+diff --git lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_own_server_SUITE.erl lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_own_server_SUITE.erl
+index 1d3f591..9dc9095 100644
+--- lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_own_server_SUITE.erl
++++ lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_own_server_SUITE.erl
+@@ -40,6 +40,7 @@ all() ->
+ expect,
+ expect_repeat,
+ expect_sequence,
++ expect_wait_until_prompt,
+ expect_error_prompt,
+ expect_error_timeout1,
+ expect_error_timeout2,
+@@ -81,6 +82,8 @@ end_per_group(_GroupName, Config) ->
+ expect(_) ->
+ {ok, Handle} = ct_telnet:open(telnet_server_conn1),
+ ok = ct_telnet:send(Handle, "echo ayt"),
++ {ok,["ayt"]} = ct_telnet:expect(Handle, "ayt"),
++ ok = ct_telnet:send(Handle, "echo ayt"),
+ {ok,["ayt"]} = ct_telnet:expect(Handle, ["ayt"]),
+ ok = ct_telnet:close(Handle),
+ ok.
+@@ -103,6 +106,21 @@ expect_sequence(_) ->
+ ok = ct_telnet:close(Handle),
+ ok.
+
++%% Check that expect can wait for delayed prompt
++expect_wait_until_prompt(_) ->
++ {ok, Handle} = ct_telnet:open(telnet_server_conn1),
++ Timeouts = [{idle_timeout,5000},{total_timeout,7000}],
++
++ ok = ct_telnet:send(Handle, "echo_delayed_prompt 3000 xxx"),
++ {ok,["xxx"]} =
++ ct_telnet:expect(Handle, "xxx",
++ [wait_for_prompt|Timeouts]),
++ ok = ct_telnet:send(Handle, "echo_delayed_prompt 3000 yyy zzz"),
++ {ok,[["yyy"],["zzz"]]} =
++ ct_telnet:expect(Handle, ["yyy","zzz"],
++ [{wait_for_prompt,"> "}|Timeouts]),
++ ok.
++
+ %% Check that expect returns when a prompt is found, even if pattern
+ %% is not matched.
+ expect_error_prompt(_) ->
+diff --git lib/common_test/test/telnet_server.erl lib/common_test/test/telnet_server.erl
+index 11959c3..2db5a9b 100644
+--- lib/common_test/test/telnet_server.erl
++++ lib/common_test/test/telnet_server.erl
+@@ -242,6 +242,12 @@ do_handle_data("echo_loop " ++ Data,State) ->
+ ReturnData = string:join(Lines,"\n"),
+ send_loop(list_to_integer(TStr),ReturnData,State),
+ {ok,State};
++do_handle_data("echo_delayed_prompt "++Data,State) ->
++ [MsStr|EchoData] = string:tokens(Data, " "),
++ send(string:join(EchoData,"\n"),State),
++ ct:sleep(list_to_integer(MsStr)),
++ send("\r\n> ",State),
++ {ok,State};
+ do_handle_data("disconnect_after " ++WaitStr,State) ->
+ Wait = list_to_integer(string:strip(WaitStr,right,$\n)),
+ dbg("Server will close connection in ~w ms...", [Wait]),
+diff --git lib/common_test/vsn.mk lib/common_test/vsn.mk
+index d654a8a..e2d9217 100644
+--- lib/common_test/vsn.mk
++++ lib/common_test/vsn.mk
+@@ -1 +1 @@
+-COMMON_TEST_VSN = 1.10
++COMMON_TEST_VSN = 1.10.1
+diff --git lib/diameter/doc/src/diameter.xml lib/diameter/doc/src/diameter.xml
+index 6e41b01..ea175a5 100644
+--- lib/diameter/doc/src/diameter.xml
++++ lib/diameter/doc/src/diameter.xml
+@@ -1820,7 +1820,8 @@ The information presented here is as in the <c>connect</c> case except
+ that the client connections are grouped under an <c>accept</c> tuple.</p>
+
+ <p>
+-Whether or not the &transport_opt; <c>pool_size</c> affects the format
++Whether or not the &transport_opt; <c>pool_size</c> has been
++configured affects the format
+ of the listing in the case of a connecting transport, since a value
+ greater than 1 implies multiple transport processes for the same
+ <c>&transport_ref;</c>, as in the listening case.
+diff --git lib/diameter/doc/src/notes.xml lib/diameter/doc/src/notes.xml
+index 479fab2..6931788 100644
+--- lib/diameter/doc/src/notes.xml
++++ lib/diameter/doc/src/notes.xml
+@@ -42,6 +42,47 @@ first.</p>
+
+ <!-- ===================================================================== -->
+
++<section><title>diameter 1.9.1</title>
++
++ <section><title>Known Bugs and Problems</title>
++ <list>
++ <item>
++ <p>
++ Don't leave extra bit in decoded AVP data.</p>
++ <p>
++ OTP-12074 in OTP 17.3 missed one case: a length error on
++ a trailing AVP unknown to the dictionary in question.</p>
++ <p>
++ Own Id: OTP-12642</p>
++ </item>
++ <item>
++ <p>
++ Don't confuse Result-Code and Experimental-Result</p>
++ <p>
++ The errors field of a decoded diameter_packet record was
++ populated with a Result-Code AVP when an
++ Experimental-Result containing a 3xxx Result-Code was
++ received in an answer not setting the E-bit. The correct
++ AVP is now extracted from the incoming message.</p>
++ <p>
++ Own Id: OTP-12654 Aux Id: seq12851 </p>
++ </item>
++ <item>
++ <p>
++ Don't count on unknown Application Id.</p>
++ <p>
++ OTP-11721 in OTP 17.1 missed the case of an Application
++ Id not agreeing with that of the dictionary in question,
++ causing counters to be accumulated on keys containing the
++ unknown id.</p>
++ <p>
++ Own Id: OTP-12701</p>
++ </item>
++ </list>
++ </section>
++
++</section>
++
+ <section><title>diameter 1.9</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+diff --git lib/diameter/include/diameter_gen.hrl lib/diameter/include/diameter_gen.hrl
+index 0eef218..e8ffe7f 100644
+--- lib/diameter/include/diameter_gen.hrl
++++ lib/diameter/include/diameter_gen.hrl
+@@ -445,7 +445,7 @@ reset(_, _) ->
+ %% undecoded. Note that the type field is 'undefined' in this case.
+
+ decode_AVP(Name, Avp, {Avps, Acc}) ->
+- {[Avp | Avps], pack_AVP(Name, Avp, Acc)}.
++ {[trim(Avp) | Avps], pack_AVP(Name, Avp, Acc)}.
+
+ %% rc/1
+
+diff --git lib/diameter/src/base/diameter_codec.erl lib/diameter/src/base/diameter_codec.erl
+index 15a4c5e..bf2fe8e 100644
+--- lib/diameter/src/base/diameter_codec.erl
++++ lib/diameter/src/base/diameter_codec.erl
+@@ -640,8 +640,12 @@ split_data(Bin, Len) ->
+ %% payload if this is a request. Do this (in cases that we
+ %% know the type) by inducing a decode failure and letting
+ %% the dictionary's decode (in diameter_gen) deal with it.
+- %% Here we don't know type. If the type isn't known, then
+- %% the decode just strips the extra bit.
++ %%
++ %% Note that the extra bit can only occur in the trailing
++ %% AVP of a message or Grouped AVP, since a faulty AVP
++ %% Length is otherwise indistinguishable from a correct
++ %% one here, since we don't know the types of the AVPs
++ %% being extracted.
+ {<<0:1, Bin/binary>>, <<>>}
+ end.
+
+@@ -690,8 +694,8 @@ pack_avp(#diameter_avp{code = undefined, data = B})
+ Len = size(<<H:5/binary, _:24, T/binary>> = <<B/binary, 0:Pad>>),
+ <<H/binary, Len:24, T/binary>>;
+
+-%% ... from a dictionary compiled against old code in diameter_gen ...
+ %% ... when ignoring errors in Failed-AVP ...
++%% ... during a relay encode ...
+ pack_avp(#diameter_avp{data = <<0:1, B/binary>>} = A) ->
+ pack_avp(A#diameter_avp{data = B});
+
+diff --git lib/diameter/src/base/diameter_traffic.erl lib/diameter/src/base/diameter_traffic.erl
+index 538ebee..ffd2c0a 100644
+--- lib/diameter/src/base/diameter_traffic.erl
++++ lib/diameter/src/base/diameter_traffic.erl
+@@ -980,8 +980,8 @@ answer_message(OH, OR, RC, Dict0, #diameter_packet{avps = Avps,
+ session_id(Code, Vid, Dict0, Avps)
+ when is_list(Avps) ->
+ try
+- {value, #diameter_avp{data = D}} = find_avp(Code, Vid, Avps),
+- [{'Session-Id', [Dict0:avp(decode, D, 'Session-Id')]}]
++ #diameter_avp{data = Bin} = find_avp(Code, Vid, Avps),
++ [{'Session-Id', [Dict0:avp(decode, Bin, 'Session-Id')]}]
+ catch
+ error: _ ->
+ []
+@@ -998,26 +998,17 @@ failed_avp(_, [] = No) ->
+
+ %% find_avp/3
+
+-find_avp(Code, Vid, Avps)
+- when is_integer(Code), (undefined == Vid orelse is_integer(Vid)) ->
+- find(fun(A) -> is_avp(Code, Vid, A) end, Avps).
++%% Grouped ...
++find_avp(Code, VId, [[#diameter_avp{code = Code, vendor_id = VId} | _] = As
++ | _]) ->
++ As;
+
+-%% The final argument here could be a list of AVP's, depending on the case,
+-%% but we're only searching at the top level.
+-is_avp(Code, Vid, #diameter_avp{code = Code, vendor_id = Vid}) ->
+- true;
+-is_avp(_, _, _) ->
+- false.
++%% ... or not.
++find_avp(Code, VId, [#diameter_avp{code = Code, vendor_id = VId} = A | _]) ->
++ A;
+
+-find(_, []) ->
+- false;
+-find(Pred, [H|T]) ->
+- case Pred(H) of
+- true ->
+- {value, H};
+- false ->
+- find(Pred, T)
+- end.
++find_avp(Code, VId, [_ | Avps]) ->
++ find_avp(Code, VId, Avps).
+
+ %% 7. Error Handling
+ %%
+@@ -1086,7 +1077,6 @@ incr_result(_, #diameter_packet{msg = undefined = No}, _, _) ->
+ incr_result(Dir, Pkt, TPid, {Dict, AppDict, Dict0}) ->
+ #diameter_packet{header = #diameter_header{is_error = E}
+ = Hdr,
+- msg = Msg,
+ errors = Es}
+ = Pkt,
+
+@@ -1096,13 +1086,13 @@ incr_result(Dir, Pkt, TPid, {Dict, AppDict, Dict0}) ->
+ recv /= Dir orelse [] == Es orelse incr_error(Dir, Id, TPid, AppDict),
+
+ %% Exit on a missing result code.
+- T = rc_counter(Dict, Msg),
++ T = rc_counter(Dict, Dir, Pkt),
+ T == false andalso ?LOGX(no_result_code, {Dict, Dir, Hdr}),
+- {Ctr, RC} = T,
++ {Ctr, RC, Avp} = T,
+
+ %% Or on an inappropriate value.
+ is_result(RC, E, Dict0)
+- orelse ?LOGX(invalid_error_bit, {Dict, Dir, Hdr, RC}),
++ orelse ?LOGX(invalid_error_bit, {Dict, Dir, Hdr, Avp}),
+
+ incr(TPid, {Id, Dir, Ctr}),
+ Ctr.
+@@ -1116,19 +1106,15 @@ msg_id(#diameter_packet{header = H}, Dict) ->
+ %% there are 2^32 (application ids) * 2^24 (command codes) = 2^56
+ %% pairs for an attacker to choose from.
+ msg_id(Hdr, Dict) ->
+- {_ApplId, Code, R} = Id = diameter_codec:msg_id(Hdr),
+- case Dict:msg_name(Code, 0 == R) of
+- '' ->
+- unknown(Dict:id(), R);
+- _ ->
+- Id
++ {Aid, Code, R} = Id = diameter_codec:msg_id(Hdr),
++ if Aid == ?APP_ID_RELAY ->
++ {relay, R};
++ true ->
++ choose(Aid /= Dict:id() orelse '' == Dict:msg_name(Code, 0 == R),
++ unknown,
++ Id)
+ end.
+
+-unknown(?APP_ID_RELAY, R) ->
+- {relay, R};
+-unknown(_, _) ->
+- unknown.
+-
+ %% No E-bit: can't be 3xxx.
+ is_result(RC, false, _Dict0) ->
+ RC < 3000 orelse 4000 =< RC;
+@@ -1148,7 +1134,7 @@ is_result(RC, true, _) ->
+ incr(TPid, Counter) ->
+ diameter_stats:incr(Counter, TPid, 1).
+
+-%% rc_counter/2
++%% rc_counter/3
+
+ %% RFC 3588, 7.6:
+ %%
+@@ -1156,39 +1142,45 @@ incr(TPid, Counter) ->
+ %% applications MUST include either one Result-Code AVP or one
+ %% Experimental-Result AVP.
+
++rc_counter(Dict, recv, #diameter_packet{header = H, avps = As}) ->
++ rc_counter(Dict, [H|As]);
++
++rc_counter(Dict, _, #diameter_packet{msg = Msg}) ->
++ rc_counter(Dict, Msg).
++
+ rc_counter(Dict, Msg) ->
+- rcc(Dict, Msg, int(get_avp_value(Dict, 'Result-Code', Msg))).
++ rcc(get_result(Dict, Msg)).
+
+-rcc(Dict, Msg, undefined) ->
+- rcc(get_avp_value(Dict, 'Experimental-Result', Msg));
+-
+-rcc(_, _, N)
++rcc(#diameter_avp{name = 'Result-Code' = Name, value = N} = A)
+ when is_integer(N) ->
+- {{'Result-Code', N}, N}.
++ {{Name, N}, N, A};
+
+-%% Outgoing answers may be in any of the forms messages can be sent
+-%% in. Incoming messages will be records. We're assuming here that the
+-%% arity of the result code AVP's is 0 or 1.
++rcc(#diameter_avp{name = 'Result-Code' = Name, value = [N|_]} = A)
++ when is_integer(N) ->
++ {{Name, N}, N, A};
+
+-rcc([{_,_,N} = T | _])
++rcc(#diameter_avp{name = 'Experimental-Result', value = {_,_,N} = T} = A)
+ when is_integer(N) ->
+- {T,N};
+-rcc({_,_,N} = T)
++ {T, N, A};
++
++rcc(#diameter_avp{name = 'Experimental-Result', value = [{_,_,N} = T|_]} = A)
+ when is_integer(N) ->
+- {T,N};
++ {T, N, A};
++
+ rcc(_) ->
+ false.
+
+-%% Extract the first good looking integer. There's no guarantee
+-%% that what we're looking for has arity 1.
+-int([N|_])
+- when is_integer(N) ->
+- N;
+-int(N)
+- when is_integer(N) ->
+- N;
+-int(_) ->
+- undefined.
++%% get_result/2
++
++get_result(Dict, Msg) ->
++ try
++ [throw(A) || N <- ['Result-Code', 'Experimental-Result'],
++ #diameter_avp{} = A <- [get_avp(Dict, N, Msg)]]
++ of
++ [] -> false
++ catch
++ #diameter_avp{} = A -> A
++ end.
+
+ x(T) ->
+ exit(T).
+@@ -1528,10 +1520,10 @@ handle_A(Pkt, SvcName, Dict, Dict0, App, #request{transport = TPid} = Req) ->
+ %% a missing AVP. If both are optional in the dictionary
+ %% then this isn't a decode error: just continue on.
+ answer(Pkt, SvcName, App, Req);
+- exit: {invalid_error_bit, {_, _, _, RC}} ->
++ exit: {invalid_error_bit, {_, _, _, Avp}} ->
+ #diameter_packet{errors = Es}
+ = Pkt,
+- E = {5004, #diameter_avp{name = 'Result-Code', value = RC}},
++ E = {5004, Avp},
+ answer(Pkt#diameter_packet{errors = [E|Es]}, SvcName, App, Req)
+ end.
+
+@@ -1868,7 +1860,7 @@ str([]) ->
+ str(T) ->
+ T.
+
+-%% get_avp_value/3
++%% get_avp/3
+ %%
+ %% Find an AVP in a message of one of three forms:
+ %%
+@@ -1885,47 +1877,71 @@ str(T) ->
+ %% look for are in the common dictionary. This is required since the
+ %% relay dictionary doesn't inherit the common dictionary (which maybe
+ %% it should).
+-get_avp_value(?RELAY, Name, Msg) ->
+- get_avp_value(?BASE, Name, Msg);
++get_avp(?RELAY, Name, Msg) ->
++ get_avp(?BASE, Name, Msg);
+
+-%% Message sent as a header/avps list, probably a relay case but not
+-%% necessarily.
+-get_avp_value(Dict, Name, [#diameter_header{} | Avps]) ->
++%% Message as a header/avps list.
++get_avp(Dict, Name, [#diameter_header{} | Avps]) ->
+ try
+ {Code, _, VId} = Dict:avp_header(Name),
+- [A|_] = lists:dropwhile(fun(#diameter_avp{code = C, vendor_id = V}) ->
+- C /= Code orelse V /= VId
+- end,
+- Avps),
+- avp_decode(Dict, Name, A)
++ find_avp(Code, VId, Avps)
++ of
++ A ->
++ avp_decode(Dict, Name, ungroup(A))
+ catch
+ error: _ ->
+ undefined
+ end;
+
+ %% Outgoing message as a name/values list.
+-get_avp_value(_, Name, [_MsgName | Avps]) ->
++get_avp(_, Name, [_MsgName | Avps]) ->
+ case lists:keyfind(Name, 1, Avps) of
+ {_, V} ->
+- V;
++ #diameter_avp{name = Name, value = V};
+ _ ->
+ undefined
+ end;
+
+ %% Message is typically a record but not necessarily.
+-get_avp_value(Dict, Name, Rec) ->
++get_avp(Dict, Name, Rec) ->
+ try
+- Dict:'#get-'(Name, Rec)
++ #diameter_avp{name = Name, value = Dict:'#get-'(Name, Rec)}
+ catch
+ error:_ ->
+ undefined
+ end.
+
++%% get_avp_value/3
++
++get_avp_value(Dict, Name, Msg) ->
++ case get_avp(Dict, Name, Msg) of
++ #diameter_avp{value = V} ->
++ V;
++ undefined = No ->
++ No
++ end.
++
++%% ungroup/1
++
++ungroup([Avp|_]) ->
++ Avp;
++ungroup(Avp) ->
++ Avp.
++
++%% avp_decode/3
++
+ avp_decode(Dict, Name, #diameter_avp{value = undefined,
+- data = Bin}) ->
+- Dict:avp(decode, Bin, Name);
+-avp_decode(_, _, #diameter_avp{value = V}) ->
+- V.
++ data = Bin}
++ = Avp) ->
++ try Dict:avp(decode, Bin, Name) of
++ V ->
++ Avp#diameter_avp{value = V}
++ catch
++ error:_ ->
++ Avp
++ end;
++avp_decode(_, _, #diameter_avp{} = Avp) ->
++ Avp.
+
+ cb(#diameter_app{module = [_|_] = M}, F, A) ->
+ eval(M, F, A);
+diff --git lib/diameter/src/diameter.appup.src lib/diameter/src/diameter.appup.src
+index a54eb24..0ef0fd3 100644
+--- lib/diameter/src/diameter.appup.src
++++ lib/diameter/src/diameter.appup.src
+@@ -35,32 +35,10 @@
+ {"1.4.3", [{restart_application, diameter}]}, %% R16B02
+ {"1.4.4", [{restart_application, diameter}]},
+ {"1.5", [{restart_application, diameter}]}, %% R16B03
+- {"1.6", [{load_module, diameter_lib}, %% 17.0
+- {load_module, diameter_traffic},
+- {load_module, diameter_watchdog},
+- {load_module, diameter_peer_fsm},
+- {load_module, diameter_service},
+- {load_module, diameter_gen_base_rfc6733},
+- {load_module, diameter_gen_acct_rfc6733},
+- {load_module, diameter_gen_base_rfc3588},
+- {load_module, diameter_gen_base_accounting},
+- {load_module, diameter_gen_relay},
+- {load_module, diameter_codec},
+- {load_module, diameter_sctp}]},
+- {"1.7", [{load_module, diameter_service}, %% 17.1
+- {load_module, diameter_codec},
+- {load_module, diameter_gen_base_rfc6733},
+- {load_module, diameter_gen_acct_rfc6733},
+- {load_module, diameter_gen_base_rfc3588},
+- {load_module, diameter_gen_base_accounting},
+- {load_module, diameter_gen_relay},
+- {load_module, diameter_traffic},
+- {load_module, diameter_peer_fsm}]},
+- {"1.7.1", [{load_module, diameter_traffic}, %% 17.3
+- {load_module, diameter_watchdog},
+- {load_module, diameter_peer_fsm},
+- {load_module, diameter_service}]},
+- {"1.8", [{load_module, diameter_lib}, %% 17.4
++ {"1.6", [{restart_application, diameter}]}, %% 17.0
++ {"1.7", [{restart_application, diameter}]}, %% 17.[12]
++ {<<"^1\\.(7\\.1|8)$">>, %% 17.[34]
++ [{load_module, diameter_lib},
+ {load_module, diameter_peer},
+ {load_module, diameter_reg},
+ {load_module, diameter_session},
+@@ -84,7 +62,14 @@
+ {load_module, diameter_gen_relay},
+ {update, diameter_transport_sup, supervisor},
+ {update, diameter_service_sup, supervisor},
+- {update, diameter_sup, supervisor}]}
++ {update, diameter_sup, supervisor}]},
++ {"1.9", [{load_module, diameter_codec}, %% 17.5
++ {load_module, diameter_traffic},
++ {load_module, diameter_gen_base_rfc6733},
++ {load_module, diameter_gen_acct_rfc6733},
++ {load_module, diameter_gen_base_rfc3588},
++ {load_module, diameter_gen_base_accounting},
++ {load_module, diameter_gen_relay}]}
+ ],
+ [
+ {"0.9", [{restart_application, diameter}]},
+@@ -102,55 +87,40 @@
+ {"1.4.3", [{restart_application, diameter}]},
+ {"1.4.4", [{restart_application, diameter}]},
+ {"1.5", [{restart_application, diameter}]},
+- {"1.6", [{load_module, diameter_sctp},
+- {load_module, diameter_codec},
++ {"1.6", [{restart_application, diameter}]},
++ {"1.7", [{restart_application, diameter}]},
++ {<<"^1\\.(7\\.1|8)$">>,
++ [{update, diameter_sup, supervisor},
++ {update, diameter_service_sup, supervisor},
++ {update, diameter_transport_sup, supervisor},
+ {load_module, diameter_gen_relay},
+ {load_module, diameter_gen_base_accounting},
+ {load_module, diameter_gen_base_rfc3588},
+ {load_module, diameter_gen_acct_rfc6733},
+ {load_module, diameter_gen_base_rfc6733},
+- {load_module, diameter_service},
+- {load_module, diameter_peer_fsm},
++ {load_module, diameter},
++ {load_module, diameter_config},
++ {load_module, diameter_sctp},
++ {load_module, diameter_tcp},
+ {load_module, diameter_watchdog},
++ {load_module, diameter_peer_fsm},
++ {load_module, diameter_service},
+ {load_module, diameter_traffic},
++ {load_module, diameter_types},
++ {load_module, diameter_codec},
++ {load_module, diameter_capx},
++ {load_module, diameter_sync},
++ {load_module, diameter_stats},
++ {load_module, diameter_session},
++ {load_module, diameter_reg},
++ {load_module, diameter_peer},
+ {load_module, diameter_lib}]},
+- {"1.7", [{load_module, diameter_peer_fsm},
+- {load_module, diameter_traffic},
+- {load_module, diameter_gen_relay},
+- {load_module, diameter_gen_base_accounting},
+- {load_module, diameter_gen_base_rfc3588},
+- {load_module, diameter_gen_acct_rfc6733},
+- {load_module, diameter_gen_base_rfc6733},
+- {load_module, diameter_codec},
+- {load_module, diameter_service}]},
+- {"1.7.1", [{load_module, diameter_service},
+- {load_module, diameter_peer_fsm},
+- {load_module, diameter_watchdog},
+- {load_module, diameter_traffic}]},
+- {"1.8", [{update, diameter_sup, supervisor},
+- {update, diameter_service_sup, supervisor},
+- {update, diameter_transport_sup, supervisor},
+- {load_module, diameter_gen_relay},
++ {"1.9", [{load_module, diameter_gen_relay},
+ {load_module, diameter_gen_base_accounting},
+ {load_module, diameter_gen_base_rfc3588},
+ {load_module, diameter_gen_acct_rfc6733},
+ {load_module, diameter_gen_base_rfc6733},
+- {load_module, diameter},
+- {load_module, diameter_config},
+- {load_module, diameter_sctp},
+- {load_module, diameter_tcp},
+- {load_module, diameter_watchdog},
+- {load_module, diameter_peer_fsm},
+- {load_module, diameter_service},
+ {load_module, diameter_traffic},
+- {load_module, diameter_types},
+- {load_module, diameter_codec},
+- {load_module, diameter_capx},
+- {load_module, diameter_sync},
+- {load_module, diameter_stats},
+- {load_module, diameter_session},
+- {load_module, diameter_reg},
+- {load_module, diameter_peer},
+- {load_module, diameter_lib}]}
++ {load_module, diameter_codec}]}
+ ]
+ }.
+diff --git lib/diameter/test/diameter_3xxx_SUITE.erl lib/diameter/test/diameter_3xxx_SUITE.erl
+index 071b1a1..44fc3a6 100644
+--- lib/diameter/test/diameter_3xxx_SUITE.erl
++++ lib/diameter/test/diameter_3xxx_SUITE.erl
+@@ -47,6 +47,7 @@
+ send_double_error/1,
+ send_3xxx/1,
+ send_5xxx/1,
++ counters/1,
+ stop/1]).
+
+ %% diameter callbacks
+@@ -111,7 +112,7 @@ all() ->
+
+ groups() ->
+ Tc = tc(),
+- [{?util:name([E,D]), [], [start] ++ Tc ++ [stop]}
++ [{?util:name([E,D]), [], [start] ++ Tc ++ [counters, stop]}
+ || E <- ?ERRORS, D <- ?RFCS].
+
+ init_per_suite(Config) ->
+@@ -169,6 +170,203 @@ stop(_Config) ->
+ ok = diameter:stop_service(?SERVER),
+ ok = diameter:stop_service(?CLIENT).
+
++%% counters/1
++%%
++%% Check that counters are as expected.
++
++counters(Config) ->
++ Group = proplists:get_value(group, Config),
++ [_Errors, _Rfc] = G = ?util:name(Group),
++ [] = ?util:run([[fun counters/3, K, S, G]
++ || K <- [statistics, transport, connections],
++ S <- [?CLIENT, ?SERVER]]).
++
++counters(Key, Svc, Group) ->
++ counters(Key, Svc, Group, [_|_] = diameter:service_info(Svc, Key)).
++
++counters(statistics, Svc, [Errors, Rfc], L) ->
++ [{P, Stats}] = L,
++ true = is_pid(P),
++ stats(Svc, Errors, Rfc, lists:sort(Stats));
++
++counters(_, _, _, _) ->
++ todo.
++
++stats(?CLIENT, E, rfc3588, L)
++ when E == answer;
++ E == answer_3xxx ->
++ [{{unknown,recv},2},
++ {{{0,257,0},recv},1},
++ {{{0,257,1},send},1},
++ {{{0,275,0},recv},6},
++ {{{0,275,1},send},10},
++ {{unknown,recv,{'Result-Code',3001}},1},
++ {{unknown,recv,{'Result-Code',3007}},1},
++ {{{0,257,0},recv,{'Result-Code',2001}},1},
++ {{{0,275,0},recv,{'Result-Code',2001}},1},
++ {{{0,275,0},recv,{'Result-Code',3008}},2},
++ {{{0,275,0},recv,{'Result-Code',3999}},1},
++ {{{0,275,0},recv,{'Result-Code',5002}},1},
++ {{{0,275,0},recv,{'Result-Code',5005}},1}]
++ = L;
++
++stats(?SERVER, E, rfc3588, L)
++ when E == answer;
++ E == answer_3xxx ->
++ [{{unknown,recv},1},
++ {{unknown,send},2},
++ {{{0,257,0},send},1},
++ {{{0,257,1},recv},1},
++ {{{0,275,0},send},6},
++ {{{0,275,1},recv},8},
++ {{unknown,recv,error},1},
++ {{unknown,send,{'Result-Code',3001}},1},
++ {{unknown,send,{'Result-Code',3007}},1},
++ {{{0,257,0},send,{'Result-Code',2001}},1},
++ {{{0,275,0},send,{'Result-Code',2001}},1},
++ {{{0,275,0},send,{'Result-Code',3008}},2},
++ {{{0,275,0},send,{'Result-Code',3999}},1},
++ {{{0,275,0},send,{'Result-Code',5002}},1},
++ {{{0,275,0},send,{'Result-Code',5005}},1},
++ {{{0,275,1},recv,error},5}]
++ = L;
++
++stats(?CLIENT, answer, rfc6733, L) ->
++ [{{unknown,recv},2},
++ {{{0,257,0},recv},1},
++ {{{0,257,1},send},1},
++ {{{0,275,0},recv},8},
++ {{{0,275,1},send},10},
++ {{unknown,recv,{'Result-Code',3001}},1},
++ {{unknown,recv,{'Result-Code',3007}},1},
++ {{{0,257,0},recv,{'Result-Code',2001}},1},
++ {{{0,275,0},recv,{'Result-Code',3008}},2},
++ {{{0,275,0},recv,{'Result-Code',3999}},1},
++ {{{0,275,0},recv,{'Result-Code',5002}},1},
++ {{{0,275,0},recv,{'Result-Code',5005}},3},
++ {{{0,275,0},recv,{'Result-Code',5999}},1}]
++ = L;
++
++stats(?SERVER, answer, rfc6733, L) ->
++ [{{unknown,recv},1},
++ {{unknown,send},2},
++ {{{0,257,0},send},1},
++ {{{0,257,1},recv},1},
++ {{{0,275,0},send},8},
++ {{{0,275,1},recv},8},
++ {{unknown,recv,error},1},
++ {{unknown,send,{'Result-Code',3001}},1},
++ {{unknown,send,{'Result-Code',3007}},1},
++ {{{0,257,0},send,{'Result-Code',2001}},1},
++ {{{0,275,0},send,{'Result-Code',3008}},2},
++ {{{0,275,0},send,{'Result-Code',3999}},1},
++ {{{0,275,0},send,{'Result-Code',5002}},1},
++ {{{0,275,0},send,{'Result-Code',5005}},3},
++ {{{0,275,0},send,{'Result-Code',5999}},1},
++ {{{0,275,1},recv,error},5}]
++ = L;
++
++stats(?CLIENT, answer_3xxx, rfc6733, L) ->
++ [{{unknown,recv},2},
++ {{{0,257,0},recv},1},
++ {{{0,257,1},send},1},
++ {{{0,275,0},recv},8},
++ {{{0,275,1},send},10},
++ {{unknown,recv,{'Result-Code',3001}},1},
++ {{unknown,recv,{'Result-Code',3007}},1},
++ {{{0,257,0},recv,{'Result-Code',2001}},1},
++ {{{0,275,0},recv,{'Result-Code',2001}},1},
++ {{{0,275,0},recv,{'Result-Code',3008}},2},
++ {{{0,275,0},recv,{'Result-Code',3999}},1},
++ {{{0,275,0},recv,{'Result-Code',5002}},1},
++ {{{0,275,0},recv,{'Result-Code',5005}},2},
++ {{{0,275,0},recv,{'Result-Code',5999}},1}]
++ = L;
++
++stats(?SERVER, answer_3xxx, rfc6733, L) ->
++ [{{unknown,recv},1},
++ {{unknown,send},2},
++ {{{0,257,0},send},1},
++ {{{0,257,1},recv},1},
++ {{{0,275,0},send},8},
++ {{{0,275,1},recv},8},
++ {{unknown,recv,error},1},
++ {{unknown,send,{'Result-Code',3001}},1},
++ {{unknown,send,{'Result-Code',3007}},1},
++ {{{0,257,0},send,{'Result-Code',2001}},1},
++ {{{0,275,0},send,{'Result-Code',2001}},1},
++ {{{0,275,0},send,{'Result-Code',3008}},2},
++ {{{0,275,0},send,{'Result-Code',3999}},1},
++ {{{0,275,0},send,{'Result-Code',5002}},1},
++ {{{0,275,0},send,{'Result-Code',5005}},2},
++ {{{0,275,0},send,{'Result-Code',5999}},1},
++ {{{0,275,1},recv,error},5}]
++ = L;
++
++stats(?CLIENT, callback, rfc3588, L) ->
++ [{{unknown,recv},1},
++ {{{0,257,0},recv},1},
++ {{{0,257,1},send},1},
++ {{{0,275,0},recv},6},
++ {{{0,275,1},send},10},
++ {{unknown,recv,{'Result-Code',3007}},1},
++ {{{0,257,0},recv,{'Result-Code',2001}},1},
++ {{{0,275,0},recv,{'Result-Code',2001}},2},
++ {{{0,275,0},recv,{'Result-Code',3999}},1},
++ {{{0,275,0},recv,{'Result-Code',5002}},1},
++ {{{0,275,0},recv,{'Result-Code',5005}},2}]
++ = L;
++
++stats(?SERVER, callback, rfc3588, L) ->
++ [{{unknown,recv},1},
++ {{unknown,send},1},
++ {{{0,257,0},send},1},
++ {{{0,257,1},recv},1},
++ {{{0,275,0},send},6},
++ {{{0,275,1},recv},8},
++ {{unknown,recv,error},1},
++ {{unknown,send,{'Result-Code',3007}},1},
++ {{{0,257,0},send,{'Result-Code',2001}},1},
++ {{{0,275,0},send,{'Result-Code',2001}},2},
++ {{{0,275,0},send,{'Result-Code',3999}},1},
++ {{{0,275,0},send,{'Result-Code',5002}},1},
++ {{{0,275,0},send,{'Result-Code',5005}},2},
++ {{{0,275,1},recv,error},5}]
++ = L;
++
++stats(?CLIENT, callback, rfc6733, L) ->
++ [{{unknown,recv},1},
++ {{{0,257,0},recv},1},
++ {{{0,257,1},send},1},
++ {{{0,275,0},recv},8},
++ {{{0,275,1},send},10},
++ {{unknown,recv,{'Result-Code',3007}},1},
++ {{{0,257,0},recv,{'Result-Code',2001}},1},
++ {{{0,275,0},recv,{'Result-Code',2001}},2},
++ {{{0,275,0},recv,{'Result-Code',3999}},1},
++ {{{0,275,0},recv,{'Result-Code',5002}},1},
++ {{{0,275,0},recv,{'Result-Code',5005}},3},
++ {{{0,275,0},recv,{'Result-Code',5999}},1}]
++ = L;
++
++stats(?SERVER, callback, rfc6733, L) ->
++ [{{unknown,recv},1},
++ {{unknown,send},1},
++ {{{0,257,0},send},1},
++ {{{0,257,1},recv},1},
++ {{{0,275,0},send},8},
++ {{{0,275,1},recv},8},
++ {{unknown,recv,error},1},
++ {{unknown,send,{'Result-Code',3007}},1},
++ {{{0,257,0},send,{'Result-Code',2001}},1},
++ {{{0,275,0},send,{'Result-Code',2001}},2},
++ {{{0,275,0},send,{'Result-Code',3999}},1},
++ {{{0,275,0},send,{'Result-Code',5002}},1},
++ {{{0,275,0},send,{'Result-Code',5005}},3},
++ {{{0,275,0},send,{'Result-Code',5999}},1},
++ {{{0,275,1},recv,error},5}]
++ = L.
++
+ %% send_unknown_application/1
+ %%
+ %% Send an unknown application that a callback (which shouldn't take
+diff --git lib/diameter/test/diameter_app_SUITE.erl lib/diameter/test/diameter_app_SUITE.erl
+index 6975e83..84f8a66 100644
+--- lib/diameter/test/diameter_app_SUITE.erl
++++ lib/diameter/test/diameter_app_SUITE.erl
+@@ -249,11 +249,10 @@ release() ->
+ end.
+
+ unversion(App) ->
+- T = lists:dropwhile(fun is_vsn_ch/1, lists:reverse(App)),
+- lists:reverse(case T of [$-|TT] -> TT; _ -> T end).
+-
+-is_vsn_ch(C) ->
+- $0 =< C andalso C =< $9 orelse $. == C.
++ {Name, [$-|Vsn]} = lists:splitwith(fun(C) -> C /= $- end, App),
++ true = is_app(Name), %% assert
++ Vsn = vsn_str(Vsn), %%
++ Name.
+
+ app('$M_EXPR') -> %% could be anything but assume it's ok
+ "erts";
+@@ -322,11 +321,11 @@ acc_rel(Dir, Rel, {Vsn, _}, Acc) ->
+
+ %% Write a rel file and return its name.
+ write_rel(Dir, [Erts | Apps], Vsn) ->
+- true = is_vsn(Vsn),
+- Name = "diameter_test_" ++ Vsn,
++ VS = vsn_str(Vsn),
++ Name = "diameter_test_" ++ VS,
+ ok = write_file(filename:join([Dir, Name ++ ".rel"]),
+ {release,
+- {"diameter " ++ Vsn ++ " test release", Vsn},
++ {"diameter " ++ VS ++ " test release", VS},
+ Erts,
+ Apps}),
+ Name.
+@@ -341,10 +340,34 @@ fetch(Key, List) ->
+ write_file(Path, T) ->
+ file:write_file(Path, io_lib:format("~p.", [T])).
+
+-%% Is a version string of the expected form? Return the argument
+-%% itself for 'false' for a useful badmatch.
++%% Is a version string of the expected form?
+ is_vsn(V) ->
+- is_list(V)
+- andalso length(V) == string:span(V, "0123456789.")
+- andalso V == string:join(string:tokens(V, [$.]), ".") %% no ".."
+- orelse {error, V}.
++ V = vsn_str(V),
++ true.
++
++%% Turn a from/to version in appup to a version string after ensuring
++%% that it's valid version number of regexp. In the regexp case, the
++%% regexp itself becomes the version string since there's no
++%% requirement that a version in appup be anything but a string. The
++%% restrictions placed on string-valued version numbers (that they be
++%% '.'-separated integers) are our own.
++
++vsn_str(S)
++ when is_list(S) ->
++ {_, match} = {S, match(S, "^(0|[1-9][0-9]*)(\\.(0|[1-9][0-9]*))*$")},
++ {_, nomatch} = {S, match(S, "\\.0\\.0$")},
++ S;
++
++vsn_str(B)
++ when is_binary(B) ->
++ {ok, _} = re:compile(B),
++ binary_to_list(B).
++
++match(S, RE) ->
++ re:run(S, RE, [{capture, none}]).
++
++%% Is an application name of the expected form?
++is_app(S)
++ when is_list(S) ->
++ {_, match} = {S, match(S, "^([a-z]([a-z_]*|[a-zA-Z]*))$")},
++ true.
+diff --git lib/diameter/test/diameter_traffic_SUITE.erl lib/diameter/test/diameter_traffic_SUITE.erl
+index 7dd9f39..7ff6ba7 100644
+--- lib/diameter/test/diameter_traffic_SUITE.erl
++++ lib/diameter/test/diameter_traffic_SUITE.erl
+@@ -41,6 +41,7 @@
+ send_eval/1,
+ send_bad_answer/1,
+ send_protocol_error/1,
++ send_experimental_result/1,
+ send_arbitrary/1,
+ send_unknown/1,
+ send_unknown_short/1,
+@@ -301,6 +302,7 @@ tc() ->
+ send_eval,
+ send_bad_answer,
+ send_protocol_error,
++ send_experimental_result,
+ send_arbitrary,
+ send_unknown,
+ send_unknown_short,
+@@ -443,7 +445,7 @@ send_ok(Config) ->
+ Req = ['ACR', {'Accounting-Record-Type', ?EVENT_RECORD},
+ {'Accounting-Record-Number', 1}],
+
+- ['ACA', _SessionId, {'Result-Code', ?SUCCESS} | _]
++ ['ACA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _]
+ = call(Config, Req).
+
+ %% Send an accounting ACR that the server answers badly to.
+@@ -459,7 +461,7 @@ send_eval(Config) ->
+ Req = ['ACR', {'Accounting-Record-Type', ?EVENT_RECORD},
+ {'Accounting-Record-Number', 3}],
+
+- ['ACA', _SessionId, {'Result-Code', ?SUCCESS} | _]
++ ['ACA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _]
+ = call(Config, Req).
+
+ %% Send an accounting ACR that the server tries to answer with an
+@@ -480,12 +482,20 @@ send_protocol_error(Config) ->
+ ?answer_message(?TOO_BUSY)
+ = call(Config, Req).
+
++%% Send a 3xxx Experimental-Result in an answer not setting the E-bit
++%% and missing a Result-Code.
++send_experimental_result(Config) ->
++ Req = ['ACR', {'Accounting-Record-Type', ?EVENT_RECORD},
++ {'Accounting-Record-Number', 5}],
++ ['ACA', {'Session-Id', _} | _]
++ = call(Config, Req).
++
+ %% Send an ASR with an arbitrary non-mandatory AVP and expect success
+ %% and the same AVP in the reply.
+ send_arbitrary(Config) ->
+ Req = ['ASR', {'AVP', [#diameter_avp{name = 'Product-Name',
+ value = "XXX"}]}],
+- ['ASA', _SessionId, {'Result-Code', ?SUCCESS} | Avps]
++ ['ASA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | Avps]
+ = call(Config, Req),
+ {'AVP', [#diameter_avp{name = 'Product-Name',
+ value = V}]}
+@@ -497,7 +507,7 @@ send_unknown(Config) ->
+ Req = ['ASR', {'AVP', [#diameter_avp{code = 999,
+ is_mandatory = false,
+ data = <<17>>}]}],
+- ['ASA', _SessionId, {'Result-Code', ?SUCCESS} | Avps]
++ ['ASA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | Avps]
+ = call(Config, Req),
+ {'AVP', [#diameter_avp{code = 999,
+ is_mandatory = false,
+@@ -513,7 +523,7 @@ send_unknown_short(Config, M, RC) ->
+ Req = ['ASR', {'AVP', [#diameter_avp{code = 999,
+ is_mandatory = M,
+ data = <<17>>}]}],
+- ['ASA', _SessionId, {'Result-Code', RC} | Avps]
++ ['ASA', {'Session-Id', _}, {'Result-Code', RC} | Avps]
+ = call(Config, Req),
+ [#'diameter_base_Failed-AVP'{'AVP' = As}]
+ = proplists:get_value('Failed-AVP', Avps),
+@@ -527,7 +537,7 @@ send_unknown_mandatory(Config) ->
+ Req = ['ASR', {'AVP', [#diameter_avp{code = 999,
+ is_mandatory = true,
+ data = <<17>>}]}],
+- ['ASA', _SessionId, {'Result-Code', ?AVP_UNSUPPORTED} | Avps]
++ ['ASA', {'Session-Id', _}, {'Result-Code', ?AVP_UNSUPPORTED} | Avps]
+ = call(Config, Req),
+ [#'diameter_base_Failed-AVP'{'AVP' = As}]
+ = proplists:get_value('Failed-AVP', Avps),
+@@ -547,7 +557,7 @@ send_unexpected_mandatory_decode(Config) ->
+ Req = ['ASR', {'AVP', [#diameter_avp{code = 27, %% Session-Timeout
+ is_mandatory = true,
+ data = <<12:32>>}]}],
+- ['ASA', _SessionId, {'Result-Code', ?AVP_UNSUPPORTED} | Avps]
++ ['ASA', {'Session-Id', _}, {'Result-Code', ?AVP_UNSUPPORTED} | Avps]
+ = call(Config, Req),
+ [#'diameter_base_Failed-AVP'{'AVP' = As}]
+ = proplists:get_value('Failed-AVP', Avps),
+@@ -583,7 +593,7 @@ send_error_bit(Config) ->
+ %% Send a bad version and check that we get 5011.
+ send_unsupported_version(Config) ->
+ Req = ['STR', {'Termination-Cause', ?LOGOUT}],
+- ['STA', _SessionId, {'Result-Code', ?UNSUPPORTED_VERSION} | _]
++ ['STA', {'Session-Id', _}, {'Result-Code', ?UNSUPPORTED_VERSION} | _]
+ = call(Config, Req).
+
+ %% Send a request containing an AVP length > data size.
+@@ -603,14 +613,14 @@ send_zero_avp_length(Config) ->
+ send_invalid_avp_length(Config) ->
+ Req = ['STR', {'Termination-Cause', ?LOGOUT}],
+
+- ['STA', _SessionId,
++ ['STA', {'Session-Id', _},
+ {'Result-Code', ?INVALID_AVP_LENGTH},
+- _OriginHost,
+- _OriginRealm,
+- _UserName,
+- _Class,
+- _ErrorMessage,
+- _ErrorReportingHost,
++ {'Origin-Host', _},
++ {'Origin-Realm', _},
++ {'User-Name', _},
++ {'Class', _},
++ {'Error-Message', _},
++ {'Error-Reporting-Host', _},
+ {'Failed-AVP', [#'diameter_base_Failed-AVP'{'AVP' = [_]}]}
+ | _]
+ = call(Config, Req).
+@@ -628,14 +638,14 @@ send_invalid_reject(Config) ->
+ send_unexpected_mandatory(Config) ->
+ Req = ['STR', {'Termination-Cause', ?LOGOUT}],
+
+- ['STA', _SessionId, {'Result-Code', ?AVP_UNSUPPORTED} | _]
++ ['STA', {'Session-Id', _}, {'Result-Code', ?AVP_UNSUPPORTED} | _]
+ = call(Config, Req).
+
+ %% Send something long that will be fragmented by TCP.
+ send_long(Config) ->
+ Req = ['STR', {'Termination-Cause', ?LOGOUT},
+ {'User-Name', [lists:duplicate(1 bsl 20, $X)]}],
+- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _]
++ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _]
+ = call(Config, Req).
+
+ %% Send something longer than the configure incoming_maxlen.
+@@ -677,7 +687,7 @@ send_any_2(Config) ->
+ send_all_1(Config) ->
+ Req = ['STR', {'Termination-Cause', ?LOGOUT}],
+ Realm = lists:foldr(fun(C,A) -> [C,A] end, [], ?REALM),
+- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _]
++ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _]
+ = call(Config, Req, [{filter, {all, [{host, any},
+ {realm, Realm}]}}]).
+ send_all_2(Config) ->
+@@ -697,9 +707,8 @@ send_timeout(Config) ->
+ %% received the Session-Id.
+ send_error(Config) ->
+ Req = ['RAR', {'Re-Auth-Request-Type', ?AUTHORIZE_AUTHENTICATE}],
+- ?answer_message(SId, ?TOO_BUSY)
+- = call(Config, Req),
+- true = undefined /= SId.
++ ?answer_message([_], ?TOO_BUSY)
++ = call(Config, Req).
+
+ %% Send a request with the detached option and receive it as a message
+ %% from handle_answer instead.
+@@ -708,7 +717,7 @@ send_detach(Config) ->
+ Ref = make_ref(),
+ ok = call(Config, Req, [{extra, [{self(), Ref}]}, detach]),
+ Ans = receive {Ref, T} -> T end,
+- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _]
++ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _]
+ = Ans.
+
+ %% Send a request which can't be encoded and expect {error, encode}.
+@@ -721,11 +730,11 @@ send_destination_1(Config) ->
+ = group(Config),
+ Req = ['STR', {'Termination-Cause', ?LOGOUT},
+ {'Destination-Host', [?HOST(SN, ?REALM)]}],
+- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _]
++ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _]
+ = call(Config, Req, [{filter, {all, [host, realm]}}]).
+ send_destination_2(Config) ->
+ Req = ['STR', {'Termination-Cause', ?LOGOUT}],
+- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _]
++ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _]
+ = call(Config, Req, [{filter, {all, [host, realm]}}]).
+
+ %% Send with filtering on and expect failure when specifying an
+@@ -789,7 +798,7 @@ send_bad_filter(Config, F) ->
+ %% Specify multiple filter options and expect them be conjunctive.
+ send_multiple_filters_1(Config) ->
+ Fun = fun(#diameter_caps{}) -> true end,
+- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _]
++ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _]
+ = send_multiple_filters(Config, [host, {eval, Fun}]).
+ send_multiple_filters_2(Config) ->
+ E = {erlang, is_tuple, []},
+@@ -800,7 +809,7 @@ send_multiple_filters_3(Config) ->
+ E2 = {erlang, is_tuple, []},
+ E3 = {erlang, is_record, [diameter_caps]},
+ E4 = [{erlang, is_record, []}, diameter_caps],
+- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _]
++ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _]
+ = send_multiple_filters(Config, [{eval, E} || E <- [E1,E2,E3,E4]]).
+
+ send_multiple_filters(Config, Fs) ->
+@@ -811,7 +820,7 @@ send_multiple_filters(Config, Fs) ->
+ %% only the return value from the prepare_request callback being
+ %% significant.
+ send_anything(Config) ->
+- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _]
++ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _]
+ = call(Config, anything).
+
+ %% ===========================================================================
+@@ -1144,6 +1153,13 @@ answer(Pkt, Req, _Peer, Name, #group{client_dict0 = Dict0}) ->
+ [R | Vs] = Dict:'#get-'(answer(Ans, Es, Name)),
+ [Dict:rec2msg(R) | Vs].
+
++%% Missing Result-Codec and inapproriate Experimental-Result-Code.
++answer(Rec, Es, send_experimental_result) ->
++ [{5004, #diameter_avp{name = 'Experimental-Result'}},
++ {5005, #diameter_avp{name = 'Result-Code'}}]
++ = Es,
++ Rec;
++
+ %% An inappropriate E-bit results in a decode error ...
+ answer(Rec, Es, send_bad_answer) ->
+ [{5004, #diameter_avp{name = 'Result-Code'}} | _] = Es,
+@@ -1175,7 +1191,9 @@ handle_error(Reason, _Req, [$C|_], _Peer, _, _Time) ->
+ %% Note that diameter will set Result-Code and Failed-AVPs if
+ %% #diameter_packet.errors is non-null.
+
+-handle_request(#diameter_packet{header = H, msg = M}, _, {_Ref, Caps}) ->
++handle_request(#diameter_packet{header = H, msg = M, avps = As},
++ _,
++ {_Ref, Caps}) ->
+ #diameter_header{end_to_end_id = EI,
+ hop_by_hop_id = HI}
+ = H,
+@@ -1183,10 +1201,12 @@ handle_request(#diameter_packet{header = H, msg = M}, _, {_Ref, Caps}) ->
+ V = EI bsr B, %% assert
+ V = HI bsr B, %%
+ #diameter_caps{origin_state_id = {_,[Id]}} = Caps,
+- answer(origin(Id), request(M, Caps)).
++ answer(origin(Id), request(M, [H|As], Caps)).
+
+ answer(T, {Tag, Action, Post}) ->
+ {Tag, answer(T, Action), Post};
++answer(_, {reply, [#diameter_header{} | _]} = T) ->
++ T;
+ answer({A,C}, {reply, Ans}) ->
+ answer(C, {reply, msg(Ans, A, diameter_gen_base_rfc3588)});
+ answer(pkt, {reply, Ans})
+@@ -1195,6 +1215,41 @@ answer(pkt, {reply, Ans})
+ answer(_, T) ->
+ T.
+
++%% request/3
++
++%% send_experimental_result
++request(#diameter_base_accounting_ACR{'Accounting-Record-Number' = 5},
++ [Hdr | Avps],
++ #diameter_caps{origin_host = {OH, _},
++ origin_realm = {OR, _}}) ->
++ [H,R|T] = [A || N <- ['Origin-Host',
++ 'Origin-Realm',
++ 'Session-Id',
++ 'Accounting-Record-Type',
++ 'Accounting-Record-Number'],
++ #diameter_avp{} = A
++ <- [lists:keyfind(N, #diameter_avp.name, Avps)]],
++ Ans = [Hdr#diameter_header{is_request = false},
++ H#diameter_avp{data = OH},
++ R#diameter_avp{data = OR},
++ #diameter_avp{name = 'Experimental-Result',
++ code = 297,
++ need_encryption = false,
++ data = [#diameter_avp{data = {?DIAMETER_DICT_COMMON,
++ 'Vendor-Id',
++ 123}},
++ #diameter_avp{data
++ = {?DIAMETER_DICT_COMMON,
++ 'Experimental-Result-Code',
++ 3987}}]}
++ | T],
++ {reply, Ans};
++
++request(Msg, _Avps, Caps) ->
++ request(Msg, Caps).
++
++%% request/2
++
+ %% send_nok
+ request(#diameter_base_accounting_ACR{'Accounting-Record-Number' = 0},
+ _) ->
+diff --git lib/diameter/vsn.mk lib/diameter/vsn.mk
+index c00bac2..db7f72c 100644
+--- lib/diameter/vsn.mk
++++ lib/diameter/vsn.mk
+@@ -16,5 +16,5 @@
+ # %CopyrightEnd%
+
+ APPLICATION = diameter
+-DIAMETER_VSN = 1.9
++DIAMETER_VSN = 1.9.1
+ APP_VSN = $(APPLICATION)-$(DIAMETER_VSN)$(PRE_VSN)
+diff --git lib/snmp/doc/src/notes.xml lib/snmp/doc/src/notes.xml
+index fd307ef..52022f5 100644
+--- lib/snmp/doc/src/notes.xml
++++ lib/snmp/doc/src/notes.xml
+@@ -33,7 +33,40 @@
+ </header>
+
+
+- <section>
++ <section><title>SNMP 5.1.2</title>
++
++ <section><title>Fixed Bugs and Malfunctions</title>
++ <list>
++ <item>
++ <p>
++ A bug in the SNMP Agent has been corrected; when opening
++ a port using the command line argument -snmpa_fd the Port
++ should be 0 when calling gen_udp:open.</p>
++ <p>
++ A bug in the SNMP manager has been corrected; it should
++ not look at the -snmp_fd command line argument, but
++ instead at -snmpm_fd.</p>
++ <p>
++ Own Id: OTP-12669 Aux Id: seq12841 </p>
++ </item>
++ </list>
++ </section>
++
++
++ <section><title>Improvements and New Features</title>
++ <list>
++ <item>
++ <p>
++ Improved cryptocraphic capability.</p>
++ <p>
++ Own Id: OTP-12452</p>
++ </item>
++ </list>
++ </section>
++
++</section>
++
++<section>
+ <title>SNMP Development Toolkit 5.1.1</title>
+ <p>Version 5.1.1 supports code replacement in runtime from/to
+ version 5.1. </p>
+diff --git lib/snmp/src/agent/snmpa_net_if.erl lib/snmp/src/agent/snmpa_net_if.erl
+index 840d56d..57d63ba 100644
+--- lib/snmp/src/agent/snmpa_net_if.erl
++++ lib/snmp/src/agent/snmpa_net_if.erl
+@@ -1,7 +1,7 @@
+ %%
+ %% %CopyrightBegin%
+ %%
+-%% Copyright Ericsson AB 2004-2014. All Rights Reserved.
++%% Copyright Ericsson AB 2004-2015. 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
+@@ -297,14 +297,14 @@ socket_open(snmpUDPDomain = Domain, [IpPort | Opts]) ->
+ Fd = list_to_integer(FdStr),
+ ?vdebug("socket_open(~p, [~p | ~p]) Fd: ~p",
+ [Domain, IpPort, Opts, Fd]),
+- gen_udp_open(IpPort, [{fd, Fd} | Opts]);
++ gen_udp_open(0, [{fd, Fd} | Opts]);
+ error ->
+ case init:get_argument(snmpa_fd) of
+ {ok, [[FdStr]]} ->
+ Fd = list_to_integer(FdStr),
+ ?vdebug("socket_open(~p, [~p | ~p]) Fd: ~p",
+ [Domain, IpPort, Opts, Fd]),
+- gen_udp_open(IpPort, [{fd, Fd} | Opts]);
++ gen_udp_open(0, [{fd, Fd} | Opts]);
+ error ->
+ ?vdebug("socket_open(~p, [~p | ~p])",
+ [Domain, IpPort, Opts]),
+diff --git lib/snmp/src/app/snmp.appup.src lib/snmp/src/app/snmp.appup.src
+index e7e54f5..081163b 100644
+--- lib/snmp/src/app/snmp.appup.src
++++ lib/snmp/src/app/snmp.appup.src
+@@ -1,7 +1,7 @@
+ %%
+ %% %CopyrightBegin%
+ %%
+-%% Copyright Ericsson AB 1999-2014. All Rights Reserved.
++%% Copyright Ericsson AB 1999-2015. 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
+@@ -28,6 +28,7 @@
+ %% {update, snmpa_local_db, soft, soft_purge, soft_purge, []}
+ %% {add_module, snmpm_net_if_mt}
+ [
++ {"5.1.1", [{restart_application, snmp}]},
+ {"5.1", [ % Only compiler changes
+ ]},
+ {"5.0", [{restart_application, snmp}]},
+@@ -46,6 +47,7 @@
+ %% {remove, {snmpm_net_if_mt, soft_purge, soft_purge}}
+
+ [
++ {"5.1.1", [{restart_application, snmp}]},
+ {"5.1", [ % Only compiler changes
+ ]},
+ {"5.0", [{restart_application, snmp}]},
+diff --git lib/snmp/src/manager/snmpm_net_if.erl lib/snmp/src/manager/snmpm_net_if.erl
+index b4cc165..0e1c51c 100644
+--- lib/snmp/src/manager/snmpm_net_if.erl
++++ lib/snmp/src/manager/snmpm_net_if.erl
+@@ -1,7 +1,7 @@
+ %%
+ %% %CopyrightBegin%
+ %%
+-%% Copyright Ericsson AB 2004-2014. All Rights Reserved.
++%% Copyright Ericsson AB 2004-2015. 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
+@@ -330,7 +330,7 @@ socket_params(Domain, {IpAddr, IpPort} = Addr, BindTo, CommonSocketOpts) ->
+ end,
+ case Family of
+ inet ->
+- case init:get_argument(snmp_fd) of
++ case init:get_argument(snmpm_fd) of
+ {ok, [[FdStr]]} ->
+ Fd = list_to_integer(FdStr),
+ case BindTo of
+diff --git lib/snmp/src/manager/snmpm_server.erl lib/snmp/src/manager/snmpm_server.erl
+index a75122d..8fc3359 100644
+--- lib/snmp/src/manager/snmpm_server.erl
++++ lib/snmp/src/manager/snmpm_server.erl
+@@ -1,7 +1,7 @@
+ %%
+ %% %CopyrightBegin%
+ %%
+-%% Copyright Ericsson AB 2004-2014. All Rights Reserved.
++%% Copyright Ericsson AB 2004-2015. 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
+@@ -2116,7 +2116,8 @@ do_handle_agent(DefUserId, DefMod,
+ ok;
+
+ InvalidResult ->
+- CallbackArgs = [Domain, Addr, Type, SnmpInfo, DefData],
++ CallbackArgs =
++ [Domain_or_Ip, Addr_or_Port, Type, SnmpInfo, DefData],
+ handle_invalid_result(handle_agent, CallbackArgs, InvalidResult)
+
+ catch
+@@ -2212,7 +2213,8 @@ do_handle_agent(DefUserId, DefMod,
+ end;
+
+ T:E ->
+- CallbackArgs = [Domain, Addr, Type, SnmpInfo, DefData],
++ CallbackArgs =
++ [Domain_or_Ip, Addr_or_Port, Type, SnmpInfo, DefData],
+ handle_invalid_result(handle_agent, CallbackArgs, T, E)
+
+ end.
+diff --git lib/snmp/vsn.mk lib/snmp/vsn.mk
+index 345cc79..67adf0a 100644
+--- lib/snmp/vsn.mk
++++ lib/snmp/vsn.mk
+@@ -2,7 +2,7 @@
+
+ # %CopyrightBegin%
+ #
+-# Copyright Ericsson AB 1997-2014. All Rights Reserved.
++# Copyright Ericsson AB 1997-2015. 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
+@@ -18,6 +18,6 @@
+ # %CopyrightEnd%
+
+ APPLICATION = snmp
+-SNMP_VSN = 5.1.1
++SNMP_VSN = 5.1.2
+ PRE_VSN =
+ APP_VSN = "$(APPLICATION)-$(SNMP_VSN)$(PRE_VSN)"
+diff --git lib/test_server/doc/src/notes.xml lib/test_server/doc/src/notes.xml
+index f21c32a..e996d2b 100644
+--- lib/test_server/doc/src/notes.xml
++++ lib/test_server/doc/src/notes.xml
+@@ -32,6 +32,28 @@
+ <file>notes.xml</file>
+ </header>
+
++<section><title>Test_Server 3.8.1</title>
++
++ <section><title>Fixed Bugs and Malfunctions</title>
++ <list>
++ <item>
++ <p>
++ If the last expression in a test case causes a timetrap
++ timeout, the stack trace is ignored and not printed to
++ the test case log file. This happens because the
++ {Suite,TestCase,Line} info is not available in the stack
++ trace in this scenario, due to tail call elimination.
++ Common Test has been modified to handle this situation by
++ inserting a {Suite,TestCase,last_expr} tuple in the
++ correct place and printing the stack trace as expected.</p>
++ <p>
++ Own Id: OTP-12697 Aux Id: seq12848 </p>
++ </item>
++ </list>
++ </section>
++
++</section>
++
+ <section><title>Test_Server 3.8</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+diff --git lib/test_server/src/erl2html2.erl lib/test_server/src/erl2html2.erl
+index 7cfaa2c..50dbbb8 100644
+--- lib/test_server/src/erl2html2.erl
++++ lib/test_server/src/erl2html2.erl
+@@ -117,9 +117,10 @@ parse_preprocessed_file(Epp,File,InCorrectFile) ->
+ parse_preprocessed_file(Epp,File,true);
+ {attribute,_,file,{_OtherFile,_}} ->
+ parse_preprocessed_file(Epp,File,false);
+- {function,L,F,A,[_|C]} when InCorrectFile ->
+- Clauses = [{clause,CL} || {clause,CL,_,_,_} <- C],
+- [{atom_to_list(F),A,L} | Clauses] ++
++ {function,L,F,A,Cs} when InCorrectFile ->
++ {CLs,LastCL} = find_clause_lines(Cs, []),
++ %% tl(CLs) cause we know the start line already
++ [{atom_to_list(F),A,L,LastCL} | tl(CLs)] ++
+ parse_preprocessed_file(Epp,File,true);
+ _ ->
+ parse_preprocessed_file(Epp,File,InCorrectFile)
+@@ -146,9 +147,10 @@ parse_non_preprocessed_file(Epp, File, Location) ->
+ case epp_dodger:parse_form(Epp, Location) of
+ {ok,Tree,Location1} ->
+ try erl_syntax:revert(Tree) of
+- {function,L,F,A,[_|C]} ->
+- Clauses = [{clause,CL} || {clause,CL,_,_,_} <- C],
+- [{atom_to_list(F),A,L} | Clauses] ++
++ {function,L,F,A,Cs} ->
++ {CLs,LastCL} = find_clause_lines(Cs, []),
++ %% tl(CLs) cause we know the start line already
++ [{atom_to_list(F),A,L,LastCL} | tl(CLs)] ++
+ parse_non_preprocessed_file(Epp, File, Location1);
+ _ ->
+ parse_non_preprocessed_file(Epp, File, Location1)
+@@ -162,22 +164,48 @@ parse_non_preprocessed_file(Epp, File, Location) ->
+ end.
+
+ %%%-----------------------------------------------------------------
++%%% Find the line number of the last expression in the function
++find_clause_lines([{clause,CL,_Params,_Op,Exprs}], CLs) -> % last clause
++ try tuple_to_list(lists:last(Exprs)) of
++ [_Type,ExprLine | _] ->
++ {lists:reverse([{clause,CL}|CLs]), ExprLine};
++ _ ->
++ {lists:reverse([{clause,CL}|CLs]), CL}
++ catch
++ _:_ ->
++ {lists:reverse([{clause,CL}|CLs]), CL}
++ end;
++
++find_clause_lines([{clause,CL,_Params,_Op,_Exprs} | Cs], CLs) ->
++ find_clause_lines(Cs, [{clause,CL}|CLs]).
++
++%%%-----------------------------------------------------------------
+ %%% Add a link target for each line and one for each function definition.
+-build_html(SFd,DFd,Encoding,Functions) ->
+- build_html(SFd,DFd,Encoding,file:read_line(SFd),1,Functions,false).
++build_html(SFd,DFd,Encoding,FuncsAndCs) ->
++ build_html(SFd,DFd,Encoding,file:read_line(SFd),1,FuncsAndCs,
++ false,undefined).
+
+-build_html(SFd,DFd,Encoding,{ok,Str},L,[{F,A,L}|Functions],_IsFuncDef) ->
++%% function start line found
++build_html(SFd,DFd,Enc,{ok,Str},L0,[{F,A,L0,LastL}|FuncsAndCs],
++ _IsFuncDef,_FAndLastL) ->
+ FALink = test_server_ctrl:uri_encode(F++"-"++integer_to_list(A),utf8),
+- file:write(DFd,["<a name=\"",to_raw_list(FALink,Encoding),"\"/>"]),
+- build_html(SFd,DFd,Encoding,{ok,Str},L,Functions,true);
+-build_html(SFd,DFd,Encoding,{ok,Str},L,[{clause,L}|Functions],_IsFuncDef) ->
+- build_html(SFd,DFd,Encoding,{ok,Str},L,Functions,true);
+-build_html(SFd,DFd,Encoding,{ok,Str},L,Functions,IsFuncDef) ->
++ file:write(DFd,["<a name=\"",to_raw_list(FALink,Enc),"\"/>"]),
++ build_html(SFd,DFd,Enc,{ok,Str},L0,FuncsAndCs,true,{F,LastL});
++%% line of last expression in function found
++build_html(SFd,DFd,Enc,{ok,Str},LastL,FuncsAndCs,_IsFuncDef,{F,LastL}) ->
++ LastLineLink = test_server_ctrl:uri_encode(F++"-last_expr",utf8),
++ file:write(DFd,["<a name=\"",
++ to_raw_list(LastLineLink,Enc),"\"/>"]),
++ build_html(SFd,DFd,Enc,{ok,Str},LastL,FuncsAndCs,true,undefined);
++build_html(SFd,DFd,Enc,{ok,Str},L,[{clause,L}|FuncsAndCs],
++ _IsFuncDef,FAndLastL) ->
++ build_html(SFd,DFd,Enc,{ok,Str},L,FuncsAndCs,true,FAndLastL);
++build_html(SFd,DFd,Enc,{ok,Str},L,FuncsAndCs,IsFuncDef,FAndLastL) ->
+ LStr = line_number(L),
+ Str1 = line(Str,IsFuncDef),
+ file:write(DFd,[LStr,Str1]),
+- build_html(SFd,DFd,Encoding,file:read_line(SFd),L+1,Functions,false);
+-build_html(_SFd,_DFd,_Encoding,eof,L,_Functions,_IsFuncDef) ->
++ build_html(SFd,DFd,Enc,file:read_line(SFd),L+1,FuncsAndCs,false,FAndLastL);
++build_html(_SFd,_DFd,_Enc,eof,L,_FuncsAndCs,_IsFuncDef,_FAndLastL) ->
+ L.
+
+ line_number(L) ->
+diff --git lib/test_server/src/test_server.erl lib/test_server/src/test_server.erl
+index 8d91778..1c33525 100644
+--- lib/test_server/src/test_server.erl
++++ lib/test_server/src/test_server.erl
+@@ -1355,12 +1355,30 @@ get_loc(Pid) ->
+ Stk = [rewrite_loc_item(Loc) || Loc <- Stk0],
+ case get(test_server_loc) of
+ [{Suite,Case}] ->
+- %% location info unknown, check if {Suite,Case,Line}
+- %% is available in stacktrace. and if so, use stacktrace
+- %% instead of current test_server_loc
++ %% Location info unknown, check if {Suite,Case,Line}
++ %% is available in stacktrace and if so, use stacktrace
++ %% instead of current test_server_loc.
++ %% If location is the last expression in a test case
++ %% function, the info is not available due to tail call
++ %% elimination. We need to check if the test case has been
++ %% called by ts_tc/3 and, if so, insert the test case info
++ %% at that position.
+ case [match || {S,C,_L} <- Stk, S == Suite, C == Case] of
+- [match|_] -> put(test_server_loc, Stk);
+- _ -> ok
++ [match|_] ->
++ put(test_server_loc, Stk);
++ _ ->
++ {PreTC,PostTC} =
++ lists:splitwith(fun({test_server,ts_tc,_}) ->
++ false;
++ (_) ->
++ true
++ end, Stk),
++ if PostTC == [] ->
++ ok;
++ true ->
++ put(test_server_loc,
++ PreTC++[{Suite,Case,last_expr} | PostTC])
++ end
+ end;
+ _ ->
+ put(test_server_loc, Stk)
+@@ -1422,7 +1440,10 @@ lookup_config(Key,Config) ->
+ undefined
+ end.
+
+-%% timer:tc/3
++%%
++%% IMPORTANT: get_loc/1 uses the name of this function when analysing
++%% stack traces. If the name changes, get_loc/1 must be updated!
++%%
+ ts_tc(M, F, A) ->
+ Before = erlang:now(),
+ Result = try
+diff --git lib/test_server/src/test_server_sup.erl lib/test_server/src/test_server_sup.erl
+index 96e369a..15a6fdd 100644
+--- lib/test_server/src/test_server_sup.erl
++++ lib/test_server/src/test_server_sup.erl
+@@ -61,33 +61,37 @@ timetrap(Timeout0, ReportTVal, Scale, Pid) ->
+ TruncTO = trunc(Timeout),
+ receive
+ after TruncTO ->
+- case is_process_alive(Pid) of
+- true ->
+- TimeToReport = if Timeout0 == ReportTVal -> TruncTO;
+- true -> ReportTVal end,
+- MFLs = test_server:get_loc(Pid),
+- Mon = erlang:monitor(process, Pid),
+- Trap = {timetrap_timeout,TimeToReport,MFLs},
+- exit(Pid, Trap),
+- receive
+- {'DOWN', Mon, process, Pid, _} ->
+- ok
+- after 10000 ->
+- %% Pid is probably trapping exits, hit it harder...
+- catch error_logger:warning_msg(
+- "Testcase process ~w not "
+- "responding to timetrap "
+- "timeout:~n"
+- " ~p.~n"
+- "Killing testcase...~n",
+- [Pid, Trap]),
+- exit(Pid, kill)
+- end;
+- false ->
++ kill_the_process(Pid, Timeout0, TruncTO, ReportTVal)
++ end.
++
++kill_the_process(Pid, Timeout0, TruncTO, ReportTVal) ->
++ case is_process_alive(Pid) of
++ true ->
++ TimeToReport = if Timeout0 == ReportTVal -> TruncTO;
++ true -> ReportTVal end,
++ MFLs = test_server:get_loc(Pid),
++ Mon = erlang:monitor(process, Pid),
++ Trap = {timetrap_timeout,TimeToReport,MFLs},
++ exit(Pid, Trap),
++ receive
++ {'DOWN', Mon, process, Pid, _} ->
+ ok
+- end
++ after 10000 ->
++ %% Pid is probably trapping exits, hit it harder...
++ catch error_logger:warning_msg(
++ "Testcase process ~w not "
++ "responding to timetrap "
++ "timeout:~n"
++ " ~p.~n"
++ "Killing testcase...~n",
++ [Pid, Trap]),
++ exit(Pid, kill)
++ end;
++ false ->
++ ok
+ end.
+
++
+ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+ %% timetrap_cancel(Handle) -> ok
+ %% Handle = term()
+@@ -812,10 +816,19 @@ format_loc1({Mod,Func,Line}) ->
+ case {lists:member(no_src, get(test_server_logopts)),
+ lists:reverse(ModStr)} of
+ {false,[$E,$T,$I,$U,$S,$_|_]} ->
+- io_lib:format("{~w,~w,<a href=\"~ts~ts#~w\">~w</a>}",
++ Link = if is_integer(Line) ->
++ integer_to_list(Line);
++ Line == last_expr ->
++ list_to_atom(atom_to_list(Func)++"-last_expr");
++ is_atom(Line) ->
++ atom_to_list(Line);
++ true ->
++ Line
++ end,
++ io_lib:format("{~w,~w,<a href=\"~ts~ts#~s\">~w</a>}",
+ [Mod,Func,
+ test_server_ctrl:uri_encode(downcase(ModStr)),
+- ?src_listing_ext,Line,Line]);
++ ?src_listing_ext,Link,Line]);
+ _ ->
+ io_lib:format("{~w,~w,~w}",[Mod,Func,Line])
+ end.
+diff --git lib/test_server/vsn.mk lib/test_server/vsn.mk
+index 77225b4..2a2ed2b 100644
+--- lib/test_server/vsn.mk
++++ lib/test_server/vsn.mk
+@@ -1 +1 @@
+-TEST_SERVER_VSN = 3.8
++TEST_SERVER_VSN = 3.8.1
+diff --git otp_versions.table otp_versions.table
+index 4bf6cb9..12790c8 100644
+--- otp_versions.table
++++ otp_versions.table
+@@ -1,3 +1,4 @@
++OTP-17.5.3 : common_test-1.10.1 diameter-1.9.1 erts-6.4.1 snmp-5.1.2 test_server-3.8.1 # asn1-3.0.4 compiler-5.0.4 cosEvent-2.1.15 cosEventDomain-1.1.14 cosFileTransfer-1.1.16 cosNotification-1.1.21 cosProperty-1.1.17 cosTime-1.1.14 cosTransactions-1.2.14 crypto-3.5 debugger-4.0.3 dialyzer-2.7.4 edoc-0.7.16 eldap-1.1.1 erl_docgen-0.3.7 erl_interface-3.7.20 et-1.5 eunit-2.2.9 gs-1.5.16 hipe-3.11.3 ic-4.3.6 inets-5.10.7 jinterface-1.5.12 kernel-3.2 megaco-3.17.3 mnesia-4.12.5 observer-2.0.4 odbc-2.10.22 orber-3.7.1 os_mon-2.3.1 ose-1.0.2 otp_mibs-1.0.10 parsetools-2.0.12 percept-0.8.10 public_key-0.23 reltool-0.6.6 runtime_tools-1.8.16 sasl-2.4.1 ssh-3.2.2 ssl-6.0 stdlib-2.4 syntax_tools-1.6.18 tools-2.7.2 typer-0.9.8 webtool-0.8.10 wx-1.3.3 xmerl-1.3.7 :
+ OTP-17.5.2 : inets-5.10.7 ssh-3.2.2 # asn1-3.0.4 common_test-1.10 compiler-5.0.4 cosEvent-2.1.15 cosEventDomain-1.1.14 cosFileTransfer-1.1.16 cosNotification-1.1.21 cosProperty-1.1.17 cosTime-1.1.14 cosTransactions-1.2.14 crypto-3.5 debugger-4.0.3 dialyzer-2.7.4 diameter-1.9 edoc-0.7.16 eldap-1.1.1 erl_docgen-0.3.7 erl_interface-3.7.20 erts-6.4 et-1.5 eunit-2.2.9 gs-1.5.16 hipe-3.11.3 ic-4.3.6 jinterface-1.5.12 kernel-3.2 megaco-3.17.3 mnesia-4.12.5 observer-2.0.4 odbc-2.10.22 orber-3.7.1 os_mon-2.3.1 ose-1.0.2 otp_mibs-1.0.10 parsetools-2.0.12 percept-0.8.10 public_key-0.23 reltool-0.6.6 runtime_tools-1.8.16 sasl-2.4.1 snmp-5.1.1 ssl-6.0 stdlib-2.4 syntax_tools-1.6.18 test_server-3.8 tools-2.7.2 typer-0.9.8 webtool-0.8.10 wx-1.3.3 xmerl-1.3.7 :
+ OTP-17.5.1 : ssh-3.2.1 # asn1-3.0.4 common_test-1.10 compiler-5.0.4 cosEvent-2.1.15 cosEventDomain-1.1.14 cosFileTransfer-1.1.16 cosNotification-1.1.21 cosProperty-1.1.17 cosTime-1.1.14 cosTransactions-1.2.14 crypto-3.5 debugger-4.0.3 dialyzer-2.7.4 diameter-1.9 edoc-0.7.16 eldap-1.1.1 erl_docgen-0.3.7 erl_interface-3.7.20 erts-6.4 et-1.5 eunit-2.2.9 gs-1.5.16 hipe-3.11.3 ic-4.3.6 inets-5.10.6 jinterface-1.5.12 kernel-3.2 megaco-3.17.3 mnesia-4.12.5 observer-2.0.4 odbc-2.10.22 orber-3.7.1 os_mon-2.3.1 ose-1.0.2 otp_mibs-1.0.10 parsetools-2.0.12 percept-0.8.10 public_key-0.23 reltool-0.6.6 runtime_tools-1.8.16 sasl-2.4.1 snmp-5.1.1 ssl-6.0 stdlib-2.4 syntax_tools-1.6.18 test_server-3.8 tools-2.7.2 typer-0.9.8 webtool-0.8.10 wx-1.3.3 xmerl-1.3.7 :
+ OTP-17.5 : asn1-3.0.4 common_test-1.10 compiler-5.0.4 crypto-3.5 debugger-4.0.3 dialyzer-2.7.4 diameter-1.9 eldap-1.1.1 erts-6.4 hipe-3.11.3 inets-5.10.6 kernel-3.2 mnesia-4.12.5 observer-2.0.4 os_mon-2.3.1 public_key-0.23 runtime_tools-1.8.16 ssh-3.2 ssl-6.0 stdlib-2.4 syntax_tools-1.6.18 test_server-3.8 tools-2.7.2 wx-1.3.3 # cosEvent-2.1.15 cosEventDomain-1.1.14 cosFileTransfer-1.1.16 cosNotification-1.1.21 cosProperty-1.1.17 cosTime-1.1.14 cosTransactions-1.2.14 edoc-0.7.16 erl_docgen-0.3.7 erl_interface-3.7.20 et-1.5 eunit-2.2.9 gs-1.5.16 ic-4.3.6 jinterface-1.5.12 megaco-3.17.3 odbc-2.10.22 orber-3.7.1 ose-1.0.2 otp_mibs-1.0.10 parsetools-2.0.12 percept-0.8.10 reltool-0.6.6 sasl-2.4.1 snmp-5.1.1 typer-0.9.8 webtool-0.8.10 xmerl-1.3.7 :