aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/diameter/doc/src/diameter.xml12
-rw-r--r--lib/diameter/doc/src/notes.xml61
-rw-r--r--lib/diameter/include/diameter_gen.hrl56
-rw-r--r--lib/diameter/src/base/diameter_codec.erl21
-rw-r--r--lib/diameter/src/base/diameter_config.erl5
-rw-r--r--lib/diameter/src/base/diameter_lib.erl62
-rw-r--r--lib/diameter/src/base/diameter_peer.erl5
-rw-r--r--lib/diameter/src/base/diameter_peer_fsm.erl7
-rw-r--r--lib/diameter/src/base/diameter_reg.erl5
-rw-r--r--lib/diameter/src/base/diameter_service.erl9
-rw-r--r--lib/diameter/src/base/diameter_stats.erl18
-rw-r--r--lib/diameter/src/base/diameter_sync.erl5
-rw-r--r--lib/diameter/src/base/diameter_watchdog.erl51
-rw-r--r--lib/diameter/src/diameter.appup.src36
-rw-r--r--lib/diameter/src/info/diameter_info.erl7
-rw-r--r--lib/diameter/src/transport/diameter_sctp.erl3
-rw-r--r--lib/diameter/test/diameter_examples_SUITE.erl2
-rw-r--r--lib/diameter/test/diameter_gen_sctp_SUITE.erl8
-rw-r--r--lib/diameter/test/diameter_relay_SUITE.erl44
-rw-r--r--lib/diameter/test/diameter_stats_SUITE.erl6
-rw-r--r--lib/diameter/test/diameter_transport_SUITE.erl5
-rw-r--r--lib/diameter/test/diameter_util.erl16
-rw-r--r--lib/diameter/test/diameter_watchdog_SUITE.erl7
-rw-r--r--lib/diameter/vsn.mk2
-rw-r--r--otp_versions.table1
25 files changed, 281 insertions, 173 deletions
diff --git a/lib/diameter/doc/src/diameter.xml b/lib/diameter/doc/src/diameter.xml
index 1e1206aa2d..47247bc2ff 100644
--- a/lib/diameter/doc/src/diameter.xml
+++ b/lib/diameter/doc/src/diameter.xml
@@ -795,14 +795,6 @@ Messages larger than the specified number of bytes are discarded.</p>
Defaults to <c>16777215</c>, the maximum value of the 24-bit Message
Length field in a Diameter Header.</p>
-<warning>
-<p>
-This option should be set to as low a value as is sufficient for the
-Diameter applications and peers in question, since decoding incoming
-messages from a malicious peer can otherwise generate significant
-load.</p>
-</warning>
-
</item>
<tag><c>{restrict_connections, false
@@ -1232,9 +1224,7 @@ is not the length of the message in question, as received over the
transport interface documented in &man_transport;.</p>
<p>
-If <c>exit</c> then a warning report is emitted and the parent of the
-transport process in question exits, which causes the transport
-process itself to exit as described in &man_transport;.
+If <c>exit</c> then the transport process in question exits.
If <c>handle</c> then the message is processed as usual, a resulting
&app_handle_request; or &app_handle_answer; callback (if one takes
place) indicating the <c>5015</c> error (DIAMETER_INVALID_MESSAGE_LENGTH).
diff --git a/lib/diameter/doc/src/notes.xml b/lib/diameter/doc/src/notes.xml
index 299d4093da..2953de95d1 100644
--- a/lib/diameter/doc/src/notes.xml
+++ b/lib/diameter/doc/src/notes.xml
@@ -43,6 +43,65 @@ first.</p>
<!-- ===================================================================== -->
+<section><title>diameter 1.11</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Don't report 5005 (DIAMETER_AVP_MISSING) errors
+ unnecessarily.</p>
+ <p>
+ An AVP whose decode failed was reported as missing,
+ despite having been reported with another error as a
+ consequence of the failure.</p>
+ <p>
+ Own Id: OTP-12871</p>
+ </item>
+ <item>
+ <p>
+ Fix relay encode of nested, Grouped AVPs.</p>
+ <p>
+ A fault in OTP-12475 caused encode to fail if the first
+ AVP in a Grouped AVP was itself Grouped.</p>
+ <p>
+ Own Id: OTP-12879 Aux Id: OTP-12475 </p>
+ </item>
+ <item>
+ <p>
+ Improve decode performance.</p>
+ <p>
+ The time required to decode a message increased
+ quadratically with the number of AVPs in the worst case,
+ leading to extremely long execution times.</p>
+ <p>
+ Own Id: OTP-12891</p>
+ </item>
+ <item>
+ <p>
+ Match acceptable peer addresses case insensitively.</p>
+ <p>
+ Regular expressions passed in an 'accept' tuple to
+ diameter_tcp or diameter_sctp inappropriately matched
+ case.</p>
+ <p>
+ Own Id: OTP-12902</p>
+ </item>
+ <item>
+ <p>
+ Improve watchdog and statistics performance.</p>
+ <p>
+ Inefficient use of timers contributed to poor performance
+ at high load, as did ordering of the table statistics are
+ written to.</p>
+ <p>
+ Own Id: OTP-12912</p>
+ </item>
+ </list>
+ </section>
+
+<!-- ===================================================================== -->
+
<section><title>diameter 1.10</title>
<section><title>Fixed Bugs and Malfunctions</title>
@@ -134,7 +193,7 @@ first.</p>
<item>
<p>
Change license text from Erlang Public License to Apache
- Public License v2</p>
+ Public License v2.</p>
<p>
Own Id: OTP-12845</p>
</item>
diff --git a/lib/diameter/include/diameter_gen.hrl b/lib/diameter/include/diameter_gen.hrl
index a43ab4edb2..5624ee6626 100644
--- a/lib/diameter/include/diameter_gen.hrl
+++ b/lib/diameter/include/diameter_gen.hrl
@@ -186,9 +186,10 @@ decode_avps(Name, Recs) ->
= lists:foldl(fun(T,A) -> decode(Name, T, A) end,
{[], {newrec(Name), []}},
Recs),
- {Rec, Avps, Failed ++ missing(Rec, Name)}.
-%% Append 5005 errors so that a 5014 for the same AVP will take
-%% precedence in a Result-Code/Failed-AVP setting.
+ {Rec, Avps, Failed ++ missing(Rec, Name, Failed)}.
+%% Append 5005 errors so that errors are reported in the order
+%% encountered. Failed-AVP should typically contain the first
+%% encountered error accordg to the RFC.
newrec(Name) ->
'#new-'(name2rec(Name)).
@@ -201,20 +202,36 @@ newrec(Name) ->
%% Failed-AVP AVP SHOULD be included in the message. The Failed-AVP
%% AVP MUST contain an example of the missing AVP complete with the
%% Vendor-Id if applicable. The value field of the missing AVP
-%% should be of correct minimum length and contain zeroes.
-
-missing(Rec, Name) ->
- [{5005, empty_avp(F)} || F <- '#info-'(element(1, Rec), fields),
- A <- [avp_arity(Name, F)],
- false <- [have_arity(A, '#get-'(F, Rec))]].
+%% should be of correct minimum length and contain zeros.
+
+missing(Rec, Name, Failed) ->
+ Avps = lists:foldl(fun({_, #diameter_avp{code = C, vendor_id = V}}, A) ->
+ sets:add_element({C,V}, A)
+ end,
+ sets:new(),
+ Failed),
+ [{5005, A} || F <- '#info-'(element(1, Rec), fields),
+ not has_arity(avp_arity(Name, F), '#get-'(F, Rec)),
+ #diameter_avp{code = C, vendor_id = V}
+ = A <- [empty_avp(F)],
+ not sets:is_element({C,V}, Avps)].
%% Maximum arities have already been checked in building the record.
-have_arity({Min, _}, L) ->
- Min =< length(L);
-have_arity(N, V) ->
+has_arity({Min, _}, L) ->
+ has_prefix(Min, L);
+has_arity(N, V) ->
N /= 1 orelse V /= undefined.
+%% Compare a non-negative integer and the length of a list without
+%% computing the length.
+has_prefix(0, _) ->
+ true;
+has_prefix(_, []) ->
+ false;
+has_prefix(N, L) ->
+ has_prefix(N-1, tl(L)).
+
%% empty_avp/1
empty_avp(Name) ->
@@ -608,14 +625,17 @@ pack(undefined, 1, FieldName, Avp, Acc) ->
%% AVP MUST be included and contain a copy of the first instance of
%% the offending AVP that exceeded the maximum number of occurrences
%%
+
pack(_, 1, _, Avp, {Rec, Failed}) ->
{Rec, [{5009, Avp} | Failed]};
-pack(L, {_, Max}, _, Avp, {Rec, Failed})
- when length(L) == Max ->
- {Rec, [{5009, Avp} | Failed]};
-
-pack(L, _, FieldName, Avp, Acc) ->
- p(FieldName, fun(V) -> [V|L] end, Avp, Acc).
+pack(L, {_, Max}, FieldName, Avp, Acc) ->
+ case '*' /= Max andalso has_prefix(Max, L) of
+ true ->
+ {Rec, Failed} = Acc,
+ {Rec, [{5009, Avp} | Failed]};
+ false ->
+ p(FieldName, fun(V) -> [V|L] end, Avp, Acc)
+ end.
%% p/4
diff --git a/lib/diameter/src/base/diameter_codec.erl b/lib/diameter/src/base/diameter_codec.erl
index 34276a1674..bcdc5b3005 100644
--- a/lib/diameter/src/base/diameter_codec.erl
+++ b/lib/diameter/src/base/diameter_codec.erl
@@ -657,16 +657,23 @@ split_data(Bin, Len) ->
%% The normal case here is data as an #diameter_avp{} list or an
%% iolist, which are the cases that generated codec modules use. The
-%% other case is as a convenience in the relay case in which the
+%% other cases are a convenience in the relay case in which the
%% dictionary doesn't know about specific AVP's.
-%% Grouped AVP whose components need packing ...
-pack_avp([#diameter_avp{} = A | Avps]) ->
- pack_avp(A#diameter_avp{data = Avps});
-pack_avp(#diameter_avp{data = [#diameter_avp{} | _] = Avps} = A) ->
- pack_avp(A#diameter_avp{data = encode_avps(Avps)});
+%% Decoded Grouped AVP with decoded components: ignore components
+%% since they're already encoded in the Grouped AVP.
+pack_avp([#diameter_avp{} = Grouped | _Components]) ->
+ pack_avp(Grouped);
-%% ... data as a type/value tuple ...
+%% Grouped AVP whose components need packing. It's intentional that
+%% this isn't equivalent to [Grouped | Components]: here the
+%% components need to be encoded before wrapping with the Grouped AVP,
+%% and the list is flat, nesting being accomplished in the data
+%% fields.
+pack_avp(#diameter_avp{data = [#diameter_avp{} | _] = Components} = Grouped) ->
+ pack_avp(Grouped#diameter_avp{data = encode_avps(Components)});
+
+%% Data as a type/value tuple ...
pack_avp(#diameter_avp{data = {Type, Value}} = A)
when is_atom(Type) ->
pack_avp(A#diameter_avp{data = diameter_types:Type(encode, Value)});
diff --git a/lib/diameter/src/base/diameter_config.erl b/lib/diameter/src/base/diameter_config.erl
index 5ae4ff1f46..b7d8345b6c 100644
--- a/lib/diameter/src/base/diameter_config.erl
+++ b/lib/diameter/src/base/diameter_config.erl
@@ -38,8 +38,7 @@
-module(diameter_config).
-behaviour(gen_server).
--compile({no_auto_import, [monitor/2, now/0]}).
--import(diameter_lib, [now/0]).
+-compile({no_auto_import, [monitor/2]}).
-export([start_service/2,
stop_service/1,
@@ -70,7 +69,7 @@
-include("diameter_internal.hrl").
%% Server state.
--record(state, {id = now()}).
+-record(state, {id = diameter_lib:now()}).
%% Registered name of the server.
-define(SERVER, ?MODULE).
diff --git a/lib/diameter/src/base/diameter_lib.erl b/lib/diameter/src/base/diameter_lib.erl
index b5b37dcf62..43b0ca24ab 100644
--- a/lib/diameter/src/base/diameter_lib.erl
+++ b/lib/diameter/src/base/diameter_lib.erl
@@ -103,32 +103,18 @@ fmt(T) ->
%% # now/0
%% ---------------------------------------------------------------------------
--type timestamp() :: {non_neg_integer(), 0..999999, 0..999999}.
--type now() :: integer() %% monotonic time
- | timestamp().
-
-spec now()
- -> now().
-
-%% Use monotonic time if it exists, fall back to erlang:now()
-%% otherwise.
+ -> integer().
now() ->
- try
- erlang:monotonic_time()
- catch
- error: undef -> erlang:now()
- end.
+ erlang:monotonic_time().
%% ---------------------------------------------------------------------------
%% # timestamp/1
%% ---------------------------------------------------------------------------
--spec timestamp(NowT :: now())
- -> timestamp().
-
-timestamp({_,_,_} = T) -> %% erlang:now()
- T;
+-spec timestamp(integer())
+ -> erlang:timestamp().
timestamp(MonoT) -> %% monotonic time
MicroSecs = monotonic_to_microseconds(MonoT + erlang:time_offset()),
@@ -142,30 +128,27 @@ monotonic_to_microseconds(MonoT) ->
%% # now_diff/1
%% ---------------------------------------------------------------------------
--spec now_diff(NowT :: now())
+-spec now_diff(T0 :: integer())
-> {Hours, Mins, Secs, MicroSecs}
when Hours :: non_neg_integer(),
Mins :: 0..59,
Secs :: 0..59,
MicroSecs :: 0..999999.
-%% Return timer:now_diff(now(), NowT) as an {H, M, S, MicroS} tuple
-%% instead of as integer microseconds.
+%% Return time difference as an {H, M, S, MicroS} tuple instead of as
+%% integer microseconds.
-now_diff(Time) ->
- time(micro_diff(Time)).
+now_diff(T0) ->
+ time(micro_diff(T0)).
%% ---------------------------------------------------------------------------
%% # micro_diff/1
%% ---------------------------------------------------------------------------
--spec micro_diff(NowT :: now())
+-spec micro_diff(T0 :: integer())
-> MicroSecs
when MicroSecs :: non_neg_integer().
-micro_diff({_,_,_} = T0) ->
- timer:now_diff(erlang:now(), T0);
-
micro_diff(T0) -> %% monotonic time
monotonic_to_microseconds(erlang:monotonic_time() - T0).
@@ -173,16 +156,12 @@ micro_diff(T0) -> %% monotonic time
%% # micro_diff/2
%% ---------------------------------------------------------------------------
--spec micro_diff(T1 :: now(), T0 :: now())
+-spec micro_diff(T1 :: integer(), T0 :: integer())
-> MicroSecs
when MicroSecs :: non_neg_integer().
-micro_diff(T1, T0)
- when is_integer(T1), is_integer(T0) -> %% monotonic time
- monotonic_to_microseconds(T1 - T0);
-
-micro_diff(T1, T0) -> %% at least one erlang:now()
- timer:now_diff(timestamp(T1), timestamp(T0)).
+micro_diff(T1, T0) -> %% monotonic time
+ monotonic_to_microseconds(T1 - T0).
%% ---------------------------------------------------------------------------
%% # time/1
@@ -190,19 +169,13 @@ micro_diff(T1, T0) -> %% at least one erlang:now()
%% Return an elapsed time as an {H, M, S, MicroS} tuple.
%% ---------------------------------------------------------------------------
--spec time(NowT | Diff)
+-spec time(Diff :: non_neg_integer())
-> {Hours, Mins, Secs, MicroSecs}
- when NowT :: timestamp(),
- Diff :: non_neg_integer(),
- Hours :: non_neg_integer(),
+ when Hours :: non_neg_integer(),
Mins :: 0..59,
Secs :: 0..59,
MicroSecs :: 0..999999.
-time({_,_,_} = NowT) -> %% time of day
- %% 24 hours = 24*60*60*1000000 = 86400000000 microsec
- time(timer:now_diff(NowT, {0,0,0}) rem 86400000000);
-
time(Micro) -> %% elapsed time
Seconds = Micro div 1000000,
H = Seconds div 3600,
@@ -215,7 +188,7 @@ time(Micro) -> %% elapsed time
%% ---------------------------------------------------------------------------
-spec seed()
- -> {timestamp(), {integer(), integer(), integer()}}.
+ -> {erlang:timestamp(), {integer(), integer(), integer()}}.
%% Return an argument for random:seed/1.
@@ -225,9 +198,6 @@ seed() ->
%% seed/1
-seed({_,_,_} = T) ->
- T;
-
seed(T) -> %% monotonic time
{erlang:phash2(node()), T, erlang:unique_integer()}.
diff --git a/lib/diameter/src/base/diameter_peer.erl b/lib/diameter/src/base/diameter_peer.erl
index acec91c43f..2759f17e64 100644
--- a/lib/diameter/src/base/diameter_peer.erl
+++ b/lib/diameter/src/base/diameter_peer.erl
@@ -21,9 +21,6 @@
-module(diameter_peer).
-behaviour(gen_server).
--compile({no_auto_import, [now/0]}).
--import(diameter_lib, [now/0]).
-
%% Interface towards transport modules ...
-export([recv/2,
up/1,
@@ -60,7 +57,7 @@
-define(SERVER, ?MODULE).
%% Server state.
--record(state, {id = now()}).
+-record(state, {id = diameter_lib:now()}).
%% Default transport_module/config.
-define(DEFAULT_TMOD, diameter_tcp).
diff --git a/lib/diameter/src/base/diameter_peer_fsm.erl b/lib/diameter/src/base/diameter_peer_fsm.erl
index f5e04d3eae..2b23183d18 100644
--- a/lib/diameter/src/base/diameter_peer_fsm.erl
+++ b/lib/diameter/src/base/diameter_peer_fsm.erl
@@ -315,7 +315,7 @@ handle_info(T, #state{} = State) ->
?LOG(stop, Reason),
{stop, {shutdown, Reason}, State};
stop ->
- ?LOG(stop, T),
+ ?LOG(stop, truncate(T)),
{stop, {shutdown, T}, State}
catch
exit: {diameter_codec, encode, T} = Reason ->
@@ -348,6 +348,11 @@ code_change(_, State, _) ->
%% ---------------------------------------------------------------------------
%% ---------------------------------------------------------------------------
+truncate({'DOWN' = T, _, process, Pid, _}) ->
+ {T, Pid};
+truncate(T) ->
+ T.
+
putr(Key, Val) ->
put({?MODULE, Key}, Val).
diff --git a/lib/diameter/src/base/diameter_reg.erl b/lib/diameter/src/base/diameter_reg.erl
index ce29baae45..7f198080ba 100644
--- a/lib/diameter/src/base/diameter_reg.erl
+++ b/lib/diameter/src/base/diameter_reg.erl
@@ -25,8 +25,7 @@
-module(diameter_reg).
-behaviour(gen_server).
--compile({no_auto_import, [monitor/2, now/0]}).
--import(diameter_lib, [now/0]).
+-compile({no_auto_import, [monitor/2]}).
-export([add/1,
add_new/1,
@@ -68,7 +67,7 @@
%% Table entry containing the Term -> Pid mapping.
-define(MAPPING(Term, Pid), {Term, Pid}).
--record(state, {id = now(),
+-record(state, {id = diameter_lib:now(),
q = []}). %% [{From, Pat}]
%% ===========================================================================
diff --git a/lib/diameter/src/base/diameter_service.erl b/lib/diameter/src/base/diameter_service.erl
index 5cb7fa5abe..e47b975768 100644
--- a/lib/diameter/src/base/diameter_service.erl
+++ b/lib/diameter/src/base/diameter_service.erl
@@ -25,9 +25,6 @@
-module(diameter_service).
-behaviour(gen_server).
--compile({no_auto_import, [now/0]}).
--import(diameter_lib, [now/0]).
-
%% towards diameter_service_sup
-export([start_link/1]).
@@ -115,7 +112,7 @@
%% to determine whether or not we need to call the process for a
%% pick_peer callback in the statefull case.
-record(state,
- {id = now(),
+ {id = diameter_lib:now(),
service_name :: diameter:service_name(), %% key in ?STATE_TABLE
service :: #diameter_service{},
watchdogT = ets_new(watchdogs) %% #watchdog{} at start
@@ -144,7 +141,7 @@
ref :: match(reference()), %% key into diameter_config
options :: match([diameter:transport_opt()]),%% from start_transport
state = ?WD_INITIAL :: match(wd_state()),
- started = now(), %% at process start
+ started = diameter_lib:now(),%% at process start
peer = false :: match(boolean() | pid())}).
%% true at accepted, pid() at okay/reopen
@@ -154,7 +151,7 @@
{pid :: pid(),
apps :: [{0..16#FFFFFFFF, diameter:app_alias()}], %% {Id, Alias}
caps :: #diameter_caps{},
- started = now(), %% at process start
+ started = diameter_lib:now(), %% at process start
watchdog :: pid()}). %% key into watchdogT
%% ---------------------------------------------------------------------------
diff --git a/lib/diameter/src/base/diameter_stats.erl b/lib/diameter/src/base/diameter_stats.erl
index 83e562e7fe..8c10464e98 100644
--- a/lib/diameter/src/base/diameter_stats.erl
+++ b/lib/diameter/src/base/diameter_stats.erl
@@ -25,9 +25,6 @@
-module(diameter_stats).
-behaviour(gen_server).
--compile({no_auto_import, [now/0]}).
--import(diameter_lib, [now/0]).
-
-export([reg/2, reg/1,
incr/3, incr/1,
read/1,
@@ -61,7 +58,7 @@
-define(SERVER, ?MODULE).
%% Server state.
--record(state, {id = now()}).
+-record(state, {id = diameter_lib:now()}).
-type counter() :: any().
-type ref() :: any().
@@ -143,9 +140,14 @@ read(Refs, B) ->
L.
to_refdict(L) ->
- lists:foldl(fun({{C,R}, N}, D) -> orddict:append(R, {C,N}, D) end,
- orddict:new(),
- L).
+ lists:foldl(fun append/2, orddict:new(), L).
+
+%% Order both references and counters in the returned list.
+append({{Ctr, Ref}, N}, Dict) ->
+ orddict:update(Ref,
+ fun(D) -> orddict:store(Ctr, N, D) end,
+ [{Ctr, N}],
+ Dict).
%% ---------------------------------------------------------------------------
%% # sum(Refs)
@@ -221,7 +223,7 @@ uptime() ->
%% ----------------------------------------------------------
init([]) ->
- ets:new(?TABLE, [named_table, ordered_set, public]),
+ ets:new(?TABLE, [named_table, set, public, {write_concurrency, true}]),
{ok, #state{}}.
%% ----------------------------------------------------------
diff --git a/lib/diameter/src/base/diameter_sync.erl b/lib/diameter/src/base/diameter_sync.erl
index 3309224399..7fb6888e21 100644
--- a/lib/diameter/src/base/diameter_sync.erl
+++ b/lib/diameter/src/base/diameter_sync.erl
@@ -28,9 +28,6 @@
-module(diameter_sync).
-behaviour(gen_server).
--compile({no_auto_import, [now/0]}).
--import(diameter_lib, [now/0]).
-
-export([call/4, call/5,
cast/4, cast/5,
carp/1, carp/2]).
@@ -73,7 +70,7 @@
%% Server state.
-record(state,
- {time = now(),
+ {time = diameter_lib:now(),
pending = 0 :: non_neg_integer(), %% outstanding requests
monitor = new() :: ets:tid(), %% MonitorRef -> {Name, From}
queue = new() :: ets:tid()}). %% Name -> queue of {Pid, Ref}
diff --git a/lib/diameter/src/base/diameter_watchdog.erl b/lib/diameter/src/base/diameter_watchdog.erl
index c6d0a0b6ed..3c0d8f6f6e 100644
--- a/lib/diameter/src/base/diameter_watchdog.erl
+++ b/lib/diameter/src/base/diameter_watchdog.erl
@@ -66,7 +66,9 @@
%% end PCB
parent = self() :: pid(), %% service process
transport :: pid() | undefined, %% peer_fsm process
- tref :: reference(), %% reference for current watchdog timer
+ tref :: reference() %% reference for current watchdog timer
+ | integer() %% monotonic time
+ | undefined,
dictionary :: module(), %% common dictionary
receive_data :: term(),
%% term passed into diameter_service with incoming message
@@ -94,7 +96,7 @@ start({_,_} = Type, T) ->
Ack = make_ref(),
{ok, Pid} = diameter_watchdog_sup:start_child({Ack, Type, self(), T}),
try
- {erlang:monitor(process, Pid), Pid}
+ {monitor(process, Pid), Pid}
after
send(Pid, Ack)
end.
@@ -121,7 +123,7 @@ i({Ack, T, Pid, {RecvData,
#diameter_service{applications = Apps,
capabilities = Caps}
= Svc}}) ->
- erlang:monitor(process, Pid),
+ monitor(process, Pid),
wait(Ack, Pid),
{_, Seed} = diameter_lib:seed(),
random:seed(Seed),
@@ -246,11 +248,16 @@ handle_info(T, #watchdog{} = State) ->
event(T, State, S), %% before 'watchdog'
{noreply, S};
stop ->
- ?LOG(stop, T),
+ ?LOG(stop, truncate(T)),
event(T, State, State#watchdog{status = down}),
{stop, {shutdown, T}, State}
end.
+truncate({'DOWN' = T, _, process, Pid, _}) ->
+ {T, Pid};
+truncate(T) ->
+ T.
+
close({'DOWN', _, process, TPid, {shutdown, Reason}},
#watchdog{transport = TPid,
parent = Pid}) ->
@@ -447,11 +454,12 @@ transition({recv, TPid, Name, Pkt}, #watchdog{transport = TPid} = S) ->
%% Current watchdog has timed out.
transition({timeout, TRef, tw}, #watchdog{tref = TRef} = S) ->
- set_watchdog(timeout(S));
+ set_watchdog(0, timeout(S));
-%% Timer was canceled after message was already sent.
-transition({timeout, _, tw}, #watchdog{}) ->
- ok;
+%% Message has arrived since the timer was started: subtract time
+%% already elapsed from new timer.
+transition({timeout, _, tw}, #watchdog{tref = T0} = S) ->
+ set_watchdog(diameter_lib:micro_diff(T0) div 1000, S);
%% State query.
transition({state, Pid}, #watchdog{status = S}) ->
@@ -527,18 +535,27 @@ role() ->
%% set_watchdog/1
-set_watchdog(#watchdog{tw = TwInit,
- tref = TRef}
- = S) ->
- cancel(TRef),
- S#watchdog{tref = erlang:start_timer(tw(TwInit), self(), tw)};
+%% Timer not yet set.
+set_watchdog(#watchdog{tref = undefined} = S) ->
+ set_watchdog(0, S);
+
+%% Timer already set: start at new one only at expiry.
+set_watchdog(#watchdog{} = S) ->
+ S#watchdog{tref = diameter_lib:now()};
+
set_watchdog(stop = No) ->
No.
-cancel(undefined) ->
- ok;
-cancel(TRef) ->
- erlang:cancel_timer(TRef).
+%% set_watchdog/2
+
+set_watchdog(Ms, #watchdog{tw = TwInit} = S) ->
+ S#watchdog{tref = erlang:start_timer(tw(TwInit, Ms), self(), tw)}.
+
+%% A callback could return anything, so ensure the result isn't
+%% negative. Don't prevent abuse, even though the smallest valid
+%% timeout is 4000.
+tw(TwInit, Ms) ->
+ max(tw(TwInit) - Ms, 0).
tw(T)
when is_integer(T), T >= 6000 ->
diff --git a/lib/diameter/src/diameter.appup.src b/lib/diameter/src/diameter.appup.src
index 86d231179c..788ea790fa 100644
--- a/lib/diameter/src/diameter.appup.src
+++ b/lib/diameter/src/diameter.appup.src
@@ -42,7 +42,23 @@
{"1.8", [{restart_application, diameter}]}, %% 17.4
{"1.9", [{restart_application, diameter}]}, %% 17.5
{"1.9.1", [{restart_application, diameter}]}, %% 17.5.3
- {"1.9.2", [{restart_application, diameter}]} %% 17.5.5
+ {"1.9.2", [{restart_application, diameter}]}, %% 17.5.5
+ {"1.9.2.1", [{restart_application, diameter}]}, %% 17.5.6.3
+ {"1.10", [{load_module, diameter_codec}, %% 18.0
+ {load_module, diameter_peer_fsm},
+ {load_module, diameter_watchdog},
+ {load_module, diameter_stats},
+ {load_module, diameter_config},
+ {load_module, diameter_lib},
+ {load_module, diameter_peer},
+ {load_module, diameter_reg},
+ {load_module, diameter_service},
+ {load_module, diameter_sync},
+ {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}]},
@@ -66,6 +82,22 @@
{"1.8", [{restart_application, diameter}]},
{"1.9", [{restart_application, diameter}]},
{"1.9.1", [{restart_application, diameter}]},
- {"1.9.2", [{restart_application, diameter}]}
+ {"1.9.2", [{restart_application, diameter}]},
+ {"1.9.2.1", [{restart_application, diameter}]},
+ {"1.10", [{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_sync},
+ {load_module, diameter_service},
+ {load_module, diameter_reg},
+ {load_module, diameter_peer},
+ {load_module, diameter_lib},
+ {load_module, diameter_config},
+ {load_module, diameter_stats},
+ {load_module, diameter_watchdog},
+ {load_module, diameter_peer_fsm},
+ {load_module, diameter_codec}]}
]
}.
diff --git a/lib/diameter/src/info/diameter_info.erl b/lib/diameter/src/info/diameter_info.erl
index 2e08662a9e..59a3b94ee4 100644
--- a/lib/diameter/src/info/diameter_info.erl
+++ b/lib/diameter/src/info/diameter_info.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2014. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2015. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -52,8 +52,6 @@
p/1,
p/3]).
--compile({no_auto_import,[max/2]}).
-
-export([collect/2]).
-define(LONG_TIMEOUT, 30000).
@@ -684,9 +682,6 @@ pt(T) ->
recsplit(SFun, Rec) ->
fun(Fs,Vs) -> SFun(element(1, Rec), Fs, Vs) end.
-max(A, B) ->
- if A > B -> A; true -> B end.
-
keyfetch(Key, List) ->
{Key,V} = lists:keyfind(Key, 1, List),
V.
diff --git a/lib/diameter/src/transport/diameter_sctp.erl b/lib/diameter/src/transport/diameter_sctp.erl
index 3e08b78ea1..678dc9b5d6 100644
--- a/lib/diameter/src/transport/diameter_sctp.erl
+++ b/lib/diameter/src/transport/diameter_sctp.erl
@@ -21,9 +21,6 @@
-module(diameter_sctp).
-behaviour(gen_server).
--compile({no_auto_import, [now/0]}).
--import(diameter_lib, [now/0]).
-
%% interface
-export([start/3]).
diff --git a/lib/diameter/test/diameter_examples_SUITE.erl b/lib/diameter/test/diameter_examples_SUITE.erl
index 2c10daec50..e4ed2b227d 100644
--- a/lib/diameter/test/diameter_examples_SUITE.erl
+++ b/lib/diameter/test/diameter_examples_SUITE.erl
@@ -299,7 +299,7 @@ slave(_) ->
T0 = diameter_lib:now(),
{ok, Node} = ct_slave:start(?MODULE, ?TIMEOUTS),
T1 = diameter_lib:now(),
- T2 = rpc:call(Node, erlang, now, []),
+ T2 = rpc:call(Node, diameter_lib, now, []),
{ok, Node} = ct_slave:stop(?MODULE),
now_diff([T0, T1, T2, diameter_lib:now()]).
diff --git a/lib/diameter/test/diameter_gen_sctp_SUITE.erl b/lib/diameter/test/diameter_gen_sctp_SUITE.erl
index c739643dbe..cbd7fc8ec5 100644
--- a/lib/diameter/test/diameter_gen_sctp_SUITE.erl
+++ b/lib/diameter/test/diameter_gen_sctp_SUITE.erl
@@ -300,10 +300,10 @@ connect2(Pid, PortNr, Bin) ->
%% T2 = time after listening process received our message
%% T3 = time after reply is received
- T1 = diameter_util:timestamp(),
+ T1 = diameter_lib:now(),
ok = send(Sock, Id, Bin),
T2 = unmark(recv(Sock, Id)),
- T3 = diameter_util:timestamp(),
+ T3 = diameter_lib:now(),
{diameter_lib:micro_diff(T2, T1), %% Outbound
diameter_lib:micro_diff(T3, T2)}. %% Inbound
@@ -330,13 +330,13 @@ send(Sock, Id, Bin) ->
%% mark/1
mark(Bin) ->
- Info = term_to_binary(diameter_util:timestamp()),
+ Info = term_to_binary(diameter_lib:now()),
<<Info/binary, Bin/binary>>.
%% unmark/1
unmark(Bin) ->
- {_,_,_} = binary_to_term(Bin).
+ binary_to_term(Bin).
%% ===========================================================================
diff --git a/lib/diameter/test/diameter_relay_SUITE.erl b/lib/diameter/test/diameter_relay_SUITE.erl
index fd83c2cd7f..f766f54a80 100644
--- a/lib/diameter/test/diameter_relay_SUITE.erl
+++ b/lib/diameter/test/diameter_relay_SUITE.erl
@@ -334,13 +334,39 @@ realm(Host) ->
call(Server) ->
Realm = realm(Server),
+ %% Include some arbitrary AVPs to exercise encode/decode, that
+ %% are received back in the STA.
+ Avps = [#diameter_avp{code = 111,
+ data = [#diameter_avp{code = 222,
+ data = <<222:24>>},
+ #diameter_avp{code = 333,
+ data = <<333:16>>}]},
+ #diameter_avp{code = 444,
+ data = <<444:24>>},
+ #diameter_avp{code = 555,
+ data = [#diameter_avp{code = 666,
+ data = [#diameter_avp
+ {code = 777,
+ data = <<7>>}]},
+ #diameter_avp{code = 888,
+ data = <<8>>},
+ #diameter_avp{code = 999,
+ data = <<9>>}]}],
+
Req = ['STR', {'Destination-Realm', Realm},
{'Destination-Host', [Server]},
{'Termination-Cause', ?LOGOUT},
- {'Auth-Application-Id', ?APP_ID}],
+ {'Auth-Application-Id', ?APP_ID},
+ {'AVP', Avps}],
+
#diameter_base_STA{'Result-Code' = ?SUCCESS,
'Origin-Host' = Server,
- 'Origin-Realm' = Realm}
+ 'Origin-Realm' = Realm,
+ %% Unknown AVPs can't be decoded as Grouped since
+ %% types aren't known.
+ 'AVP' = [#diameter_avp{code = 111},
+ #diameter_avp{code = 444},
+ #diameter_avp{code = 555}]}
= call(Req, [{filter, realm}]).
call(Req, Opts) ->
@@ -434,9 +460,18 @@ request(_Pkt, #diameter_caps{origin_host = {OH, _}})
request(#diameter_packet{msg = #diameter_base_STR{'Session-Id' = SId,
'Origin-Host' = Host,
'Origin-Realm' = Realm,
- 'Route-Record' = Route}},
+ 'Route-Record' = Route,
+ 'AVP' = Avps}},
#diameter_caps{origin_host = {OH, _},
origin_realm = {OR, _}}) ->
+
+ %% Payloads of unknown AVPs aren't decoded, so we don't know that
+ %% some types here are Grouped.
+ [#diameter_avp{code = 111, vendor_id = undefined},
+ #diameter_avp{code = 444, vendor_id = undefined, data = <<444:24>>},
+ #diameter_avp{code = 555, vendor_id = undefined}]
+ = Avps,
+
%% The request should have the Origin-Host/Realm of the original
%% sender.
R = realm(?CLIENT),
@@ -447,4 +482,5 @@ request(#diameter_packet{msg = #diameter_base_STR{'Session-Id' = SId,
{reply, #diameter_base_STA{'Result-Code' = ?SUCCESS,
'Session-Id' = SId,
'Origin-Host' = OH,
- 'Origin-Realm' = OR}}.
+ 'Origin-Realm' = OR,
+ 'AVP' = Avps}}.
diff --git a/lib/diameter/test/diameter_stats_SUITE.erl b/lib/diameter/test/diameter_stats_SUITE.erl
index 39cb6ae30d..4716082394 100644
--- a/lib/diameter/test/diameter_stats_SUITE.erl
+++ b/lib/diameter/test/diameter_stats_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2015. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -96,7 +96,7 @@ read(_) ->
7 = ?stat:incr(C1, Ref, 7),
Self = self(),
[{Ref, [{C1,7}]}, {Self, [{C1,2}, {C2,1}]}]
- = lists:sort(?stat:read([self(), Ref, make_ref()])),
+ = ?stat:read([self(), Ref, make_ref()]),
[] = ?stat:read([]),
[] = ?stat:read([make_ref()]),
?stat:flush([self(), Ref, make_ref()]).
@@ -116,7 +116,7 @@ sum(_) ->
[{Self, [{C1,1}, {C2,2}]}]
= ?stat:sum([self()]),
[{Ref, [{C1,7}]}, {Self, [{C1,1}, {C2,2}]}]
- = lists:sort(?stat:flush([self(), Ref])).
+ = ?stat:flush([self(), Ref]).
flush(_) ->
Ref = make_ref(),
diff --git a/lib/diameter/test/diameter_transport_SUITE.erl b/lib/diameter/test/diameter_transport_SUITE.erl
index 701fe070d0..53d2d6660e 100644
--- a/lib/diameter/test/diameter_transport_SUITE.erl
+++ b/lib/diameter/test/diameter_transport_SUITE.erl
@@ -54,7 +54,7 @@
%% Receive a message.
-define(RECV(Pat, Ret), receive Pat -> Ret end).
--define(RECV(Pat), ?RECV(Pat, diameter_util:timestamp())).
+-define(RECV(Pat), ?RECV(Pat, diameter_lib:now())).
%% Sockets are opened on the loopback address.
-define(ADDR, {127,0,0,1}).
@@ -417,8 +417,7 @@ gen_accept(tcp, LSock) ->
gen_send(sctp, Sock, Bin) ->
{OS, _IS, Id} = getr(assoc),
- {_, _, Us} = diameter_util:timestamp(),
- gen_sctp:send(Sock, Id, Us rem OS, Bin);
+ gen_sctp:send(Sock, Id, erlang:unique_integer([positive]) rem OS, Bin);
gen_send(tcp, Sock, Bin) ->
gen_tcp:send(Sock, Bin).
diff --git a/lib/diameter/test/diameter_util.erl b/lib/diameter/test/diameter_util.erl
index c727d10ddf..52b747e99c 100644
--- a/lib/diameter/test/diameter_util.erl
+++ b/lib/diameter/test/diameter_util.erl
@@ -31,7 +31,6 @@
fold/3,
foldl/3,
scramble/1,
- timestamp/0,
seed/0,
unique_string/0,
have_sctp/0]).
@@ -189,12 +188,6 @@ s(Acc, L) ->
s([T|Acc], H ++ Rest).
%% ---------------------------------------------------------------------------
-%% timestamp/0
-
-timestamp() ->
- diameter_lib:timestamp(diameter_lib:now()).
-
-%% ---------------------------------------------------------------------------
%% seed/0
seed() ->
@@ -205,14 +198,7 @@ seed() ->
%% unique_string/0
unique_string() ->
- try erlang:unique_integer() of
- N ->
- integer_to_list(N)
- catch
- error: undef -> %% OTP < 18
- {M,S,U} = timestamp(),
- tl(lists:append(["-" ++ integer_to_list(N) || N <- [M,S,U]]))
- end.
+ integer_to_list(erlang:unique_integer()).
%% ---------------------------------------------------------------------------
%% have_sctp/0
diff --git a/lib/diameter/test/diameter_watchdog_SUITE.erl b/lib/diameter/test/diameter_watchdog_SUITE.erl
index 016801383b..6d22ddcc18 100644
--- a/lib/diameter/test/diameter_watchdog_SUITE.erl
+++ b/lib/diameter/test/diameter_watchdog_SUITE.erl
@@ -49,7 +49,8 @@
accept/1,
connect/3,
send/2,
- setopts/2]).
+ setopts/2,
+ close/1]).
-include("diameter.hrl").
-include("diameter_ct.hrl").
@@ -421,7 +422,6 @@ suspect(TRef, false, SvcName, N) ->
%% abuse/1
abuse(F) ->
-
[] = run([[abuse, F, T] || T <- [listen, connect]]).
abuse(F, [_,_,_|_] = Args) ->
@@ -546,6 +546,9 @@ setopts(Sock, Opts) ->
send(Sock, Bin) ->
send(getr(config), Sock, Bin).
+close(Sock) ->
+ gen_tcp:close(Sock).
+
%% send/3
%% First outgoing message from a new transport process is CER/CEA.
diff --git a/lib/diameter/vsn.mk b/lib/diameter/vsn.mk
index 1e3135680d..041d21b261 100644
--- a/lib/diameter/vsn.mk
+++ b/lib/diameter/vsn.mk
@@ -17,5 +17,5 @@
# %CopyrightEnd%
APPLICATION = diameter
-DIAMETER_VSN = 1.10
+DIAMETER_VSN = 1.11
APP_VSN = $(APPLICATION)-$(DIAMETER_VSN)$(PRE_VSN)
diff --git a/otp_versions.table b/otp_versions.table
index ff6c262743..2adb77bdb7 100644
--- a/otp_versions.table
+++ b/otp_versions.table
@@ -1,6 +1,7 @@
OTP-18.0.2 : erts-7.0.2 runtime_tools-1.9.1 # asn1-4.0 common_test-1.11 compiler-6.0 cosEvent-2.2 cosEventDomain-1.2 cosFileTransfer-1.2 cosNotification-1.2 cosProperty-1.2 cosTime-1.2 cosTransactions-1.3 crypto-3.6 debugger-4.1 dialyzer-2.8 diameter-1.10 edoc-0.7.17 eldap-1.2 erl_docgen-0.4 erl_interface-3.8 et-1.5.1 eunit-2.2.10 gs-1.6 hipe-3.12 ic-4.4 inets-6.0 jinterface-1.6 kernel-4.0 megaco-3.18 mnesia-4.13 observer-2.1 odbc-2.11 orber-3.8 os_mon-2.4 ose-1.1 otp_mibs-1.1 parsetools-2.1 percept-0.8.11 public_key-1.0 reltool-0.7 sasl-2.5 snmp-5.2 ssh-4.0 ssl-7.0 stdlib-2.5 syntax_tools-1.7 test_server-3.9 tools-2.8 typer-0.9.9 webtool-0.9 wx-1.4 xmerl-1.3.8 :
OTP-18.0.1 : erts-7.0.1 # asn1-4.0 common_test-1.11 compiler-6.0 cosEvent-2.2 cosEventDomain-1.2 cosFileTransfer-1.2 cosNotification-1.2 cosProperty-1.2 cosTime-1.2 cosTransactions-1.3 crypto-3.6 debugger-4.1 dialyzer-2.8 diameter-1.10 edoc-0.7.17 eldap-1.2 erl_docgen-0.4 erl_interface-3.8 et-1.5.1 eunit-2.2.10 gs-1.6 hipe-3.12 ic-4.4 inets-6.0 jinterface-1.6 kernel-4.0 megaco-3.18 mnesia-4.13 observer-2.1 odbc-2.11 orber-3.8 os_mon-2.4 ose-1.1 otp_mibs-1.1 parsetools-2.1 percept-0.8.11 public_key-1.0 reltool-0.7 runtime_tools-1.9 sasl-2.5 snmp-5.2 ssh-4.0 ssl-7.0 stdlib-2.5 syntax_tools-1.7 test_server-3.9 tools-2.8 typer-0.9.9 webtool-0.9 wx-1.4 xmerl-1.3.8 :
OTP-18.0 : asn1-4.0 common_test-1.11 compiler-6.0 cosEvent-2.2 cosEventDomain-1.2 cosFileTransfer-1.2 cosNotification-1.2 cosProperty-1.2 cosTime-1.2 cosTransactions-1.3 crypto-3.6 debugger-4.1 dialyzer-2.8 diameter-1.10 edoc-0.7.17 eldap-1.2 erl_docgen-0.4 erl_interface-3.8 erts-7.0 et-1.5.1 eunit-2.2.10 gs-1.6 hipe-3.12 ic-4.4 inets-6.0 jinterface-1.6 kernel-4.0 megaco-3.18 mnesia-4.13 observer-2.1 odbc-2.11 orber-3.8 os_mon-2.4 ose-1.1 otp_mibs-1.1 parsetools-2.1 percept-0.8.11 public_key-1.0 reltool-0.7 runtime_tools-1.9 sasl-2.5 snmp-5.2 ssh-4.0 ssl-7.0 stdlib-2.5 syntax_tools-1.7 test_server-3.9 tools-2.8 typer-0.9.9 webtool-0.9 wx-1.4 xmerl-1.3.8 # :
+OTP-17.5.6.3 : diameter-1.9.2.1 # asn1-3.0.4 common_test-1.10.1 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 erts-6.4.1.2 et-1.5 eunit-2.2.9 gs-1.5.16 hipe-3.11.3 ic-4.3.6 inets-5.10.9 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.1 sasl-2.4.1 snmp-5.1.2 ssh-3.2.4 ssl-6.0.1 stdlib-2.4 syntax_tools-1.6.18 test_server-3.8.1 tools-2.7.2 typer-0.9.8 webtool-0.8.10 wx-1.3.3 xmerl-1.3.7 :
OTP-17.5.6.2 : erts-6.4.1.2 runtime_tools-1.8.16.1 # asn1-3.0.4 common_test-1.10.1 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.2 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.9 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 sasl-2.4.1 snmp-5.1.2 ssh-3.2.4 ssl-6.0.1 stdlib-2.4 syntax_tools-1.6.18 test_server-3.8.1 tools-2.7.2 typer-0.9.8 webtool-0.8.10 wx-1.3.3 xmerl-1.3.7 :
OTP-17.5.6.1 : erts-6.4.1.1 # asn1-3.0.4 common_test-1.10.1 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.2 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.9 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.2 ssh-3.2.4 ssl-6.0.1 stdlib-2.4 syntax_tools-1.6.18 test_server-3.8.1 tools-2.7.2 typer-0.9.8 webtool-0.8.10 wx-1.3.3 xmerl-1.3.7 :
OTP-17.5.6 : inets-5.10.9 ssh-3.2.4 ssl-6.0.1 # asn1-3.0.4 common_test-1.10.1 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.2 edoc-0.7.16 eldap-1.1.1 erl_docgen-0.3.7 erl_interface-3.7.20 erts-6.4.1 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.2 stdlib-2.4 syntax_tools-1.6.18 test_server-3.8.1 tools-2.7.2 typer-0.9.8 webtool-0.8.10 wx-1.3.3 xmerl-1.3.7 :