aboutsummaryrefslogtreecommitdiffstats
path: root/lib/diameter
diff options
context:
space:
mode:
Diffstat (limited to 'lib/diameter')
-rw-r--r--lib/diameter/doc/src/diameter.xml14
-rw-r--r--lib/diameter/doc/src/diameter_make.xml2
-rw-r--r--lib/diameter/doc/src/notes.xml418
-rw-r--r--lib/diameter/examples/code/client.erl2
-rw-r--r--lib/diameter/examples/code/client_cb.erl14
-rw-r--r--lib/diameter/examples/code/peer.erl2
-rw-r--r--lib/diameter/examples/code/redirect_cb.erl8
-rw-r--r--lib/diameter/examples/code/relay_cb.erl8
-rw-r--r--lib/diameter/examples/code/server_cb.erl53
-rw-r--r--lib/diameter/include/diameter.hrl11
-rw-r--r--lib/diameter/include/diameter_gen.hrl187
-rw-r--r--lib/diameter/src/Makefile24
-rw-r--r--lib/diameter/src/app.sed40
-rw-r--r--lib/diameter/src/base/diameter_codec.erl224
-rw-r--r--lib/diameter/src/base/diameter_config.erl4
-rw-r--r--lib/diameter/src/base/diameter_lib.erl56
-rw-r--r--lib/diameter/src/base/diameter_peer_fsm.erl171
-rw-r--r--lib/diameter/src/base/diameter_service.erl216
-rw-r--r--lib/diameter/src/base/diameter_traffic.erl301
-rw-r--r--lib/diameter/src/base/diameter_watchdog.erl97
-rw-r--r--lib/diameter/src/compiler/diameter_codegen.erl41
-rw-r--r--lib/diameter/src/compiler/diameter_dict_util.erl9
-rw-r--r--lib/diameter/src/compiler/diameter_forms.hrl6
-rw-r--r--lib/diameter/src/compiler/diameter_make.erl32
-rw-r--r--lib/diameter/src/diameter.app.src26
-rw-r--r--lib/diameter/src/diameter.appup.src54
-rw-r--r--lib/diameter/src/info/diameter_dbg.erl (renamed from lib/diameter/src/base/diameter_dbg.erl)167
-rw-r--r--lib/diameter/src/info/diameter_info.erl (renamed from lib/diameter/src/base/diameter_info.erl)14
-rw-r--r--lib/diameter/src/modules.mk12
-rw-r--r--lib/diameter/src/transport/diameter_sctp.erl2
-rw-r--r--lib/diameter/test/diameter_app_SUITE.erl66
-rw-r--r--lib/diameter/test/diameter_codec_test.erl3
-rw-r--r--lib/diameter/test/diameter_compiler_SUITE.erl25
-rw-r--r--lib/diameter/test/diameter_config_SUITE.erl2
-rw-r--r--lib/diameter/test/diameter_dpr_SUITE.erl8
-rw-r--r--lib/diameter/test/diameter_event_SUITE.erl30
-rw-r--r--lib/diameter/test/diameter_examples_SUITE.erl85
-rw-r--r--lib/diameter/test/diameter_failover_SUITE.erl8
-rw-r--r--lib/diameter/test/diameter_traffic_SUITE.erl85
-rw-r--r--lib/diameter/test/diameter_transport_SUITE.erl2
-rw-r--r--lib/diameter/vsn.mk2
41 files changed, 1855 insertions, 676 deletions
diff --git a/lib/diameter/doc/src/diameter.xml b/lib/diameter/doc/src/diameter.xml
index 7d6a28e51c..00b54ffbc4 100644
--- a/lib/diameter/doc/src/diameter.xml
+++ b/lib/diameter/doc/src/diameter.xml
@@ -227,7 +227,7 @@ question is as if a callback had taken place and returned
<c>{error, failure}</c>.</p>
<p>
-Defaults to <c>report</c> if unspecified.</p>
+Defaults to <c>discard</c> if unspecified.</p>
</item>
<tag><c>{request_errors, answer_3xxx|answer|callback}</c></tag>
@@ -500,6 +500,18 @@ Matches only those peers matched by each filter in the specified list.</p>
<p>
Matches only those peers matched by at least one filter in the
specified list.</p>
+
+<p>
+The resulting peer list will be in match order, peers matching the
+first filter of the list sorting before those matched by the second,
+and so on.
+For example, the following filter causes peers matching both the host
+and realm filters to be presented before those matching only the realm
+filter.</p>
+
+<pre>
+{any, [{all, [host, realm]}, realm]}
+</pre>
</item>
</taglist>
diff --git a/lib/diameter/doc/src/diameter_make.xml b/lib/diameter/doc/src/diameter_make.xml
index 13ec5bbfc1..0c7e6b794d 100644
--- a/lib/diameter/doc/src/diameter_make.xml
+++ b/lib/diameter/doc/src/diameter_make.xml
@@ -52,7 +52,7 @@ under the License.
<p>
The function &codec; is used to compile a diameter
&dictionary; into Erlang source.
-The resulting source implements the interface diameter required
+The resulting source implements the interface diameter requires
to encode and decode the dictionary's messages and AVPs.</p>
<p>
diff --git a/lib/diameter/doc/src/notes.xml b/lib/diameter/doc/src/notes.xml
index cd14195e78..e6ac332c10 100644
--- a/lib/diameter/doc/src/notes.xml
+++ b/lib/diameter/doc/src/notes.xml
@@ -11,7 +11,7 @@
<header>
<copyright>
<year>2011</year>
-<year>2013</year>
+<year>2014</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -42,6 +42,414 @@ first.</p>
<!-- ===================================================================== -->
+<section><title>diameter 1.8</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix remote diameter_request table leak.</p>
+ <p>
+ An outgoing request whose pick_peer callback selected a
+ transport on another node resulted in an orphaned table
+ entry on that node.</p>
+ <p>
+ Own Id: OTP-12196</p>
+ </item>
+ <item>
+ <p>
+ Fix handling of 3xxx Result-Code without E-bit.</p>
+ <p>
+ OTP-12233 broke the population of the errors field of the
+ diameter_packet record when an incoming request with an
+ E-bit/Result-Code mismatch was detected, causing a
+ 4-tuple to be inserted as Result-Code in a diameter_avp
+ record.</p>
+ <p>
+ Own Id: OTP-12233</p>
+ </item>
+ <item>
+ <p>
+ Fix ignored connect timer.</p>
+ <p>
+ There are two timers governing the establishment of peer
+ connections: connect_timer and watchdog_timer. The former
+ is the RFC 6733 Tc timer, and is used at initial
+ connection establishment. The latter is RFC 3539 TwInit,
+ and is used for connection reestablishment. A connecting
+ transport erroneously used watchdog_timer in both cases.</p>
+ <p>
+ Own Id: OTP-12281 Aux Id: seq12728 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Order candidate peers in pick_peer callbacks.</p>
+ <p>
+ The order of candidate peers presented to a
+ diameter_app(3) pick_peer callback has previously not
+ been documented, but there are use cases that are
+ simplified by an ordering. The order is now determined by
+ the filter.</p>
+ <p>
+ Own Id: OTP-12308</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>diameter 1.7.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Don't leave extra bit in decoded AVP data.</p>
+ <p>
+ An extra bit could be communicated in the data field of a
+ diameter_avp record in the case of length errors. Of no
+ consequence for code using the record encoding of
+ Diameter messages, but code examining diameter_avp
+ records would see this bit.</p>
+ <p>
+ Dictionary files must be recompiled for the fix to have
+ effect.</p>
+ <p>
+ Own Id: OTP-12074</p>
+ </item>
+ <item>
+ <p>
+ Fix counting of outgoing requests and answers setting the
+ E-bit.</p>
+ <p>
+ OTP-11721 broke these counters for all outgoing requests
+ except DWR, and caused answers setting the E-bit to be
+ counted as unknown messages.</p>
+ <p>
+ Own Id: OTP-12080</p>
+ </item>
+ <item>
+ <p>
+ Fix Failed-AVP decode.</p>
+ <p>
+ The best-effort decode only worked for AVPs in the common
+ dictionary, not for those in the dictionary of the
+ application identified in the Diameter Header of the
+ answer message in question.</p>
+ <p>
+ Failed-AVP in an answer decoded with the RFC 3588 common
+ dictionary (diameter_gen_base_rfc3588) was regarded as an
+ error. The RFC 6733 dictionary was unaffected.</p>
+ <p>
+ Dictionary files must be recompiled for the fix to have
+ effect.</p>
+ <p>
+ Own Id: OTP-12094</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>diameter 1.7</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Improve robustness.</p>
+ <p>
+ Counters returned by diameter:service_info/2 now only
+ count messages known to the dictionary in question, so
+ that an attacker cannot cause arbitrarily many counters
+ to be created.</p>
+ <p>
+ Messages to the Erlang log have been minimized, and those
+ related to traffic have been removed entirely since an
+ attacker could cause a node to be logged to death.
+ Consequently, the default answer_errors configuration has
+ been changed from report to discard. A service needs to
+ be restarted for the change in default to take effect.</p>
+ <p>
+ Own Id: OTP-11721</p>
+ </item>
+ <item>
+ <p>
+ Fix request table leak.</p>
+ <p>
+ Outgoing Diameter requests are stored in a table until an
+ answer is received or times out. Calling
+ diameter:stop_service/1 before this took place would
+ orphan the entries, resulting in a memory leak.</p>
+ <p>
+ Own Id: OTP-11893</p>
+ </item>
+ <item>
+ <p>
+ Fix broken SCTP transport.</p>
+ <p>
+ OTP-11593 caused the sending of answer messages over SCTP
+ to fail.</p>
+ <p>
+ Own Id: OTP-11901 Aux Id: OTP-11593 </p>
+ </item>
+ <item>
+ <p>
+ Fix watchdog process leak.</p>
+ <p>
+ A failed capabilities exchange on a listening transport
+ would orphan a process, causing a memory leak.</p>
+ <p>
+ Own Id: OTP-11934</p>
+ </item>
+ <item>
+ <p>
+ Fix incorrect handling of incoming DPR.</p>
+ <p>
+ In the case of a listening transport, a reconnection by a
+ peer following DPR could transition the watchdog state to
+ REOPEN instead of OKAY.</p>
+ <p>
+ Own Id: OTP-11938</p>
+ </item>
+ <item>
+ <p>
+ Fix handling of AVP length errors on unknown AVPs.</p>
+ <p>
+ An AVP (Header) length that pointed past the end of the
+ message was not flagged as a 5014 error in this case.
+ Moreover, encoding such an AVP in the Failed-AVP of an
+ answer message as a consequence of other errors (eg.
+ M-bit, resulting in 5001) failed if the AVP contained a
+ complete header.</p>
+ <p>
+ Dictionary files must be recompiled for the fix to have
+ effect.</p>
+ <p>
+ Own Id: OTP-11946</p>
+ </item>
+ <item>
+ <p>
+ Fix broken check in dictionary compilation.</p>
+ <p>
+ That an AVP specified in the content of a @codecs or
+ @custom_types section was undefined went undetected,
+ causing compilation to fail when attempting to lookup the
+ AVP's type.</p>
+ <p>
+ Own Id: OTP-11958</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Add result code counters for CEA, DWA, and DPA.</p>
+ <p>
+ In addition to the existing result code counters on other
+ answer messages.</p>
+ <p>
+ Own Id: OTP-11891</p>
+ </item>
+ <item>
+ <p>
+ Add best-effort decode of AVPs within Failed-AVP.</p>
+ <p>
+ OTP-11007 disabled the decode of AVPs in Failed-AVP since
+ errors could cause the decode of Failed-AVP itself to
+ fail. Component AVPs are now decoded if possible,
+ otherwise not. AVPs of type Grouped are decoded as much
+ as possible, as deeply as possible.</p>
+ <p>
+ Dictionary files must be recompiled for the fix to have
+ effect.</p>
+ <p>
+ Own Id: OTP-11936 Aux Id: OTP-11007 </p>
+ </item>
+ <item>
+ <p>
+ Add counters for encode errors in outgoing Diameter
+ messages.</p>
+ <p>
+ In addition to the existing counters on decode errors.
+ The latter now count independently of result codes in
+ answer messages since decode errors do not preclude the
+ presence of a result code.</p>
+ <p>
+ Own Id: OTP-11937</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>diameter 1.6</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Add missing check at dictionary compilation.</p>
+ <p>
+ In particular, that an AVP defined as having type Grouped
+ in an @avp_types section has a corresponding definition
+ in a @grouped section.</p>
+ <p>
+ Own Id: OTP-11561</p>
+ </item>
+ <item>
+ <p>
+ Correct documentation on the setting of Origin-State-Id</p>
+ <p>
+ It was incorrectly stated that the AVP would be set in an
+ outgoing DPR/DPA.</p>
+ <p>
+ Own Id: OTP-11583</p>
+ </item>
+ <item>
+ <p>
+ Change interface for communicating outbound stream id to
+ diameter_sctp</p>
+ <p>
+ The module uses the transport_data field of record
+ diameter_packet to communicate the stream on which the an
+ incoming message is received and on which an outgoing
+ message should be sent, the previous interface being that
+ both are communicated as a tuple of the form {stream,
+ Id}. However, since diameter retains the value of an
+ incoming request's transport_data unless the
+ corresponding answer message specifies otherwise, the
+ behaviour in this case is to send an answer on the
+ outbound stream with the same identifier as the that of
+ the inbound stream on which the request was received. If
+ the inbound stream id is greater than or equal to the
+ number of outbound streams then this is guaranteed to
+ fail, causing the transport process in question to
+ terminate. There is no relationship between inbound and
+ outbound stream identifiers so diameter_sctp's imposition
+ of one is simply wrong.</p>
+ <p>
+ Outbound stream ids are now communicated with a different
+ tuple: {outstream, Id}, interpreted modulo the number of
+ outbound streams. Thus, retention of an inbound request's
+ transport_data has no effect on the selection of an
+ outbound stream.</p>
+ <p>
+ The change in interface is not strictly backwards
+ compatible because of the new atom for the outbound
+ stream. However, as there is currently no documented way
+ of obtaining the available number of outbound streams for
+ a peer connection, there is no way for a client to have
+ known the range of ids from which it could reliably have
+ chosen with the previous interface, so any setting of the
+ outbound stream has probably been unintentional. Not
+ explicitly specifying an outbound stream now results in a
+ round-robin selection.</p>
+ <p>
+ Thanks to Sharmila Pillai for reporting the problem.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-11593</p>
+ </item>
+ <item>
+ <p>
+ Fix unicode path failure in diameter_make:codec/2.</p>
+ <p>
+ A dictionary path containing a unicode codepoint > 255
+ caused the function to fail.</p>
+ <p>
+ Own Id: OTP-11655</p>
+ </item>
+ <item>
+ <p>
+ Fix 'accept' config to diameter_sctp.</p>
+ <p>
+ OTP-10893 added support for {accept, Match} tuples to
+ specify addresses or regexps that should be matched
+ against peer addresses to decide whether or not a newly
+ established association should be retained, but this
+ hasn't been functional in the SCTP case because of
+ missing support in inet(3).</p>
+ <p>
+ The display of both local and peer addresses in
+ diameter:service_info/2 output has also been corrected.</p>
+ <p>
+ Own Id: OTP-11661 Aux Id: OTP-10229 </p>
+ </item>
+ <item>
+ <p>
+ Be lenient with the M-bit in Grouped AVPs.</p>
+ <p>
+ RFC 6733 says this, in 4.4:</p>
+ <p>
+ <taglist><item><p><c>Receivers of a Grouped AVP that does
+ not have the 'M' (mandatory) bit set and one or more of
+ the encapsulated AVPs within the group has the 'M'
+ (mandatory) bit set MAY simply be ignored if the Grouped
+ AVP itself is unrecognized. The rule applies even if the
+ encapsulated AVP with its 'M' (mandatory) bit set is
+ further encapsulated within other sub-groups, i.e., other
+ Grouped AVPs embedded within the Grouped
+ AVP.</c></p></item></taglist></p>
+ <p>
+ The first sentence is mangled but take it to mean this:</p>
+ <p>
+ <taglist><item><p><c>An unrecognized AVP of type Grouped
+ that does not set the 'M' bit MAY be ignored even if one
+ of its encapsulated AVPs sets the 'M'
+ bit.</c></p></item></taglist></p>
+ <p>
+ This is a bit of a non-statement since if the AVP is
+ unrecognized then its type is unknown. We therefore don't
+ know that its data bytes contain encapsulated AVPs, so
+ can't but ignore any of those that set the M-bit. Doing
+ anything else when the type *is* known would be
+ inconsistent.</p>
+ <p>
+ OTP-11087 (R16B03) caused the M-bit on any unrecognized
+ AVP to be regarded as an error, unrecognized being taken
+ to mean "not explicitly defined as a member of its
+ container". (That is, an AVP that can't be packed into a
+ dedicated record field, which is slightly stronger than
+ "not defined".) This fixed the original intention for
+ top-level AVPs but broke the required leniency for
+ Grouped AVPs whose type is known. This leniency is now
+ restored.</p>
+ <p>
+ Note that dictionary files need to be recompiled for the
+ change to have effect.</p>
+ <p>
+ Thanks to Rory McKeown for reporting the problem.</p>
+ <p>
+ Own Id: OTP-11675 Aux Id: OTP-11087 </p>
+ </item>
+ <item>
+ <p>
+ Fix pick_peer case clause failure.</p>
+ <p>
+ In the case of {call_mutates_state, true} configuration
+ on the service in question, any peer selection that
+ failed to select a peer resulted in a case clause
+ failure. This was noticed in the case of a peer failover
+ in which an alternate peer wasn't available.</p>
+ <p>
+ Own Id: OTP-11789</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>diameter 1.5</title>
<section><title>Improvements and New Features</title>
@@ -73,12 +481,6 @@ first.</p>
<p>
Own Id: OTP-11361</p>
</item>
- <item>
- <p>
- Fix silent make rules (Thanks to Anthony Ramine)</p>
- <p>
- Own Id: OTP-11514</p>
- </item>
</list>
</section>
@@ -86,7 +488,7 @@ first.</p>
<section><title>diameter 1.4.4</title>
- <section><title>Known Bugs and Problems</title>
+ <section><title>Fixed Bugs and Malfunctions</title>
<list>
<item>
<p>
diff --git a/lib/diameter/examples/code/client.erl b/lib/diameter/examples/code/client.erl
index bfe71b0e56..46eb4a55db 100644
--- a/lib/diameter/examples/code/client.erl
+++ b/lib/diameter/examples/code/client.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2012. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2014. 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
diff --git a/lib/diameter/examples/code/client_cb.erl b/lib/diameter/examples/code/client_cb.erl
index ee3dcb2fec..843cdd9262 100644
--- a/lib/diameter/examples/code/client_cb.erl
+++ b/lib/diameter/examples/code/client_cb.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2012. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2014. 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
@@ -77,23 +77,11 @@ prepare_retransmit(Packet, SvcName, Peer) ->
%% handle_answer/4
-%% Since client.erl has detached the call when using the list
-%% encoding and not otherwise, output to the terminal in the
-%% the former case, return in the latter.
-
-handle_answer(#diameter_packet{msg = Msg}, Request, _SvcName, _Peer)
- when is_list(Request) ->
- io:format("answer: ~p~n", [Msg]);
-
handle_answer(#diameter_packet{msg = Msg}, _Request, _SvcName, _Peer) ->
{ok, Msg}.
%% handle_error/4
-handle_error(Reason, Request, _SvcName, _Peer)
- when is_list(Request) ->
- io:format("error: ~p~n", [Reason]);
-
handle_error(Reason, _Request, _SvcName, _Peer) ->
{error, Reason}.
diff --git a/lib/diameter/examples/code/peer.erl b/lib/diameter/examples/code/peer.erl
index b4ee17e4b7..7519abfb2c 100644
--- a/lib/diameter/examples/code/peer.erl
+++ b/lib/diameter/examples/code/peer.erl
@@ -74,7 +74,7 @@ start(Name, Opts)
| {error, term()}.
connect(Name, T) ->
- diameter:add_transport(Name, {connect, [{reconnect_timer, 5000}
+ diameter:add_transport(Name, {connect, [{connect_timer, 5000}
| client(T)]}).
%% listen/2
diff --git a/lib/diameter/examples/code/redirect_cb.erl b/lib/diameter/examples/code/redirect_cb.erl
index 69836774a1..8d98b0d2df 100644
--- a/lib/diameter/examples/code/redirect_cb.erl
+++ b/lib/diameter/examples/code/redirect_cb.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2014. 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
@@ -34,12 +34,10 @@
-define(UNEXPECTED, erlang:error({unexpected, ?MODULE, ?LINE})).
-peer_up(_SvcName, {PeerRef, _}, State) ->
- io:format("up: ~p~n", [PeerRef]),
+peer_up(_SvcName, _Peer, State) ->
State.
-peer_down(_SvcName, {PeerRef, _}, State) ->
- io:format("down: ~p~n", [PeerRef]),
+peer_down(_SvcName, _Peer, State) ->
State.
pick_peer(_, _, _SvcName, _State) ->
diff --git a/lib/diameter/examples/code/relay_cb.erl b/lib/diameter/examples/code/relay_cb.erl
index 9f9cd8d5ae..68798014e6 100644
--- a/lib/diameter/examples/code/relay_cb.erl
+++ b/lib/diameter/examples/code/relay_cb.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2012. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2014. 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
@@ -32,12 +32,10 @@
handle_error/5,
handle_request/3]).
-peer_up(_SvcName, {PeerRef, _}, State) ->
- io:format("up: ~p~n", [PeerRef]),
+peer_up(_SvcName, _Peer, State) ->
State.
-peer_down(_SvcName, {PeerRef, _}, State) ->
- io:format("down: ~p~n", [PeerRef]),
+peer_down(_SvcName, _Peer, State) ->
State.
%% Returning 'relay' from handle_request causes diameter to resend the
diff --git a/lib/diameter/examples/code/server_cb.erl b/lib/diameter/examples/code/server_cb.erl
index 0f6eb32ed6..9d8d395d06 100644
--- a/lib/diameter/examples/code/server_cb.erl
+++ b/lib/diameter/examples/code/server_cb.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2012. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2014. 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
@@ -38,12 +38,10 @@
-define(UNEXPECTED, erlang:error({unexpected, ?MODULE, ?LINE})).
-peer_up(_SvcName, {PeerRef, _}, State) ->
- io:format("up: ~p~n", [PeerRef]),
+peer_up(_SvcName, _Peer, State) ->
State.
-peer_down(_SvcName, {PeerRef, _}, State) ->
- io:format("down: ~p~n", [PeerRef]),
+peer_down(_SvcName, _Peer, State) ->
State.
pick_peer(_, _, _SvcName, _State) ->
@@ -68,10 +66,13 @@ handle_request(#diameter_packet{msg = Req, errors = []}, _SvcName, {_, Caps})
origin_realm = {OR,_}}
= Caps,
#diameter_base_RAR{'Session-Id' = Id,
- 'Re-Auth-Request-Type' = RT}
+ 'Re-Auth-Request-Type' = Type}
= Req,
- {reply, answer(RT, Id, OH, OR)};
+ {reply, #diameter_base_RAA{'Result-Code' = rc(Type),
+ 'Origin-Host' = OH,
+ 'Origin-Realm' = OR,
+ 'Session-Id' = Id}};
%% ... or one that wasn't. 3xxx errors are answered by diameter itself
%% but these are 5xxx errors for which we must contruct a reply.
@@ -84,32 +85,18 @@ handle_request(#diameter_packet{msg = Req}, _SvcName, {_, Caps})
#diameter_base_RAR{'Session-Id' = Id}
= Req,
- Ans = #diameter_base_RAA{'Origin-Host' = OH,
- 'Origin-Realm' = OR,
- 'Session-Id' = Id},
+ {reply, #diameter_base_RAA{'Origin-Host' = OH,
+ 'Origin-Realm' = OR,
+ 'Session-Id' = Id}};
- {reply, Ans};
+%% Answer that any other message is unsupported.
+handle_request(#diameter_packet{}, _SvcName, _) ->
+ {answer_message, 3001}. %% DIAMETER_COMMAND_UNSUPPORTED
-%% Should really reply to other base messages that we don't support
-%% but simply discard them instead.
-handle_request(#diameter_packet{}, _SvcName, {_,_}) ->
- discard.
+%% Map Re-Auth-Request-Type to Result-Code just for the purpose of
+%% generating different answers.
-%% ---------------------------------------------------------------------------
-
-%% Answer using the record or list encoding depending on
-%% Re-Auth-Request-Type. This is just as an example. You would
-%% typically just choose one, and this has nothing to do with the how
-%% client.erl sends.
-
-answer(0, Id, OH, OR) ->
- #diameter_base_RAA{'Result-Code' = 2001, %% DIAMETER_SUCCESS
- 'Origin-Host' = OH,
- 'Origin-Realm' = OR,
- 'Session-Id' = Id};
-
-answer(_, Id, OH, OR) ->
- ['RAA', {'Result-Code', 5012}, %% DIAMETER_UNABLE_TO_COMPLY
- {'Origin-Host', OH},
- {'Origin-Realm', OR},
- {'Session-Id', Id}].
+rc(0) ->
+ 2001; %% DIAMETER_SUCCESS
+rc(_) ->
+ 5012. %% DIAMETER_UNABLE_TO_COMPLY
diff --git a/lib/diameter/include/diameter.hrl b/lib/diameter/include/diameter.hrl
index 79c4dce541..c2c271a9a3 100644
--- a/lib/diameter/include/diameter.hrl
+++ b/lib/diameter/include/diameter.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2014. 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
@@ -58,8 +58,8 @@
-record(diameter_header,
{version, %% 8-bit unsigned
length, %% 24-bit unsigned
- cmd_code, %% 8-bit unsigned
- application_id, %% 24-bit unsigned
+ cmd_code, %% 24-bit unsigned
+ application_id, %% 32-bit unsigned
hop_by_hop_id, %% 32-bit unsigned
end_to_end_id, %% 32-bit unsigned
is_request, %% boolean() R flag
@@ -126,7 +126,7 @@
default,
extra = []}).
-%% The diameter service and diameter_apps records are only passed
+%% The diameter service and diameter_app records are only passed
%% through the transport interface when starting a transport process,
%% although typically a transport implementation will (and probably
%% should) only be interested host_ip_address.
@@ -143,6 +143,7 @@
init_state, %% option 'state', initial callback state
id, %% 32-bit unsigned application identifier = Dict:id()
mutable = false, %% boolean(), do traffic callbacks modify state?
- options = [{answer_errors, report}, %% | callback | discard
+ options = [{answer_errors, discard}, %% | callback | report
{request_errors, answer_3xxx}]}). %% | callback | answer
+
-endif. %% -ifdef(diameter_hrl).
diff --git a/lib/diameter/include/diameter_gen.hrl b/lib/diameter/include/diameter_gen.hrl
index c8f706dc3e..bc25f7d472 100644
--- a/lib/diameter/include/diameter_gen.hrl
+++ b/lib/diameter/include/diameter_gen.hrl
@@ -30,6 +30,10 @@
%% error or not. See is_strict/0.
-define(STRICT_KEY, strict).
+%% Key that says whether or not we should do a best-effort decode
+%% within Failed-AVP.
+-define(FAILED_KEY, failed).
+
-type parent_name() :: atom(). %% parent = Message or AVP
-type parent_record() :: tuple(). %%
-type avp_name() :: atom().
@@ -286,15 +290,7 @@ decode(Name, 'AVP', Avp, Acc) ->
%% d/3
-%% Don't try to decode the value of a Failed-AVP component since it
-%% probably won't. Note that matching on 'Failed-AVP' assumes that
-%% this is the RFC AVP, with code 279. Strictly, this doesn't need to
-%% be the case, so we're assuming no one defines another Failed-AVP.
-d('Failed-AVP' = Name, Avp, Acc) ->
- decode_AVP(Name, Avp, Acc);
-
-%% Or try to decode.
-d(Name, Avp, {Avps, Acc}) ->
+d(Name, Avp, Acc) ->
#diameter_avp{name = AvpName,
data = Data,
type = Type,
@@ -307,51 +303,120 @@ d(Name, Avp, {Avps, Acc}) ->
%% value around through the entire decode. The solution here is
%% simple in comparison, both to implement and to understand.
- Reset = relax(Type, M),
+ Strict = relax(Type, M),
+
+ %% Use the process dictionary again to keep track of whether we're
+ %% decoding within Failed-AVP and should ignore decode errors
+ %% altogether.
+
+ Failed = relax(Name), %% Not AvpName or else a failed Failed-AVP
+ %% decode is packed into 'AVP'.
+ Mod = dict(Failed), %% Dictionary to decode in.
- try avp(decode, Data, AvpName) of
+ try Mod:avp(decode, Data, AvpName) of
V ->
+ {Avps, T} = Acc,
{H, A} = ungroup(V, Avp),
- {[H | Avps], pack_avp(Name, A, Acc)}
+ {[H | Avps], pack_avp(Name, A, T)}
catch
error: Reason ->
- %% Failures here won't be visible since they're a "normal"
- %% occurrence if the peer sends a faulty AVP that we need to
- %% respond sensibly to. Log the occurence for traceability,
- %% but the peer will also receive info in the resulting
- %% answer-message.
- diameter_lib:log({decode, failure},
- ?MODULE,
- ?LINE,
- {Reason, Avp, erlang:get_stacktrace()}),
- {Rec, Failed} = Acc,
- {[Avp|Avps], {Rec, [rc(Reason, Avp) | Failed]}}
+ d(undefined == Failed orelse is_failed(),
+ Reason,
+ Name,
+ trim(Avp),
+ Acc)
after
- relax(Reset)
+ reset(?STRICT_KEY, Strict),
+ reset(?FAILED_KEY, Failed)
end.
+%% trim/1
+%%
+%% Remove any extra bit that was added in diameter_codec to induce a
+%% 5014 error.
+
+trim(#diameter_avp{data = <<0:1, Bin/binary>>} = Avp) ->
+ Avp#diameter_avp{data = Bin};
+
+trim(Avp) ->
+ Avp.
+
+%% dict/1
+%%
+%% Retrieve the dictionary for the best-effort decode of Failed-AVP,
+%% as put by diameter_codec:decode/2. See that function for the
+%% explanation.
+
+dict(true) ->
+ case get({diameter_codec, dictionary}) of
+ undefined ->
+ ?MODULE;
+ Mod ->
+ Mod
+ end;
+
+dict(_) ->
+ ?MODULE.
+
+%% d/5
+
+%% Ignore a decode error within Failed-AVP ...
+d(true, _, Name, Avp, Acc) ->
+ decode_AVP(Name, Avp, Acc);
+
+%% ... or not. Failures here won't be visible since they're a "normal"
+%% occurrence if the peer sends a faulty AVP that we need to respond
+%% sensibly to. Log the occurence for traceability, but the peer will
+%% also receive info in the resulting answer message.
+d(false, Reason, Name, Avp, {Avps, Acc}) ->
+ Stack = diameter_lib:get_stacktrace(),
+ diameter_lib:log(decode_error,
+ ?MODULE,
+ ?LINE,
+ {Reason, Name, Avp#diameter_avp.name, Stack}),
+ {Rec, Failed} = Acc,
+ {[Avp|Avps], {Rec, [rc(Reason, Avp) | Failed]}}.
+
+%% relax/2
+
%% Set false in the process dictionary as soon as we see a Grouped AVP
%% that doesn't set the M-bit, so that is_strict() can say whether or
%% not to ignore the M-bit on an encapsulated AVP.
relax('Grouped', M) ->
- V = getr(?STRICT_KEY),
- if V == undefined andalso not M ->
+ case getr(?STRICT_KEY) of
+ undefined when not M ->
putr(?STRICT_KEY, M);
- true ->
+ _ ->
false
end;
relax(_, _) ->
false.
-%% Reset strictness.
-relax(undefined) ->
- eraser(?STRICT_KEY);
-relax(false) ->
- ok.
-
is_strict() ->
false /= getr(?STRICT_KEY).
+%% relax/1
+%%
+%% Set true in the process dictionary as soon as we see Failed-AVP.
+%% Matching on 'Failed-AVP' assumes that this is the RFC AVP.
+%% Strictly, this doesn't need to be the case.
+
+relax('Failed-AVP') ->
+ is_failed() orelse putr(?FAILED_KEY, true);
+
+relax(_) ->
+ is_failed().
+
+is_failed() ->
+ true == getr(?FAILED_KEY).
+
+%% reset/2
+
+reset(Key, undefined) ->
+ eraser(Key);
+reset(_, _) ->
+ ok.
+
%% decode_AVP/3
%%
%% Don't know this AVP: see if it can be packed in an 'AVP' field
@@ -410,8 +475,25 @@ pack_avp(_, Arity, Avp, Acc) ->
%% pack_AVP/3
-pack_AVP(Name, #diameter_avp{is_mandatory = M} = Avp, Acc) ->
- case pack_arity(Name, M) of
+%% Length failure was induced because of a header/payload length
+%% mismatch. The AVP Length is reset to match the received data if
+%% this AVP is encoded in an answer message, since the length is
+%% computed.
+%%
+%% Data is a truncated header if command_code = undefined, otherwise
+%% payload bytes. The former is padded to the length of a header if
+%% the AVP reaches an outgoing encode in diameter_codec.
+%%
+%% RFC 6733 says that an AVP returned with 5014 can contain a minimal
+%% payload for the AVP's type, but in this case we don't know the
+%% type.
+
+pack_AVP(_, #diameter_avp{data = <<0:1, Data/binary>>} = Avp, Acc) ->
+ {Rec, Failed} = Acc,
+ {Rec, [{5014, Avp#diameter_avp{data = Data}} | Failed]};
+
+pack_AVP(Name, #diameter_avp{is_mandatory = M, name = AvpName} = Avp, Acc) ->
+ case pack_arity(Name, AvpName, M) of
0 ->
{Rec, Failed} = Acc,
{Rec, [{if M -> 5001; true -> 5008 end, Avp} | Failed]};
@@ -419,15 +501,32 @@ pack_AVP(Name, #diameter_avp{is_mandatory = M} = Avp, Acc) ->
pack(Arity, 'AVP', Avp, Acc)
end.
-%% Give Failed-AVP special treatment since it'll contain any
-%% unrecognized mandatory AVP's.
-pack_arity(Name, M) ->
- case Name /= 'Failed-AVP' andalso M andalso is_strict() of
- true ->
- 0;
- false ->
- avp_arity(Name, 'AVP')
- end.
+%% Give Failed-AVP special treatment since (1) it'll contain any
+%% unrecognized mandatory AVP's and (2) the RFC 3588 grammar failed to
+%% allow for Failed-AVP in an answer-message.
+
+pack_arity(Name, AvpName, M) ->
+ IsFailed = Name == 'Failed-AVP' orelse is_failed(),
+
+ %% Not testing just Name /= 'Failed-AVP' means we're changing the
+ %% packing of AVPs nested within Failed-AVP, but the point of
+ %% ignoring errors within Failed-AVP is to decode as much as
+ %% possible, and failing because a mandatory AVP couldn't be
+ %% packed into a dedicated field defeats that point. Note that we
+ %% can't just test not is_failed() since this will be 'true' when
+ %% packing an unknown AVP directly within Failed-AVP.
+
+ pack_arity(IsFailed
+ orelse {Name, AvpName} == {'answer-message', 'Failed-AVP'}
+ orelse not M
+ orelse not is_strict(),
+ Name).
+
+pack_arity(true, Name) ->
+ avp_arity(Name, 'AVP');
+
+pack_arity(false, _) ->
+ 0.
%% 3588:
%%
diff --git a/lib/diameter/src/Makefile b/lib/diameter/src/Makefile
index 578bbaee2e..9afccf298c 100644
--- a/lib/diameter/src/Makefile
+++ b/lib/diameter/src/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 2010-2013. All Rights Reserved.
+# Copyright Ericsson AB 2010-2014. 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
@@ -41,7 +41,7 @@ INCDIR = ../include
ABS_EBIN := $(shell cd $(EBIN) && pwd)
# Where make should look for dependencies.
-VPATH = .:base:compiler:transport:gen
+VPATH = .:base:compiler:transport:gen:info
# ----------------------------------------------------
# Target specs
@@ -55,13 +55,13 @@ DICT_ERLS = $(DICT_MODULES:%=%.erl)
DICT_HRLS = $(DICT_MODULES:%=%.hrl)
# Modules to build before compiling dictionaries.
-COMPILER_MODULES = $(notdir $(filter compiler/%, $(CT_MODULES))) \
- $(DICT_YRL)
+COMPILER_MODULES = $(notdir $(CT_MODULES)) $(DICT_YRL)
# All handwritten modules from which a depend.mk is generated.
MODULES = \
$(RT_MODULES) \
- $(CT_MODULES)
+ $(CT_MODULES) \
+ $(INFO_MODULES)
# Modules whose names are inserted into the app file.
APP_MODULES = \
@@ -72,6 +72,7 @@ APP_MODULES = \
TARGET_MODULES = \
$(APP_MODULES) \
$(CT_MODULES) \
+ $(INFO_MODULES) \
$(DICT_YRL:%=gen/%)
# What to build for the 'opt' target.
@@ -147,14 +148,19 @@ gen/$(DICT_YRL).erl: compiler/$(DICT_YRL).yrl
$(ERLC) -Werror -o $(@D) $<
# Generate the app file.
-$(APP_TARGET): $(APP_SRC) ../vsn.mk modules.mk
+$(APP_TARGET): $(APP_SRC) ../vsn.mk modules.mk app.sed
$(gen_verbose) \
M=`echo $(notdir $(APP_MODULES)) | tr ' ' ,`; \
+ C=`echo $(COMPILER_MODULES) | tr ' ' ,`; \
+ I=`echo $(notdir $(INFO_MODULES)) | tr ' ' ,`; \
R=`echo $(REGISTERED) | tr ' ' ,`; \
sed -e 's;%VSN%;$(VSN);' \
-e "s;%MODULES%;$$M;" \
+ -e "s;%COMPILER%;$$C;" \
+ -e "s;%INFO%;$$I;" \
-e "s;%REGISTERED%;$$R;" \
- $< > $@
+ $< \
+ | sed -f app.sed > $@
$(APPUP_TARGET): $(APPUP_SRC) ../vsn.mk
$(vsn_verbose) \
@@ -177,6 +183,8 @@ info:
@echo
@$(call list,CT_MODULES)
@echo
+ @$(call list,INFO_MODULES)
+ @echo
@$(call list,TARGET_MODULES)
@echo
@$(call list,TARGET_DIRS)
@@ -216,7 +224,7 @@ dialyze: opt $(PLT)
-Wno_improper_lists \
$(EBIN)/diameter_gen_base_rfc3588.$(EMULATOR) \
$(patsubst %, $(EBIN)/%.$(EMULATOR), \
- $(notdir $(RT_MODULES) $(CT_MODULES)))
+ $(notdir $(RT_MODULES) $(CT_MODULES) $(INFO_MODULES)))
# Omit all but the common dictionary module since these
# (diameter_gen_relay in particular) generate warning depending on how
# much of the included diameter_gen.hrl they use.
diff --git a/lib/diameter/src/app.sed b/lib/diameter/src/app.sed
new file mode 100644
index 0000000000..7916f65002
--- /dev/null
+++ b/lib/diameter/src/app.sed
@@ -0,0 +1,40 @@
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 2014. 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%
+
+#
+# Generate runtime_dependencies from applications to avoid having to
+# specify the same application more than once.
+#
+
+/{runtime_dependencies,/b v
+/{[-a-z]*, "[0-9.]*"}/!b
+/{vsn,/b
+
+/%%/!H
+s/{\([^,]*\)[^}]*}/\1/g
+s/%%/%,/
+b
+
+:v
+
+p
+x
+s/\n//
+s/%//g
+s/\n */ /g
+s/{\([^,]*\), "\([^"]*"\)}/"\1-\2/g
diff --git a/lib/diameter/src/base/diameter_codec.erl b/lib/diameter/src/base/diameter_codec.erl
index 0de4d53973..a2b04bfd63 100644
--- a/lib/diameter/src/base/diameter_codec.erl
+++ b/lib/diameter/src/base/diameter_codec.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2014. 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
@@ -70,12 +70,15 @@ encode(Mod, #diameter_packet{} = Pkt) ->
try
e(Mod, Pkt)
catch
+ exit: {Reason, Stack, #diameter_header{} = H} = T ->
+ %% Exit with a header in the reason to let the caller
+ %% count encode errors.
+ ?LOG(encode_error, {Reason, Stack, H}),
+ exit({?MODULE, encode, T});
error: Reason ->
- %% Be verbose since a crash report may be truncated and
- %% encode errors are self-inflicted.
- X = {?MODULE, encode, {Reason, ?STACK}},
- diameter_lib:error_report(X, {?MODULE, encode, [Mod, Pkt]}),
- exit(X)
+ T = {Reason, diameter_lib:get_stacktrace()},
+ ?LOG(encode_error, T),
+ exit({?MODULE, encode, T})
end;
encode(Mod, Msg) ->
@@ -87,53 +90,62 @@ encode(Mod, Msg) ->
msg = Msg}).
e(_, #diameter_packet{msg = [#diameter_header{} = Hdr | As]} = Pkt) ->
- Avps = encode_avps(As),
- Length = size(Avps) + 20,
-
- #diameter_header{version = Vsn,
- cmd_code = Code,
- application_id = Aid,
- hop_by_hop_id = Hid,
- end_to_end_id = Eid}
- = Hdr,
-
- Flags = make_flags(0, Hdr),
-
- Pkt#diameter_packet{header = Hdr,
- bin = <<Vsn:8, Length:24,
- Flags:8, Code:24,
- Aid:32,
- Hid:32,
- Eid:32,
- Avps/binary>>};
+ try encode_avps(As) of
+ Avps ->
+ Length = size(Avps) + 20,
+
+ #diameter_header{version = Vsn,
+ cmd_code = Code,
+ application_id = Aid,
+ hop_by_hop_id = Hid,
+ end_to_end_id = Eid}
+ = Hdr,
+
+ Flags = make_flags(0, Hdr),
+
+ Pkt#diameter_packet{header = Hdr,
+ bin = <<Vsn:8, Length:24,
+ Flags:8, Code:24,
+ Aid:32,
+ Hid:32,
+ Eid:32,
+ Avps/binary>>}
+ catch
+ error: Reason ->
+ exit({Reason, diameter_lib:get_stacktrace(), Hdr})
+ end;
-e(Mod, #diameter_packet{header = Hdr, msg = Msg} = Pkt) ->
+e(Mod, #diameter_packet{header = Hdr0, msg = Msg} = Pkt) ->
#diameter_header{version = Vsn,
hop_by_hop_id = Hid,
end_to_end_id = Eid}
- = Hdr,
+ = Hdr0,
MsgName = rec2msg(Mod, Msg),
- {Code, Flags0, Aid} = msg_header(Mod, MsgName, Hdr),
- Flags = make_flags(Flags0, Hdr),
-
- Avps = encode_avps(Mod, MsgName, values(Msg)),
- Length = size(Avps) + 20,
-
- Pkt#diameter_packet{header = Hdr#diameter_header
- {length = Length,
- cmd_code = Code,
- application_id = Aid,
- is_request = 0 /= ?MASK(7, Flags),
- is_proxiable = 0 /= ?MASK(6, Flags),
- is_error = 0 /= ?MASK(5, Flags),
- is_retransmitted = 0 /= ?MASK(4, Flags)},
- bin = <<Vsn:8, Length:24,
- Flags:8, Code:24,
- Aid:32,
- Hid:32,
- Eid:32,
- Avps/binary>>}.
+ {Code, Flags0, Aid} = msg_header(Mod, MsgName, Hdr0),
+ Flags = make_flags(Flags0, Hdr0),
+ Hdr = Hdr0#diameter_header{cmd_code = Code,
+ application_id = Aid,
+ is_request = 0 /= ?MASK(7, Flags),
+ is_proxiable = 0 /= ?MASK(6, Flags),
+ is_error = 0 /= ?MASK(5, Flags),
+ is_retransmitted = 0 /= ?MASK(4, Flags)},
+ Values = values(Msg),
+
+ try encode_avps(Mod, MsgName, Values) of
+ Avps ->
+ Length = size(Avps) + 20,
+ Pkt#diameter_packet{header = Hdr#diameter_header{length = Length},
+ bin = <<Vsn:8, Length:24,
+ Flags:8, Code:24,
+ Aid:32,
+ Hid:32,
+ Eid:32,
+ Avps/binary>>}
+ catch
+ error: Reason ->
+ exit({Reason, diameter_lib:get_stacktrace(), Hdr})
+ end.
%% make_flags/2
@@ -225,15 +237,35 @@ rec2msg(Mod, Rec) ->
%% Unsuccessfully decoded AVPs will be placed in #diameter_packet.errors.
--spec decode(module(), #diameter_packet{} | bitstring())
+-spec decode(module() | {module(), module()}, #diameter_packet{} | binary())
-> #diameter_packet{}.
+%% An Answer setting the E-bit. The application dictionary is needed
+%% for the best-effort decode of Failed-AVP, and the best way to make
+%% this available to the AVP decode in diameter_gen.hrl, without
+%% having to rewrite the entire codec generation, is to place it in
+%% the process dictionary. It's the code in diameter_gen.hrl (that's
+%% included by every generated codec module) that looks for the entry.
+%% Not ideal, but it solves the problem relatively simply.
+decode({Mod, Mod}, Pkt) ->
+ decode(Mod, Pkt);
+decode({Mod, AppMod}, Pkt) ->
+ Key = {?MODULE, dictionary},
+ put(Key, AppMod),
+ try
+ decode(Mod, Pkt)
+ after
+ erase(Key)
+ end;
+
+%% Or not: a request, or an answer not setting the E-bit.
decode(Mod, Pkt) ->
decode(Mod:id(), Mod, Pkt).
-%% If we're a relay application then just extract the avp's without
-%% any decoding of their data since we don't know the application in
-%% question.
+%% decode/3
+
+%% Relay application: just extract the avp's without any decoding of
+%% their data since we don't know the application in question.
decode(?APP_ID_RELAY, _, #diameter_packet{} = Pkt) ->
case collect_avps(Pkt) of
{E, As} ->
@@ -259,34 +291,36 @@ decode(_, Mod, #diameter_packet{header = Hdr} = Pkt) ->
decode_avps(MsgName, Mod, Pkt, collect_avps(Pkt));
decode(Id, Mod, Bin)
- when is_bitstring(Bin) ->
+ when is_binary(Bin) ->
decode(Id, Mod, #diameter_packet{header = decode_header(Bin), bin = Bin}).
+%% decode_avps/4
+
decode_avps(MsgName, Mod, Pkt, {E, Avps}) ->
- ?LOG(invalid, Pkt#diameter_packet.bin),
+ ?LOG(invalid_avp_length, Pkt#diameter_packet.header),
#diameter_packet{errors = Failed}
= P
= decode_avps(MsgName, Mod, Pkt, Avps),
P#diameter_packet{errors = [E | Failed]};
-decode_avps('', Mod, Pkt, Avps) -> %% unknown message ...
- ?LOG(unknown, {Mod, Pkt#diameter_packet.header}),
+decode_avps('', _, Pkt, Avps) -> %% unknown message ...
+ ?LOG(unknown_message, Pkt#diameter_packet.header),
Pkt#diameter_packet{avps = lists:reverse(Avps),
errors = [3001]}; %% DIAMETER_COMMAND_UNSUPPORTED
%% msg = undefined identifies this case.
decode_avps(MsgName, Mod, Pkt, Avps) -> %% ... or not
- {Rec, As, Failed} = Mod:decode_avps(MsgName, Avps),
- ?LOGC([] /= Failed, failed, {Mod, Failed}),
+ {Rec, As, Errors} = Mod:decode_avps(MsgName, Avps),
+ ?LOGC([] /= Errors, decode_errors, Pkt#diameter_packet.header),
Pkt#diameter_packet{msg = Rec,
- errors = Failed,
+ errors = Errors,
avps = As}.
%%% ---------------------------------------------------------------------------
%%% # decode_header/1
%%% ---------------------------------------------------------------------------
--spec decode_header(bitstring())
+-spec decode_header(binary())
-> #diameter_header{}
| false.
@@ -297,7 +331,7 @@ decode_header(<<Version:8,
ApplicationId:32,
HopByHopId:32,
EndToEndId:32,
- _/bitstring>>) ->
+ _/binary>>) ->
<<R:1, P:1, E:1, T:1, _:4>>
= CmdFlags,
%% 3588 (ch 3) says that reserved bits MUST be set to 0 and ignored
@@ -410,7 +444,7 @@ msg_id(#diameter_header{application_id = A,
is_request = R}) ->
{A, C, if R -> 1; true -> 0 end};
-msg_id(<<_:32, Rbit:1, _:7, CmdCode:24, ApplId:32, _/bitstring>>) ->
+msg_id(<<_:32, Rbit:1, _:7, CmdCode:24, ApplId:32, _/binary>>) ->
{ApplId, CmdCode, Rbit}.
%%% ---------------------------------------------------------------------------
@@ -421,17 +455,18 @@ msg_id(<<_:32, Rbit:1, _:7, CmdCode:24, ApplId:32, _/bitstring>>) ->
%% order in the binary. Note also that grouped avp's aren't unraveled,
%% only those at the top level.
--spec collect_avps(#diameter_packet{} | bitstring())
+-spec collect_avps(#diameter_packet{} | binary())
-> [Avp]
| {Error, [Avp]}
when Avp :: #diameter_avp{},
Error :: {5014, #diameter_avp{}}.
collect_avps(#diameter_packet{bin = Bin}) ->
- <<_:20/binary, Avps/bitstring>> = Bin,
+ <<_:20/binary, Avps/binary>> = Bin,
collect_avps(Avps);
-collect_avps(Bin) ->
+collect_avps(Bin)
+ when is_binary(Bin) ->
collect_avps(Bin, 0, []).
collect_avps(<<>>, _, Acc) ->
@@ -461,7 +496,9 @@ collect_avps(Bin, N, Acc) ->
split_avp(Bin) ->
{Code, V, M, P, Len, HdrLen} = split_head(Bin),
- {Data, B} = split_data(Bin, HdrLen, Len - HdrLen),
+
+ <<_:HdrLen/binary, Rest/binary>> = Bin,
+ {Data, B} = split_data(Rest, Len - HdrLen),
{B, #diameter_avp{code = Code,
vendor_id = V,
@@ -471,17 +508,15 @@ split_avp(Bin) ->
%% split_head/1
-split_head(<<Code:32, 1:1, M:1, P:1, _:5, Len:24, V:32, _/bitstring>>) ->
+split_head(<<Code:32, 1:1, M:1, P:1, _:5, Len:24, V:32, _/binary>>) ->
{Code, V, M, P, Len, 12};
-split_head(<<Code:32, 0:1, M:1, P:1, _:5, Len:24, _/bitstring>>) ->
+split_head(<<Code:32, 0:1, M:1, P:1, _:5, Len:24, _/binary>>) ->
{Code, undefined, M, P, Len, 8};
-%% Header is truncated: pack_avp/1 will pad to the minimum header
-%% length.
-split_head(B)
- when is_bitstring(B) ->
- ?THROW({5014, #diameter_avp{data = B}}).
+%% Header is truncated.
+split_head(Bin) ->
+ ?THROW({5014, #diameter_avp{data = Bin}}).
%% 3588:
%%
@@ -516,34 +551,27 @@ split_head(B)
%% split_data/3
-split_data(Bin, HdrLen, Len)
- when 0 =< Len ->
- split_data(Bin, HdrLen, Len, (4 - (Len rem 4)) rem 4);
-
-split_data(_, _, _) ->
- invalid_avp_length().
+split_data(Bin, Len) ->
+ Pad = (4 - (Len rem 4)) rem 4,
-%% split_data/4
+ %% Len might be negative here, but that ensures the failure of the
+ %% binary match.
-split_data(Bin, HdrLen, Len, Pad) ->
case Bin of
- <<_:HdrLen/binary, Data:Len/binary, _:Pad/binary, Rest/bitstring>> ->
+ <<Data:Len/binary, _:Pad/binary, Rest/binary>> ->
{Data, Rest};
_ ->
- invalid_avp_length()
+ %% Header length points past the end of the message. As
+ %% stated in the 6733 text above, it's sufficient to
+ %% return a zero-filled minimal 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.
+ {<<0:1, Bin/binary>>, <<>>}
end.
-%% invalid_avp_length/0
-%%
-%% AVP Length doesn't mesh with payload. Induce a decode error by
-%% returning a payload that no valid Diameter type can have. This is
-%% so that a known AVP will result in 5014 error with a zero'd
-%% payload. Here we simply don't know how to construct this payload.
-%% (Yes, this solution is an afterthought.)
-
-invalid_avp_length() ->
- {<<0:1>>, <<>>}.
-
%%% ---------------------------------------------------------------------------
%%% # pack_avp/1
%%% ---------------------------------------------------------------------------
@@ -575,17 +603,23 @@ pack_avp(#diameter_avp{data = {Dict, Name, Value}} = A) ->
{Name, Type} = Dict:avp_name(Code, Vid),
pack_avp(A#diameter_avp{data = {Hdr, {Type, Value}}});
+%% ... with a truncated header ...
pack_avp(#diameter_avp{code = undefined, data = B})
- when is_bitstring(B) ->
+ when is_binary(B) ->
%% Reset the AVP Length of an AVP Header resulting from a 5014
%% error. The RFC doesn't explicitly say to do this but the
%% receiver can't correctly extract this and following AVP's
%% without a correct length. On the downside, the header doesn't
%% reveal if the received header has been padded.
Pad = 8*header_length(B) - bit_size(B),
- Len = size(<<H:5/binary, _:24, T/binary>> = <<B/bitstring, 0:Pad>>),
+ 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 ...
+pack_avp(#diameter_avp{data = <<0:1, B/binary>>} = A) ->
+ pack_avp(A#diameter_avp{data = B});
+
%% ... or as an iolist.
pack_avp(#diameter_avp{code = Code,
vendor_id = V,
diff --git a/lib/diameter/src/base/diameter_config.erl b/lib/diameter/src/base/diameter_config.erl
index f5ea459fd0..dd1c9b73bb 100644
--- a/lib/diameter/src/base/diameter_config.erl
+++ b/lib/diameter/src/base/diameter_config.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2014. 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
@@ -753,7 +753,7 @@ app_acc({application, Opts} = T, Acc) ->
Alias = get_opt(alias, Opts, Dict),
ModS = get_opt(state, Opts, Alias),
M = get_opt(call_mutates_state, Opts, false, [true]),
- A = get_opt(answer_errors, Opts, report, [callback, discard]),
+ A = get_opt(answer_errors, Opts, discard, [callback, report]),
P = get_opt(request_errors, Opts, answer_3xxx, [answer, callback]),
[#diameter_app{alias = Alias,
dictionary = Dict,
diff --git a/lib/diameter/src/base/diameter_lib.erl b/lib/diameter/src/base/diameter_lib.erl
index 44d81e2778..5b3a2063f8 100644
--- a/lib/diameter/src/base/diameter_lib.erl
+++ b/lib/diameter/src/base/diameter_lib.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2014. 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
@@ -25,6 +25,8 @@
now_diff/1,
time/1,
eval/1,
+ eval_name/1,
+ get_stacktrace/0,
ipaddr/1,
spawn_opts/2,
wait/1,
@@ -32,6 +34,22 @@
log/4]).
%% ---------------------------------------------------------------------------
+%% # get_stacktrace/0
+%% ---------------------------------------------------------------------------
+
+%% Return a stacktrace with a leading, potentially large, argument
+%% list replaced by an arity. Trace on stacktrace/0 to see the
+%% original.
+
+get_stacktrace() ->
+ stacktrace(erlang:get_stacktrace()).
+
+stacktrace([{M,F,A,L} | T]) when is_list(A) ->
+ [{M, F, length(A), L} | T];
+stacktrace(L) ->
+ L.
+
+%% ---------------------------------------------------------------------------
%% # info_report/2
%% ---------------------------------------------------------------------------
@@ -60,9 +78,17 @@ warning_report(Reason, T) ->
report(fun error_logger:warning_report/1, Reason, T).
report(Fun, Reason, T) ->
- Fun([{why, Reason}, {who, self()}, {what, T}]),
+ Fun(io_lib:format("diameter: ~" ++ fmt(Reason) ++ "~n ~p~n",
+ [Reason, T])),
false.
+fmt(T) ->
+ if is_list(T) ->
+ "s";
+ true ->
+ "p"
+ end.
+
%% ---------------------------------------------------------------------------
%% # now_diff/1
%% ---------------------------------------------------------------------------
@@ -129,8 +155,8 @@ eval({M,F,A}) ->
eval([{M,F,A} | X]) ->
apply(M, F, X ++ A);
-eval([[F|A] | X]) ->
- eval([F | X ++ A]);
+eval([[F|X] | A]) ->
+ eval([F | A ++ X]);
eval([F|A]) ->
apply(F,A);
@@ -142,6 +168,28 @@ eval(F) ->
F().
%% ---------------------------------------------------------------------------
+%% eval_name/1
+%% ---------------------------------------------------------------------------
+
+eval_name({M,F,A}) ->
+ {M, F, length(A)};
+
+eval_name([{M,F,A} | X]) ->
+ {M, F, length(A) + length(X)};
+
+eval_name([[F|A] | X]) ->
+ eval_name([F | X ++ A]);
+
+eval_name([F|_]) ->
+ F;
+
+eval_name({F}) ->
+ eval_name(F);
+
+eval_name(F) ->
+ F.
+
+%% ---------------------------------------------------------------------------
%% # ipaddr/1
%%
%% Parse an IP address.
diff --git a/lib/diameter/src/base/diameter_peer_fsm.erl b/lib/diameter/src/base/diameter_peer_fsm.erl
index f76bd96c3c..ee6e7dd89e 100644
--- a/lib/diameter/src/base/diameter_peer_fsm.erl
+++ b/lib/diameter/src/base/diameter_peer_fsm.erl
@@ -225,8 +225,8 @@ start_transport(Addrs0, T) ->
erlang:monitor(process, TPid),
q_next(TPid, Addrs0, Tmo, Data),
{TPid, Addrs};
- No ->
- exit({shutdown, No})
+ {error, No} ->
+ exit({shutdown, {no_connection, No}})
end.
svc(#diameter_service{capabilities = LCaps0} = Svc, Addrs) ->
@@ -283,7 +283,7 @@ handle_info(T, #state{} = State) ->
ok ->
{noreply, State};
#state{state = X} = S ->
- ?LOGC(X =/= State#state.state, transition, X),
+ ?LOGC(X /= State#state.state, transition, X),
{noreply, S};
{stop, Reason} ->
?LOG(stop, Reason),
@@ -292,15 +292,12 @@ handle_info(T, #state{} = State) ->
?LOG(stop, T),
{stop, {shutdown, T}, State}
catch
- exit: {diameter_codec, encode, _} = Reason ->
+ exit: {diameter_codec, encode, T} = Reason ->
+ incr_error(send, T, State#state.dictionary),
?LOG(stop, Reason),
- %% diameter_codec:encode/2 emits an error report. Only
- %% indicate the probable reason here.
- diameter_lib:info_report(probable_configuration_error,
- insufficient_capabilities),
{stop, {shutdown, Reason}, State};
{?MODULE, Tag, Reason} ->
- ?LOG(Tag, {Reason, T}),
+ ?LOG(stop, Tag),
{stop, {shutdown, Reason}, State}
end.
%% The form of the throw caught here is historical. It's
@@ -371,11 +368,8 @@ transition({diameter, {TPid, connected}},
%% message. This may be followed by an incoming message which arrived
%% before the transport was killed and this can't be distinguished
%% from one from the transport that's been started to replace it.
-transition({diameter, {_, connected}}, _) ->
- {stop, connection_timeout};
-transition({diameter, {_, connected, _}}, _) ->
- {stop, connection_timeout};
-transition({diameter, {_, connected, _, _}}, _) ->
+transition({diameter, T}, _)
+ when tuple_size(T) < 5, connected == element(2,T) ->
{stop, connection_timeout};
%% Connection has timed out: start an alternate.
@@ -476,12 +470,13 @@ send_CER(#state{state = {'Wait-Conn-Ack', Tmo},
orelse
close({already_connected, Remote, LCaps}),
CER = build_CER(S),
- ?LOG(send, 'CER'),
#diameter_packet{header = #diameter_header{end_to_end_id = Eid,
hop_by_hop_id = Hid}}
= Pkt
= encode(CER, Dict),
+ incr(send, Pkt, Dict),
send(TPid, Pkt),
+ ?LOG(send, 'CER'),
start_timer(Tmo, S#state{state = {'Wait-CEA', Hid, Eid}}).
%% Register ourselves as connecting to the remote endpoint in
@@ -526,7 +521,6 @@ recv(#diameter_packet{header = #diameter_header{} = Hdr}
= S) ->
Name = diameter_codec:msg_name(Dict0, Hdr),
Pid ! {recv, self(), Name, Pkt},
- diameter_stats:incr({msg_id(Name, Hdr), recv}), %% count received
rcv(Name, Pkt, S);
recv(#diameter_packet{header = undefined,
@@ -553,42 +547,30 @@ recv(#diameter_header{length = Len}
recv(#diameter_header{}
= H,
#diameter_packet{bin = Bin},
- #state{length_errors = E}
- = S) ->
- invalid(E,
- invalid_message_length,
- recv,
- [size(Bin), bit_size(Bin) rem 8, H, S]);
+ #state{length_errors = E}) ->
+ T = {size(Bin), bit_size(Bin) rem 8, H},
+ invalid(E, message_length_mismatch, T);
-recv(false, Pkt, #state{length_errors = E} = S) ->
- invalid(E, truncated_header, recv, [Pkt, S]).
+recv(false, #diameter_packet{bin = Bin}, #state{length_errors = E}) ->
+ invalid(E, truncated_header, Bin).
%% Note that counters here only count discarded messages.
-invalid(E, Reason, F, A) ->
+invalid(E, Reason, T) ->
diameter_stats:incr(Reason),
- abort(E, Reason, F, A).
-
-abort(exit, Reason, F, A) ->
- diameter_lib:warning_report(Reason, {?MODULE, F, A}),
- throw({?MODULE, abort, Reason});
-
-abort(_, _, _, _) ->
+ E == exit andalso close({Reason, T}),
+ ?LOG(Reason, T),
ok.
-msg_id({_,_,_} = T, _) ->
- T;
-msg_id(_, Hdr) ->
- {_,_,_} = diameter_codec:msg_id(Hdr).
-
%% rcv/3
%% Incoming CEA.
-rcv('CEA',
+rcv('CEA' = N,
#diameter_packet{header = #diameter_header{end_to_end_id = Eid,
hop_by_hop_id = Hid}}
= Pkt,
#state{state = {'Wait-CEA', Hid, Eid}}
= S) ->
+ ?LOG(recv, N),
handle_CEA(Pkt, S);
%% Incoming CER
@@ -609,34 +591,71 @@ rcv('DPR' = N, Pkt, S) ->
%% DPA in response to DPR and with the expected identifiers.
rcv('DPA' = N,
#diameter_packet{header = #diameter_header{end_to_end_id = Eid,
- hop_by_hop_id = Hid}},
- #state{transport = TPid,
+ hop_by_hop_id = Hid}
+ = H}
+ = Pkt,
+ #state{dictionary = Dict0,
+ transport = TPid,
dpr = {Hid, Eid}}) ->
+ ?LOG(recv, N),
+ incr(recv, H, Dict0),
+ incr_rc(recv, diameter_codec:decode(Dict0, Pkt), Dict0),
diameter_peer:close(TPid),
{stop, N};
%% Ignore anything else, an unsolicited DPA in particular.
+rcv(N, #diameter_packet{header = H}, _)
+ when N == 'CER';
+ N == 'CEA';
+ N == 'DPR';
+ N == 'DPA' ->
+ ?LOG(ignored, N),
+ %% Note that these aren't counted in the normal recv counter.
+ diameter_stats:incr({diameter_codec:msg_id(H), recv, ignored}),
+ ok;
+
rcv(_, _, _) ->
ok.
+%% incr/3
+
+incr(Dir, Hdr, Dict0) ->
+ diameter_traffic:incr(Dir, Hdr, self(), Dict0).
+
+%% incr_rc/3
+
+incr_rc(Dir, Pkt, Dict0) ->
+ diameter_traffic:incr_rc(Dir, Pkt, self(), Dict0).
+
+%% incr_error/3
+
+incr_error(Dir, Pkt, Dict0) ->
+ diameter_traffic:incr_error(Dir, Pkt, self(), Dict0).
+
%% send/2
%% Msg here could be a #diameter_packet or a binary depending on who's
%% sending. In particular, the watchdog will send DWR as a binary
%% while messages coming from clients will be in a #diameter_packet.
send(Pid, Msg) ->
- diameter_stats:incr({diameter_codec:msg_id(Msg), send}),
diameter_peer:send(Pid, Msg).
%% handle_request/3
+%%
+%% Incoming CER or DPR.
-handle_request(Type, #diameter_packet{} = Pkt, #state{dictionary = D} = S) ->
- ?LOG(recv, Type),
- send_answer(Type, diameter_codec:decode(D, Pkt), S).
+handle_request(Name,
+ #diameter_packet{header = H} = Pkt,
+ #state{dictionary = Dict0} = S) ->
+ ?LOG(recv, Name),
+ incr(recv, H, Dict0),
+ send_answer(Name, diameter_codec:decode(Dict0, Pkt), S).
%% send_answer/3
send_answer(Type, ReqPkt, #state{transport = TPid, dictionary = Dict} = S) ->
+ incr_error(recv, ReqPkt, Dict),
+
#diameter_packet{header = H,
transport_data = TD}
= ReqPkt,
@@ -653,13 +672,19 @@ send_answer(Type, ReqPkt, #state{transport = TPid, dictionary = Dict} = S) ->
msg = Msg,
transport_data = TD},
- send(TPid, diameter_codec:encode(Dict, Pkt)),
+ AnsPkt = diameter_codec:encode(Dict, Pkt),
+
+ incr(send, AnsPkt, Dict),
+ incr_rc(send, AnsPkt, Dict),
+ send(TPid, AnsPkt),
+ ?LOG(send, ans(Type)),
eval(PostF, S).
+ans('CER') -> 'CEA';
+ans('DPR') -> 'DPA'.
+
eval([F|A], S) ->
apply(F, A ++ [S]);
-eval(ok, S) ->
- S;
eval(T, _) ->
close(T).
@@ -723,8 +748,8 @@ cea(CEA, RC, Dict0) ->
post('CER' = T, RC, Pkt, S) ->
{T, caps(S), {RC, Pkt}};
-post('DPR', _, _, _) ->
- ok.
+post('DPR' = T, _, _, #state{parent = Pid}) ->
+ [fun(S) -> Pid ! {T, self()}, S end].
rejected({capabilities_cb, _F, Reason}, T, S) ->
rejected(Reason, T, S);
@@ -734,7 +759,7 @@ rejected(discard, T, _) ->
rejected({N, Es}, T, S) ->
{answer('CER', N, failed_avp(N, Es), S), T};
rejected(N, T, S) ->
- rejected({N, []}, T, S).
+ {answer('CER', N, [], S), T}.
failed_avp(RC, [{RC, Avp} | _]) ->
[{'Failed-AVP', [[{'AVP', [Avp]}]]}];
@@ -848,28 +873,27 @@ recv_CER(CER, #state{service = Svc, dictionary = Dict}) ->
close({'CER', CER, Svc, Dict, Reason})
end.
-%% handle_CEA/1
+%% handle_CEA/2
-handle_CEA(#diameter_packet{bin = Bin}
+handle_CEA(#diameter_packet{header = H}
= Pkt,
#state{dictionary = Dict0,
service = #diameter_service{capabilities = LCaps}}
- = S)
- when is_binary(Bin) ->
- ?LOG(recv, 'CEA'),
+ = S) ->
+ incr(recv, H, Dict0),
- #diameter_packet{msg = CEA}
+ #diameter_packet{}
= DPkt
= diameter_codec:decode(Dict0, Pkt),
+ RC = result_code(incr_rc(recv, DPkt, Dict0)),
+
{SApps, IS, RCaps} = recv_CEA(DPkt, S),
#diameter_caps{origin_host = {OH, DH}}
= Caps
= capz(LCaps, RCaps),
- RC = Dict0:'#get-'('Result-Code', CEA),
-
%% Ensure that we don't already have a connection to the peer in
%% question. This isn't the peer election of 3588 except in the
%% sense that, since we don't know who we're talking to until we
@@ -877,7 +901,7 @@ handle_CEA(#diameter_packet{bin = Bin}
%% connection with the peer.
try
- ?IS_SUCCESS(RC)
+ is_integer(RC) andalso ?IS_SUCCESS(RC)
orelse ?THROW(RC),
[] == SApps
andalso ?THROW(no_common_application),
@@ -897,6 +921,11 @@ handle_CEA(#diameter_packet{bin = Bin}
%% capabilities exchange could send DIAMETER_LIMITED_SUCCESS = 2002,
%% even if this isn't required by RFC 3588.
+result_code({'Result-Code', N}) ->
+ N;
+result_code(_) ->
+ undefined.
+
%% recv_CEA/2
recv_CEA(#diameter_packet{header = #diameter_header{version
@@ -988,19 +1017,13 @@ capz(#diameter_caps{} = L, #diameter_caps{} = R) ->
tl(tuple_to_list(R)))]).
%% close/1
+%%
+%% A good function to trace on in case of problems with capabilities
+%% exchange.
close(Reason) ->
- report(Reason),
throw({?MODULE, close, Reason}).
-%% Could possibly log more here.
-report({M, _, _, _, _} = T)
- when M == 'CER';
- M == 'CEA' ->
- diameter_lib:error_report(failure, T);
-report(_) ->
- ok.
-
%% dpr/2
%%
%% The RFC isn't clear on whether DPR should be send in a non-Open
@@ -1034,7 +1057,7 @@ dpr(_Reason, _S) ->
%% process and contact it. (eg. diameter:service_info/2)
dpr([CB|Rest], [Reason | _] = Args, S) ->
- try diameter_lib:eval([CB | Args]) of
+ case diameter_lib:eval([CB | Args]) of
{dpr, Opts} when is_list(Opts) ->
send_dpr(Reason, Opts, S);
dpr ->
@@ -1044,14 +1067,7 @@ dpr([CB|Rest], [Reason | _] = Args, S) ->
ignore ->
dpr(Rest, Args, S);
T ->
- No = {disconnect_cb, T},
- diameter_lib:error_report(invalid, No),
- {stop, No}
- catch
- E:R ->
- No = {disconnect_cb, E, R, ?STACK},
- diameter_lib:error_report(failure, No),
- {stop, No}
+ ?ERROR({disconnect_cb, CB, Args, T})
end;
dpr([], [Reason | _], S) ->
@@ -1082,6 +1098,7 @@ send_dpr(Reason, Opts, #state{transport = TPid,
{'Origin-Realm', OR},
{'Disconnect-Cause', Cause}],
Dict),
+ incr(send, Pkt, Dict),
send(TPid, Pkt),
dpa_timer(Tmo),
?LOG(send, 'DPR'),
diff --git a/lib/diameter/src/base/diameter_service.erl b/lib/diameter/src/base/diameter_service.erl
index 1274e0fc48..76b05a2ad4 100644
--- a/lib/diameter/src/base/diameter_service.erl
+++ b/lib/diameter/src/base/diameter_service.erl
@@ -499,9 +499,21 @@ transition(Req, S) ->
%% # terminate/2
%% ---------------------------------------------------------------------------
-terminate(Reason, #state{service_name = Name} = S) ->
+terminate(Reason, #state{service_name = Name, peerT = PeerT} = S) ->
send_event(Name, stop),
ets:delete(?STATE_TABLE, Name),
+
+ %% Communicate pending loss of any peers that connection_down/3
+ %% won't. This is needed when stopping a service since we don't
+ %% wait for watchdog state changes to take care of if. That this
+ %% takes place after deleting the state entry ensures that the
+ %% resulting failover by request processes accomplishes nothing.
+ ets:foldl(fun(#peer{pid = TPid}, _) ->
+ diameter_traffic:peer_down(TPid)
+ end,
+ ok,
+ PeerT),
+
shutdown == Reason %% application shutdown
andalso shutdown(application, S).
@@ -719,14 +731,27 @@ remotes(F) ->
L when is_list(L) ->
L;
T ->
- diameter_lib:error_report({invalid_return, T}, F),
+ ?LOG(invalid_return, {F,T}),
+ error_report(invalid_return, share_peers, F),
[]
catch
E:R ->
- diameter_lib:error_report({failure, {E, R, ?STACK}}, F),
+ ?LOG(failure, {E, R, F, diameter_lib:get_stacktrace()}),
+ error_report(failure, share_peers, F),
[]
end.
+%% error_report/3
+
+error_report(T, What, F) ->
+ Reason = io_lib:format("~s from ~p callback", [reason(T), What]),
+ diameter_lib:error_report(Reason, diameter_lib:eval_name(F)).
+
+reason(invalid_return) ->
+ "invalid return";
+reason(failure) ->
+ "failure".
+
%% ---------------------------------------------------------------------------
%% # start/3
%% ---------------------------------------------------------------------------
@@ -869,7 +894,7 @@ watchdog(TPid, [], ?WD_OKAY, ?WD_SUSPECT = To, Wd, State) ->
%% Watchdog has lost its connection.
watchdog(TPid, [], _, ?WD_DOWN = To, Wd, #state{peerT = PeerT} = S) ->
- close(Wd, S),
+ close(Wd),
watchdog_down(Wd, To, S),
ets:delete(PeerT, TPid);
@@ -1026,8 +1051,11 @@ peer_cb(App, F, A) ->
true
catch
E:R ->
- diameter_lib:error_report({failure, {E, R, ?STACK}},
- {App, F, A}),
+ %% Don't include arguments since a #diameter_caps{} strings
+ %% from the peer, which could be anything (especially, large).
+ [Mod|X] = App#diameter_app.module,
+ ?LOG(failure, {E, R, Mod, F, diameter_lib:get_stacktrace()}),
+ error_report(failure, F, {Mod, F, A ++ X}),
false
end.
@@ -1187,26 +1215,16 @@ tc(false = No, _, _) -> %% removed
%% another watchdog to be able to detect that it should transition
%% from initial into reopen rather than okay. That someone is either
%% the accepting watchdog upon reception of a CER from the previously
-%% connected peer, or us after connect_timer timeout.
+%% connected peer, or us after connect_timer timeout or immediately.
-close(#watchdog{type = connect}, _) ->
+close(#watchdog{type = connect}) ->
ok;
+
close(#watchdog{type = accept,
pid = Pid,
- ref = Ref,
- options = Opts},
- #state{service_name = SvcName}) ->
- c(Pid, diameter_config:have_transport(SvcName, Ref), Opts).
-
-%% Tell watchdog to (maybe) die later ...
-c(Pid, true, Opts) ->
+ options = Opts}) ->
Tc = connect_timer(Opts, 2*?DEFAULT_TC),
- erlang:send_after(Tc, Pid, close);
-
-%% ... or now.
-c(Pid, false, _Opts) ->
- Pid ! close.
-
+ erlang:send_after(Tc, Pid, close).
%% The RFC's only document the behaviour of Tc, our connect_timer,
%% for the establishment of connections but we also give
%% connect_timer semantics for a listener, being the time within
@@ -1260,13 +1278,14 @@ cm([#diameter_app{alias = Alias} = App], Req, From, Svc) ->
mod_state(Alias, ModS),
{T, RC};
T ->
- diameter_lib:error_report({invalid, T},
- {App, handle_call, Args}),
+ ModX = App#diameter_app.module,
+ ?LOG(invalid_return, {ModX, handle_call, Args, T}),
invalid
catch
E: Reason ->
- diameter_lib:error_report({failure, {E, Reason, ?STACK}},
- {App, handle_call, Args}),
+ ModX = App#diameter_app.module,
+ Stack = diameter_lib:get_stacktrace(),
+ ?LOG(failure, {E, Reason, ModX, handle_call, Stack}),
failure
end;
@@ -1389,6 +1408,8 @@ pick_peer(Local, Remote, Pid, _SvcName, #diameter_app{mutable = true} = App)
case call_service(Pid, {pick_peer, Local, Remote, App}) of
{TPid, _} = T when is_pid(TPid) ->
T;
+ false = No ->
+ No;
{error, _} ->
false
end;
@@ -1422,13 +1443,16 @@ pick_peer(Local,
T; %% Accept returned state in the immutable
{false = No, S} -> %% case as long it isn't changed.
No;
- T ->
- diameter_lib:error_report({invalid, T, App},
- {App, pick_peer, Args})
+ T when M ->
+ ModX = App#diameter_app.module,
+ ?LOG(invalid_return, {ModX, pick_peer, T}),
+ false
catch
- E: Reason ->
- diameter_lib:error_report({failure, {E, Reason, ?STACK}},
- {App, pick_peer, Args})
+ E: Reason when M ->
+ ModX = App#diameter_app.module,
+ Stack = diameter_lib:get_stacktrace(),
+ ?LOG(failure, {E, Reason, ModX, pick_peer, Stack}),
+ false
end.
%% peers/4
@@ -1436,42 +1460,52 @@ pick_peer(Local,
peers(Alias, RH, Filter, Peers) ->
case ?Dict:find(Alias, Peers) of
{ok, L} ->
- ps(L, RH, Filter, {[],[]});
+ filter(L, RH, Filter);
error ->
[]
end.
-%% Place a peer whose Destination-Host/Realm matches those of the
-%% request at the front of the result list. Could add some sort of
-%% 'sort' option to allow more control.
-
-ps([], _, _, {Ys, Ns}) ->
- lists:reverse(Ys, Ns);
-ps([{_TPid, #diameter_caps{} = Caps} = TC | Rest], RH, Filter, Acc) ->
- ps(Rest, RH, Filter, pacc(caps_filter(Caps, RH, Filter),
- caps_filter(Caps, RH, {all, [host, realm]}),
- TC,
- Acc)).
-
-pacc(true, true, Peer, {Ts, Fs}) ->
- {[Peer|Ts], Fs};
-pacc(true, false, Peer, {Ts, Fs}) ->
- {Ts, [Peer|Fs]};
-pacc(_, _, _, Acc) ->
- Acc.
+%% filter/3
+%%
+%% Return peers in match order.
-%% caps_filter/3
+filter(Peers, RH, Filter) ->
+ {Ts, _} = fltr(Peers, RH, Filter),
+ Ts.
+
+%% fltr/4
-caps_filter(C, RH, {neg, F}) ->
- not caps_filter(C, RH, F);
+fltr(Peers, _, none) ->
+ {Peers, []};
-caps_filter(C, RH, {all, L})
+fltr(Peers, RH, {neg, F}) ->
+ {Ts, Fs} = fltr(Peers, RH, F),
+ {Fs, Ts};
+
+fltr(Peers, RH, {all, L})
when is_list(L) ->
- lists:all(fun(F) -> caps_filter(C, RH, F) end, L);
+ lists:foldl(fun(F,A) -> fltr_all(F, A, RH) end,
+ {Peers, []},
+ L);
-caps_filter(C, RH, {any, L})
+fltr(Peers, RH, {any, L})
when is_list(L) ->
- lists:any(fun(F) -> caps_filter(C, RH, F) end, L);
+ lists:foldl(fun(F,A) -> fltr_any(F, A, RH) end,
+ {[], Peers},
+ L);
+
+fltr(Peers, RH, F) ->
+ lists:partition(fun({_,C}) -> caps_filter(C, RH, F) end, Peers).
+
+fltr_all(F, {Ts0, Fs0}, RH) ->
+ {Ts1, Fs1} = fltr(Ts0, RH, F),
+ {Ts1, Fs0 ++ Fs1}.
+
+fltr_any(F, {Ts0, Fs0}, RH) ->
+ {Ts1, Fs1} = fltr(Fs0, RH, F),
+ {Ts0 ++ Ts1, Fs1}.
+
+%% caps_filter/3
caps_filter(#diameter_caps{origin_host = {_,OH}}, [_,DH], host) ->
eq(undefined, DH, OH);
@@ -1484,9 +1518,6 @@ caps_filter(C, _, Filter) ->
%% caps_filter/2
-caps_filter(_, none) ->
- true;
-
caps_filter(#diameter_caps{origin_host = {_,OH}}, {host, H}) ->
eq(any, H, OH);
@@ -1549,7 +1580,8 @@ transports(#state{watchdogT = WatchdogT}) ->
-define(OTHER_INFO, [connections,
name,
peers,
- statistics]).
+ statistics,
+ info]).
service_info(Item, S)
when is_atom(Item) ->
@@ -1639,6 +1671,7 @@ complete_info(Item, #state{service = Svc} = S) ->
keys -> ?ALL_INFO ++ ?CAP_INFO ++ ?OTHER_INFO;
all -> service_info(?ALL_INFO, S);
statistics -> info_stats(S);
+ info -> info_info(S);
connections -> info_connections(S);
peers -> info_peers(S)
end.
@@ -1721,12 +1754,11 @@ peer_acc(PeerT, Acc, #watchdog{pid = Pid,
state = WS,
started = At,
peer = TPid}) ->
- dict:append(Ref,
- [{type, Type},
- {options, Opts},
- {watchdog, {Pid, At, WS}}
- | info_peer(PeerT, TPid, WS)],
- Acc).
+ Info = [{type, Type},
+ {options, Opts},
+ {watchdog, {Pid, At, WS}}
+ | info_peer(PeerT, TPid, WS)],
+ dict:append(Ref, Info ++ [{info, info_process_info(Info)}], Acc).
info_peer(PeerT, TPid, WS)
when is_pid(TPid), WS /= ?WD_DOWN ->
@@ -1738,6 +1770,49 @@ info_peer(PeerT, TPid, WS)
info_peer(_, _, _) ->
[].
+info_process_info(Info) ->
+ lists:flatmap(fun ipi/1, Info).
+
+ipi({watchdog, {Pid, _, _}}) ->
+ info_pid(Pid);
+
+ipi({peer, {Pid, _}}) ->
+ info_pid(Pid);
+
+ipi({port, [{owner, Pid} | _]}) ->
+ info_pid(Pid);
+
+ipi(_) ->
+ [].
+
+info_pid(Pid) ->
+ case process_info(Pid, [message_queue_len, memory, binary]) of
+ undefined ->
+ [];
+ L ->
+ [{Pid, lists:map(fun({K,V}) -> {K, map_info(K,V)} end, L)}]
+ end.
+
+%% The binary list consists of 3-tuples {Ptr, Size, Count}, where Ptr
+%% is a C pointer value, Size is the size of a referenced binary in
+%% bytes, and Count is a global reference count. The same Ptr can
+%% occur multiple times, once for each reference on the process heap.
+%% In this case, the corresponding tuples will have Size in common but
+%% Count may differ just because no global lock is taken when the
+%% value is retrieved.
+%%
+%% The list can be quite large, and we aren't often interested in the
+%% pointers or counts, so whittle this down to the number of binaries
+%% referenced and their total byte count.
+map_info(binary, L) ->
+ SzD = lists:foldl(fun({P,S,_}, D) -> dict:store(P,S,D) end,
+ dict:new(),
+ L),
+ {dict:size(SzD), dict:fold(fun(_,S,N) -> S + N end, 0, SzD)};
+
+map_info(_, T) ->
+ T.
+
%% The point of extracting the config here is so that 'transport' info
%% has one entry for each transport ref, the peer table only
%% containing entries that have a living watchdog.
@@ -1795,6 +1870,13 @@ mk_app(#diameter_app{} = A) ->
info_pending(#state{} = S) ->
diameter_traffic:pending(transports(S)).
+%% info_info/1
+%%
+%% Extract process_info from connections info.
+
+info_info(S) ->
+ [I || L <- conn_list(S), {info, I} <- L].
+
%% info_connections/1
%%
%% One entry per transport connection. Statistics for each entry are
diff --git a/lib/diameter/src/base/diameter_traffic.erl b/lib/diameter/src/base/diameter_traffic.erl
index 7fbb306b02..3b62afca47 100644
--- a/lib/diameter/src/base/diameter_traffic.erl
+++ b/lib/diameter/src/base/diameter_traffic.erl
@@ -31,6 +31,11 @@
%% towards diameter_watchdog
-export([receive_message/4]).
+%% towards diameter_peer_fsm and diameter_watchdog
+-export([incr/4,
+ incr_error/4,
+ incr_rc/4]).
+
%% towards diameter_service
-export([make_recvdata/1,
peer_up/1,
@@ -44,6 +49,8 @@
-include_lib("diameter/include/diameter.hrl").
-include("diameter_internal.hrl").
+-define(LOGX(Reason, T), begin ?LOG(Reason, T), x({Reason, T}) end).
+
-define(RELAY, ?DIAMETER_DICT_RELAY).
-define(BASE, ?DIAMETER_DICT_COMMON). %% Note: the RFC 3588 dictionary
@@ -109,6 +116,72 @@ peer_down(TPid) ->
failover(TPid).
%% ---------------------------------------------------------------------------
+%% incr/4
+%% ---------------------------------------------------------------------------
+
+incr(Dir, #diameter_packet{header = H}, TPid, Dict) ->
+ incr(Dir, H, TPid, Dict);
+
+incr(Dir, #diameter_header{} = H, TPid, Dict) ->
+ incr(TPid, {msg_id(H, Dict), Dir}).
+
+%% ---------------------------------------------------------------------------
+%% incr_error/4
+%% ---------------------------------------------------------------------------
+
+%% Identify messages using the application dictionary, not the encode
+%% dictionary, which may differ in the case of answer-message.
+incr_error(Dir, T, Pid, {_Dict, AppDict}) ->
+ incr_error(Dir, T, Pid, AppDict);
+
+%% Decoded message without errors.
+incr_error(recv, #diameter_packet{errors = []}, _, _) ->
+ ok;
+
+incr_error(recv = D, #diameter_packet{header = H}, TPid, Dict) ->
+ incr_error(D, H, TPid, Dict);
+
+%% Encoded message with errors and an identifiable header ...
+incr_error(send = D, {_, _, #diameter_header{} = H}, TPid, Dict) ->
+ incr_error(D, H, TPid, Dict);
+
+%% ... or not.
+incr_error(send = D, {_,_}, TPid, _) ->
+ incr_error(D, unknown, TPid);
+
+incr_error(Dir, #diameter_header{} = H, TPid, Dict) ->
+ incr_error(Dir, msg_id(H, Dict), TPid);
+
+incr_error(Dir, Id, TPid, _) ->
+ incr_error(Dir, Id, TPid).
+
+incr_error(Dir, Id, TPid) ->
+ incr(TPid, {Id, Dir, error}).
+
+%% ---------------------------------------------------------------------------
+%% incr_rc/4
+%% ---------------------------------------------------------------------------
+
+-spec incr_rc(send|recv, Pkt, TPid, Dict0)
+ -> {Counter, non_neg_integer()}
+ | Reason
+ when Pkt :: #diameter_packet{},
+ TPid :: pid(),
+ Dict0 :: module(),
+ Counter :: {'Result-Code', integer()}
+ | {'Experimental-Result', integer(), integer()},
+ Reason :: atom().
+
+incr_rc(Dir, Pkt, TPid, Dict0) ->
+ try
+ incr_result(Dir, Pkt, TPid, {Dict0, Dict0, Dict0})
+ catch
+ exit: {E,_} when E == no_result_code;
+ E == invalid_error_bit ->
+ E
+ end.
+
+%% ---------------------------------------------------------------------------
%% pending/1
%% ---------------------------------------------------------------------------
@@ -182,7 +255,7 @@ spawn_request(TPid, Pkt, Dict0, Opts, RecvData) ->
spawn_opt(fun() -> recv_request(TPid, Pkt, Dict0, RecvData) end, Opts)
catch
error: system_limit = E -> %% discard
- ?LOG({error, E}, now())
+ ?LOG(error, E)
end.
%% ---------------------------------------------------------------------------
@@ -211,7 +284,9 @@ recv_R({#diameter_app{id = Id, dictionary = Dict} = App, Caps},
Pkt0,
Dict0,
RecvData) ->
+ incr(recv, Pkt0, TPid, Dict),
Pkt = errors(Id, diameter_codec:decode(Id, Dict, Pkt0)),
+ incr_error(recv, Pkt, TPid, Dict),
{Caps, Pkt, App, recv_R(App, TPid, Dict0, Caps, RecvData, Pkt)};
%% Note that the decode is different depending on whether or not Id is
%% ?APP_ID_RELAY.
@@ -283,23 +358,25 @@ rc(N) ->
%% This error is returned when a request is received with an invalid
%% message length.
-errors(_, #diameter_packet{header = #diameter_header{length = Len},
+errors(_, #diameter_packet{header = #diameter_header{length = Len} = H,
bin = Bin,
errors = Es}
= Pkt)
when Len < 20;
0 /= Len rem 4;
8*Len /= bit_size(Bin) ->
+ ?LOG(invalid_message_length, {H, bit_size(Bin)}),
Pkt#diameter_packet{errors = [5015 | Es]};
%% DIAMETER_UNSUPPORTED_VERSION 5011
%% This error is returned when a request was received, whose version
%% number is unsupported.
-errors(_, #diameter_packet{header = #diameter_header{version = V},
+errors(_, #diameter_packet{header = #diameter_header{version = V} = H,
errors = Es}
= Pkt)
when V /= ?DIAMETER_VERSION ->
+ ?LOG(unsupported_version, H),
Pkt#diameter_packet{errors = [5011 | Es]};
%% DIAMETER_COMMAND_UNSUPPORTED 3001
@@ -307,12 +384,13 @@ errors(_, #diameter_packet{header = #diameter_header{version = V},
%% recognize or support. This MUST be used when a Diameter node
%% receives an experimental command that it does not understand.
-errors(Id, #diameter_packet{header = #diameter_header{is_proxiable = P},
+errors(Id, #diameter_packet{header = #diameter_header{is_proxiable = P} = H,
msg = M,
errors = Es}
= Pkt)
when ?APP_ID_RELAY /= Id, undefined == M; %% don't know the command
?APP_ID_RELAY == Id, not P -> %% command isn't proxiable
+ ?LOG(command_unsupported, H),
Pkt#diameter_packet{errors = [3001 | Es]};
%% DIAMETER_INVALID_HDR_BITS 3008
@@ -321,9 +399,11 @@ errors(Id, #diameter_packet{header = #diameter_header{is_proxiable = P},
%% inconsistent with the command code's definition.
errors(_, #diameter_packet{header = #diameter_header{is_request = true,
- is_error = true},
+ is_error = true}
+ = H,
errors = Es}
= Pkt) ->
+ ?LOG(invalid_hdr_bits, H),
Pkt#diameter_packet{errors = [3008 | Es]};
%% Green.
@@ -396,7 +476,7 @@ send_A({Caps, Pkt}, TPid, Dict0, _RecvData) -> %% unsupported application
#diameter_packet{errors = [RC|_]} = Pkt,
send_A(answer_message(RC, Caps, Dict0, Pkt),
TPid,
- Dict0,
+ {Dict0, Dict0},
Pkt,
[],
[]);
@@ -404,7 +484,7 @@ send_A({Caps, Pkt}, TPid, Dict0, _RecvData) -> %% unsupported application
send_A({Caps, Pkt, App, {T, EvalPktFs, EvalFs}}, TPid, Dict0, RecvData) ->
send_A(answer(T, Caps, Pkt, App, Dict0, RecvData),
TPid,
- Dict0,
+ {App#diameter_app.dictionary, Dict0},
Pkt,
EvalPktFs,
EvalFs);
@@ -414,8 +494,8 @@ send_A(_, _, _, _) ->
%% send_A/6
-send_A(T, TPid, Dict0, ReqPkt, EvalPktFs, EvalFs) ->
- reply(T, TPid, Dict0, EvalPktFs, ReqPkt),
+send_A(T, TPid, DictT, ReqPkt, EvalPktFs, EvalFs) ->
+ reply(T, TPid, DictT, EvalPktFs, ReqPkt),
lists:foreach(fun diameter_lib:eval/1, EvalFs).
%% answer/6
@@ -479,7 +559,6 @@ answer_message(RC,
origin_realm = {OR,_}},
Dict0,
Pkt) ->
- ?LOG({error, RC}, Pkt),
{Dict0, answer_message(OH, OR, RC, Dict0, Pkt)}.
%% resend/7
@@ -574,30 +653,32 @@ is_loop(Code, Vid, OH, Dict0, Avps) ->
%% reply/5
%% Local answer ...
-reply({Dict, Ans}, TPid, Dict0, Fs, ReqPkt) ->
- reply(Ans, Dict, TPid, Dict0, Fs, ReqPkt);
+reply({Dict, Ans}, TPid, {AppDict, Dict0}, Fs, ReqPkt) ->
+ local(Ans, TPid, {Dict, AppDict, Dict0}, Fs, ReqPkt);
%% ... or relayed.
reply(#diameter_packet{} = Pkt, TPid, _Dict0, Fs, _ReqPkt) ->
eval_packet(Pkt, Fs),
send(TPid, Pkt).
-%% reply/6
+%% local/5
%%
%% Send a locally originating reply.
%% Skip the setting of Result-Code and Failed-AVP's below. This is
%% undocumented and shouldn't be relied on.
-reply([Msg], Dict, TPid, Dict0, Fs, ReqPkt)
+local([Msg], TPid, DictT, Fs, ReqPkt)
when is_list(Msg);
is_tuple(Msg) ->
- reply(Msg, Dict, TPid, Dict0, Fs, ReqPkt#diameter_packet{errors = []});
+ local(Msg, TPid, DictT, Fs, ReqPkt#diameter_packet{errors = []});
-reply(Msg, Dict, TPid, Dict0, Fs, ReqPkt) ->
- Pkt = encode(Dict,
+local(Msg, TPid, {Dict, AppDict, Dict0} = DictT, Fs, ReqPkt) ->
+ Pkt = encode({Dict, AppDict},
+ TPid,
reset(make_answer_packet(Msg, ReqPkt), Dict, Dict0),
Fs),
- incr(send, Pkt, Dict, TPid, Dict0), %% count outgoing result codes
+ incr(send, Pkt, TPid, AppDict),
+ incr_result(send, Pkt, TPid, DictT), %% count outgoing
send(TPid, Pkt).
%% reset/3
@@ -962,35 +1043,63 @@ find(Pred, [H|T]) ->
%% code, the missing vendor id, and a zero filled payload of the minimum
%% required length for the omitted AVP will be added.
-%% incr/4
+%% incr_result/5
%%
%% Increment a stats counter for result codes in incoming and outgoing
%% answers.
%% Outgoing message as binary: don't count. (Sending binaries is only
%% partially supported.)
-incr(_, #diameter_packet{msg = undefined}, _, _, _) ->
- ok;
-
-%% Incoming with decode errors.
-incr(recv = D, #diameter_packet{header = H, errors = [_|_]}, _, TPid, _) ->
- incr(TPid, {diameter_codec:msg_id(H), D, error});
+incr_result(_, #diameter_packet{msg = undefined = No}, _, _) ->
+ No;
-%% Incoming without errors or outgoing. Outgoing with encode errors
-%% never gets here since encode fails.
-incr(Dir, Pkt, Dict, TPid, Dict0) ->
+%% Incoming or outgoing. Outgoing with encode errors never gets here
+%% since encode fails.
+incr_result(Dir, Pkt, TPid, {Dict, AppDict, Dict0}) ->
#diameter_packet{header = #diameter_header{is_error = E}
= Hdr,
- msg = Rec}
+ msg = Msg,
+ errors = Es}
= Pkt,
- RC = int(get_avp_value(Dict, 'Result-Code', Rec)),
+ Id = msg_id(Hdr, AppDict),
+
+ %% Count incoming decode errors.
+ recv /= Dir orelse [] == Es orelse incr_error(Dir, Id, TPid, AppDict),
- %% Exit on an improper Result-Code.
+ %% Exit on a missing result code.
+ T = rc_counter(Dict, Msg),
+ T == false andalso ?LOGX(no_result_code, {Dict, Dir, Hdr}),
+ {Ctr, RC} = T,
+
+ %% Or on an inappropriate value.
is_result(RC, E, Dict0)
- orelse x({invalid_error_bit, RC}, answer, [Dir, Pkt]),
+ orelse ?LOGX(invalid_error_bit, {Dict, Dir, Hdr, RC}),
+
+ incr(TPid, {Id, Dir, Ctr}),
+ Ctr.
+
+%% msg_id/2
- irc(TPid, Hdr, Dir, rc_counter(Dict, Rec, RC)).
+msg_id(#diameter_packet{header = H}, Dict) ->
+ msg_id(H, Dict);
+
+%% Only count on known keys so as not to be vulnerable to attack:
+%% 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
+ end.
+
+unknown(?APP_ID_RELAY, R) ->
+ {relay, R};
+unknown(_, _) ->
+ unknown.
%% No E-bit: can't be 3xxx.
is_result(RC, false, _Dict0) ->
@@ -1006,12 +1115,6 @@ is_result(RC, true, _) ->
orelse
5000 =< RC andalso RC < 6000.
-irc(_, _, _, undefined) ->
- false;
-
-irc(TPid, Hdr, Dir, Ctr) ->
- incr(TPid, {diameter_codec:msg_id(Hdr), Dir, Ctr}).
-
%% incr/2
incr(TPid, Counter) ->
@@ -1024,14 +1127,16 @@ incr(TPid, Counter) ->
%% All Diameter answer messages defined in vendor-specific
%% applications MUST include either one Result-Code AVP or one
%% Experimental-Result AVP.
-%%
-%% Maintain statistics assuming one or the other, not both, which is
-%% surely the intent of the RFC.
-rc_counter(Dict, Rec, undefined) ->
- rcc(get_avp_value(Dict, 'Experimental-Result', Rec));
-rc_counter(_, _, RC) ->
- {'Result-Code', RC}.
+rc_counter(Dict, Msg) ->
+ rcc(Dict, Msg, int(get_avp_value(Dict, 'Result-Code', Msg))).
+
+rcc(Dict, Msg, undefined) ->
+ rcc(get_avp_value(Dict, 'Experimental-Result', Msg));
+
+rcc(_, _, N)
+ when is_integer(N) ->
+ {{'Result-Code', N}, N}.
%% 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
@@ -1039,12 +1144,12 @@ rc_counter(_, _, RC) ->
rcc([{_,_,N} = T | _])
when is_integer(N) ->
- T;
+ {T,N};
rcc({_,_,N} = T)
when is_integer(N) ->
- T;
+ {T,N};
rcc(_) ->
- undefined.
+ false.
%% Extract the first good looking integer. There's no guarantee
%% that what we're looking for has arity 1.
@@ -1057,13 +1162,6 @@ int(N)
int(_) ->
undefined.
--spec x(any(), atom(), list()) -> no_return().
-
-%% Warn and exit request process on errors in an incoming answer.
-x(Reason, F, A) ->
- diameter_lib:warning_report(Reason, {?MODULE, F, A}),
- x(Reason).
-
x(T) ->
exit(T).
@@ -1305,7 +1403,7 @@ send_R(Pkt0,
{Pid, Ref},
SvcName,
Fs) ->
- Pkt = encode(Dict, Pkt0, Fs),
+ Pkt = encode(Dict, TPid, Pkt0, Fs),
#options{timeout = Timeout}
= Opts,
@@ -1318,6 +1416,7 @@ send_R(Pkt0,
packet = Pkt0},
try
+ incr(send, Pkt, TPid, Dict),
TRef = send_request(TPid, Pkt, Req, SvcName, Timeout),
Pid ! Ref, %% tell caller a send has been attempted
handle_answer(SvcName,
@@ -1353,14 +1452,14 @@ handle_answer(SvcName, App, {error, Req, Reason}) ->
handle_error(App, Req, Reason, SvcName);
handle_answer(SvcName,
- #diameter_app{dictionary = Dict,
+ #diameter_app{dictionary = AppDict,
id = Id}
= App,
{answer, Req, Dict0, Pkt}) ->
- Mod = dict(Dict, Dict0, Pkt),
- handle_A(errors(Id, diameter_codec:decode(Mod, Pkt)),
+ Dict = dict(AppDict, Dict0, Pkt),
+ handle_A(errors(Id, diameter_codec:decode({Dict, AppDict}, Pkt)),
SvcName,
- Mod,
+ Dict,
Dict0,
App,
Req).
@@ -1370,18 +1469,30 @@ handle_answer(SvcName,
%% want to examine the answer?
handle_A(Pkt, SvcName, Dict, Dict0, App, #request{transport = TPid} = Req) ->
+ AppDict = App#diameter_app.dictionary,
+
+ incr(recv, Pkt, TPid, AppDict),
+
try
- incr(recv, Pkt, Dict, TPid, Dict0) %% count incoming result codes
+ incr_result(recv, Pkt, TPid, {Dict, AppDict, Dict0}) %% count incoming
of
_ -> answer(Pkt, SvcName, App, Req)
catch
- exit: {invalid_error_bit, RC} ->
+ exit: {no_result_code, _} ->
+ %% RFC 6733 requires one of Result-Code or
+ %% Experimental-Result, but the decode will have detected
+ %% 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}} ->
#diameter_packet{errors = Es}
= Pkt,
E = {5004, #diameter_avp{name = 'Result-Code', value = RC}},
answer(Pkt#diameter_packet{errors = [E|Es]}, SvcName, App, Req)
end.
+%% answer/4
+
answer(Pkt,
SvcName,
#diameter_app{module = ModX,
@@ -1401,11 +1512,16 @@ a(#diameter_packet{errors = Es}
callback == AE ->
cb(ModX, handle_answer, [Pkt, msg(P), SvcName, {TPid, Caps}]);
-a(Pkt, SvcName, _, report, Req) ->
- x(errors, handle_answer, [SvcName, Req, Pkt]);
+a(Pkt, SvcName, _, AE, _) ->
+ a(Pkt#diameter_packet.header, SvcName, AE).
+
+a(Hdr, SvcName, report) ->
+ MFA = {?MODULE, handle_answer, [SvcName, Hdr]},
+ diameter_lib:warning_report(errors, MFA),
+ a(Hdr, SvcName, discard);
-a(Pkt, SvcName, _, discard, Req) ->
- x({errors, handle_answer, [SvcName, Req, Pkt]}).
+a(Hdr, SvcName, discard) ->
+ x({answer_errors, {SvcName, Hdr}}).
%% Note that we don't check that the application id in the answer's
%% header is what we expect. (TODO: Does the rfc says anything about
@@ -1463,10 +1579,10 @@ msg(#diameter_packet{msg = undefined, bin = Bin}) ->
msg(#diameter_packet{msg = Msg}) ->
Msg.
-%% encode/3
+%% encode/4
-encode(Dict, Pkt, Fs) ->
- P = encode(Dict, Pkt),
+encode(Dict, TPid, Pkt, Fs) ->
+ P = encode(Dict, TPid, Pkt),
eval_packet(P, Fs),
P.
@@ -1477,12 +1593,23 @@ encode(Dict, Pkt, Fs) ->
%% an encoded binary. This isn't the usual case and doesn't properly
%% support retransmission but is useful for test.
+encode(Dict, TPid, Pkt)
+ when is_atom(Dict) ->
+ encode({Dict, Dict}, TPid, Pkt);
+
%% A message to be encoded.
-encode(Dict, #diameter_packet{bin = undefined} = Pkt) ->
- diameter_codec:encode(Dict, Pkt);
+encode(DictT, TPid, #diameter_packet{bin = undefined} = Pkt) ->
+ {Dict, AppDict} = DictT,
+ try
+ diameter_codec:encode(Dict, Pkt)
+ catch
+ exit: {diameter_codec, encode, T} = Reason ->
+ incr_error(send, T, TPid, AppDict),
+ exit(Reason)
+ end;
%% An encoded binary: just send.
-encode(_, #diameter_packet{} = Pkt) ->
+encode(_, _, #diameter_packet{} = Pkt) ->
Pkt.
%% send_request/5
@@ -1505,12 +1632,23 @@ send_request(TPid, #diameter_packet{} = Pkt, Req, SvcName, Timeout) ->
%% send/1
-send({TPid, Pkt, #request{handler = Pid} = Req, SvcName, Timeout, TRef}) ->
- Ref = send_request(TPid,
- Pkt,
- Req#request{handler = self()},
- SvcName,
- Timeout),
+send({TPid, Pkt, #request{handler = Pid} = Req0, SvcName, Timeout, TRef}) ->
+ Seqs = diameter_codec:sequence_numbers(Pkt),
+ Req = Req0#request{handler = self()},
+ Ref = send_request(TPid, Pkt, Req, SvcName, Timeout),
+
+ try
+ recv(TPid, Pid, TRef, Ref)
+ after
+ %% Remove only the entry for this specific send since a resend
+ %% from the originating node can pick another transport on
+ %% this one.
+ ets:delete_object(?REQUEST_TABLE, {Seqs, Req, Ref})
+ end.
+
+%% recv/4
+
+recv(TPid, Pid, TRef, Ref) ->
receive
{answer, _, _, _, _} = A ->
Pid ! A;
@@ -1579,13 +1717,14 @@ resend_request(Pkt0,
SvcName,
Tmo,
Fs) ->
- Pkt = encode(Dict, Pkt0, Fs),
+ Pkt = encode(Dict, TPid, Pkt0, Fs),
Req = Req0#request{transport = TPid,
packet = Pkt0,
caps = Caps},
- ?LOG(retransmission, Req),
+ ?LOG(retransmission, Pkt#diameter_packet.header),
+ incr(TPid, {msg_id(Pkt, Dict), send, retransmission}),
TRef = send_request(TPid, Pkt, Req, SvcName, Tmo),
{TRef, Req}.
diff --git a/lib/diameter/src/base/diameter_watchdog.erl b/lib/diameter/src/base/diameter_watchdog.erl
index 53e659e3f6..b7f2d24941 100644
--- a/lib/diameter/src/base/diameter_watchdog.erl
+++ b/lib/diameter/src/base/diameter_watchdog.erl
@@ -49,8 +49,6 @@
-define(IS_NATURAL(N), (is_integer(N) andalso 0 =< N)).
--define(CHOOSE(B,T,F), if (B) -> T; true -> F end).
-
-record(config,
{suspect = 1 :: non_neg_integer(), %% OKAY -> SUSPECT
okay = 3 :: non_neg_integer()}). %% REOPEN -> OKAY
@@ -221,7 +219,6 @@ dict0(_, _, Acc) ->
Acc.
config_error(T) ->
- diameter_lib:error_report(configuration_error, T),
exit({shutdown, {configuration_error, T}}).
%% handle_call/3
@@ -258,11 +255,15 @@ close({'DOWN', _, process, TPid, {shutdown, Reason}},
close(_, _) ->
ok.
-event(_, #watchdog{status = T}, #watchdog{status = T}) ->
- ok;
-
-event(_, #watchdog{transport = undefined}, #watchdog{transport = undefined}) ->
+event(_,
+ #watchdog{status = From, transport = F},
+ #watchdog{status = To, transport = T})
+ when F == undefined, T == undefined; %% transport not started
+ From == initial, To == down; %% never really left INITIAL
+ From == To -> %% no state transition
ok;
+%% Note that there is no INITIAL -> DOWN transition in RFC 3539: ours
+%% is just a consequence of stop.
event(Msg,
#watchdog{status = From, transport = F, parent = Pid},
@@ -270,7 +271,7 @@ event(Msg,
TPid = tpid(F,T),
E = {[TPid | data(Msg, TPid, From, To)], From, To},
send(Pid, {watchdog, self(), E}),
- ?LOG(transition, {self(), E}).
+ ?LOG(transition, {From, To}).
data(Msg, TPid, reopen, okay) ->
{recv, TPid, 'DWA', _Pkt} = Msg, %% assert
@@ -313,14 +314,13 @@ code_change(_, State, _) ->
%% The state transitions documented here are extracted from RFC 3539,
%% the commentary is ours.
-%% Service or watchdog is telling the watchdog of an accepting
-%% transport to die after connect_timer expiry or reestablished
-%% connection (in another transport process) respectively.
-transition(close, #watchdog{status = down}) ->
+%% Service is telling the watchdog of an accepting transport to die
+%% following transport death in state INITIAL, or after connect_timer
+%% expiry; or another watchdog is saying the same after reestablishing
+%% a connection previously had by this one.
+transition(close, #watchdog{}) ->
{{accept, _}, _, _} = getr(restart), %% assert
stop;
-transition(close, #watchdog{}) ->
- ok;
%% Service is asking for the peer to be taken down gracefully.
transition({shutdown, Pid, _}, #watchdog{parent = Pid,
@@ -332,6 +332,11 @@ transition({shutdown = T, Pid, Reason}, #watchdog{parent = Pid,
send(TPid, {T, self(), Reason}),
S#watchdog{shutdown = true};
+%% Transport is telling us that DPA has been sent in response to DPR:
+%% its death should lead to ours.
+transition({'DPR', TPid}, #watchdog{transport = TPid} = S) ->
+ S#watchdog{shutdown = true};
+
%% Parent process has died,
transition({'DOWN', _, process, Pid, _Reason},
#watchdog{parent = Pid}) ->
@@ -403,18 +408,33 @@ transition({open = Key, TPid, _Hosts, T},
%% REOPEN Connection down CloseConnection()
%% SetWatchdog() DOWN
+%% Transport has died after DPA or service requested termination ...
transition({'DOWN', _, process, TPid, _Reason},
#watchdog{transport = TPid,
shutdown = true}) ->
stop;
-transition({'DOWN', _, process, TPid, _Reason},
+%% ... or not.
+transition({'DOWN', _, process, TPid, _Reason} = D,
#watchdog{transport = TPid,
- status = T}
- = S) ->
- set_watchdog(S#watchdog{status = ?CHOOSE(initial == T, T, down),
- pending = false,
- transport = undefined});
+ status = T,
+ restrict = {_,R}}
+ = S0) ->
+ S = S0#watchdog{pending = false,
+ transport = undefined},
+ {{M,_}, _, _} = getr(restart),
+
+ %% Close an accepting watchdog immediately if there's no
+ %% restriction on the number of connections to the same peer: the
+ %% state machine never enters state REOPEN in this case.
+
+ if T == initial;
+ M == accept, not R ->
+ close(D, S0),
+ stop;
+ true ->
+ set_watchdog(S#watchdog{status = down})
+ end;
%% Incoming message.
transition({recv, TPid, Name, Pkt}, #watchdog{transport = TPid} = S) ->
@@ -454,9 +474,7 @@ encode(dwr = M, Dict0, Mask) ->
hop_by_hop_id = Seq},
Pkt = #diameter_packet{header = Hdr,
msg = Msg},
- #diameter_packet{bin = Bin} = diameter_codec:encode(Dict0, Pkt),
- Bin;
-
+ diameter_codec:encode(Dict0, Pkt);
encode(dwa, Dict0, #diameter_packet{header = H, transport_data = TD}
= ReqPkt) ->
@@ -525,10 +543,14 @@ send_watchdog(#watchdog{pending = false,
dictionary = Dict0,
sequence = Mask}
= S) ->
- send(TPid, {send, encode(dwr, Dict0, Mask)}),
+ #diameter_packet{bin = Bin} = EPkt = encode(dwr, Dict0, Mask),
+ diameter_traffic:incr(send, EPkt, TPid, Dict0),
+ send(TPid, {send, Bin}),
?LOG(send, 'DWR'),
S#watchdog{pending = true}.
+%% Dont' count encode errors since we don't expect any on DWR/DWA.
+
%% recv/3
recv(Name, Pkt, S) ->
@@ -545,13 +567,29 @@ recv(Name, Pkt, S) ->
rcv('DWR', Pkt, #watchdog{transport = TPid,
dictionary = Dict0}) ->
- send(TPid, {send, encode(dwa, Dict0, Pkt)}),
+ ?LOG(recv, 'DWR'),
+ DPkt = diameter_codec:decode(Dict0, Pkt),
+ diameter_traffic:incr(recv, DPkt, TPid, Dict0),
+ diameter_traffic:incr_error(recv, DPkt, TPid, Dict0),
+ EPkt = encode(dwa, Dict0, Pkt),
+ diameter_traffic:incr(send, EPkt, TPid, Dict0),
+ diameter_traffic:incr_rc(send, EPkt, TPid, Dict0),
+
+ send(TPid, {send, EPkt}),
?LOG(send, 'DWA');
+rcv('DWA', Pkt, #watchdog{transport = TPid,
+ dictionary = Dict0}) ->
+ ?LOG(recv, 'DWA'),
+ diameter_traffic:incr(recv, Pkt, TPid, Dict0),
+ diameter_traffic:incr_rc(recv,
+ diameter_codec:decode(Dict0, Pkt),
+ TPid,
+ Dict0);
+
rcv(N, _, _)
when N == 'CER';
N == 'CEA';
- N == 'DWA';
N == 'DPR';
N == 'DPA' ->
false;
@@ -740,7 +778,7 @@ timeout(#watchdog{status = T} = S)
restart(#watchdog{transport = undefined} = S) ->
restart(getr(restart), S);
-restart(S) ->
+restart(S) -> %% reconnect has won race with timeout
S.
%% restart/2
@@ -770,9 +808,10 @@ restart({{connect, _} = T, Opts, Svc},
%% die. Note that a state machine never enters state REOPEN in this
%% case.
restart({{accept, _}, _, _}, #watchdog{restrict = {_, false}}) ->
- stop;
+ stop; %% 'DOWN' was in old code: 'close' was not sent
-%% Otherwise hang around until told to die.
+%% Otherwise hang around until told to die, either by the service or
+%% by another watchdog.
restart({{accept, _}, _, _}, S) ->
S.
diff --git a/lib/diameter/src/compiler/diameter_codegen.erl b/lib/diameter/src/compiler/diameter_codegen.erl
index 22422f2ef2..d91a776321 100644
--- a/lib/diameter/src/compiler/diameter_codegen.erl
+++ b/lib/diameter/src/compiler/diameter_codegen.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2014. 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
@@ -31,7 +31,8 @@
%% on the beam file of another dictionary.
%%
--export([from_dict/4]).
+-export([from_dict/4,
+ is_printable_ascii/1]). %% used by ?TERM/1 in diameter_forms.hrl
-include("diameter_forms.hrl").
-include("diameter_vsn.hrl").
@@ -121,6 +122,9 @@ eraser(Key) ->
%% ===========================================================================
%% ===========================================================================
+is_printable_ascii(C) ->
+ 16#20 =< C andalso C =< 16#7F.
+
get_value(Key, Plist) ->
proplists:get_value(Key, Plist, []).
@@ -128,7 +132,7 @@ gen(parse, ParseD, _Mod) ->
[?VERSION | ParseD];
gen(forms, ParseD, Mod) ->
- pp(erl_forms(Mod, ParseD));
+ preprocess(Mod, erl_forms(Mod, ParseD));
gen(hrl, ParseD, Mod) ->
gen_hrl(Mod, ParseD);
@@ -834,19 +838,19 @@ rec_name(Name, Prefix) ->
Prefix ++ Name.
%% ===========================================================================
-%% pp/1
+%% preprocess/2
%%
%% Preprocess forms as generated by 'forms' option. In particular,
%% replace the include_lib attributes in generated forms by the
%% corresponding forms, extracting the latter from an existing
%% dictionary (diameter_gen_relay). The resulting forms can be
%% compiled to beam using compile:forms/2 (which does no preprocessing
-%% or it's own; DiY currently appears to be the only way to preprocess
+%% of it's own; DiY currently appears to be the only way to preprocess
%% a forms list).
-pp(Forms) ->
+preprocess(Mod, Forms) ->
{_, Beam, _} = code:get_object_code(diameter_gen_relay),
- pp(Forms, abstract_code(Beam)).
+ pp(Forms, remod(Mod, abstract_code(Beam))).
pp(Forms, {ok, Code}) ->
Files = files(Code, []),
@@ -855,6 +859,25 @@ pp(Forms, {ok, Code}) ->
pp(Forms, {error, Reason}) ->
erlang:error({forms, Reason, Forms}).
+%% Replace literal diameter_gen_relay atoms in the extracted forms.
+%% ?MODULE for example.
+
+remod(Mod, L)
+ when is_list(L) ->
+ [remod(Mod, T) || T <- L];
+
+remod(Mod, {atom, _, diameter_gen_relay} = T) ->
+ setelement(3, T, Mod);
+
+remod(Mod, T)
+ when is_tuple(T) ->
+ list_to_tuple(remod(Mod, tuple_to_list(T)));
+
+remod(_, T) ->
+ T.
+
+%% Replace include_lib by the corresponding forms.
+
include({attribute, _, include_lib, Path}, Files) ->
Inc = filename:basename(Path),
[{Inc, Forms}] = [T || {F, _} = T <- Files, F == Inc], %% expect one
@@ -863,6 +886,8 @@ include({attribute, _, include_lib, Path}, Files) ->
include(T, _) ->
[T].
+%% Extract abstract code.
+
abstract_code(Beam) ->
case beam_lib:chunks(Beam, [abstract_code]) of
{ok, {_Mod, [{abstract_code, {_Vsn, Code}}]}} ->
@@ -873,6 +898,8 @@ abstract_code(Beam) ->
{E, Reason}
end.
+%% Extract filename/forms pairs for included forms.
+
files([{attribute, _, file, {Path, _}} | T], Acc) ->
{Body, Rest} = lists:splitwith(fun({attribute, _, file, _}) -> false;
(_) -> true
diff --git a/lib/diameter/src/compiler/diameter_dict_util.erl b/lib/diameter/src/compiler/diameter_dict_util.erl
index 136bba16cb..cf4741e563 100644
--- a/lib/diameter/src/compiler/diameter_dict_util.erl
+++ b/lib/diameter/src/compiler/diameter_dict_util.erl
@@ -731,8 +731,8 @@ no_messages_without_id(Dict) ->
%% explode/4
%%
-%% {avp_vendor_id, AvpName} -> [Lineno, Id::integer()]
-%% {custom_types|codecs|inherits, AvpName} -> [Lineno, Mod::string()]
+%% {avp_vendor_id, AvpName} -> [Lineno, Id::integer()]
+%% {custom|inherits, AvpName} -> [Lineno, Mod::string()]
explode({_, Line, AvpName}, Dict, {_, _, X} = T, K) ->
true = K /= avp_vendor_id orelse is_uint32(T, [K]),
@@ -1094,7 +1094,7 @@ explode_avps([{_, Line, Name} | Toks], Dict) ->
Vid = avp_vendor_id(Flags, Name, Line, Dict),
%% An AVP is uniquely defined by its AVP code and vendor id (if any).
- %% Ensure there are no duplicate.
+ %% Ensure there are no duplicates.
store_new({avp_types, {Code, Vid}},
[Line, Name],
Dict,
@@ -1302,8 +1302,7 @@ x({K, {Name, AvpName}}, [Line | _], Dict)
%% Ditto.
x({K, AvpName}, [Line | _], Dict)
when K == avp_vendor_id;
- K == custom_types;
- K == codecs ->
+ K == custom ->
true = avp_is_defined(AvpName, Dict, Line);
%% Ensure that all local AVP's of type Grouped are also present in @grouped.
diff --git a/lib/diameter/src/compiler/diameter_forms.hrl b/lib/diameter/src/compiler/diameter_forms.hrl
index 9b14c1715a..dd03401b9e 100644
--- a/lib/diameter/src/compiler/diameter_forms.hrl
+++ b/lib/diameter/src/compiler/diameter_forms.hrl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2014. 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
@@ -57,4 +57,6 @@
-define(FIELDS(Fs), [{?record_field, ?ATOM(F), V} || {F,V} <- Fs]).
%% Literal term.
--define(TERM(T), erl_parse:abstract(T, ?LINE)).
+-define(TERM(T), erl_parse:abstract(T, [
+ {line, ?LINE},
+ {encoding, fun diameter_codegen:is_printable_ascii/1}])).
diff --git a/lib/diameter/src/compiler/diameter_make.erl b/lib/diameter/src/compiler/diameter_make.erl
index adc7808e49..72f5d36da4 100644
--- a/lib/diameter/src/compiler/diameter_make.erl
+++ b/lib/diameter/src/compiler/diameter_make.erl
@@ -232,21 +232,29 @@ identify([Vsn | [T|_] = ParseD])
identify({path, File} = T) ->
{T, File};
identify(File) ->
- Bin = iolist_to_binary([File]),
- case is_path(Bin) of
+ case is_path([File]) of
true -> {{path, File}, File};
- false -> {Bin, ?DEFAULT_DICT_FILE}
+ false -> {File, ?DEFAULT_DICT_FILE}
end.
-%% Interpret anything containing \n or \r as a literal dictionary,
-%% otherwise a path. (Which might be the wrong guess in the worst case.)
-is_path(Bin) ->
- try
- [throw(C) || <<C>> <= Bin, $\n == C orelse $\r == C],
- true
- catch
- throw:_ -> false
- end.
+%% Interpret anything containing \n or \r as a literal dictionary.
+
+is_path([<<C,B/binary>> | T]) ->
+ is_path([C, B | T]);
+
+is_path([[C|L] | T]) ->
+ is_path([C, L | T]);
+
+is_path([C|_])
+ when $\n == C;
+ $\r == C ->
+ false;
+
+is_path([_|T]) ->
+ is_path(T);
+
+is_path([]) ->
+ true.
make(File, Opts, Dict) ->
ok(lists:foldl(fun(M,A) -> [make(File, Opts, Dict, M) | A] end,
diff --git a/lib/diameter/src/diameter.app.src b/lib/diameter/src/diameter.app.src
index ceefb9b398..ac1d847753 100644
--- a/lib/diameter/src/diameter.app.src
+++ b/lib/diameter/src/diameter.app.src
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2014. 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
@@ -20,9 +20,27 @@
{application, diameter,
[{description, "Diameter protocol"},
{vsn, "%VSN%"},
- {modules, [%MODULES%]},
+ {modules, [
+ %MODULES%
+ %,%COMPILER%
+ %,%INFO%
+ ]},
{registered, [%REGISTERED%]},
- {applications, [stdlib, kernel]},
+ {applications, [
+ {stdlib, "2.0"}, {kernel, "3.0"}%, {erts, "6.0"}
+ %% {syntax-tools, "1.6.14"}
+ %% {runtime-tools, "1.8.14"}
+ %, {ssl, "5.3.4"}
+ ]},
{env, []},
- {mod, {diameter_app, []}}
+ {mod, {diameter_app, []}},
+ {runtime_dependencies, [
+ ]}
+ %%
+ %% Note that ssl is only required if configured on TCP transports,
+ %% and syntax-tools and runtime-tools are only required if the
+ %% dictionary compiler and debug modules (respectively) are
+ %% needed/wanted at runtime, which they typically aren't. These
+ %% modules are the two commented lines in the 'modules' tuple.
+ %%
]}.
diff --git a/lib/diameter/src/diameter.appup.src b/lib/diameter/src/diameter.appup.src
index 0d421c229e..881d25b5fb 100644
--- a/lib/diameter/src/diameter.appup.src
+++ b/lib/diameter/src/diameter.appup.src
@@ -34,7 +34,32 @@
{"1.4.2", [{restart_application, diameter}]}, %% R16B01
{"1.4.3", [{restart_application, diameter}]}, %% R16B02
{"1.4.4", [{restart_application, diameter}]},
- {"1.5", [{restart_application, diameter}]} %% R16B03
+ {"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}]}
],
[
{"0.9", [{restart_application, diameter}]},
@@ -51,6 +76,31 @@
{"1.4.2", [{restart_application, diameter}]},
{"1.4.3", [{restart_application, diameter}]},
{"1.4.4", [{restart_application, diameter}]},
- {"1.5", [{restart_application, diameter}]}
+ {"1.5", [{restart_application, diameter}]},
+ {"1.6", [{load_module, diameter_sctp},
+ {load_module, diameter_codec},
+ {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_watchdog},
+ {load_module, diameter_traffic},
+ {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}]}
]
}.
diff --git a/lib/diameter/src/base/diameter_dbg.erl b/lib/diameter/src/info/diameter_dbg.erl
index 5b0ac3a3b6..b536e5e80b 100644
--- a/lib/diameter/src/base/diameter_dbg.erl
+++ b/lib/diameter/src/info/diameter_dbg.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2014. 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
@@ -17,12 +17,15 @@
%% %CopyrightEnd%
%%
+%%
+%% Information and debug functions.
+%%
+
-module(diameter_dbg).
-export([table/1,
tables/0,
fields/1,
- help/0,
modules/0,
versions/0,
version_info/0,
@@ -30,7 +33,7 @@
procs/0,
latest/0,
nl/0,
- log/4]).
+ sizes/0]).
-export([diameter_config/0,
diameter_peer/0,
@@ -52,11 +55,9 @@
tp/1]).
-include_lib("diameter/include/diameter.hrl").
--include("diameter_internal.hrl").
-
--define(INFO, diameter_info).
--define(SEP(), ?INFO:sep()).
+-define(APP, diameter).
+-define(I, diameter_info).
-define(LOCAL, [diameter_config,
diameter_peer,
@@ -68,27 +69,25 @@
-define(VALUES(Rec), tl(tuple_to_list(Rec))).
-log(_Slogan, _Mod, _Line, _Details) ->
- ok.
-
-%%% ----------------------------------------------------------
-%%% # help()
-%%% ----------------------------------------------------------
+%% ----------------------------------------------------------
+%% # sizes/0
+%%
+%% Return sizes of named tables.
+%% ----------------------------------------------------------
-help() ->
- not_yet_implemented.
+sizes() ->
+ [{T, ets:info(T, size)} || T <- ?LOCAL, T /= diameter_peer].
-%%% ----------------------------------------------------------
-%%% # table(TableName)
-%%%
-%%% Input: TableName = diameter table containing record entries.
-%%%
-%%% Output: Count | undefined
-%%% ----------------------------------------------------------
+%% ----------------------------------------------------------
+%% # table/1
+%%
+%% Pretty-print a diameter table. Returns the number of records
+%% printed, or undefined.
+%% ----------------------------------------------------------
table(T)
when (T == diameter_peer) orelse (T == diameter_reg) ->
- ?INFO:format(collect(T), fields(T), fun ?INFO:split/2);
+ ?I:format(collect(T), fields(T), fun ?I:split/2);
table(Table)
when is_atom(Table) ->
@@ -96,7 +95,7 @@ table(Table)
undefined = No ->
No;
Fields ->
- ?INFO:format(Table, Fields, fun split/2)
+ ?I:format(Table, Fields, fun split/2)
end.
split([started, name | Fs], [S, N | Vs]) ->
@@ -107,9 +106,9 @@ split([[F|FT]|Fs], [Rec|Vs]) ->
split([F|Fs], [V|Vs]) ->
{F, Fs, V, Vs}.
-%%% ----------------------------------------------------------
-%%% # TableName()
-%%% ----------------------------------------------------------
+%% ----------------------------------------------------------
+%% # TableName/0
+%% ----------------------------------------------------------
-define(TABLE(Name), Name() -> table(Name)).
@@ -121,16 +120,15 @@ split([F|Fs], [V|Vs]) ->
?TABLE(diameter_service).
?TABLE(diameter_stats).
-%%% ----------------------------------------------------------
-%%% # tables()
-%%%
-%%% Output: Number of records output.
-%%%
-%%% Description: Pretty-print records in diameter tables from all nodes.
-%%% ----------------------------------------------------------
+%% ----------------------------------------------------------
+%% # tables/0
+%%
+%% Pretty-print diameter tables from all nodes. Returns the number of
+%% records printed.
+%% ----------------------------------------------------------
tables() ->
- ?INFO:format(field(?LOCAL), fun split/3, fun collect/1).
+ ?I:format(field(?LOCAL), fun split/3, fun collect/1).
field(Tables) ->
lists:map(fun(T) -> {T, fields(T)} end, lists:sort(Tables)).
@@ -138,66 +136,66 @@ field(Tables) ->
split(_, Fs, Vs) ->
split(Fs, Vs).
-%%% ----------------------------------------------------------
-%%% # modules()
-%%% ----------------------------------------------------------
+%% ----------------------------------------------------------
+%% # modules/0
+%% ----------------------------------------------------------
modules() ->
- Path = filename:join([appdir(), atom_to_list(?APPLICATION) ++ ".app"]),
- {ok, [{application, ?APPLICATION, Attrs}]} = file:consult(Path),
+ Path = filename:join([appdir(), atom_to_list(?APP) ++ ".app"]),
+ {ok, [{application, ?APP, Attrs}]} = file:consult(Path),
{modules, Mods} = lists:keyfind(modules, 1, Attrs),
Mods.
appdir() ->
- [_|_] = code:lib_dir(?APPLICATION, ebin).
+ [_|_] = code:lib_dir(?APP, ebin).
-%%% ----------------------------------------------------------
-%%% # versions()
-%%% ----------------------------------------------------------
+%% ----------------------------------------------------------
+%% # versions/0
+%% ----------------------------------------------------------
versions() ->
- ?INFO:versions(modules()).
+ ?I:versions(modules()).
-%%% ----------------------------------------------------------
-%%% # versions()
-%%% ----------------------------------------------------------
+%% ----------------------------------------------------------
+%% # version_info/0
+%% ----------------------------------------------------------
version_info() ->
- ?INFO:version_info(modules()).
+ ?I:version_info(modules()).
-%%% ----------------------------------------------------------
-%%% # compiled()
-%%% ----------------------------------------------------------
+%% ----------------------------------------------------------
+%% # compiled/0
+%% ----------------------------------------------------------
compiled() ->
- ?INFO:compiled(modules()).
+ ?I:compiled(modules()).
-%%% ----------------------------------------------------------
-%%% procs()
-%%% ----------------------------------------------------------
+%% ----------------------------------------------------------
+%% # procs/0
+%% ----------------------------------------------------------
procs() ->
- ?INFO:procs(?APPLICATION).
+ ?I:procs(?APP).
-%%% ----------------------------------------------------------
-%%% # latest()
-%%% ----------------------------------------------------------
+%% ----------------------------------------------------------
+%% # latest/0
+%% ----------------------------------------------------------
latest() ->
- ?INFO:latest(modules()).
+ ?I:latest(modules()).
-%%% ----------------------------------------------------------
-%%% # nl()
-%%% ----------------------------------------------------------
+%% ----------------------------------------------------------
+%% # nl/0
+%% ----------------------------------------------------------
nl() ->
lists:foreach(fun(M) -> abcast = c:nl(M) end, modules()).
-%%% ----------------------------------------------------------
-%%% # pp(Bin)
-%%%
-%%% Description: Pretty-print a message binary.
-%%% ----------------------------------------------------------
+%% ----------------------------------------------------------
+%% # pp/1
+%%
+%% Description: Pretty-print a message binary.
+%% ----------------------------------------------------------
%% Network byte order = big endian.
@@ -207,7 +205,7 @@ pp(<<Version:8, MsgLength:24,
HbHid:32,
E2Eid:32,
AVPs/binary>>) ->
- ?SEP(),
+ ?I:sep(),
ppp(["Version",
"Message length",
"[Actual length]",
@@ -227,7 +225,7 @@ pp(<<Version:8, MsgLength:24,
HbHid,
E2Eid]),
N = avp_loop({AVPs, MsgLength - 20}, 0),
- ?SEP(),
+ ?I:sep(),
N;
pp(<<_Version:8, MsgLength:24, _/binary>> = Bin) ->
@@ -328,23 +326,23 @@ ppp(Fields, Values) ->
ppp({Field, Value}) ->
io:format(": ~-22s : ~p~n", [Field, Value]).
-%%% ----------------------------------------------------------
-%%% # subscriptions()
-%%%
-%%% Output: list of {SvcName, Pid}
-%%% ----------------------------------------------------------
+%% ----------------------------------------------------------
+%% # subscriptions/0
+%%
+%% Returns a list of {SvcName, Pid}.
+%% ----------------------------------------------------------
subscriptions() ->
diameter_service:subscriptions().
-%%% ----------------------------------------------------------
-%%% # children()
-%%% ----------------------------------------------------------
+%% ----------------------------------------------------------
+%% # children/0
+%% ----------------------------------------------------------
children() ->
diameter_sup:tree().
-%%% ----------------------------------------------------------
+%% ----------------------------------------------------------
%% tracer/[12]
@@ -430,7 +428,7 @@ peers(Name, Ts) ->
mk_peers(Name, [_, {type, connect} | _] = Ts) ->
[[Name | mk_peer(Ts)]];
-mk_peers(Name, [R, {type, listen}, O, {accept = A, As}]) ->
+mk_peers(Name, [R, {type, listen}, O, {accept = A, As} | _]) ->
[[Name | mk_peer([R, {type, A}, O | Ts])] || Ts <- As].
%% This is a bit lame: service_info works to build this list and out
%% of something like what we want here and then we take it apart.
@@ -485,13 +483,12 @@ fields(diameter_service) ->
[started,
name,
record_info(fields, diameter_service),
+ watchdogT,
peerT,
- connT,
- share_peers,
- use_shared_peers,
shared_peers,
local_peers,
- monitor];
+ monitor,
+ options];
?FIELDS(diameter_event);
?FIELDS(diameter_uri);
diff --git a/lib/diameter/src/base/diameter_info.erl b/lib/diameter/src/info/diameter_info.erl
index 39d32d07cd..10972f3231 100644
--- a/lib/diameter/src/base/diameter_info.erl
+++ b/lib/diameter/src/info/diameter_info.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2014. 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
@@ -17,6 +17,11 @@
%% %CopyrightEnd%
%%
+%%
+%% Generic functions for formatting table listings and more. Used by
+%% diameter_dbg.
+%%
+
-module(diameter_info).
-export([usage/1,
@@ -573,12 +578,7 @@ sys_info() ->
{A,V}.
os_info() ->
- {os:version(), case os:type() of
- {_Fam, _Name} = T ->
- T;
- Fam ->
- {Fam, ""}
- end}.
+ {os:version(), os:type()}.
chomp(S) ->
string:strip(S, right, $\n).
diff --git a/lib/diameter/src/modules.mk b/lib/diameter/src/modules.mk
index f8d3cf1d6f..a2a7a51892 100644
--- a/lib/diameter/src/modules.mk
+++ b/lib/diameter/src/modules.mk
@@ -1,8 +1,7 @@
-#-*-makefile-*- ; force emacs to enter makefile-mode
# %CopyrightBegin%
#
-# Copyright Ericsson AB 2010-2013. All Rights Reserved.
+# Copyright Ericsson AB 2010-2014. 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
@@ -64,16 +63,19 @@ RT_MODULES = \
transport/diameter_transport \
transport/diameter_transport_sup
-# Handwritten (compile time) modules not included in the app file.
+# Handwritten compiler modules not included in the app file.
CT_MODULES = \
- base/diameter_dbg \
- base/diameter_info \
compiler/diameter_codegen \
compiler/diameter_exprecs \
compiler/diameter_dict_scanner \
compiler/diameter_dict_util \
compiler/diameter_make
+# Info/debug modules, also not included in the app file.
+INFO_MODULES = \
+ info/diameter_dbg \
+ info/diameter_info
+
# Released hrl files in ../include intended for public consumption.
EXTERNAL_HRLS = \
diameter.hrl \
diff --git a/lib/diameter/src/transport/diameter_sctp.erl b/lib/diameter/src/transport/diameter_sctp.erl
index d0a01351f3..32e7aaca39 100644
--- a/lib/diameter/src/transport/diameter_sctp.erl
+++ b/lib/diameter/src/transport/diameter_sctp.erl
@@ -616,6 +616,8 @@ send(#diameter_packet{bin = Bin, transport_data = {outstream, SId}},
S;
%% ... or not: rotate through all streams.
+send(#diameter_packet{bin = Bin}, S) ->
+ send(Bin, S);
send(Bin, #transport{streams = {_, OS},
os = N}
= S)
diff --git a/lib/diameter/test/diameter_app_SUITE.erl b/lib/diameter/test/diameter_app_SUITE.erl
index 1e262895a6..f68a18b5c2 100644
--- a/lib/diameter/test/diameter_app_SUITE.erl
+++ b/lib/diameter/test/diameter_app_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2014. 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
@@ -50,7 +50,7 @@
diameter_exprecs,
diameter_make]).
--define(HELP_MODULES, [diameter_dbg,
+-define(INFO_MODULES, [diameter_dbg,
diameter_info]).
%% ===========================================================================
@@ -99,13 +99,13 @@ vsn(Config) ->
%% # modules/1
%%
%% Ensure that the app file modules and installed modules differ by
-%% compiler/help modules.
+%% compiler/info modules.
%% ===========================================================================
modules(Config) ->
Mods = fetch(modules, fetch(app, Config)),
Installed = code_mods(),
- Help = lists:sort(?HELP_MODULES ++ ?COMPILER_MODULES),
+ Help = lists:sort(?INFO_MODULES ++ ?COMPILER_MODULES),
{[], Help} = {Mods -- Installed, lists:sort(Installed -- Mods)}.
@@ -158,12 +158,15 @@ appvsn(Name) ->
%% # xref/1
%%
%% Ensure that no function in our application calls an undefined function
-%% or one in an application we haven't specified as a dependency. (Almost.)
+%% or one in an application we haven't declared as a dependency. (Almost.)
%% ===========================================================================
xref(Config) ->
App = fetch(app, Config),
- Mods = fetch(modules, App),
+ Mods = fetch(modules, App), %% modules listed in the app file
+
+ %% List of application names extracted from runtime_dependencies.
+ Deps = lists:map(fun unversion/1, fetch(runtime_dependencies, App)),
{ok, XRef} = xref:start(make_name(xref_test_name)),
ok = xref:set_default(XRef, [{verbose, false}, {warnings, false}]),
@@ -178,7 +181,9 @@ xref(Config) ->
[?APP, erts | fetch(applications, App)]),
{ok, Undefs} = xref:analyze(XRef, undefined_function_calls),
- {ok, Called} = xref:analyze(XRef, {module_call, ?COMPILER_MODULES}),
+ {ok, RTmods} = xref:analyze(XRef, {module_use, Mods}),
+ {ok, CTmods} = xref:analyze(XRef, {module_use, ?COMPILER_MODULES}),
+ {ok, RTdeps} = xref:analyze(XRef, {module_call, Mods}),
xref:stop(XRef),
@@ -190,18 +195,41 @@ xref(Config) ->
Undefs),
%% diameter_tcp does call ssl despite the latter not being listed
%% as a dependency in the app file since ssl is only required for
- %% TLS security: it's up to a client who wants TLS it to start
- %% ssl.
-
- [] = lists:filter(fun is_bad_dependency/1, Called).
-
-%% It's not strictly necessary that diameter compiler modules not
-%% depend on other diameter modules but it's a simple source of build
-%% errors if not encoded in the makefile (hence the test) so guard
-%% against it.
-is_bad_dependency(Mod) ->
- lists:prefix("diameter", atom_to_list(Mod))
- andalso not lists:member(Mod, ?COMPILER_MODULES).
+ %% TLS security: it's up to a client who wants TLS to start ssl.
+
+ %% Ensure that only runtime or info modules call runtime modules.
+ %% It's not strictly necessary that diameter compiler modules not
+ %% depend on other diameter modules but it's a simple source of
+ %% build errors if not properly encoded in the makefile so guard
+ %% against it.
+ [] = (RTmods -- Mods) -- ?INFO_MODULES,
+
+ %% Ensure that runtime modules don't call compiler modules.
+ CTmods = CTmods -- Mods,
+
+ %% Ensure that runtime modules only call other runtime modules, or
+ %% applications declared as in runtime_dependencies in the app
+ %% file. Note that the declared application versions are ignored
+ %% since we only know what we can see now.
+ [] = lists:filter(fun(M) -> not lists:member(app(M), Deps) end,
+ RTdeps -- Mods).
+
+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.
+
+app('$M_EXPR') -> %% could be anything but assume it's ok
+ "erts";
+app(Mod) ->
+ case code:which(Mod) of
+ preloaded ->
+ "erts";
+ Path ->
+ unversion(lists:nth(3, lists:reverse(filename:split(Path))))
+ end.
add_application(XRef, App) ->
add_application(XRef, App, code:lib_dir(App)).
diff --git a/lib/diameter/test/diameter_codec_test.erl b/lib/diameter/test/diameter_codec_test.erl
index 0b4568a9e5..90536dcf2b 100644
--- a/lib/diameter/test/diameter_codec_test.erl
+++ b/lib/diameter/test/diameter_codec_test.erl
@@ -1,8 +1,7 @@
-%% coding: utf-8
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2014. 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
diff --git a/lib/diameter/test/diameter_compiler_SUITE.erl b/lib/diameter/test/diameter_compiler_SUITE.erl
index df4dde6240..20c9275808 100644
--- a/lib/diameter/test/diameter_compiler_SUITE.erl
+++ b/lib/diameter/test/diameter_compiler_SUITE.erl
@@ -120,6 +120,16 @@
{avp_has_duplicate_flag,
" -",
" MM"},
+ {ok,
+ "@vendor 0",
+ "@vendor 10415"},
+ {ok,
+ [{"@vendor 0", "@vendor 10415"},
+ {"Proxy-Info .*M$", "&V"},
+ {"Proxy-Info ::= [^>]*", "& 10415 "}]},
+ {grouped_vendor_id_without_flag,
+ [{"@vendor 0", "@vendor 10415"},
+ {"Proxy-Info ::= [^>]*", "& 10415 "}]},
{avp_has_vendor_id,
"@avp_types",
"@avp_vendor_id 667 Class\n&"},
@@ -307,6 +317,21 @@
{avp_not_defined,
"CEA ::=",
"<XXX> &"},
+ {ok,
+ "@avp_types",
+ "@codecs tmod Session-Id &"},
+ {ok,
+ "@avp_types",
+ "@custom_types tmod Session-Id &"},
+ {avp_not_defined,
+ "@avp_types",
+ "@codecs tmod OctetString &"},
+ {avp_not_defined,
+ "@avp_types",
+ "@custom_types tmod OctetString &"},
+ {avp_already_defined,
+ "@avp_types",
+ "@codecs tmod Session-Id @custom_types tmod Session-Id &"},
{not_loaded,
[{"@avp_types", "@inherits nomod XXX &"},
{"CEA ::=", "<XXX> &"}]},
diff --git a/lib/diameter/test/diameter_config_SUITE.erl b/lib/diameter/test/diameter_config_SUITE.erl
index d10ee83ba4..ad5b3f9420 100644
--- a/lib/diameter/test/diameter_config_SUITE.erl
+++ b/lib/diameter/test/diameter_config_SUITE.erl
@@ -157,7 +157,7 @@
{length_errors,
[[exit], [handle], [discard]],
[[x]]},
- {reconnect_timer,
+ {connect_timer,
[[3000]],
[[infinity]]},
{watchdog_timer,
diff --git a/lib/diameter/test/diameter_dpr_SUITE.erl b/lib/diameter/test/diameter_dpr_SUITE.erl
index 9252650bf7..f3f16b06e0 100644
--- a/lib/diameter/test/diameter_dpr_SUITE.erl
+++ b/lib/diameter/test/diameter_dpr_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2012. All Rights Reserved.
+%% Copyright Ericsson AB 2012-2014. 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
@@ -73,7 +73,7 @@
%% Valid values for Disconnect-Cause.
-define(CAUSES, [0, rebooting, 1, busy, 2, goaway]).
-%% Establish one client connection for element of this list,
+%% Establish one client connection for each element of this list,
%% configured with disconnect/5 as disconnect_cb and returning the
%% specified value.
-define(RETURNS,
@@ -129,8 +129,8 @@ stop_service(Config) ->
service == group(Config)
andalso (ok = diameter:stop_service(?CLIENT)).
-%% Check for callbacks and stop the service. (Not the other way around
-%% for the timing reason explained below.)
+%% Check for callbacks before diameter:stop/0, not the other way around
+%% for the timing reason explained below.
check(Config) ->
Grp = group(Config),
[Pid | Refs] = ?util:read_priv(Config, config),
diff --git a/lib/diameter/test/diameter_event_SUITE.erl b/lib/diameter/test/diameter_event_SUITE.erl
index 94b4967921..f43f111d20 100644
--- a/lib/diameter/test/diameter_event_SUITE.erl
+++ b/lib/diameter/test/diameter_event_SUITE.erl
@@ -107,29 +107,38 @@ start_server(Config) ->
%% Connect with matching capabilities and expect the connection to
%% come up.
up(Config) ->
- {Svc, Ref} = connect(Config, []),
+ {Svc, Ref} = connect(Config, [{connect_timer, 5000},
+ {watchdog_timer, 15000}]),
start = event(Svc),
- {up, Ref, {_,_Caps}, _Config, #diameter_packet{}} = event(Svc),
- {watchdog, Ref, _, {initial, okay}, _} = event(Svc).
+ {up, Ref, {TPid, Caps}, Cfg, #diameter_packet{}} = event(Svc),
+ {watchdog, Ref, _, {initial, okay}, _} = event(Svc),
+ %% Kill the transport process and see that the connection is
+ %% reestablished after a watchdog timeout, not after connect_timer
+ %% expiry.
+ exit(TPid, kill),
+ {down, Ref, {TPid, Caps}, Cfg} = event(Svc),
+ {watchdog, Ref, _, {okay, down}, _} = event(Svc),
+ {reconnect, Ref, _} = event(Svc, 10000, 20000).
%% Connect with non-matching capabilities and expect CEA from the peer
%% to indicate as much and then for the transport to be restarted
-%% (after reconnect_timer).
+%% (after connect_timer).
down(Config) ->
{Svc, Ref} = connect(Config, [{capabilities, [{'Acct-Application-Id',
[?DICT_ACCT:id()]}]},
{applications, [?DICT_ACCT]},
- {reconnect_timer, 5000}]),
+ {connect_timer, 5000},
+ {watchdog_timer, 20000}]),
start = event(Svc),
{closed, Ref, {'CEA', ?NO_COMMON_APP, _, #diameter_packet{}}, _}
= event(Svc),
- {reconnect, Ref, _} = event(Svc).
+ {reconnect, Ref, _} = event(Svc, 4000, 10000).
%% Connect with matching capabilities but have the server delay its
%% CEA and cause the client to timeout.
cea_timeout(Config) ->
{Svc, Ref} = connect(Config, [{capx_timeout, ?SERVER_CAPX_TMO div 2},
- {reconnect_timer, 2*?SERVER_CAPX_TMO}]),
+ {connect_timer, 2*?SERVER_CAPX_TMO}]),
start = event(Svc),
{closed, Ref, {'CEA', timeout}, _} = event(Svc).
@@ -165,6 +174,13 @@ uniq() ->
event(Name) ->
receive #diameter_event{service = Name, info = T} -> T end.
+event(Name, TL, TH) ->
+ T0 = now(),
+ Event = event(Name),
+ DT = timer:now_diff(now(), T0) div 1000,
+ {true, true, DT, Event} = {TL < DT, DT < TH, DT, Event},
+ Event.
+
start_service(Name, Opts) ->
diameter:start_service(Name, [{monitor, self()} | Opts]).
diff --git a/lib/diameter/test/diameter_examples_SUITE.erl b/lib/diameter/test/diameter_examples_SUITE.erl
index 02c8d34361..aef4bc35ef 100644
--- a/lib/diameter/test/diameter_examples_SUITE.erl
+++ b/lib/diameter/test/diameter_examples_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2014. 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
@@ -24,7 +24,10 @@
-module(diameter_examples_SUITE).
-export([suite/0,
- all/0]).
+ all/0,
+ groups/0,
+ init_per_group/2,
+ end_per_group/2]).
%% testcases
-export([dict/1, dict/0,
@@ -46,7 +49,7 @@
%% The order here is significant and causes the server to listen
%% before the clients connect.
--define(NODES, [compile, server, client]).
+-define(NODES, [server, client]).
%% Options to ct_slave:start/2.
-define(TIMEOUTS, [{T, 15000} || T <- [boot_timeout,
@@ -63,6 +66,9 @@
%% Common dictionaries to inherit from examples.
-define(DICT0, [rfc3588_base, rfc6733_base]).
+%% Transport protocols over which the example Diameter nodes are run.
+-define(PROTS, [tcp, sctp]).
+
%% ===========================================================================
suite() ->
@@ -71,7 +77,34 @@ suite() ->
all() ->
[dict,
code,
- slave,
+ {group, all}].
+
+groups() ->
+ Tc = tc(),
+ [{all, [parallel], [{group, P} || P <- ?PROTS]}
+ | [{P, [], Tc} || P <- ?PROTS]].
+
+init_per_group(all, Config) ->
+ Config;
+
+init_per_group(tcp = N, Config) ->
+ [{group, N} | Config];
+
+init_per_group(sctp = N, Config) ->
+ case gen_sctp:open() of
+ {ok, Sock} ->
+ gen_sctp:close(Sock),
+ [{group, N} | Config];
+ {error, E} when E == eprotonosupport;
+ E == esocktnosupport -> %% fail on any other reason
+ {skip, no_sctp}
+ end.
+
+end_per_group(_, _) ->
+ ok.
+
+tc() ->
+ [slave,
enslave,
start,
traffic,
@@ -88,7 +121,7 @@ dict() ->
dict(_Config) ->
Dirs = [filename:join(H ++ ["examples", "dict"])
|| H <- [[code:lib_dir(diameter)], [here(), ".."]]],
- [] = [{F,D,RC} || {_,F} <- sort(find_files(Dirs, ".*\\.dia")),
+ [] = [{F,D,RC} || {_,F} <- sort(find_files(Dirs, ".*\\.dia$")),
D <- ?DICT0,
RC <- [make(F,D)],
RC /= ok].
@@ -184,17 +217,18 @@ make_name(Dict) ->
%% Compile example code under examples/code.
code(Config) ->
- Node = slave(hd(?NODES), here()),
+ Node = slave(compile, here()),
[] = rpc:call(Node,
?MODULE,
install,
- [proplists:get_value(priv_dir, Config)]).
+ [proplists:get_value(priv_dir, Config)]),
+ {ok, Node} = ct_slave:stop(compile).
%% Compile on another node since the code path may be modified.
install(PrivDir) ->
Top = install(here(), PrivDir),
Src = filename:join([Top, "examples", "code"]),
- Files = find_files([Src], ".*\\.erl"),
+ Files = find_files([Src], ".*\\.erl$"),
[] = [{F,E} || {_,F} <- Files,
{error, _, _} = E <- [compile:file(F, [warnings_as_errors,
return_errors])]].
@@ -226,7 +260,7 @@ install(Dir, PrivDir) ->
Inc = filename:join([Top, "include"]),
Gen = filename:join([Top, "src", "gen"]),
- Files = find_files([Inc, Gen], ".*\\.hrl"),
+ Files = find_files([Inc, Gen], ".*\\.hrl$"),
[] = [{F,E} || {_,F} <- Files,
B <- [filename:basename(F)],
D <- [filename:join([TmpInc, B])],
@@ -280,9 +314,10 @@ now_diff(_) ->
%% Start two nodes: one for the server, one for the client.
enslave(Config) ->
+ Prot = proplists:get_value(group, Config),
Dir = here(),
- Nodes = [{N, slave(N, Dir)} || N <- tl(?NODES)],
- ?util:write_priv(Config, nodes, Nodes).
+ Nodes = [{S, slave(N, Dir)} || S <- ?NODES, N <- [concat(Prot, S)]],
+ ?util:write_priv(Config, Prot, Nodes).
slave(Name, Dir) ->
{ok, Node} = ct_slave:start(Name, ?TIMEOUTS),
@@ -292,6 +327,9 @@ slave(Name, Dir) ->
[[Dir, filename:join([Dir, "..", "ebin"])]]),
Node.
+concat(Prot, Svc) ->
+ list_to_atom(atom_to_list(Prot) ++ atom_to_list(Svc)).
+
here() ->
filename:dirname(code:which(?MODULE)).
@@ -304,24 +342,25 @@ top(Dir, LibDir) ->
%% start/1
-start(server) ->
+start({server, Prot}) ->
ok = diameter:start(),
ok = server:start(),
- {ok, Ref} = server:listen(tcp),
- [_] = ?util:lport(tcp, Ref),
+ {ok, Ref} = server:listen(Prot),
+ [_] = ?util:lport(Prot, Ref),
ok;
-start(client) ->
+start({client = Svc, Prot}) ->
ok = diameter:start(),
- true = diameter:subscribe(client),
+ true = diameter:subscribe(Svc),
ok = client:start(),
- {ok, Ref} = client:connect(tcp),
+ {ok, Ref} = client:connect(Prot),
receive #diameter_event{info = {up, Ref, _, _, _}} -> ok end;
start(Config) ->
- Nodes = ?util:read_priv(Config, nodes),
+ Prot = proplists:get_value(group, Config),
+ Nodes = ?util:read_priv(Config, Prot),
[] = [RC || {T,N} <- Nodes,
- RC <- [rpc:call(N, ?MODULE, start, [T])],
+ RC <- [rpc:call(N, ?MODULE, start, [{T, Prot}])],
RC /= ok].
%% traffic/1
@@ -336,7 +375,8 @@ traffic(client) ->
receive {'DOWN', MRef, process, _, Reason} -> Reason end;
traffic(Config) ->
- Nodes = ?util:read_priv(Config, nodes),
+ Prot = proplists:get_value(group, Config),
+ Nodes = ?util:read_priv(Config, Prot),
[] = [RC || {T,N} <- Nodes,
RC <- [rpc:call(N, ?MODULE, traffic, [T])],
RC /= ok].
@@ -355,5 +395,6 @@ stop(Name)
{ok, _Node} = ct_slave:stop(Name),
ok;
-stop(_Config) ->
- [] = [RC || N <- ?NODES, RC <- [stop(N)], RC /= ok].
+stop(Config) ->
+ Prot = proplists:get_value(group, Config),
+ [] = [RC || N <- ?NODES, RC <- [stop(concat(Prot, N))], RC /= ok].
diff --git a/lib/diameter/test/diameter_failover_SUITE.erl b/lib/diameter/test/diameter_failover_SUITE.erl
index dfd3253827..c1494dcdb1 100644
--- a/lib/diameter/test/diameter_failover_SUITE.erl
+++ b/lib/diameter/test/diameter_failover_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2014. 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
@@ -47,6 +47,7 @@
send_discard_1/1,
send_discard_2/1,
stop_services/1,
+ empty/1,
stop/1]).
%% diameter callbacks
@@ -121,6 +122,7 @@ all() ->
send_discard_1,
send_discard_2,
stop_services,
+ empty,
stop].
%% ===========================================================================
@@ -147,6 +149,10 @@ stop_services(_Config) ->
T <- [diameter:stop_service(H)],
T /= ok].
+%% Ensure transports have been removed from request table.
+empty(_Config) ->
+ [] = ets:tab2list(diameter_request).
+
stop(_Config) ->
ok = diameter:stop().
diff --git a/lib/diameter/test/diameter_traffic_SUITE.erl b/lib/diameter/test/diameter_traffic_SUITE.erl
index a97c54fc04..4b67372016 100644
--- a/lib/diameter/test/diameter_traffic_SUITE.erl
+++ b/lib/diameter/test/diameter_traffic_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2014. 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
@@ -43,7 +43,9 @@
send_protocol_error/1,
send_arbitrary/1,
send_unknown/1,
+ send_unknown_short/1,
send_unknown_mandatory/1,
+ send_unknown_short_mandatory/1,
send_noreply/1,
send_unsupported/1,
send_unsupported_app/1,
@@ -54,7 +56,8 @@
send_zero_avp_length/1,
send_invalid_avp_length/1,
send_invalid_reject/1,
- send_unrecognized_mandatory/1,
+ send_unexpected_mandatory_decode/1,
+ send_unexpected_mandatory/1,
send_long/1,
send_nopeer/1,
send_noapp/1,
@@ -266,7 +269,9 @@ tc() ->
send_protocol_error,
send_arbitrary,
send_unknown,
+ send_unknown_short,
send_unknown_mandatory,
+ send_unknown_short_mandatory,
send_noreply,
send_unsupported,
send_unsupported_app,
@@ -277,7 +282,8 @@ tc() ->
send_zero_avp_length,
send_invalid_avp_length,
send_invalid_reject,
- send_unrecognized_mandatory,
+ send_unexpected_mandatory_decode,
+ send_unexpected_mandatory,
send_long,
send_nopeer,
send_noapp,
@@ -447,6 +453,24 @@ send_unknown(Config) ->
data = <<17>>}]}
= lists:last(Avps).
+%% Ditto, and point the AVP length past the end of the message. Expect
+%% 5014.
+send_unknown_short(Config) ->
+ send_unknown_short(Config, false, ?INVALID_AVP_LENGTH).
+
+send_unknown_short(Config, M, RC) ->
+ Req = ['ASR', {'AVP', [#diameter_avp{code = 999,
+ is_mandatory = M,
+ data = <<17>>}]}],
+ ['ASA', _SessionId, {'Result-Code', RC} | Avps]
+ = call(Config, Req),
+ [#'diameter_base_Failed-AVP'{'AVP' = As}]
+ = proplists:get_value('Failed-AVP', Avps),
+ [#diameter_avp{code = 999,
+ is_mandatory = M,
+ data = <<17, _/binary>>}] %% extra bits from padding
+ = As.
+
%% Ditto but set the M flag.
send_unknown_mandatory(Config) ->
Req = ['ASR', {'AVP', [#diameter_avp{code = 999,
@@ -461,6 +485,27 @@ send_unknown_mandatory(Config) ->
data = <<17>>}]
= As.
+%% Ditto, and point the AVP length past the end of the message. Expect
+%% 5014 instead of 5001.
+send_unknown_short_mandatory(Config) ->
+ send_unknown_short(Config, true, ?INVALID_AVP_LENGTH).
+
+%% Send an ACR containing an unexpected mandatory Session-Timeout.
+%% Expect 5001, and check that the value in Failed-AVP was decoded.
+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]
+ = call(Config, Req),
+ [#'diameter_base_Failed-AVP'{'AVP' = As}]
+ = proplists:get_value('Failed-AVP', Avps),
+ [#diameter_avp{code = 27,
+ is_mandatory = true,
+ value = 12,
+ data = <<12:32>>}]
+ = As.
+
%% Send an STR that the server ignores.
send_noreply(Config) ->
Req = ['STR', {'Termination-Cause', ?BAD_ANSWER}],
@@ -527,9 +572,9 @@ send_invalid_reject(Config) ->
?answer_message(?TOO_BUSY)
= call(Config, Req).
-%% Send an STR containing a known AVP, but one that's not allowed and
-%% sets the M-bit.
-send_unrecognized_mandatory(Config) ->
+%% Send an STR containing a known AVP, but one that's not expected and
+%% that sets the M-bit.
+send_unexpected_mandatory(Config) ->
Req = ['STR', {'Termination-Cause', ?LOGOUT}],
['STA', _SessionId, {'Result-Code', ?AVP_UNSUPPORTED} | _]
@@ -836,6 +881,26 @@ log(#diameter_packet{bin = Bin} = P, T)
%% prepare/4
prepare(Pkt, Caps, N, #group{client_dict0 = Dict0} = Group)
+ when N == send_unknown_short_mandatory;
+ N == send_unknown_short ->
+ Req = prepare(Pkt, Caps, Group),
+
+ #diameter_packet{header = #diameter_header{length = L},
+ bin = Bin}
+ = E
+ = diameter_codec:encode(Dict0, Pkt#diameter_packet{msg = Req}),
+
+ %% Find the unknown AVP data at the end of the message and alter
+ %% its length header.
+
+ {Padding, [17|_]} = lists:splitwith(fun(C) -> C == 0 end,
+ lists:reverse(binary_to_list(Bin))),
+
+ Offset = L - length(Padding) - 4,
+ <<H:Offset/binary, Len:24, T/binary>> = Bin,
+ E#diameter_packet{bin = <<H/binary, (Len+9):24, T/binary>>};
+
+prepare(Pkt, Caps, N, #group{client_dict0 = Dict0} = Group)
when N == send_long_avp_length;
N == send_short_avp_length;
N == send_zero_avp_length ->
@@ -876,8 +941,8 @@ prepare(Pkt, Caps, N, #group{client_dict0 = Dict0} = Group)
<<V, L:24, H/binary>> = H0, %% assert
E#diameter_packet{bin = <<V, (L+4):24, H/binary, 16:24, 0:32, T/binary>>};
-prepare(Pkt, Caps, send_unrecognized_mandatory, #group{client_dict0 = Dict0}
- = Group) ->
+prepare(Pkt, Caps, send_unexpected_mandatory, #group{client_dict0 = Dict0}
+ = Group) ->
Req = prepare(Pkt, Caps, Group),
#diameter_packet{bin = <<V, Len:24, T/binary>>}
= E
@@ -997,7 +1062,9 @@ answer(Rec, [_|_], N)
N == send_short_avp_length;
N == send_zero_avp_length;
N == send_invalid_avp_length;
- N == send_invalid_reject ->
+ N == send_invalid_reject;
+ N == send_unknown_short_mandatory;
+ N == send_unexpected_mandatory_decode ->
Rec;
answer(Rec, [], _) ->
Rec.
diff --git a/lib/diameter/test/diameter_transport_SUITE.erl b/lib/diameter/test/diameter_transport_SUITE.erl
index 9408fae62c..fcffa69c24 100644
--- a/lib/diameter/test/diameter_transport_SUITE.erl
+++ b/lib/diameter/test/diameter_transport_SUITE.erl
@@ -194,7 +194,7 @@ reconnect({connect, Ref}) ->
true = diameter:subscribe(SvcName),
ok = start_service(SvcName),
[{{_, _, LRef}, Pid}] = diameter_reg:wait({?MODULE, Ref, '_'}),
- CRef = ?util:connect(SvcName, tcp, LRef, [{reconnect_timer, 2000},
+ CRef = ?util:connect(SvcName, tcp, LRef, [{connect_timer, 2000},
{watchdog_timer, 6000}]),
%% Tell partner to kill transport after seeing that there are no
diff --git a/lib/diameter/vsn.mk b/lib/diameter/vsn.mk
index 54019fa46c..587ae08b3d 100644
--- a/lib/diameter/vsn.mk
+++ b/lib/diameter/vsn.mk
@@ -18,5 +18,5 @@
# %CopyrightEnd%
APPLICATION = diameter
-DIAMETER_VSN = 1.6
+DIAMETER_VSN = 1.8
APP_VSN = $(APPLICATION)-$(DIAMETER_VSN)$(PRE_VSN)